import Long from "../../../../../libraries/long.js";

export default class Random {

    static instances = 0;

    constructor(seed = Date.now() % 1000000000 ^ Random.instances++ * 1000) {
        this.multiplier = Long.fromString("25214903917");
        this.mask = Long.fromInt(1).shiftLeft(48).subtract(1);
        this.addend = Long.fromInt(0xB);
        this.doubleUnit = 1.1102230246251565E-16;
        this.maxInt = Long.fromInt(0x7fffffff);
        this.intMask = Long.fromInt(0x80000000);

        this.setSeed(seed);
    }

    nextFloat() {
        return this.next(24).toNumber() / (1 << 24);
    }

    nextDouble() {
        return this.next(26).shiftLeft(27).add(this.next(27)).toNumber() * this.doubleUnit;
    }

    nextInt(max = -1) {
        if (max === -1) {
            return this.next(32).toNumber();
        }

        let r = this.next(31);
        let m = max - 1;
        if ((max & m) === 0)  // i.e., bound is a power of 2
            r = Long.fromInt(max).multiply(r).shiftRightUnsigned(31).toNumber();
        else {
            r = r.toNumber();
            for (let u = r;
                 u - (r = u % max) + m < 0;
                 u = this.next(31).toNumber())
                ;
        }
        return r;
    }

    nextLong() {
        return this.next(32).shiftLeft(32).add(this.next(32));
    }

    next(bits) {
        let oldSeed;
        let nextSeed;
        do {
            oldSeed = this.seed;
            nextSeed = oldSeed.multiply(this.multiplier).add(this.addend).and(this.mask);
        } while (!this._compareAndSet(oldSeed, nextSeed));

        let num = nextSeed.shiftRight(48 - bits);
        return num.greaterThan(this.maxInt) ? num.or(this.intMask) : num; // Cast to integer
    }

    _compareAndSet(expect, update) {
        if (!this.seed.equals(expect)) {
            return false;
        }

        this.seed = update;
        return true;
    }

    setSeed(n) {
        let long;

        if (typeof n === "number") {
            long = Long.fromInt(n);
        } else if (n instanceof Long) {
            long = n;
        } else {
            long = Long.fromString(n);
        }

        this.seed = long.xor(this.multiplier).and(this.mask);
    }
}