/**
 * @author David Furfero <david.furfero@mlb.com>
 */
(function () {

  /**
   * @class PeriodicalExecuter
   * @param {Function} callback
   * @param {Number} interval
   * @param {Boolean} defer
   * @constructs
   */
  function PeriodicalExecuter (callback, interval, defer) {

    this.callback = callback;
    this.interval = interval;

    this._timer = null;
    this._lastExecutionTime = null;
    this._nextExecutionTime = null;

    if (!defer) {
      this.start();
    }
  };

  PeriodicalExecuter.prototype = {

    _getCurrentTimestamp: function () {
      return (new Date()).getTime();
    },

    /**
     * Sets the interval (in milliseconds) between callback executions. If the
     * PeriodicalExecuter is running, it will restart using the new interval.
     */
    setInterval: function (interval) {
      this.interval = interval;

      if (this.isExecuting()) {
        this.restart();
      }
    },

    getInterval: function () {
      return this.interval;
    },

    isExecuting: function () {
      return !!this._timer;
    },

    getTimeUntilNextExecution: function () {
      if (this.isExecuting() && this._nextExecutionTime !== null) {
        return this._nextExecutionTime - this._getCurrentTimestamp();
      } else {
        return null;
      }
    },

    _execute: function () {
      this._lastExecutionTime = this._getCurrentTimestamp();
      this._nextExecutionTime = this._lastExecutionTime + this.interval;
      this.execute();
    },

    execute: function () {
      this.callback();
    },

    start: function () {
      if (!this.isExecuting()) {
        this._nextExecutionTime = this._getCurrentTimestamp() + this.interval;
        this._timer = setInterval(bam.object.proxy(this._execute, this), this.interval);
      }
    },

    stop: function () {
      if (this.isExecuting()) {
        this._timer = clearInterval(this._timer);
        this._lastExecutionTime = null;
        this._nextExecutionTime = null;
      }
    },

    restart: function (execute) {
      this.stop();
      if (execute) {
        this._execute();
      }
      this.start();
    }
  };

  bam.namespace('util').PeriodicalExecuter = PeriodicalExecuter;

})();

