export class RandomSFC {
    private static DEFAULT_SEED = 'Cookies';
    private static SEEDS: string[] = [];
    private static randomCache: {
        [key: string]: sfc32Func;
    } = {};

    static default() {
        return RandomSFC.random(RandomSFC.DEFAULT_SEED);
    }

    static random(seed?: string, refresh?: boolean): number {
        const lookupSeed = seed || RandomSFC.getLastSeed();
        if (!RandomSFC.randomCache[lookupSeed] || refresh) {
            return RandomSFC.buildRandom(lookupSeed)();
        }
        return RandomSFC.randomCache[lookupSeed]();
    }

    static range(range: number = 1000, seed?: string): number {
        const random = RandomSFC.random(seed);
        return Math.round(random * range);
    }

    private static getLastSeed() {
        return RandomSFC.SEEDS[RandomSFC.SEEDS.length - 1] || RandomSFC.DEFAULT_SEED;
    }

    private static buildRandom(seed: string) {
        const hash = RandomSFC.cyrb128(seed);
        const seedFunc = RandomSFC.sfc32(hash[0], hash[1], hash[2], hash[3]);
        RandomSFC.SEEDS.push(seed);
        return RandomSFC.randomCache[seed] = seedFunc;
    }
    
    /**
     * Hash function for turning strings into 32-bit number array
     * @param str to hash string
     * @returns length 4 number array
     */
    private static cyrb128(str: string): [number, number, number, number] {
        let h1 = 1779033703, h2 = 3144134277,
            h3 = 1013904242, h4 = 2773480762;
        for (let i = 0, k; i < str.length; i++) {
            k = str.charCodeAt(i);
            h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
            h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
            h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
            h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
        }
        h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
        h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
        h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
        h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
        h1 ^= (h2 ^ h3 ^ h4), h2 ^= h1, h3 ^= h1, h4 ^= h1;
        return [h1>>>0, h2>>>0, h3>>>0, h4>>>0];
    }

    /**
     * Create a function for generating sudo-random numbers. 
     * 
     * Below default values are provided from 
     * https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number
     * @param a 32-bit number, recommend values from hash or default (0x9E3779B9) Phi
     * @param b 32-bit number, recommend values from hash or default (0x243F6A88) Pi
     * @param c 32-bit number, recommend values from hash or default (0xB7E15162) E
     * @param d 32-bit number, recommend values from hash or default ()
     * @returns sudo-random number function from seeded value
     */
    private static sfc32(a: number, b: number, c: number, d: number): sfc32Func {
        return function() {
            a |= 0; b |= 0; c |= 0; d |= 0;
            let t = (a + b | 0) + d | 0;
            d = d + 1 | 0;
            a = b ^ b >>> 9;
            b = c + (c << 3) | 0;
            c = (c << 21 | c >>> 11);
            c = c + t | 0;
            return (t >>> 0) / 4294967296;
        }
    }
}

type sfc32Func = () => number;