import { clampNumber, interpolateWith, mapRange } from "./Math.utils";

/**
 * map representation
 */
export class Vault {
    constructor(name = "") {
        this.name = name;
        this.storage = new Map();
        /**
         * set key-value pair in storage
         * @param {String} key
         * @param {*} value
         */
        this.set = (key, value) => {
            this.storage.set(key, value);
        };
        /**
         * return element from storage
         * @param {String} key
         * @returns element (ref) or undefined
         */
        this.get = (key) => {
            return this.storage.get(key);
        };

        /**
         *
         * @param {String} key
         * @returns true/false
         */
        this.existsKey = (key) => {
            // return Object.keys(this.storage).includes(key);
            return [...this.storage.keys()].includes(key);
        };

        /**
         * remove key from storage
         * @param {String} key
         * @returns true/false
         */
        this.del = (key) => {
            return this.storage.delete(key);
        };

        /**
         * clears storage
         */
        this.clear = () => {
            this.storage.clear();
        };

        /**
         *
         * @param {*} value
         * @returns found value (ref) or false
         */
        this.find = (value) => {
            let t = [...this.storage.keys()];
            for (const i of t) {
                if (this.storage.get(i) === value) return this.storage.get(i);
            }
            return false;
        };

        /**
         * run 'for' loop through all storage and call 'fn(storage_element)'
         * @param {Function} fn
         */
        this.forAll = (fn) => {
            let keys = [...this.storage.keys()];
            for (const i of keys) {
                fn(this.storage.get(i));
            }
        };
    }
}

export class RGBColor {
    /**
     *
     * @param {Number} r
     * @param {Number} g
     * @param {Number} b
     */
    constructor(r, g, b) {
        this.r = r;
        this.g = g;
        this.b = b;

        /**
         *
         * @returns hex string
         */
        this.toHex = () => {
            return ((r << 16) + (g << 8) + b).toString(16).padStart(6, "0");
        };

        /**
         * converts color to Number
         * @returns Number
         */
        this.toNumber = () => {
            return Number("0x" + this.toHex());
        };

        /**
         *
         * @param {Number} num
         */
        this.multiplyByNumber = (num) => {
            return new RGBColor(
                clampNumber(Math.floor(num * this.r), 0, 255),
                clampNumber(Math.floor(num * this.g), 0, 255),
                clampNumber(Math.floor(num * this.b), 0, 255)
            );
        };
    }

    /**
     * 
     * @param {RGBColor} colorA
     * @param {RGBColor} colorB
     * @param {Number} t
     * @param {String} f interpolation type. default 'lin'
     * @returns
     */
    static interpolateColor(colorA, colorB, t, f = 'lin') {
        return new RGBColor(
            Math.floor(interpolateWith(colorA.r, colorB.r, t, f)),
            Math.floor(interpolateWith(colorA.g, colorB.g, t, f)),
            Math.floor(interpolateWith(colorA.b, colorB.b, t, f))
        );
    }
};

export class RGBCue {
    /**
     * 
     * @param {[RGBColor]} colors 
     * @param {[Number]} midpoints points in range [0, 1] 
     * @param {String} f interpolation type. default 'lin'
     */
    constructor(colors, midpoints, f = 'lin')
    {
        this.colors = colors;
        this.midpoints = midpoints;
        this.interpolationFunction = f;
    }

    /**
     * 
     * @param {Number} point 
     */
    getColorAt(point)
    {
        let idx = this.#getPointIndex(point);
        return RGBColor.interpolateColor(this.colors[idx-1], this.colors[idx], mapRange(point, this.midpoints[idx-1], this.midpoints[idx], 0.0, 1.0), this.interpolationFunction);
    }

    /**
     * 
     * @param {Number} point 
     */
    #getPointIndex(point)
    {
        for (let i = 0; i < this.midpoints.length; i++) {
            if(point < this.midpoints[i]) return i;
        }
    }
};

export class NumberCue {
        /**
     * 
     * @param {[Number]} numbers
     * @param {[Number]} midpoints points in range [0, 1] 
     * @param {String} f interpolation type. default 'lin'
     */
        constructor(numbers, midpoints, f = 'lin')
        {
            this.numbers = numbers;
            this.midpoints = midpoints;
            this.interpolationFunction = f;
        }
    
        /**
         * 
         * @param {Number} point 
         */
        getValueAt(point)
        {
            let idx = this.#getPointIndex(point);
            return interpolateWith(this.numbers[idx-1], this.numbers[idx], mapRange(point, this.midpoints[idx-1], this.midpoints[idx], 0.0, 1.0), this.interpolationFunction);
        }
    
        /**
         * 
         * @param {Number} point 
         */
        #getPointIndex(point)
        {
            for (let i = 0; i < this.midpoints.length; i++) {
                if(point < this.midpoints[i]) return i;
            }
        }
}
