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

var Class = require('../../utils/Class');

/**
 * @classdesc
 * A Game Object Animation Controller.
 *
 * This controller lives as an instance within a Game Object, accessible as `sprite.anims`.
 *
 * @class Animation
 * @memberOf Phaser.GameObjects.Components
 * @constructor
 * @since 3.0.0
 *
 * @param {Phaser.GameObjects.GameObject} parent - The Game Object to which this animation controller belongs.
 */
var Animation = new Class({

    initialize:

    function Animation (parent)
    {
        /**
         * The Game Object to which this animation controller belongs.
         *
         * @name Phaser.GameObjects.Components.Animation#parent
         * @type {Phaser.GameObjects.GameObject}
         * @since 3.0.0
         */
        this.parent = parent;

        /**
         * A reference to the global Animation Manager.
         *
         * @name Phaser.GameObjects.Components.Animation#animationManager
         * @type {Phaser.Animations.AnimationManager}
         * @since 3.0.0
         */
        this.animationManager = parent.scene.sys.anims;

        this.animationManager.once('remove', this.remove, this);

        /**
         * Is an animation currently playing or not?
         *
         * @name Phaser.GameObjects.Components.Animation#isPlaying
         * @type {boolean}
         * @default false
         * @since 3.0.0
         */
        this.isPlaying = false;

        //  Reference to the Phaser.Animation object

        /**
         * The current Animation loaded into this Animation Controller.
         *
         * @name Phaser.GameObjects.Components.Animation#currentAnim
         * @type {?Phaser.Animations.Animation}
         * @default null
         * @since 3.0.0
         */
        this.currentAnim = null;

        /**
         * The current AnimationFrame being displayed by this Animation Controller.
         *
         * @name Phaser.GameObjects.Components.Animation#currentFrame
         * @type {?Phaser.Animations.AnimationFrame}
         * @default null
         * @since 3.0.0
         */
        this.currentFrame = null;

        /**
         * Time scale factor.
         *
         * @name Phaser.GameObjects.Components.Animation#_timeScale
         * @type {number}
         * @private
         * @default 1
         * @since 3.0.0
         */
        this._timeScale = 1;

        /**
         * The frame rate of playback in frames per second.
         * The default is 24 if the `duration` property is `null`.
         *
         * @name Phaser.GameObjects.Components.Animation#frameRate
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.frameRate = 0;

        /**
         * How long the animation should play for.
         * If the `frameRate` property has been set then it overrides this value,
         * otherwise frameRate is derived from `duration`.
         *
         * @name Phaser.GameObjects.Components.Animation#duration
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.duration = 0;

        /**
         * ms per frame, not including frame specific modifiers that may be present in the Animation data.
         *
         * @name Phaser.GameObjects.Components.Animation#msPerFrame
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.msPerFrame = 0;

        /**
         * Skip frames if the time lags, or always advanced anyway?
         *
         * @name Phaser.GameObjects.Components.Animation#skipMissedFrames
         * @type {boolean}
         * @default true
         * @since 3.0.0
         */
        this.skipMissedFrames = true;

        /**
         * A delay before starting playback, in seconds.
         *
         * @name Phaser.GameObjects.Components.Animation#_delay
         * @type {number}
         * @private
         * @default 0
         * @since 3.0.0
         */
        this._delay = 0;

        /**
         * Number of times to repeat the animation (-1 for infinity)
         *
         * @name Phaser.GameObjects.Components.Animation#_repeat
         * @type {number}
         * @private
         * @default 0
         * @since 3.0.0
         */
        this._repeat = 0;

        /**
         * Delay before the repeat starts, in seconds.
         *
         * @name Phaser.GameObjects.Components.Animation#_repeatDelay
         * @type {number}
         * @private
         * @default 0
         * @since 3.0.0
         */
        this._repeatDelay = 0;

        /**
         * Should the animation yoyo? (reverse back down to the start) before repeating?
         *
         * @name Phaser.GameObjects.Components.Animation#_yoyo
         * @type {boolean}
         * @private
         * @default false
         * @since 3.0.0
         */
        this._yoyo = false;

        /**
         * Will the playhead move forwards (`true`) or in reverse (`false`)
         *
         * @name Phaser.GameObjects.Components.Animation#forward
         * @type {boolean}
         * @default true
         * @since 3.0.0
         */
        this.forward = true;

        /**
         * Internal time overflow accumulator.
         *
         * @name Phaser.GameObjects.Components.Animation#accumulator
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.accumulator = 0;

        /**
         * The time point at which the next animation frame will change.
         *
         * @name Phaser.GameObjects.Components.Animation#nextTick
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.nextTick = 0;

        /**
         * An internal counter keeping track of how many repeats are left to play.
         *
         * @name Phaser.GameObjects.Components.Animation#repeatCounter
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.repeatCounter = 0;

        /**
         * An internal flag keeping track of pending repeats.
         *
         * @name Phaser.GameObjects.Components.Animation#pendingRepeat
         * @type {boolean}
         * @default false
         * @since 3.0.0
         */
        this.pendingRepeat = false;

        /**
         * Is the Animation paused?
         *
         * @name Phaser.GameObjects.Components.Animation#_paused
         * @type {boolean}
         * @private
         * @default false
         * @since 3.0.0
         */
        this._paused = false;

        /**
         * Was the animation previously playing before being paused?
         *
         * @name Phaser.GameObjects.Components.Animation#_wasPlaying
         * @type {boolean}
         * @private
         * @default false
         * @since 3.0.0
         */
        this._wasPlaying = false;

        /**
         * Container for the callback arguments.
         *
         * @name Phaser.GameObjects.Components.Animation#_callbackArgs
         * @type {array}
         * @private
         * @since 3.0.0
         */
        this._callbackArgs = [ parent, null ];

        /**
         * Container for the update arguments.
         *
         * @name Phaser.GameObjects.Components.Animation#_updateParams
         * @type {array}
         * @private
         * @since 3.0.0
         */
        this._updateParams = [];
    },

    /**
     * Sets the amount of time, in seconds that the animation will be delayed before starting playback.
     *
     * @method Phaser.GameObjects.Components.Animation#delay
     * @since 3.0.0
     *
     * @param {number} value - The amount of time, in seconds, to wait before starting playback.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    delay: function (value)
    {
        if (value === undefined)
        {
            return this._delay;
        }
        else
        {
            this._delay = value;

            return this;
        }
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#delayedPlay
     * @since 3.0.0
     *
     * @param {number} delay - [description]
     * @param {string} key - [description]
     * @param {integer} startFrame - [description]
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    delayedPlay: function (delay, key, startFrame)
    {
        this.play(key, true, startFrame);

        this.nextTick += (delay * 1000);

        return this;
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#getCurrentKey
     * @since 3.0.0
     *
     * @return {string} [description]
     */
    getCurrentKey: function ()
    {
        if (this.currentAnim)
        {
            return this.currentAnim.key;
        }
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#load
     * @since 3.0.0
     *
     * @param {string} key - [description]
     * @param {integer} [startFrame=0] - [description]
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    load: function (key, startFrame)
    {
        if (startFrame === undefined) { startFrame = 0; }

        if (this.isPlaying)
        {
            this.stop();
        }

        //  Load the new animation in
        this.animationManager.load(this, key, startFrame);

        return this;
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#pause
     * @since 3.0.0
     *
     * @param {Phaser.Animations.Animation} [atFrame] - [description]
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    pause: function (atFrame)
    {
        if (!this._paused)
        {
            this._paused = true;
            this._wasPlaying = this.isPlaying;
            this.isPlaying = false;
        }

        if (atFrame !== undefined)
        {
            this.updateFrame(atFrame);
        }

        return this;
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#paused
     * @since 3.0.0
     *
     * @param {boolean} [value] - [description]
     *
     * @return {(boolean|Phaser.GameObjects.GameObject)} [description]
     */
    paused: function (value)
    {
        if (value !== undefined)
        {
            //  Setter
            if (value)
            {
                return this.pause();
            }
            else
            {
                return this.resume();
            }
        }
        else
        {
            return this._paused;
        }
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#play
     * @since 3.0.0
     *
     * @param {string} key - [description]
     * @param {boolean} [ignoreIfPlaying=false] - [description]
     * @param {integer} [startFrame=0] - [description]
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    play: function (key, ignoreIfPlaying, startFrame)
    {
        if (ignoreIfPlaying === undefined) { ignoreIfPlaying = false; }
        if (startFrame === undefined) { startFrame = 0; }

        if (ignoreIfPlaying && this.isPlaying && this.currentAnim.key === key)
        {
            return this;
        }

        this.load(key, startFrame);

        var anim = this.currentAnim;
        var gameObject = this.parent;

        //  Should give us 9,007,199,254,740,991 safe repeats
        this.repeatCounter = (this._repeat === -1) ? Number.MAX_VALUE : this._repeat;

        anim.getFirstTick(this);

        this.forward = true;
        this.isPlaying = true;
        this.pendingRepeat = false;

        if (anim.showOnStart)
        {
            gameObject.visible = true;
        }

        if (anim.onStart)
        {
            anim.onStart.apply(anim.callbackScope, this._callbackArgs.concat(anim.onStartParams));
        }

        gameObject.setSizeToFrame();
        gameObject.updateDisplayOrigin();

        return this;
    },

    /**
     * Value between 0 and 1. How far this animation is through, ignoring repeats and yoyos.
     * If the animation has a non-zero repeat defined, progress and totalProgress will be different
     * because progress doesn't include any repeats or repeatDelays whereas totalProgress does.
     *
     * @method Phaser.GameObjects.Components.Animation#progress
     * @since 3.0.0
     *
     * @param {number} [value] - [description]
     *
     * @return {(number|Phaser.GameObjects.GameObject)} [description]
     */
    progress: function (value)
    {
        if (value === undefined)
        {
            var p = this.currentFrame.progress;

            if (!this.forward)
            {
                p = 1 - p;
            }

            return p;
        }
        else
        {
            //  TODO: Set progress

            return this;
        }
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#remove
     * @since 3.0.0
     *
     * @param {Phaser.Animations.Animation} [event] - [description]
     */
    remove: function (event)
    {
        if (event === undefined) { event = this.currentAnim; }

        if (this.isPlaying && event.key === this.currentAnim.key)
        {
            this.stop();

            var sprite = this.parent;
            var frame = this.currentAnim.frames[0];

            this.currentFrame = frame;

            sprite.texture = frame.frame.texture;
            sprite.frame = frame.frame;
        }
    },

    /**
     * Gets or sets the number of times that the animation should repeat
     * after its first iteration. For example, if repeat is 1, the animation will
     * play a total of twice (the initial play plus 1 repeat).
     * To repeat indefinitely, use -1. repeat should always be an integer.
     *
     * @method Phaser.GameObjects.Components.Animation#repeat
     * @since 3.0.0
     *
     * @param {number} value - [description]
     *
     * @return {(number|Phaser.GameObjects.GameObject)} [description]
     */
    repeat: function (value)
    {
        if (value === undefined)
        {
            return this._repeat;
        }
        else
        {
            this._repeat = value;
            this.repeatCounter = 0;

            return this;
        }
    },

    /**
     * Gets or sets the amount of time in seconds between repeats.
     * For example, if repeat is 2 and repeatDelay is 1, the animation will play initially,
     * then wait for 1 second before it repeats, then play again, then wait 1 second again
     * before doing its final repeat.
     *
     * @method Phaser.GameObjects.Components.Animation#repeatDelay
     * @since 3.0.0
     *
     * @param {number} [value] - [description]
     *
     * @return {(number|Phaser.GameObjects.GameObject)} [description]
     */
    repeatDelay: function (value)
    {
        if (value === undefined)
        {
            return this._repeatDelay;
        }
        else
        {
            this._repeatDelay = value;

            return this;
        }
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#restart
     * @since 3.0.0
     *
     * @param {boolean} [includeDelay=false] - [description]
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    restart: function (includeDelay)
    {
        if (includeDelay === undefined) { includeDelay = false; }

        this.currentAnim.getFirstTick(this, includeDelay);

        this.forward = true;
        this.isPlaying = true;
        this.pendingRepeat = false;
        this._paused = false;

        //  Set frame
        this.updateFrame(this.currentAnim.frames[0]);

        return this;
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#resume
     * @since 3.0.0
     *
     * @param {Phaser.Animations.AnimationFrame} fromFrame - [description]
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    resume: function (fromFrame)
    {
        if (this._paused)
        {
            this._paused = false;
            this.isPlaying = this._wasPlaying;
        }

        if (fromFrame !== undefined)
        {
            this.updateFrame(fromFrame);
        }

        return this;
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#stop
     * @since 3.0.0
     *
     * @param {boolean} [dispatchCallbacks=false] - [description]
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    stop: function (dispatchCallbacks)
    {
        if (dispatchCallbacks === undefined) { dispatchCallbacks = false; }

        this.isPlaying = false;

        var anim = this.currentAnim;

        if (dispatchCallbacks && anim.onComplete)
        {
            anim.onComplete.apply(anim.callbackScope, this._callbackArgs.concat(anim.onCompleteParams));
        }

        return this;
    },

    /**
     * Scale the time (make it go faster / slower)
     * Factor that's used to scale time where 1 = normal speed (the default), 0.5 = half speed, 2 = double speed, etc.
     *
     * @method Phaser.GameObjects.Components.Animation#timeScale
     * @since 3.0.0
     *
     * @param {number} [value] - [description]
     *
     * @return {(number|Phaser.GameObjects.GameObject)} [description]
     */
    timeScale: function (value)
    {
        if (value === undefined)
        {
            return this._timeScale;
        }
        else
        {
            this._timeScale = value;

            return this;
        }
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#totalFrames
     * @since 3.0.0
     *
     * @return {number} [description]
     */
    totalFrames: function ()
    {
        return this.currentAnim.frames.length;
    },

    //  Value between 0 and 1. How far this animation is through, including things like delays
    //  repeats, custom frame durations, etc. If the animation is set to repeat -1 it can never
    //  have a duration, therefore this will return -1.
    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#totalProgres
     * @since 3.0.0
     */
    totalProgres: function ()
    {
        // TODO
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#update
     * @since 3.0.0
     *
     * @param {number} timestamp - [description]
     * @param {number} delta - The delta time, in ms, elapsed since the last frame.
     */
    update: function (timestamp, delta)
    {
        if (!this.isPlaying || this.currentAnim.paused)
        {
            return;
        }

        this.accumulator += delta * this._timeScale;

        if (this.accumulator >= this.nextTick)
        {
            this.currentAnim.setFrame(this);
        }
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#updateFrame
     * @since 3.0.0
     *
     * @param {Phaser.Animations.AnimationFrame} animationFrame - [description]
     */
    updateFrame: function (animationFrame)
    {
        var sprite = this.parent;

        this.currentFrame = animationFrame;

        sprite.texture = animationFrame.frame.texture;
        sprite.frame = animationFrame.frame;

        if (this.isPlaying)
        {
            if (animationFrame.setAlpha)
            {
                sprite.alpha = animationFrame.alpha;
            }

            var anim = this.currentAnim;

            if (anim.onUpdate)
            {
                anim.onUpdate.apply(anim.callbackScope, this._updateParams);
            }

            if (animationFrame.onUpdate)
            {
                animationFrame.onUpdate(sprite, animationFrame);
            }
        }
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#yoyo
     * @since 3.0.0
     *
     * @param {boolean} [value] - [description]
     *
     * @return {(boolean|Phaser.GameObjects.GameObject)} [description]
     */
    yoyo: function (value)
    {
        if (value === undefined)
        {
            return this._yoyo;
        }
        else
        {
            this._yoyo = value;

            return this;
        }
    },

    /**
     * [description]
     *
     * @method Phaser.GameObjects.Components.Animation#destroy
     * @since 3.0.0
     */
    destroy: function ()
    {
        //  TODO
    }

});

module.exports = Animation;
