"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 getTimelineMaxTime_1 = require("./helpers/getTimelineMaxTime");
var assign_1 = require("./vendor/assign");
var NUMBER = 'number';
var DEFAULT_OPTIONS = {
    // loop: Boolean
    // Determines whether this timeline should loop (start at its beginning when finished)
    loop: true,
};
var TimeUnit;
(function (TimeUnit) {
    TimeUnit["Millisecond"] = "ms";
    TimeUnit["Frame"] = "fr";
})(TimeUnit = exports.TimeUnit || (exports.TimeUnit = {}));
// tslint:disable:variable-name
var HaikuTimeline = /** @class */ (function (_super) {
    __extends(HaikuTimeline, _super);
    function HaikuTimeline(component, name, descriptor, options) {
        var _this = _super.call(this) || this;
        _this.component = component;
        _this.name = name;
        _this.descriptor = descriptor;
        _this.assignOptions(options || {});
        _this._globalClockTime = 0;
        _this._localElapsedTime = 0;
        _this._localExplicitlySetTime = null; // Only set this to a number if time is 'controlled'
        _this._maxExplicitlyDefinedTime = getTimelineMaxTime_1.default(descriptor);
        _this._isPlaying = null;
        return _this;
    }
    HaikuTimeline.prototype.getMs = function (amount, unit) {
        switch (unit) {
            case "fr" /* Frame */:
                return ~~(this.component.getClock().getFrameDuration() * amount);
            case "ms" /* Millisecond */:
            default:
                // The only currently valid alternative to TimeUnit.Frame is TimeUnit.Millisecond.
                return amount;
        }
    };
    HaikuTimeline.prototype.assignOptions = function (options) {
        this.options = assign_1.default(this.options || {}, DEFAULT_OPTIONS, options || {});
    };
    HaikuTimeline.prototype.ensureClockIsRunning = function () {
        var clock = this.component.getClock();
        if (!clock.isRunning()) {
            clock.start();
        }
    };
    /**
     * @method setComponent
     * @description Internal hook to allow Haiku to hot swap on-stage components during editing.
     */
    HaikuTimeline.prototype.setComponent = function (component) {
        this.component = component;
    };
    HaikuTimeline.prototype.updateInternalProperties = function (updatedGlobalClockTime) {
        var previousGlobalClockTime = this._globalClockTime;
        var deltaGlobalClockTime = updatedGlobalClockTime - previousGlobalClockTime;
        this._globalClockTime = updatedGlobalClockTime;
        if (this.isTimeControlled()) {
            this._localElapsedTime = this._localExplicitlySetTime;
        }
        else {
            // If we are a looping timeline, reset to zero once we've gone past our max
            if (this.isLooping() &&
                this._localElapsedTime > this._maxExplicitlyDefinedTime) {
                this._localElapsedTime =
                    0 + this._maxExplicitlyDefinedTime - this._localElapsedTime;
            }
            this._localElapsedTime += deltaGlobalClockTime;
        }
        if (this.isFinished()) {
            this.setPlaying(false);
        }
    };
    HaikuTimeline.prototype.doUpdateWithGlobalClockTime = function (globalClockTime) {
        if (this.isFrozen()) {
            this.updateInternalProperties(this._globalClockTime);
        }
        else {
            this.updateInternalProperties(globalClockTime);
        }
        if (this.isPlaying()) {
            var frame = this.getFrame();
            var time = Math.round(this.getTime());
            this.component.routeEventToHandlerAndEmit(HaikuBase_1.GLOBAL_LISTENER_KEY, "timeline:" + this.getName() + ":" + frame, [frame, time]);
            this.emit('tick', frame, time);
        }
    };
    HaikuTimeline.prototype.resetMaxDefinedTimeFromDescriptor = function (descriptor) {
        this._maxExplicitlyDefinedTime = getTimelineMaxTime_1.default(descriptor);
    };
    HaikuTimeline.prototype.isTimeControlled = function () {
        return typeof this.getControlledTime() === NUMBER;
    };
    HaikuTimeline.prototype.controlTime = function (controlledTimeToSet, updatedGlobalClockTime) {
        this._localExplicitlySetTime = parseInt(controlledTimeToSet || 0, 10);
        // Need to update the properties so that accessors like .getFrame() work after this update.
        this.updateInternalProperties(updatedGlobalClockTime);
    };
    /**
     * @method getName
     * @description Return the name of this timeline
     */
    HaikuTimeline.prototype.getName = function () {
        return this.name;
    };
    /**
     * @method getMaxTime
     * @description Return the maximum time that this timeline will reach, in ms.
     */
    HaikuTimeline.prototype.getMaxTime = function () {
        return this._maxExplicitlyDefinedTime;
    };
    /**
     * @method getClockTime
     * @description Return the global clock time that this timeline is at, in ms,
     * whether or not our local time matches it or it has exceeded our max.
     * This value is ultimately managed by the clock and passed in.
     */
    HaikuTimeline.prototype.getClockTime = function () {
        return this._globalClockTime;
    };
    /**
     * @method getElapsedTime
     * @description Return the amount of time that has elapsed on this timeline since
     * it started updating, up to the most recent time update it received from the clock.
     * Note that for inactive timelines, this value will cease increasing as of the last update.
     */
    HaikuTimeline.prototype.getElapsedTime = function () {
        return this._localElapsedTime;
    };
    /**
     * @method getControlledTime
     * @description If time has been explicitly set here via time control, this value will
     * be the number of that setting.
     */
    HaikuTimeline.prototype.getControlledTime = function () {
        return this._localExplicitlySetTime;
    };
    /**
     * @method getBoundedTime
     * @description Return the locally elapsed time, or the maximum time of this timeline,
     * whichever is smaller. Useful if you want to know what the "effective" time of this
     * timeline is, not necessarily how much has elapsed in an absolute sense. This is used
     * in the renderer to determine what value to calculate "now" deterministically.
     */
    HaikuTimeline.prototype.getBoundedTime = function () {
        var max = this.getMaxTime();
        var elapsed = this.getElapsedTime();
        if (elapsed > max) {
            return max;
        }
        return elapsed;
    };
    /**
     * @method getTime
     * @description Convenience wrapper. Currently returns the bounded time. There's an argument
     * that this should return the elapsed time, though. #TODO
     */
    HaikuTimeline.prototype.getTime = function () {
        return this.getBoundedTime();
    };
    /**
     * @method getBoundedFrame
     * @description Return the current frame up to the maximum frame available for this timeline's duration.
     */
    HaikuTimeline.prototype.getBoundedFrame = function () {
        var time = this.getBoundedTime();
        var timeStep = this.component.getClock().getFrameDuration();
        return Math.round(time / timeStep);
    };
    /**
     * @method getUnboundedFrame
     * @description Return the current frame, even if it is above the maximum frame.
     */
    HaikuTimeline.prototype.getUnboundedFrame = function () {
        var time = this.getElapsedTime(); // The elapsed time can go larger than the max time; see timeline.js
        var timeStep = this.component.getClock().getFrameDuration();
        return Math.round(time / timeStep);
    };
    /**
     * @method getFrame
     * @description Return the bounded frame.
     * There's an argument that this should return the absolute frame. #TODO
     */
    HaikuTimeline.prototype.getFrame = function () {
        return this.getBoundedFrame();
    };
    /**
     * @method isPlaying
     * @description Returns T/F if the timeline is playing
     */
    HaikuTimeline.prototype.isPlaying = function () {
        return !!this._isPlaying;
    };
    /**
     * @method isExplicitlyPaused
     * @description Returns T/F if the timeline has actually been paused; differentiate from
     * the falsy state timelines have when first constructed.
     */
    HaikuTimeline.prototype.isExplicitlyPaused = function () {
        return this._isPlaying === false;
    };
    /**
     * @method isFrozen
     * @description Returns T/F if the timeline is frozen
     */
    HaikuTimeline.prototype.isFrozen = function () {
        return !!this.options.freeze;
    };
    /**
     * @method isFinished
     * @description Returns T/F if the timeline is finished.
     * If this timeline is set to loop, it is never "finished".
     */
    HaikuTimeline.prototype.isFinished = function () {
        if (this.isLooping() || this.isTimeControlled()) {
            return false;
        }
        return ~~this.getElapsedTime() > this.getMaxTime();
    };
    HaikuTimeline.prototype.isUnfinished = function () {
        return !this.isFinished();
    };
    HaikuTimeline.prototype.duration = function () {
        return this.getMaxTime() || 0;
    };
    HaikuTimeline.prototype.getDuration = function () {
        return this.duration();
    };
    HaikuTimeline.prototype.setRepeat = function (bool) {
        this.options.loop = bool;
    };
    HaikuTimeline.prototype.getRepeat = function () {
        return !!this.options.loop;
    };
    HaikuTimeline.prototype.isRepeating = function () {
        return this.getRepeat();
    };
    HaikuTimeline.prototype.isLooping = function () {
        return this.isRepeating();
    };
    HaikuTimeline.prototype.freeze = function () {
        this.options.freeze = true;
    };
    HaikuTimeline.prototype.unfreeze = function () {
        this.options.freeze = false;
    };
    HaikuTimeline.prototype.setPlaying = function (isPlaying) {
        if (isPlaying === void 0) { isPlaying = true; }
        this._isPlaying = isPlaying;
    };
    HaikuTimeline.prototype.start = function (maybeGlobalClockTime, descriptor) {
        this.startSoftly(maybeGlobalClockTime, descriptor);
        this.emit('start');
    };
    HaikuTimeline.prototype.startSoftly = function (maybeGlobalClockTime, descriptor) {
        this._localElapsedTime = 0;
        this.setPlaying(true);
        this._globalClockTime = maybeGlobalClockTime || 0;
        this._maxExplicitlyDefinedTime = getTimelineMaxTime_1.default(descriptor);
    };
    HaikuTimeline.prototype.stop = function (maybeGlobalClockTime, descriptor) {
        this.stopSoftly(maybeGlobalClockTime, descriptor);
        this.emit('stop');
    };
    HaikuTimeline.prototype.stopSoftly = function (maybeGlobalClockTime, descriptor) {
        this.setPlaying(false);
        this._maxExplicitlyDefinedTime = getTimelineMaxTime_1.default(descriptor);
    };
    HaikuTimeline.prototype.pause = function () {
        this.pauseSoftly();
        this.emit('pause');
    };
    HaikuTimeline.prototype.pauseSoftly = function () {
        var time = this.component.getClock().getTime();
        var descriptor = this.component.getTimelineDescriptor(this.name);
        this.stopSoftly(time, descriptor);
    };
    HaikuTimeline.prototype.play = function (options) {
        if (options === void 0) { options = {}; }
        this.playSoftly();
        if (!options || !options.skipMarkForFullFlush) {
            this.component.markForFullFlush();
        }
        this.emit('play');
    };
    HaikuTimeline.prototype.playSoftly = function () {
        this.ensureClockIsRunning();
        var time = this.component.getClock().getTime();
        var descriptor = this.component.getTimelineDescriptor(this.name);
        var local = this._localElapsedTime;
        this.startSoftly(time, descriptor);
        if (this._localExplicitlySetTime !== null) {
            this._localElapsedTime = this._localExplicitlySetTime;
            this._localExplicitlySetTime = null;
        }
        else {
            this._localElapsedTime = local;
        }
    };
    HaikuTimeline.prototype.seek = function (amount, unit) {
        if (unit === void 0) { unit = "fr" /* Frame */; }
        var ms = this.getMs(amount, unit);
        this.seekSoftly(ms);
        this.component.markForFullFlush();
        this.emit('seek', ms);
    };
    HaikuTimeline.prototype.seekSoftly = function (ms) {
        this.ensureClockIsRunning();
        var clockTime = this.component.getClock().getTime();
        this.controlTime(ms, clockTime);
        var descriptor = this.component.getTimelineDescriptor(this.name);
        this.startSoftly(clockTime, descriptor);
    };
    HaikuTimeline.prototype.gotoAndPlay = function (amount, unit) {
        if (unit === void 0) { unit = "fr" /* Frame */; }
        var ms = this.getMs(amount, unit);
        this.ensureClockIsRunning();
        this.seekSoftly(ms);
        this.play(null);
    };
    HaikuTimeline.prototype.gotoAndStop = function (amount, unit) {
        if (unit === void 0) { unit = "fr" /* Frame */; }
        var ms = this.getMs(amount, unit);
        this.ensureClockIsRunning();
        this.seekSoftly(ms);
        if (this.component && this.component.context && this.component.context.tick) {
            this.component.context.tick();
        }
        this.pause();
    };
    HaikuTimeline.__name__ = 'HaikuTimeline';
    HaikuTimeline.all = function () { return HaikuBase_1.default.getRegistryForClass(HaikuTimeline); };
    HaikuTimeline.where = function (criteria) {
        var all = HaikuTimeline.all();
        return all.filter(function (timeline) {
            return timeline.matchesCriteria(criteria);
        });
    };
    HaikuTimeline.create = function (component, name, descriptor, config) {
        return new HaikuTimeline(component, name, descriptor, config);
    };
    return HaikuTimeline;
}(HaikuBase_1.default));
exports.default = HaikuTimeline;
//# sourceMappingURL=HaikuTimeline.js.map