var expect = require('chai').expect;
var {assert} = require('chai');
var randomInteger = require('../lib/randomInteger');

describe("randomInteger", function () {
    it("should throw when passed a negative value", function () {
        expect(function () {
            randomInteger(-10);
        }).to.throw(Error, /must be a natural number/);
    });

    it("should throw when passed an unsafe integer", function () {
        expect(function () {
            randomInteger(Number.MAX_VALUE);
        }).to.throw(Error, /must be a safe integer/);
    });

    it("should return a random number less than the specified maximum and greater than 0", function () {
        var randomInts = [];

        for (var i = 0; i <= 1000; i++) {
            randomInts.push(randomInteger(16));
        }
        randomInts.forEach(e => {
            assert.isAtMost(e, 16);
            assert.isAtLeast(e, 0);
        });
    });

    it("number returned should be exclusive of maximum", function () {
        var randomInts = [];

        for (var i = 0; i <= 1000; i++) {
            randomInts.push(randomInteger(1));
        }
        expect(randomInts).not.to.include.members([1]);
    });

    it("number returned should be inclusive of 0", function () {
        var randomInts = [];

        for (var i = 0; i <= 1000; i++) {
            randomInts.push(randomInteger(1));
        }
        expect(randomInts).to.include.members([0]);
    });

    it("probability distribution should be highly uniform for our preferred dictionary length", function () {

        /*
            We want to ensure that there's a fairly uniform probability distribution
            across a given range, and that the generated values are not
            weighted too far from the mean value.

            We use the mean of the sample to determine whether there is a bias towards
            the start or end of the distribution (modulo bias would introduce a bias
            towards the start of the sample).

            We use the variance of the probability distribution to determine the 'spread'
            of probabilities from the mean - in our case, the more uniform the better - there
            should be a lower variance.
         */

        var SAMPLE_SIZE = 1000000;
        var SAMPLE_RANGE = 1000;

        // This value originates from testing the unbiased sample-discarding
        // method - the mean value fluctuates by this range.
        var ALLOWED_MEAN_DIVERGENCE = (SAMPLE_RANGE / 2) * 0.002;

        // The tolerable variance of the probability density - what's the biggest
        // difference in probability that we want to tolerate?
        // This value, like mean divergence, is derived from testing - this was the variance figure's observed
        // ceiling. Changing the sample size and range will likely require a change in this variance
        var ALLOWED_PDF_VARIANCE = 1.67e-7

        function getMeanValue(array) {
            return array.reduce((acc, val) => acc + val) / array.length;
        }

        this.timeout(5000);

        var sample = [];

        // Generate a sample of randomly generated numbers within our test range
        for (var i = 0; i <= SAMPLE_SIZE; i++) {
            sample.push(randomInteger(SAMPLE_RANGE));
        }

        // Get the mean generated value, and check that there's minimal movement from the mean value
        var mean = getMeanValue(sample);
        expect(mean).to.lessThan((SAMPLE_RANGE / 2) + ALLOWED_MEAN_DIVERGENCE)
        expect(mean).to.greaterThan((SAMPLE_RANGE / 2) - ALLOWED_MEAN_DIVERGENCE)

        // Work out the probability distribution of the randomInteger function
        var variance = getMeanValue(sample.map(function (num) {
            return Math.pow(num - mean, 2);
        }));
        var stdDev = Math.sqrt(variance)
        var pdfDistribution = new Array(SAMPLE_RANGE);

        // Generate a sample of probability distributions
        // for each value in the range, what is the probability that it will
        // be generated by our randomInteger function?
        for (var i = 0; i <= SAMPLE_RANGE; i++) {
            // calculate the probability density
            var m = stdDev * Math.sqrt(2 * Math.PI);
            var e = Math.exp(-Math.pow(i - mean, 2) / (2 * variance));
            pdfDistribution.push(e / m);
        }

        // get the variance in probabilities of our distribution
        var pdfMean = getMeanValue(pdfDistribution);
        var pdfVariance = getMeanValue(pdfDistribution.map(function (num) {
            return Math.pow(num - pdfMean, 2);
        }));

        expect(pdfVariance).is.lessThan(ALLOWED_PDF_VARIANCE)
    });
});