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

//  Based on the three.js Curve classes created by [zz85](http://www.lab4games.net/zz85/blog)

var Class = require('../utils/Class');
var Curve = require('./Curve');
var DegToRad = require('../math/DegToRad');
var GetValue = require('../utils/object/GetValue');
var RadToDeg = require('../math/RadToDeg');
var Vector2 = require('../math/Vector2');

/**
 * @typedef {object} JSONEllipseCurve
 *
 * @property {string} type - The of the curve.
 * @property {number} x - [description]
 * @property {number} y - [description]
 * @property {number} xRadius - The horizontal radius of ellipse.
 * @property {number} yRadius - The vertical radius of ellipse.
 * @property {integer} startAngle - The start angle of ellipse.
 * @property {integer} endAngle - The end angle of ellipse.
 * @property {boolean} clockwise - The clockwise of ellipse.
 * @property {integer} rotation - The rotation of ellipse.
 */

/**
 * @classdesc
 * [description]
 *
 * @class EllipseCurve
 * @extends Phaser.Curves.Curve
 * @memberOf Phaser.Curves
 * @constructor
 * @since 3.0.0
 *
 * @param {number} [x=0] - [description]
 * @param {number} [y=0] - [description]
 * @param {number} [xRadius=0] - [description]
 * @param {number} [yRadius=0] - [description]
 * @param {number} [startAngle=0] - [description]
 * @param {number} [endAngle=360] - [description]
 * @param {boolean} [clockwise=false] - [description]
 * @param {number} [rotation=0] - [description]
 */
var EllipseCurve = new Class({

    Extends: Curve,

    initialize:

    function EllipseCurve (x, y, xRadius, yRadius, startAngle, endAngle, clockwise, rotation)
    {
        if (typeof x === 'object')
        {
            var config = x;

            x = GetValue(config, 'x', 0);
            y = GetValue(config, 'y', 0);
            xRadius = GetValue(config, 'xRadius', 0);
            yRadius = GetValue(config, 'yRadius', xRadius);
            startAngle = GetValue(config, 'startAngle', 0);
            endAngle = GetValue(config, 'endAngle', 360);
            clockwise = GetValue(config, 'clockwise', false);
            rotation = GetValue(config, 'rotation', 0);
        }
        else
        {
            if (yRadius === undefined) { yRadius = xRadius; }
            if (startAngle === undefined) { startAngle = 0; }
            if (endAngle === undefined) { endAngle = 360; }
            if (clockwise === undefined) { clockwise = false; }
            if (rotation === undefined) { rotation = 0; }
        }

        Curve.call(this, 'EllipseCurve');

        //  Center point

        /**
         * [description]
         *
         * @name {Phaser.Curves.EllipseCurve#p0
         * @type {Phaser.Math.Vector2}
         * @since 3.0.0
         */
        this.p0 = new Vector2(x, y);

        /**
         * [description]
         *
         * @name Phaser.Curves.EllipseCurve#_xRadius
         * @type {number}
         * @private
         * @since 3.0.0
         */
        this._xRadius = xRadius;

        /**
         * [description]
         *
         * @name Phaser.Curves.EllipseCurve#_yRadius
         * @type {number}
         * @private
         * @since 3.0.0
         */
        this._yRadius = yRadius;

        //  Radians

        /**
         * [description]
         *
         * @name Phaser.Curves.EllipseCurve#_startAngle
         * @type {number}
         * @private
         * @since 3.0.0
         */
        this._startAngle = DegToRad(startAngle);

        /**
         * [description]
         *
         * @name Phaser.Curves.EllipseCurve#_endAngle
         * @type {number}
         * @private
         * @since 3.0.0
         */
        this._endAngle = DegToRad(endAngle);

        /**
         * Anti-clockwise direction.
         *
         * @name Phaser.Curves.EllipseCurve#_clockwise
         * @type {boolean}
         * @private
         * @since 3.0.0
         */
        this._clockwise = clockwise;

        /**
         * The rotation of the arc.
         *
         * @name Phaser.Curves.EllipseCurve#_rotation
         * @type {number}
         * @private
         * @since 3.0.0
         */
        this._rotation = DegToRad(rotation);
    },

    /**
     * Gets the starting point on the curve.
     *
     * @method Phaser.Curves.EllipseCurve#getStartPoint
     * @since 3.0.0
     *
     * @param {Phaser.Math.Vector2} [out] - A Vector2 object to store the result in. If not given will be created.
     *
     * @return {Phaser.Math.Vector2} The coordinates of the point on the curve. If an `out` object was given this will be returned.
     */
    getStartPoint: function (out)
    {
        if (out === undefined) { out = new Vector2(); }

        return this.getPoint(0, out);
    },

    /**
     * [description]
     *
     * @method Phaser.Curves.EllipseCurve#getResolution
     * @since 3.0.0
     *
     * @param {number} divisions - [description]
     *
     * @return {number} [description]
     */
    getResolution: function (divisions)
    {
        return divisions * 2;
    },

    /**
     * Get point at relative position in curve according to length.
     *
     * @method Phaser.Curves.EllipseCurve#getPoint
     * @since 3.0.0
     *
     * @param {float} t - The position along the curve to return. Where 0 is the start and 1 is the end.
     * @param {Phaser.Math.Vector2} [out] - A Vector2 object to store the result in. If not given will be created.
     *
     * @return {Phaser.Math.Vector2} The coordinates of the point on the curve. If an `out` object was given this will be returned.
     */
    getPoint: function (t, out)
    {
        if (out === undefined) { out = new Vector2(); }

        var twoPi = Math.PI * 2;
        var deltaAngle = this._endAngle - this._startAngle;
        var samePoints = Math.abs(deltaAngle) < Number.EPSILON;

        // ensures that deltaAngle is 0 .. 2 PI
        while (deltaAngle < 0)
        {
            deltaAngle += twoPi;
        }

        while (deltaAngle > twoPi)
        {
            deltaAngle -= twoPi;
        }

        if (deltaAngle < Number.EPSILON)
        {
            if (samePoints)
            {
                deltaAngle = 0;
            }
            else
            {
                deltaAngle = twoPi;
            }
        }

        if (this._clockwise && !samePoints)
        {
            if (deltaAngle === twoPi)
            {
                deltaAngle = - twoPi;
            }
            else
            {
                deltaAngle = deltaAngle - twoPi;
            }
        }

        var angle = this._startAngle + t * deltaAngle;
        var x = this.p0.x + this._xRadius * Math.cos(angle);
        var y = this.p0.y + this._yRadius * Math.sin(angle);

        if (this._rotation !== 0)
        {
            var cos = Math.cos(this._rotation);
            var sin = Math.sin(this._rotation);

            var tx = x - this.p0.x;
            var ty = y - this.p0.y;

            // Rotate the point about the center of the ellipse.
            x = tx * cos - ty * sin + this.p0.x;
            y = tx * sin + ty * cos + this.p0.y;
        }

        return out.set(x, y);
    },

    /**
     * Sets the horizontal radius of this curve.
     *
     * @method Phaser.Curves.EllipseCurve#setXRadius
     * @since 3.0.0
     *
     * @param {number} value - The horizontal radius of this curve.
     *
     * @return {Phaser.Curves.EllipseCurve} This curve object.
     */
    setXRadius: function (value)
    {
        this.xRadius = value;

        return this;
    },

    /**
     * Sets the vertical radius of this curve.
     *
     * @method Phaser.Curves.EllipseCurve#setYRadius
     * @since 3.0.0
     *
     * @param {number} value - The vertical radius of this curve.
     *
     * @return {Phaser.Curves.EllipseCurve} This curve object.
     */
    setYRadius: function (value)
    {
        this.yRadius = value;

        return this;
    },

    /**
     * Sets the width of this curve.
     *
     * @method Phaser.Curves.EllipseCurve#setWidth
     * @since 3.0.0
     *
     * @param {number} value - The width of this curve.
     *
     * @return {Phaser.Curves.EllipseCurve} This curve object.
     */
    setWidth: function (value)
    {
        this.xRadius = value * 2;

        return this;
    },

    /**
     * Sets the height of this curve.
     *
     * @method Phaser.Curves.EllipseCurve#setHeight
     * @since 3.0.0
     *
     * @param {number} value - The height of this curve.
     *
     * @return {Phaser.Curves.EllipseCurve} This curve object.
     */
    setHeight: function (value)
    {
        this.yRadius = value * 2;

        return this;
    },

    /**
     * Sets the start angle of this curve.
     *
     * @method Phaser.Curves.EllipseCurve#setStartAngle
     * @since 3.0.0
     *
     * @param {number} value - The start angle of this curve, in radians.
     *
     * @return {Phaser.Curves.EllipseCurve} This curve object.
     */
    setStartAngle: function (value)
    {
        this.startAngle = value;

        return this;
    },

    /**
     * Sets the end angle of this curve.
     *
     * @method Phaser.Curves.EllipseCurve#setEndAngle
     * @since 3.0.0
     *
     * @param {number} value - The end angle of this curve, in radians.
     *
     * @return {Phaser.Curves.EllipseCurve} This curve object.
     */
    setEndAngle: function (value)
    {
        this.endAngle = value;

        return this;
    },

    /**
     * Sets if this curve extends clockwise or anti-clockwise.
     *
     * @method Phaser.Curves.EllipseCurve#setClockwise
     * @since 3.0.0
     *
     * @param {boolean} value - The clockwise state of this curve.
     *
     * @return {Phaser.Curves.EllipseCurve} This curve object.
     */
    setClockwise: function (value)
    {
        this.clockwise = value;

        return this;
    },

    /**
     * Sets the rotation of this curve.
     *
     * @method Phaser.Curves.EllipseCurve#setRotation
     * @since 3.0.0
     *
     * @param {number} value - The rotation of this curve, in radians.
     *
     * @return {Phaser.Curves.EllipseCurve} This curve object.
     */
    setRotation: function (value)
    {
        this.rotation = value;

        return this;
    },

    /**
     * [description]
     *
     * @name Phaser.Curves.EllipseCurve#x
     * @type {number}
     * @since 3.0.0
     */
    x: {

        get: function ()
        {
            return this.p0.x;
        },

        set: function (value)
        {
            this.p0.x = value;
        }

    },

    /**
     * [description]
     *
     * @name Phaser.Curves.EllipseCurve#y
     * @type {number}
     * @since 3.0.0
     */
    y: {

        get: function ()
        {
            return this.p0.y;
        },

        set: function (value)
        {
            this.p0.y = value;
        }

    },

    /**
     * [description]
     *
     * @name Phaser.Curves.EllipseCurve#xRadius
     * @type {number}
     * @since 3.0.0
     */
    xRadius: {

        get: function ()
        {
            return this._xRadius;
        },

        set: function (value)
        {
            this._xRadius = value;
        }

    },

    /**
     * [description]
     *
     * @name Phaser.Curves.EllipseCurve#yRadius
     * @type {number}
     * @since 3.0.0
     */
    yRadius: {

        get: function ()
        {
            return this._yRadius;
        },

        set: function (value)
        {
            this._yRadius = value;
        }

    },

    /**
     * [description]
     *
     * @name Phaser.Curves.EllipseCurve#startAngle
     * @type {number}
     * @since 3.0.0
     */
    startAngle: {

        get: function ()
        {
            return RadToDeg(this._startAngle);
        },

        set: function (value)
        {
            this._startAngle = DegToRad(value);
        }

    },

    /**
     * [description]
     *
     * @name Phaser.Curves.EllipseCurve#endAngle
     * @type {number}
     * @since 3.0.0
     */
    endAngle: {

        get: function ()
        {
            return RadToDeg(this._endAngle);
        },

        set: function (value)
        {
            this._endAngle = DegToRad(value);
        }

    },

    /**
     * [description]
     *
     * @name Phaser.Curves.EllipseCurve#clockwise
     * @type {boolean}
     * @since 3.0.0
     */
    clockwise: {

        get: function ()
        {
            return this._clockwise;
        },

        set: function (value)
        {
            this._clockwise = value;
        }

    },

    /**
     * [description]
     *
     * @name Phaser.Curves.EllipseCurve#rotation
     * @type {number}
     * @since 3.0.0
     */
    rotation: {

        get: function ()
        {
            return this._rotation;
        },

        set: function (value)
        {
            this._rotation = DegToRad(value);
        }

    },

    /**
     * [description]
     *
     * @method Phaser.Curves.EllipseCurve#toJSON
     * @since 3.0.0
     *
     * @return {JSONEllipseCurve} The JSON object containing this curve data.
     */
    toJSON: function ()
    {
        return {
            type: this.type,
            x: this.p0.x,
            y: this.p0.y,
            xRadius: this._xRadius,
            yRadius: this._yRadius,
            startAngle: RadToDeg(this._startAngle),
            endAngle: RadToDeg(this._endAngle),
            clockwise: this._clockwise,
            rotation: RadToDeg(this._rotation)
        };
    }

});

/**
 * [description]
 *
 * @function Phaser.Curves.EllipseCurve.fromJSON
 * @since 3.0.0
 *
 * @param {JSONEllipseCurve} data - The JSON object containing this curve data.
 *
 * @return {Phaser.Curves.EllipseCurve} [description]
 */
EllipseCurve.fromJSON = function (data)
{
    return new EllipseCurve(data);
};

module.exports = EllipseCurve;
