import timer from "./../system/timer.js";
import * as event from "./../system/event.js";
import { game } from "../index.js";
import { Easing } from "./easing.js";
import { Interpolation } from "./interpolation.js";

/*
 * Tween.js - Licensed under the MIT license
 * https://github.com/tweenjs/tween.js
 */

/**
 * @classdesc
 * Javascript Tweening Engine<p>
 * Super simple, fast and easy to use tweening engine which incorporates optimised Robert Penner's equation<p>
 * <a href="https://github.com/sole/Tween.js">https://github.com/sole/Tween.js</a><p>
 * author sole / http://soledadpenades.com<br>
 * author mr.doob / http://mrdoob.com<br>
 * author Robert Eisele / http://www.xarg.org<br>
 * author Philippe / http://philippe.elsass.me<br>
 * author Robert Penner / http://www.robertpenner.com/easing_terms_of_use.html<br>
 * author Paul Lewis / http://www.aerotwist.com/<br>
 * author lechecacharro<br>
 * author Josh Faul / http://jocafa.com/
 */
export default class Tween {

    /**
     * @param {object} object - object on which to apply the tween
     * @example
     * // add a tween to change the object pos.x and pos.y variable to 200 in 3 seconds
     * tween = new me.Tween(myObject.pos).to({
     *       x: 200,
     *       y: 200,
     *    }, {
     *       duration: 3000,
     *       easing: me.Tween.Easing.Bounce.Out,
     *       autoStart : true
     * }).onComplete(myFunc);
     */
    constructor (object) {
        this.setProperties(object);
    }

    /**
     * reset the tween object to default value
     * @ignore
     */
    onResetEvent(object) {
        this.setProperties(object);
    }

    /**
     * @ignore
     */
    setProperties(object) {
        this._object = object;
        this._valuesStart = {};
        this._valuesEnd = {};
        this._valuesStartRepeat = {};
        this._duration = 1000;
        this._repeat = 0;
        this._yoyo = false;
        this._reversed = false;
        this._delayTime = 0;
        this._startTime = null;
        this._easingFunction = Easing.Linear.None;
        this._interpolationFunction = Interpolation.Linear;
        this._chainedTweens = [];
        this._onStartCallback = null;
        this._onStartCallbackFired = false;
        this._onUpdateCallback = null;
        this._onCompleteCallback = null;
        // tweens are synchronized with the game update loop
        this._tweenTimeTracker = game.lastUpdate;

        // reset flags to default value
        this.isPersistent = false;
        // this is not really supported
        this.updateWhenPaused = false;
        // comply with the container contract
        this.isRenderable = false;

        // Set all starting values present on the target object
        for (let field in object) {
            if (typeof object !== "object") {
                this._valuesStart[ field ] = parseFloat(object[field]);
            }
        }
    }

    /**
     * @ignore
     */
    _resumeCallback(elapsed) {
        if (this._startTime) {
            this._startTime += elapsed;
        }
    }



    /**
     * subscribe to the resume event when added
     * @ignore
     */
    onActivateEvent() {
        event.on(event.STATE_RESUME, this._resumeCallback, this);
    }

    /**
     * Unsubscribe when tween is removed
     * @ignore
     */
    onDeactivateEvent() {
        event.off(event.STATE_RESUME, this._resumeCallback);
    }

    /**
     * object properties to be updated and duration
     * @name to
     * @memberof Tween
     * @public
     * @param {object} properties - hash of properties
     * @param {object|number} [options] - object of tween properties, or a duration if a numeric value is passed
     * @param {number} [options.duration] - tween duration
     * @param {Tween.Easing} [options.easing] - easing function
     * @param {number} [options.delay] - delay amount expressed in milliseconds
     * @param {boolean} [options.yoyo] - allows the tween to bounce back to their original value when finished. To be used together with repeat to create endless loops.
     * @param {number} [options.repeat] - amount of times the tween should be repeated
     * @param {Tween.Interpolation} [options.interpolation] - interpolation function
     * @param {boolean} [options.autoStart] - allow this tween to start automatically. Otherwise call me.Tween.start().
     * @returns {Tween} this instance for object chaining
     */
    to(properties, options) {

        this._valuesEnd = properties;

        if (typeof options !== "undefined") {
            if (typeof options === "number") {
                // for backward compatiblity
                this._duration = options;
            } else if (typeof options === "object") {
                if (options.duration) { this._duration = options.duration; }
                if (options.yoyo) { this.yoyo(options.yoyo); }
                if (options.easing) { this.easing(options.easing); }
                if (options.repeat) { this.repeat(options.repeat); }
                if (options.delay) { this.delay(options.delay); }
                if (options.interpolation) { this.interpolation(options.interpolation); }

                if (options.autoStart) {
                    this.start();
                }
            }
        }

        return this;
    }

    /**
     * start the tween
     * @name start
     * @memberof Tween
     * @public
     * @param {number} [time] - the current time when the tween was started
     * @returns {Tween} this instance for object chaining
     */
    start(time = timer.getTime()) {

        this._onStartCallbackFired = false;

        // add the tween to the object pool on start
        game.world.addChild(this);

        this._startTime =  time + this._delayTime;

        for (let property in this._valuesEnd) {

            // check if an Array was provided as property value
            if (this._valuesEnd[ property ] instanceof Array) {

                if (this._valuesEnd[ property ].length === 0) {

                    continue;

                }

                // create a local copy of the Array with the start value at the front
                this._valuesEnd[ property ] = [ this._object[ property ] ].concat(this._valuesEnd[ property ]);

            }

            this._valuesStart[ property ] = this._object[ property ];

            if ((this._valuesStart[ property ] instanceof Array) === false) {
                this._valuesStart[ property ] *= 1.0; // Ensures we're using numbers, not strings
            }

            this._valuesStartRepeat[ property ] = this._valuesStart[ property ] || 0;

        }

        return this;
    }

    /**
     * stop the tween
     * @name stop
     * @memberof Tween
     * @public
     * @returns {Tween} this instance for object chaining
     */
    stop() {
        // remove the tween from the world container
        game.world.removeChildNow(this);
        return this;
    }

    /**
     * delay the tween
     * @name delay
     * @memberof Tween
     * @public
     * @param {number} amount - delay amount expressed in milliseconds
     * @returns {Tween} this instance for object chaining
     */
    delay(amount) {

        this._delayTime = amount;
        return this;

    }

    /**
     * Repeat the tween
     * @name repeat
     * @memberof Tween
     * @public
     * @param {number} times - amount of times the tween should be repeated
     * @returns {Tween} this instance for object chaining
     */
    repeat(times) {

        this._repeat = times;
        return this;

    }

    /**
     * Allows the tween to bounce back to their original value when finished.
     * To be used together with repeat to create endless loops.
     * @name yoyo
     * @memberof Tween
     * @public
     * @see Tween#repeat
     * @param {boolean} yoyo
     * @returns {Tween} this instance for object chaining
     */
    yoyo(yoyo) {

        this._yoyo = yoyo;
        return this;

    }

    /**
     * set the easing function
     * @name easing
     * @memberof Tween
     * @public
     * @param {Tween.Easing} easing - easing function
     * @returns {Tween} this instance for object chaining
     */
    easing(easing) {
        if (typeof easing !== "function") {
            throw new Error("invalid easing function for me.Tween.easing()");
        }
        this._easingFunction = easing;
        return this;
    }

    /**
     * set the interpolation function
     * @name interpolation
     * @memberof Tween
     * @public
     * @param {Tween.Interpolation} interpolation - interpolation function
     * @returns {Tween} this instance for object chaining
     */
    interpolation(interpolation) {
        this._interpolationFunction = interpolation;
        return this;
    }

    /**
     * chain the tween
     * @name chain
     * @memberof Tween
     * @public
     * @param {...Tween} chainedTween - Tween(s) to be chained
     * @returns {Tween} this instance for object chaining
     */
    chain() {
        this._chainedTweens = arguments;
        return this;
    }

    /**
     * onStart callback
     * @name onStart
     * @memberof Tween
     * @public
     * @param {Function} onStartCallback - callback
     * @returns {Tween} this instance for object chaining
     */
    onStart(onStartCallback) {
        this._onStartCallback = onStartCallback;
        return this;
    }

    /**
     * onUpdate callback
     * @name onUpdate
     * @memberof Tween
     * @public
     * @param {Function} onUpdateCallback - callback
     * @returns {Tween} this instance for object chaining
     */
    onUpdate(onUpdateCallback) {
        this._onUpdateCallback = onUpdateCallback;
        return this;
    }

    /**
     * onComplete callback
     * @name onComplete
     * @memberof Tween
     * @public
     * @param {Function} onCompleteCallback - callback
     * @returns {Tween} this instance for object chaining
     */
    onComplete(onCompleteCallback) {
        this._onCompleteCallback = onCompleteCallback;
        return this;
    }

    /** @ignore */
    update(dt) {

        // the original Tween implementation expect
        // a timestamp and not a time delta
        this._tweenTimeTracker = (game.lastUpdate > this._tweenTimeTracker) ? game.lastUpdate : this._tweenTimeTracker + dt;
        let time = this._tweenTimeTracker;

        let property;

        if (time < this._startTime) {

            return true;

        }

        if (this._onStartCallbackFired === false) {

            if (this._onStartCallback !== null) {

                this._onStartCallback.call(this._object);

            }

            this._onStartCallbackFired = true;

        }

        let elapsed = (time - this._startTime) / this._duration;
        elapsed = elapsed > 1 ? 1 : elapsed;

        let value = this._easingFunction(elapsed);

        for (property in this._valuesEnd) {

            let start = this._valuesStart[ property ] || 0;
            let end = this._valuesEnd[ property ];

            if (end instanceof Array) {

                this._object[ property ] = this._interpolationFunction(end, value);

            } else {

                // Parses relative end values with start as base (e.g.: +10, -3)
                if (typeof(end) === "string") {
                    end = start + parseFloat(end);
                }

                // protect against non numeric properties.
                if (typeof(end) === "number") {
                    this._object[ property ] = start + (end - start) * value;
                }

            }

        }

        if (this._onUpdateCallback !== null) {

            this._onUpdateCallback.call(this._object, value);

        }

        if (elapsed === 1) {

            if (this._repeat > 0) {

                if (isFinite(this._repeat)) {
                    this._repeat--;
                }

                // reassign starting values, restart by making startTime = now
                for (property in this._valuesStartRepeat) {

                    if (typeof(this._valuesEnd[ property ]) === "string") {
                        this._valuesStartRepeat[ property ] = this._valuesStartRepeat[ property ] + parseFloat(this._valuesEnd[ property ]);
                    }

                    if (this._yoyo) {
                        let tmp = this._valuesStartRepeat[ property ];
                        this._valuesStartRepeat[ property ] = this._valuesEnd[ property ];
                        this._valuesEnd[ property ] = tmp;
                    }
                    this._valuesStart[ property ] = this._valuesStartRepeat[ property ];

                }

                if (this._yoyo) {
                    this._reversed = !this._reversed;
                }

                this._startTime = time + this._delayTime;

                return true;

            } else {
                // remove the tween from the world container
                game.world.removeChildNow(this);

                if (this._onCompleteCallback !== null) {

                    this._onCompleteCallback.call(this._object);

                }

                for (let i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i ++) {

                    this._chainedTweens[ i ].start(time);

                }

                return false;

            }

        }
        return true;
    }

    // export easing function as static class property
    static get Easing() { return Easing; }
    static get Interpolation() { return Interpolation; }
}
Powered by webdoc!