/**
 * A static class that provides extended mathematics functionality and constants.
 *
 * @class
 */
export class Mathf {

    static MC_MAX = 2 ** 31 - 1;
    static MC_MIN = -this.MC_MAX;

    /**
     * Returns the value clamped between the specified range.
     *
     * @param {Number} x - A numeric expression for a value to be clamped.
     * @param {Number} min - Minimum allowable value.
     * @param {Number} max - Maximum allowable value.
     * @returns {Number} Clamped value.
    */
    static clamp(x, min, max) {
        return Math.min(Math.max(min, x), max);
    }



    /**
     * Returns a random number between min and max (min inclusive, max exclusive).
     *
     * @param {Number} max - Maximum value.
     * @returns {Number} Random number between min and max (min inclusive, max exclusive).
    */
    static randomRange(min, max) {
        return min + (max - min) * Math.random();
    }
    /**
     * Returns a random integer between 0 and max (inclusive).
     *
     * @param {Number} max - Maximum value.
     * @returns {Number} Random integer between 0 and max (inclusive).
    */
    static randomInt(max) {
        return ((1 + max) * Math.random()) >> 0;
    }
    /**
     * Returns a random integer between min and max (inclusive).
     *
     * @param {Number} min - Minimum value.
     * @param {Number} max - Maximum value.
     * @returns {Number} Random integer between min and max (inclusive).
    */
    static randomIntRange(min, max) {
        return (min + (1 + max - min) * Math.random()) >> 0;
    }

}



/**
 * A static class that provides easing functionality and constants.
 *
 * @class
 */
export class Easing {

    /**
     * Returns the value linearly interpolated between the min and max value (default 0 to 1).
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @param {Number} [min=0] - Minimum range value.
     * @param {Number} [max=0] - Maximum range value.
     * @returns {Number} The value linearly interpolated between the min and max value.
    */
    static linear(x, min=0, max=1) {
        return x === 0
        ? min
        : x === 1
        ? max
        : min + (max - min) * x;
    }
    /**
     * Returns the value linearly interpolated between the chain of nodes (2 nodes minimum).
     *
     * @param {Number} x - A numeric expression for a value to be interpolated.
     * @param {Number[]} nodes - Array of nodes (2 minimum).
     * @returns {Number} The value linearly interpolated between the chain of nodes.
    */
    static linearChain(x, ...nodes) {
        const nl = nodes.length - 1;
        if (nl < 1) return x;

        const ni = (nl * x) >> 0;
        return x <= 0
            ? nodes[0]
            : x >= 1
            ? nodes[nl]
            : nodes[ni] + (nodes[ni + 1] - nodes[ni]) * (nl * x - ni);
    }
    /**
     * Returns the value interpolated along the bezier curve.
     *
     * @param {Number} x - A numeric expression for a value to be interpolated.
     * @param {Number} y1 - Value of 1st node.
     * @param {Number} y2 - Value of 2nd node.
     * @param {Number} y3 - Value of 3rd node.
     * @param {Number} y4 - Value of 4th node.
     * @returns {Number} The value linearly interpolated along the bezier curve.
    */
    static cubicBezier(x, y1, y2, y3, y4) {
        const p1 = ((1 - x) ** 3) * y1;
        const p2 = 3 * x * ((1 - x) ** 2) * y2;
        const p3 = 3 * (x ** 2) * (1 - x) * y3;
        const p4 = (x ** 3) * y4;

        return p1 + p2 + p3 + p4;
    }


    /**
     * Returns the value eased in with a sine curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in with a sine curve.
    */
    static inSine(x) {
        return 1 - Math.cos(x * Math.PI / 2);
    }
    /**
     * Returns the value eased out with a sine curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased out with a sine curve.
    */
    static outSine(x) {
        return Math.sin(x * Math.PI / 2);
    }
    /**
     * Returns the value eased in and out with a sine curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in and out with a sine curve.
    */
    static inOutSine(x) {
        return (1 - Math.cos(Math.PI * x)) / 2;
    }

    /**
     * Returns the value eased in with a quad curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in with a quad curve.
    */
    static inQuad(x) {
        return x * x;
    }
    /**
     * Returns the value eased out with a quad curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased out with a quad curve.
    */
    static outQuad(x) {
        return 1 - (1 - x) ** 2;
    }
    /**
     * Returns the value eased in and out with a quad curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in and out with a quad curve.
    */
    static inOutQuad(x) {
        return x < 0.5 ? 2 * x * x : 1 - ((-2 * x + 2) ** 2) / 2;
    }

    /**
     * Returns the value eased in with a cubic curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in with a cubic curve.
    */
    static inCubic(x) {
        return x * x * x;
    }
    /**
     * Returns the value eased out with a cubic curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased out with a cubic curve.
    */
    static outCubic(x) {
        return 1 - ((1 - x) ** 3);
    }
    /**
     * Returns the value eased in and out with a cubic curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in and out with a cubic curve.
    */
    static inOutCubic(x) {
        return x < 0.5 ? 4 * x * x * x : 1 - ((-2 * x + 2) ** 3) / 2;
    }

    /**
     * Returns the value eased in with a quart curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in with a quart curve.
    */
    static inQuart(x) {
        return x ** 4;
    }
    /**
     * Returns the value eased out with a quart curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased out with a quart curve.
    */
    static outQuart(x) {
        return 1 - ((1 - x) ** 4);
    }
    /**
     * Returns the value eased in and out with a quart curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in and out with a quart curve.
    */
    static inOutQuart(x) {
        return x < 0.5 ? 8 * (x ** 4) : 1 - ((-2 * x + 2) ** 4) / 2;
    }
    
    /**
     * Returns the value eased in with a quint curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in with a quint curve.
    */
    static inQuint(x) {
        return x ** 5;
    }
    /**
     * Returns the value eased out with a quint curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased out with a quint curve.
    */
    static outQuint(x) {
        return 1 - ((1 - x) ** 5);
    }
    /**
     * Returns the value eased in and out with a quint curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in and out with a quint curve.
    */
    static inOutQuint(x) {
        return x < 0.5 ? 16 * (x ** 5) : 1 - ((-2 * x + 2) ** 5) / 2;
    }
    
    /**
     * Returns the value eased in with a expo curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in with a expo curve.
    */
    static inExpo(x) {
        return x === 0 ? 0 : Math.pow(2, 10 * x - 10);
    }
    /**
     * Returns the value eased out with a expo curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased out with a expo curve.
    */
    static outExpo(x) {
        return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);
    }
    /**
     * Returns the value eased in and out with a expo curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in and out with a expo curve.
    */
    static inOutExpo(x) {
        return x === 0
            ? 0
            : x === 1
            ? 1
            : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2
            : (2 - Math.pow(2, -20 * x + 10)) / 2;
    }

    /**
     * Returns the value eased in with a circ curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in with a circ curve.
    */
    static inCirc(x) {
        return 1 - Math.sqrt(1 - (x ** 2));
    }
    /**
     * Returns the value eased out with a circ curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased out with a circ curve.
    */
    static outCirc(x) {
        return Math.sqrt(1 - ((x - 1) ** 2));
    }
    /**
     * Returns the value eased in and out with a circ curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in and out with a circ curve.
    */
    static inOutCirc(x) {
        return x < 0.5
            ? (1 - Math.sqrt(1 - ((2 * x) ** 2))) / 2
            : (Math.sqrt(1 - ((-2 * x + 2) ** 2)) + 1) / 2;
    }

    /**
     * Returns the value eased in with a back curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in with a back curve.
    */
    static inBack(x) {
        const c1 = 1.70158;

        return (1 + c1) * x * x * x - c1 * x * x;
    }
    /**
     * Returns the value eased out with a back curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased out with a back curve.
    */
    static outBack(x) {
        const c1 = 1.70158;

        return 1 + (c1 + 1) * ((x - 1) ** 3) + c1 * ((x - 1) ** 2);
    }
    /**
     * Returns the value eased in and out with a back curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in and out with a back curve.
    */
    static inOutBack(x) {
        const c1 = 1.70158;
        const c2 = c1 * 1.525;

        return x < 0.5
            ? (4 * x * x * ((c2 + 1) * 2 * x - c2)) / 2
            : (((2 * x - 2) ** 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
    }
    
    /**
     * Returns the value eased in with an elastic curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in with an elastic curve.
    */
    static inElastic(x) {
        const c1 = (2 * Math.PI) / 3;

        return x === 0
            ? 0
            : x === 1
            ? 1
            : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c1);
    }
    /**
     * Returns the value eased out with an elastic curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased out with an elastic curve.
    */
    static outElastic(x) {
        const c1 = (2 * Math.PI) / 3;

        return x === 0
            ? 0
            : x === 1
            ? 1
            : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c1) + 1;
    }
    /**
     * Returns the value eased in and out with an elastic curve.
     *
     * @param {Number} x - A numeric expression for a value to be eased.
     * @returns {Number} The value eased in and out with an elastic curve.
    */
    static inOutElastic(x) {
        const c1 = (2 * Math.PI) / 4.5;

        return x === 0
            ? 0
            : x === 1
            ? 1
            : x < 0.5
            ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c1)) / 2
            : (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c1)) / 2 + 1;
    }
    
}