"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Seedable random number generator functions.
 * @version 1.0.0
 * @license Public Domain
 *
 * @example
 * var rng = new RNG('Example');
 * rng.random(40, 50);  // =>  42
 * rng.uniform();       // =>  0.7972798995050903
 * rng.normal();        // => -0.6698504543216376
 * rng.exponential();   // =>  1.0547367609131555
 * rng.poisson(4);      // =>  2
 * rng.gamma(4);        // =>  2.781724687386858
 */
/**
 * Get the underlying bytes of this string.
 * @return {Array} An array of bytes
 */
function getBytes(str) {
    let output = [];
    for (let i = 0; i < str.length; i++) {
        let c = str.charCodeAt(i);
        const bytes = [];
        do {
            bytes.push(c & 0xFF); // tslint:disable-line:no-bitwise
            c = c >> 8; // tslint:disable-line:no-bitwise
        } while (c > 0);
        output = output.concat(bytes.reverse());
    }
    return output;
}
class RC4 {
    /**
     * @param {String} seed A string to seed the generator.
     * @constructor
     */
    constructor(seed) {
        this.s = new Array(256);
        this.i = 0;
        this.j = 0;
        for (let i = 0; i < 256; i++) {
            this.s[i] = i;
        }
        if (seed) {
            this.mix(seed);
        }
    }
    _swap(i, j) {
        const tmp = this.s[i];
        this.s[i] = this.s[j];
        this.s[j] = tmp;
    }
    /**
     * Mix additional entropy into this generator.
     * @param {String} seed
     */
    mix(seed) {
        const input = getBytes(seed);
        let j = 0;
        for (let i = 0; i < this.s.length; i++) {
            j += this.s[i] + input[i % input.length];
            j %= 256;
            this._swap(i, j);
        }
    }
    /**
     * @return The next byte of output from the generator.
     */
    next() {
        this.i = (this.i + 1) % 256;
        this.j = (this.j + this.s[this.i]) % 256;
        this._swap(this.i, this.j);
        return this.s[(this.s[this.i] + this.s[this.j]) % 256];
    }
}
class RNG {
    /**
     * Create a new random number generator with optional seed. If the
     * provided seed is a function (i.e. Math.random) it will be used as
     * the uniform number generator.
     * @param seed An arbitrary object used to seed the generator.
     * @constructor
     */
    constructor(seed) {
        // this.seed = seed;
        // if (seed == null) {
        //     seed = (Math.random() + Date.now()).toString();
        //     //window.console.log("setting random seed "+seed);
        //     //print_call_stack();
        // } else if (typeof seed === 'function') {
        //     // Use it as a uniform number generator
        //     this.uniform = seed;
        //     this.nextByte = function() {
        //         return ~~(this.uniform() * 256);
        //     };
        //     seed = null;
        // } else if (Object.prototype.toString.call(seed) !== '[object String]') {
        //     seed = JSON.stringify(seed);
        // } else {
        //     //window.console.log("setting seed "+seed);
        //     //print_call_stack();
        // }
        this._normal = null;
        this._state = new RC4(JSON.stringify(seed));
    }
    /**
     * @return {number} Uniform random number between 0 and 255.
     */
    nextByte() {
        return this._state.next();
    }
    /**
     * @return {number} Uniform random number between 0 and 1.
     */
    uniform() {
        const BYTES = 7; // 56 bits to make a 53-bit double
        let output = 0;
        for (let i = 0; i < BYTES; i++) {
            output *= 256;
            output += this.nextByte();
        }
        return output / (Math.pow(2, BYTES * 8) - 1);
    }
    /**
     * Produce a random integer within [n, m).
     * @param {number} [n=0]
     * @param {number} m
     *
     */
    random(n, m) {
        if (n == null) {
            return this.uniform();
        }
        else if (m == null) {
            m = n;
            n = 0;
        }
        return n + Math.floor(this.uniform() * (m - n));
    }
    /**
     * Generates numbers using this.uniform() with the Box-Muller transform.
     * @return {number} Normally-distributed random number of mean 0, variance 1.
     */
    normal() {
        if (this._normal !== null) {
            const n = this._normal;
            this._normal = null;
            return n;
        }
        else {
            const x = this.uniform() || Math.pow(2, -53); // can't be exactly 0
            const y = this.uniform();
            this._normal = Math.sqrt(-2 * Math.log(x)) * Math.sin(2 * Math.PI * y);
            return Math.sqrt(-2 * Math.log(x)) * Math.cos(2 * Math.PI * y);
        }
    }
    /**
     * Generates numbers using this.uniform().
     * @return {number} Number from the exponential distribution, lambda = 1.
     */
    exponential() {
        return -Math.log(this.uniform() || Math.pow(2, -53));
    }
    /**
     * Generates numbers using this.uniform() and Knuth's method.
     * @param {number} [mean=1]
     * @return {number} Number from the Poisson distribution.
     */
    poisson(mean) {
        const L = Math.exp(-(mean || 1));
        let k = 0;
        let p = 1;
        do {
            k++;
            p *= this.uniform();
        } while (p > L);
        return k - 1;
    }
}
exports.RNG = RNG;
//# sourceMappingURL=rng.js.map