# Source: src/tween/TweenData.js

    /**
    * @author       Richard Davey <rich@photonstorm.com>
    * @copyright    2016 Photon Storm Ltd.
    * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
    */

    /**
    * A Phaser.Tween contains at least one TweenData object. It contains all of the tween data values, such as the
    * starting and ending values, the ease function, interpolation and duration. The Tween acts as a timeline manager for
    * TweenData objects and can contain multiple TweenData objects.
    *
    * @class Phaser.TweenData
    * @constructor
    * @param {Phaser.Tween} parent - The Tween that owns this TweenData object.
    */
    Phaser.TweenData = function (parent) {

        /**
        * @property {Phaser.Tween} parent - The Tween which owns this TweenData.
        */
        this.parent = parent;

        /**
        * @property {Phaser.Game} game - A reference to the currently running Game.
        */
        this.game = parent.game;

        /**
        * @property {object} vStart - An object containing the values at the start of the tween.
        * @private
        */
        this.vStart = {};

        /**
        * @property {object} vStartCache - Cached starting values.
        * @private
        */
        this.vStartCache = {};

        /**
        * @property {object} vEnd - An object containing the values at the end of the tween.
        * @private
        */
        this.vEnd = {};

        /**
        * @property {object} vEndCache - Cached ending values.
        * @private
        */
        this.vEndCache = {};

        /**
        * @property {number} duration - The duration of the tween in ms.
        * @default
        */
        this.duration = 1000;

        /**
        * @property {number} percent - A value between 0 and 1 that represents how far through the duration this tween is.
        * @readonly
        */
        this.percent = 0;

        /**
        * @property {number} value - The current calculated value.
        * @readonly
        */
        this.value = 0;

        /**
        * @property {number} repeatCounter - If the Tween is set to repeat this contains the current repeat count.
        */
        this.repeatCounter = 0;

        /**
        * @property {number} repeatDelay - The amount of time in ms between repeats of this tween.
        */
        this.repeatDelay = 0;

        /**
        * @property {number} repeatTotal - The total number of times this Tween will repeat.
        * @readonly
        */
        this.repeatTotal = 0;

        /**
        * @property {boolean} interpolate - True if the Tween will use interpolation (i.e. is an Array to Array tween)
        * @default
        */
        this.interpolate = false;

        /**
        * @property {boolean} yoyo - True if the Tween is set to yoyo, otherwise false.
        * @default
        */
        this.yoyo = false;

        /**
        * @property {number} yoyoDelay - The amount of time in ms between yoyos of this tween.
        */
        this.yoyoDelay = 0;

        /**
        * @property {boolean} inReverse - When a Tween is yoyoing this value holds if it's currently playing forwards (false) or in reverse (true).
        * @default
        */
        this.inReverse = false;

        /**
        * @property {number} delay - The amount to delay by until the Tween starts (in ms). Only applies to the start, use repeatDelay to handle repeats.
        * @default
        */
        this.delay = 0;

        /**
        * @property {number} dt - Current time value.
        */
        this.dt = 0;

        /**
        * @property {number} startTime - The time the Tween started or null if it hasn't yet started.
        */
        this.startTime = null;

        /**
        * @property {function} easingFunction - The easing function used for the Tween.
        * @default Phaser.Easing.Default
        */
        this.easingFunction = Phaser.Easing.Default;

        /**
        * @property {function} interpolationFunction - The interpolation function used for the Tween.
        * @default Phaser.Math.linearInterpolation
        */
        this.interpolationFunction = Phaser.Math.linearInterpolation;

        /**
        * @property {object} interpolationContext - The interpolation function context used for the Tween.
        * @default Phaser.Math
        */
        this.interpolationContext = Phaser.Math;

        /**
        * @property {boolean} isRunning - If the tween is running this is set to `true`. Unless Phaser.Tween a TweenData that is waiting for a delay to expire is *not* considered as running.
        * @default
        */
        this.isRunning = false;

        /**
        * @property {boolean} isFrom - Is this a from tween or a to tween?
        * @default
        */
        this.isFrom = false;

    };

    /**
    * @constant
    * @type {number}
    */
    Phaser.TweenData.PENDING = 0;

    /**
    * @constant
    * @type {number}
    */
    Phaser.TweenData.RUNNING = 1;

    /**
    * @constant
    * @type {number}
    */
    Phaser.TweenData.LOOPED = 2;

    /**
    * @constant
    * @type {number}
    */
    Phaser.TweenData.COMPLETE = 3;

    Phaser.TweenData.prototype = {

        /**
        * Sets this tween to be a `to` tween on the properties given. A `to` tween starts at the current value and tweens to the destination value given.
        * For example a Sprite with an `x` coordinate of 100 could be tweened to `x` 200 by giving a properties object of `{ x: 200 }`.
        *
        * @method Phaser.TweenData#to
        * @param {object} properties - The properties you want to tween, such as `Sprite.x` or `Sound.volume`. Given as a JavaScript object.
        * @param {number} [duration=1000] - Duration of this tween in ms.
        * @param {function} [ease=null] - Easing function. If not set it will default to Phaser.Easing.Default, which is Phaser.Easing.Linear.None by default but can be over-ridden at will.
        * @param {number} [delay=0] - Delay before this tween will start, defaults to 0 (no delay). Value given is in ms.
        * @param {number} [repeat=0] - Should the tween automatically restart once complete? If you want it to run forever set as -1. This ignores any chained tweens.
        * @param {boolean} [yoyo=false] - A tween that yoyos will reverse itself and play backwards automatically. A yoyo'd tween doesn't fire the Tween.onComplete event, so listen for Tween.onLoop instead.
        * @return {Phaser.TweenData} This Tween object.
        */
        to: function (properties, duration, ease, delay, repeat, yoyo) {

            this.vEnd = properties;
            this.duration = duration;
            this.easingFunction = ease;
            this.delay = delay;
            this.repeatTotal = repeat;
            this.yoyo = yoyo;

            this.isFrom = false;

            return this;

        },

        /**
        * Sets this tween to be a `from` tween on the properties given. A `from` tween sets the target to the destination value and tweens to its current value.
        * For example a Sprite with an `x` coordinate of 100 tweened from `x` 500 would be set to `x` 500 and then tweened to `x` 100 by giving a properties object of `{ x: 500 }`.
        *
        * @method Phaser.TweenData#from
        * @param {object} properties - The properties you want to tween, such as `Sprite.x` or `Sound.volume`. Given as a JavaScript object.
        * @param {number} [duration=1000] - Duration of this tween in ms.
        * @param {function} [ease=null] - Easing function. If not set it will default to Phaser.Easing.Default, which is Phaser.Easing.Linear.None by default but can be over-ridden at will.
        * @param {number} [delay=0] - Delay before this tween will start, defaults to 0 (no delay). Value given is in ms.
        * @param {number} [repeat=0] - Should the tween automatically restart once complete? If you want it to run forever set as -1. This ignores any chained tweens.
        * @param {boolean} [yoyo=false] - A tween that yoyos will reverse itself and play backwards automatically. A yoyo'd tween doesn't fire the Tween.onComplete event, so listen for Tween.onLoop instead.
        * @return {Phaser.TweenData} This Tween object.
        */
        from: function (properties, duration, ease, delay, repeat, yoyo) {

            this.vEnd = properties;
            this.duration = duration;
            this.easingFunction = ease;
            this.delay = delay;
            this.repeatTotal = repeat;
            this.yoyo = yoyo;

            this.isFrom = true;

            return this;

        },

        /**
        * Starts the Tween running.
        *
        * @method Phaser.TweenData#start
        * @return {Phaser.TweenData} This Tween object.
        */
        start: function () {

            this.startTime = this.game.time.time + this.delay;

            if (this.parent.reverse)
            {
                this.dt = this.duration;
            }
            else
            {
                this.dt = 0;
            }

            if (this.delay > 0)
            {
                this.isRunning = false;
            }
            else
            {
                this.isRunning = true;
            }

            if (this.isFrom)
            {
                //  Reverse them all and instant set them
                for (var property in this.vStartCache)
                {
                    this.vStart[property] = this.vEndCache[property];
                    this.vEnd[property] = this.vStartCache[property];
                    this.parent.target[property] = this.vStart[property];
                }
            }

            this.value = 0;
            this.yoyoCounter = 0;
            this.repeatCounter = this.repeatTotal;

            return this;

        },

        /**
        * Loads the values from the target object into this Tween.
        *
        * @private
        * @method Phaser.TweenData#loadValues
        * @return {Phaser.TweenData} This Tween object.
        */
        loadValues: function () {

            for (var property in this.parent.properties)
            {
                //  Load the property from the parent object
                this.vStart[property] = this.parent.properties[property];

                //  Check if an Array was provided as property value
                if (Array.isArray(this.vEnd[property]))
                {
                    if (this.vEnd[property].length === 0)
                    {
                        continue;
                    }

                    if (this.percent === 0)
                    {
                        //  Put the start value at the beginning of the array
                        //  but we only want to do this once, if the Tween hasn't run before
                        this.vEnd[property] = [this.vStart[property]].concat(this.vEnd[property]);
                    }
                }

                if (typeof this.vEnd[property] !== 'undefined')
                {
                    if (typeof this.vEnd[property] === 'string')
                    {
                        //  Parses relative end values with start as base (e.g.: +10, -3)
                        this.vEnd[property] = this.vStart[property] + parseFloat(this.vEnd[property], 10);
                    }

                    this.parent.properties[property] = this.vEnd[property];
                }
                else
                {
                    //  Null tween
                    this.vEnd[property] = this.vStart[property];
                }

                this.vStartCache[property] = this.vStart[property];
                this.vEndCache[property] = this.vEnd[property];
            }

            return this;

        },

        /**
        * Updates this Tween. This is called automatically by Phaser.Tween.
        *
        * @protected
        * @method Phaser.TweenData#update
        * @param {number} time - A timestamp passed in by the Tween parent.
        * @return {number} The current status of this Tween. One of the Phaser.TweenData constants: PENDING, RUNNING, LOOPED or COMPLETE.
        */
        update: function (time) {

            if (!this.isRunning)
            {
                if (time >= this.startTime)
                {
                    this.isRunning = true;
                }
                else
                {
                    return Phaser.TweenData.PENDING;
                }
            }
            else
            {
                //  Is Running, but is waiting to repeat
                if (time < this.startTime)
                {
                    return Phaser.TweenData.RUNNING;
                }
            }

            var ms = (this.parent.frameBased) ? this.game.time.physicsElapsedMS : this.game.time.elapsedMS;

            if (this.parent.reverse)
            {
                this.dt -= ms * this.parent.timeScale;
                this.dt = Math.max(this.dt, 0);
            }
            else
            {
                this.dt += ms * this.parent.timeScale;
                this.dt = Math.min(this.dt, this.duration);
            }

            this.percent = this.dt / this.duration;

            this.value = this.easingFunction(this.percent);

            for (var property in this.vEnd)
            {
                var start = this.vStart[property];
                var end = this.vEnd[property];

                if (Array.isArray(end))
                {
                    this.parent.target[property] = this.interpolationFunction.call(this.interpolationContext, end, this.value);
                }
                else
                {
                    this.parent.target[property] = start + ((end - start) * this.value);
                }
            }

            if ((!this.parent.reverse && this.percent === 1) || (this.parent.reverse && this.percent === 0))
            {
                return this.repeat();
            }

            return Phaser.TweenData.RUNNING;

        },

        /**
        * This will generate an array populated with the tweened object values from start to end.
        * It works by running the tween simulation at the given frame rate based on the values set-up in Tween.to and Tween.from.
        * Just one play through of the tween data is returned, including yoyo if set.
        *
        * @method Phaser.TweenData#generateData
        * @param {number} [frameRate=60] - The speed in frames per second that the data should be generated at. The higher the value, the larger the array it creates.
        * @return {array} An array of tweened values.
        */
        generateData: function (frameRate) {

            if (this.parent.reverse)
            {
                this.dt = this.duration;
            }
            else
            {
                this.dt = 0;
            }

            var data = [];
            var complete = false;
            var fps = (1 / frameRate) * 1000;

            do
            {
                if (this.parent.reverse)
                {
                    this.dt -= fps;
                    this.dt = Math.max(this.dt, 0);
                }
                else
                {
                    this.dt += fps;
                    this.dt = Math.min(this.dt, this.duration);
                }

                this.percent = this.dt / this.duration;

                this.value = this.easingFunction(this.percent);

                var blob = {};

                for (var property in this.vEnd)
                {
                    var start = this.vStart[property];
                    var end = this.vEnd[property];

                    if (Array.isArray(end))
                    {
                        blob[property] = this.interpolationFunction(end, this.value);
                    }
                    else
                    {
                        blob[property] = start + ((end - start) * this.value);
                    }
                }

                data.push(blob);

                if ((!this.parent.reverse && this.percent === 1) || (this.parent.reverse && this.percent === 0))
                {
                    complete = true;
                }

            } while (!complete);

            if (this.yoyo)
            {
                var reversed = data.slice();
                reversed.reverse();
                data = data.concat(reversed);
            }

            return data;

        },

        /**
        * Checks if this Tween is meant to repeat or yoyo and handles doing so.
        *
        * @private
        * @method Phaser.TweenData#repeat
        * @return {number} Either Phaser.TweenData.LOOPED or Phaser.TweenData.COMPLETE.
        */
        repeat: function () {

            //  If not a yoyo and repeatCounter = 0 then we're done
            if (this.yoyo)
            {
                //  We're already in reverse mode, which means the yoyo has finished and there's no repeats, so end
                if (this.inReverse && this.repeatCounter === 0)
                {
                    //  Restore the properties
                    for (var property in this.vStartCache)
                    {
                        this.vStart[property] = this.vStartCache[property];
                        this.vEnd[property] = this.vEndCache[property];
                    }

                    this.inReverse = false;

                    return Phaser.TweenData.COMPLETE;
                }

                this.inReverse = !this.inReverse;
            }
            else
            {
                if (this.repeatCounter === 0)
                {
                    return Phaser.TweenData.COMPLETE;
                }
            }

            if (this.inReverse)
            {
                //  If inReverse we're going from vEnd to vStartCache
                for (var property in this.vStartCache)
                {
                    this.vStart[property] = this.vEndCache[property];
                    this.vEnd[property] = this.vStartCache[property];
                }
            }
            else
            {
                //  If not inReverse we're just repopulating the cache again
                for (var property in this.vStartCache)
                {
                    this.vStart[property] = this.vStartCache[property];
                    this.vEnd[property] = this.vEndCache[property];
                }

                //  -1 means repeat forever, otherwise decrement the repeatCounter
                //  We only decrement this counter if the tween isn't doing a yoyo, as that doesn't count towards the repeat total
                if (this.repeatCounter > 0)
                {
                    this.repeatCounter--;
                }
            }

            this.startTime = this.game.time.time;

            if (this.yoyo && this.inReverse)
            {
                this.startTime += this.yoyoDelay;
            }
            else if (!this.inReverse)
            {
                this.startTime += this.repeatDelay;
            }

            if (this.parent.reverse)
            {
                this.dt = this.duration;
            }
            else
            {
                this.dt = 0;
            }

            return Phaser.TweenData.LOOPED;

        }

    };

    Phaser.TweenData.prototype.constructor = Phaser.TweenData;