"use strict";
/**
 * Copyright (c) Haiku 2016-2018. All rights reserved.
 */
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
var HaikuBase_1 = require("./HaikuBase");
var HaikuGlobal_1 = require("./HaikuGlobal");
var assign_1 = require("./vendor/assign");
var raf_1 = require("./vendor/raf");
var NUMBER = 'number';
var DEFAULT_OPTIONS = {
    // frameDuration: Number
    // Time to elapse per frame (ms)
    frameDuration: 16.666,
    // frameDelay: Number
    // How long to wait between each tick (ms)
    frameDelay: 16.666,
    // marginOfErrorForDelta: Number
    // A bit of grace when calculating whether a new frame should be run
    marginOfErrorForDelta: 1.0,
};
// The global animation harness is a singleton
// We don't want to create new ones even on reload
if (!HaikuGlobal_1.default.HaikuGlobalAnimationHarness) {
    var queue_1 = [];
    var frame_1 = function () {
        var length = queue_1.length;
        for (var i = 0; i < length; i++) {
            queue_1[i]();
        }
        HaikuGlobal_1.default.HaikuGlobalAnimationHarness.raf = raf_1.default.request(frame_1);
    };
    HaikuGlobal_1.default.HaikuGlobalAnimationHarness = {
        // Array of functions to call on every rAF tick
        queue: queue_1,
        // The main frame function, loops through all those who
        // need an animation tick and calls them
        frame: frame_1,
        // Need a mechanism to cancel the rAF loop, or else some contexts
        // (e.g. tests) will have leaked handles
        cancel: function () {
            if (HaikuGlobal_1.default.HaikuGlobalAnimationHarness.raf) {
                raf_1.default.cancel(HaikuGlobal_1.default.HaikuGlobalAnimationHarness.raf);
            }
        },
    };
    // Trigger the loop to start; we'll push frame functions into its queue later
    HaikuGlobal_1.default.HaikuGlobalAnimationHarness.frame();
}
// tslint:disable:variable-name
var HaikuClock = /** @class */ (function (_super) {
    __extends(HaikuClock, _super);
    function HaikuClock(tickables, options) {
        var _this = _super.call(this) || this;
        _this.boundRunner = function () {
            _this.run();
        };
        _this._tickables = tickables;
        _this.assignOptions(options);
        _this._isRunning = false;
        _this.reinitialize();
        // Bind to avoid `this`-detachment when called by raf
        HaikuGlobal_1.default.HaikuGlobalAnimationHarness.queue.push(_this.boundRunner);
        // Tests and others may need this to cancel the rAF loop, to avoid leaked handles
        _this.GLOBAL_ANIMATION_HARNESS = HaikuGlobal_1.default.HaikuGlobalAnimationHarness;
        return _this;
    }
    HaikuClock.prototype.reinitialize = function () {
        this._numLoopsRun = 0;
        this._localFramesElapsed = 0;
        this._localTimeElapsed = 0;
        this._deltaSinceLastTick = 0;
        this._localExplicitlySetTime = null;
        return this;
    };
    HaikuClock.prototype.addTickable = function (tickable) {
        this._tickables.push(tickable);
        return this;
    };
    HaikuClock.prototype.assignOptions = function (options) {
        this.options = assign_1.default(this.options || {}, DEFAULT_OPTIONS, options || {});
        return this;
    };
    HaikuClock.prototype.run = function () {
        if (this.isRunning()) {
            // If time is "controlled" we are locked to an explicitly set local time, so no math is needed.
            if (this.isTimeControlled()) {
                this.tick();
            }
            else {
                // If we got here, we need to evaluate the time elapsed, and determine if we've waited long enough for a frame
                this._numLoopsRun++;
                var prevTime = this._localTimeElapsed;
                var nextTime = prevTime + this.options.frameDuration;
                var deltaSinceLastTick = nextTime - prevTime + this._deltaSinceLastTick;
                if (deltaSinceLastTick >=
                    this.options.frameDelay - this.options.marginOfErrorForDelta) {
                    this.tick();
                    this._localFramesElapsed++;
                    this._localTimeElapsed = nextTime;
                    this._deltaSinceLastTick = 0; // Must reset delta when frame has been completed
                }
                else {
                    // If we got here, this loop is faster than the desired speed; wait till next call
                    this._deltaSinceLastTick = deltaSinceLastTick;
                }
            }
        }
    };
    HaikuClock.prototype.tick = function () {
        for (var i = 0; i < this._tickables.length; i++) {
            this._tickables[i].performTick();
        }
    };
    HaikuClock.prototype.getTime = function () {
        return this.getExplicitTime();
    };
    HaikuClock.prototype.setTime = function (time) {
        this._localExplicitlySetTime = parseInt(time || 0, 10);
        return this;
    };
    HaikuClock.prototype.getFPS = function () {
        return Math.round(1000 / this.options.frameDuration);
    };
    /**
     * @method getExplicitTime
     * @description Return either the running time or the controlled time, depending on whether this
     * clock is in control mode or not.
     */
    HaikuClock.prototype.getExplicitTime = function () {
        if (this.isTimeControlled()) {
            return this.getControlledTime();
        }
        return this.getRunningTime();
    };
    /**
     * @method getControlledTime
     * @description Return the value of time that has been explicitly controlled.
     */
    HaikuClock.prototype.getControlledTime = function () {
        return this._localExplicitlySetTime;
    };
    HaikuClock.prototype.isTimeControlled = function () {
        return typeof this._localExplicitlySetTime === NUMBER;
    };
    /**
     * @method getRunningTime
     * @description Return the running time, which is the value of time that has elapsed whether or
     * not time has been 'controlled' in control mode.
     */
    HaikuClock.prototype.getRunningTime = function () {
        return this._localTimeElapsed;
    };
    HaikuClock.prototype.isRunning = function () {
        return this._isRunning;
    };
    HaikuClock.prototype.start = function () {
        this._isRunning = true;
        return this;
    };
    HaikuClock.prototype.stop = function () {
        this._isRunning = false;
        return this;
    };
    HaikuClock.prototype.getFrameDuration = function () {
        return this.options.frameDuration;
    };
    HaikuClock.prototype.destroy = function () {
        _super.prototype.destroy.call(this);
        for (var i = 0; i < HaikuGlobal_1.default.HaikuGlobalAnimationHarness.queue.length; i++) {
            if (HaikuGlobal_1.default.HaikuGlobalAnimationHarness.queue[i] === this.boundRunner) {
                HaikuGlobal_1.default.HaikuGlobalAnimationHarness.queue.splice(i, 1);
                return;
            }
        }
    };
    HaikuClock.__name__ = 'HaikuClock';
    return HaikuClock;
}(HaikuBase_1.default));
exports.default = HaikuClock;
//# sourceMappingURL=HaikuClock.js.map