module BABYLON {

    export interface IEasingFunction {
        ease(gradient: number): number;
    }

    export class EasingFunction implements IEasingFunction {
        //Statics
        private static _EASINGMODE_EASEIN = 0;
        private static _EASINGMODE_EASEOUT = 1;
        private static _EASINGMODE_EASEINOUT = 2;

        public static get EASINGMODE_EASEIN(): number {
            return EasingFunction._EASINGMODE_EASEIN;
        }

        public static get EASINGMODE_EASEOUT(): number {
            return EasingFunction._EASINGMODE_EASEOUT;
        }

        public static get EASINGMODE_EASEINOUT(): number {
            return EasingFunction._EASINGMODE_EASEINOUT;
        }

        // Properties
        private _easingMode = EasingFunction.EASINGMODE_EASEIN;

        public setEasingMode(easingMode: number) {
            var n = Math.min(Math.max(easingMode, 0), 2);
            this._easingMode = n;
        }
        public getEasingMode(): number {
            return this._easingMode;
        }

        public easeInCore(gradient: number): number {
            throw new Error('You must implement this method');
        }

        public ease(gradient: number): number {
            switch (this._easingMode) {
                case EasingFunction.EASINGMODE_EASEIN:
                    return this.easeInCore(gradient);
                case EasingFunction.EASINGMODE_EASEOUT:
                    return (1 - this.easeInCore(1 - gradient));
            }

            if (gradient >= 0.5) {
                return (((1 - this.easeInCore((1 - gradient) * 2)) * 0.5) + 0.5);
            }

            return (this.easeInCore(gradient * 2) * 0.5);
        }

    }

    export class CircleEase extends EasingFunction implements IEasingFunction {
        public easeInCore(gradient: number): number {
            gradient = Math.max(0, Math.min(1, gradient));
            return (1.0 - Math.sqrt(1.0 - (gradient * gradient)));
        }
    }

    export class BackEase extends EasingFunction implements IEasingFunction {
        constructor(public amplitude: number = 1) {
            super();
        }

        public easeInCore(gradient: number): number {
            var num = Math.max(0, this.amplitude);
            return (Math.pow(gradient, 3.0) - ((gradient * num) * Math.sin(3.1415926535897931 * gradient)));
        }
    }

    export class BounceEase extends EasingFunction implements IEasingFunction {
        constructor(public bounces: number= 3, public bounciness: number= 2) {
            super();
        }

        public easeInCore(gradient: number): number {
            var y = Math.max(0.0, this.bounces);
            var bounciness = this.bounciness;
            if (bounciness <= 1.0) {
                bounciness = 1.001;
            }
            var num9 = Math.pow(bounciness, y);
            var num5 = 1.0 - bounciness;
            var num4 = ((1.0 - num9) / num5) + (num9 * 0.5);
            var num15 = gradient * num4;
            var num65 = Math.log((-num15 * (1.0 - bounciness)) + 1.0) / Math.log(bounciness);
            var num3 = Math.floor(num65);
            var num13 = num3 + 1.0;
            var num8 = (1.0 - Math.pow(bounciness, num3)) / (num5 * num4);
            var num12 = (1.0 - Math.pow(bounciness, num13)) / (num5 * num4);
            var num7 = (num8 + num12) * 0.5;
            var num6 = gradient - num7;
            var num2 = num7 - num8;
            return (((-Math.pow(1.0 / bounciness, y - num3) / (num2 * num2)) * (num6 - num2)) * (num6 + num2));
        }
    }

    export class CubicEase extends EasingFunction implements IEasingFunction {
        public easeInCore(gradient: number): number {
            return (gradient * gradient * gradient);
        }
    }

    export class ElasticEase extends EasingFunction implements IEasingFunction {
        constructor(public oscillations: number= 3, public springiness: number= 3) {
            super();
        }

        public easeInCore(gradient: number): number {
            var num2;
            var num3 = Math.max(0.0, this.oscillations);
            var num = Math.max(0.0, this.springiness);

            if (num == 0) {
                num2 = gradient;
            }else {
                num2 = (Math.exp(num * gradient) - 1.0) / (Math.exp(num) - 1.0);
            }
            return (num2 * Math.sin(((6.2831853071795862 * num3) + 1.5707963267948966) * gradient));
        }
    }

    export class ExponentialEase  extends EasingFunction implements IEasingFunction {
        constructor(public exponent: number= 2) {
            super();
        }

        public easeInCore(gradient: number): number {
            if (this.exponent <= 0) {
                return gradient;
            }

            return ((Math.exp(this.exponent * gradient) - 1.0) / (Math.exp(this.exponent) - 1.0));
        }
    }

    export class PowerEase  extends EasingFunction implements IEasingFunction {
        constructor(public power: number= 2) {
            super();
        }

        public easeInCore(gradient: number): number {
            var y = Math.max(0.0, this.power);
            return Math.pow(gradient, y);
        }
    }

    export class QuadraticEase extends EasingFunction implements IEasingFunction {
        public easeInCore(gradient: number): number {
            return (gradient * gradient);

           

        }
    }

    export class QuarticEase extends EasingFunction implements IEasingFunction {
        public easeInCore(gradient: number): number {
            return (gradient * gradient * gradient * gradient);
        }
    }

    export class QuinticEase extends EasingFunction implements IEasingFunction {
        public easeInCore(gradient: number): number {
            return (gradient * gradient * gradient * gradient * gradient);
        }
    }

    export class SineEase  extends EasingFunction implements IEasingFunction {
        public easeInCore(gradient: number): number {
            return (1.0 - Math.sin(1.5707963267948966 * (1.0 - gradient)));
        }
    }

    export class BezierCurveEase extends EasingFunction implements IEasingFunction {
        constructor(public x1: number= 0, public y1: number= 0, public x2: number= 1, public y2: number= 1) {
            super();
        }

        public easeInCore(gradient: number): number {
            return BezierCurve.interpolate(gradient, this.x1, this.y1, this.x2, this.y2);
        }
    }
}
