'use strict';

/** @typedef {{ execute: function (), label: string, lag: number= }} */
var CommandExecutable;

/**
 * Keeps track of the queue off commands to execute for a player
 */
class CommandQueue {
  constructor() {
    this.commands = [];
    this.lag = 0;
    this.lastRun = 0;
  }

  /**
   * Safely add lag to the current queue. This method will not let you add a
   * negative amount as a safety measure. If you want to subtract lag you can
   * directly manipulate the `lag` property.
   * @param {number} amount milliseconds of lag
   */
  addLag(amount) {
    this.lag += Math.max(0, amount);
  }

  /**
   * @param {CommandExecutable} executable Thing to run with an execute and a queue label
   * @param {number} lag Amount of lag to apply to the queue after the command is run
   */
  enqueue(executable, lag) {
    let newIndex = this.commands.push(Object.assign(executable, { lag })) - 1;
    return newIndex;
  }

  get hasPending() {
    return this.commands.length > 0;
  }

  /**
   * Execute the currently pending command if it's ready
   * @return {boolean} whether the command was executed
   */
  execute() {
    if (!this.commands.length || this.msTilNextRun > 0) {
      return false;
    }

    const command = this.commands.shift();

    this.lastRun = Date.now();
    this.lag = command.lag;
    command.execute();
    return true;
  }

  /**
   * @type {Array<Object>}
   */
  get queue() {
    return this.commands;
  }

  /**
   * Flush all pending commands. Does _not_ reset lastRun/lag. Meaning that if
   * the queue is flushed after a command was just run its lag will still have
   * to expire before another command can be run. To fully reset the queue use
   * the reset() method.
   */
  flush() {
    this.commands = [];
  }

  /**
   * Completely reset the queue and any lag. This is fairly dangerous as if the
   * player could reliably reset the queue they could negate any command lag. To
   * clear commands without altering lag use flush()
   */
  reset() {
    this.flush();
    this.lastRun = 0;
    this.lag = 0;
  }

  /**
   * Seconds until the next command can be executed
   * @type {number}
   */
  get lagRemaining() {
    return this.msTilNextRun / 1000;
  }

  /**
   * Milliseconds til the next command can be executed
   * @type {number}
   */
  get msTilNextRun() {
    return Math.max(0, (this.lastRun + this.lag) - Date.now());
  }

  /**
   * For a given command index find how many seconds until it will run
   * @param {number} commandIndex
   * @param {boolean} milliseconds
   * @return {number}
   */
  getTimeTilRun(commandIndex) {
    return this.getMsTilRun(commandIndex) / 1000;
  }

  /**
   * Milliseconds until the command at the given index can be run
   * @param {number} commandIndex
   * @return {number}
   */
  getMsTilRun(commandIndex) {
    if (!this.commands[commandIndex]) {
      throw new RangeError("Invalid command index");
    }

    let lagTotal = this.msTilNextRun;
    for (let i = 0; i < this.commands.length; i++) {
      if (i === commandIndex) {
        return lagTotal;
      }

      lagTotal += this.commands[i].lag;
    }
  }
}

module.exports = CommandQueue;