/* basic entropy accumulator */
var accum = (function() {

    var pool,    // randomness pool
        time,    // start timestamp
        last;    // last step timestamp

    /* initialize with default pool */
    function init() {
        pool = [];
        time = new Date().getTime();
        last = time;
        // use Math.random
        pool.push((Math.random() * 0xffffffff)|0);
        // use current time
        pool.push(time|0);
    }

    /* perform one step */
    function step() {
        if (!to)
            return;
        if (pool.length >= 255) { // stop at 255 values (1 more is added on fetch)
            stop();
            return;
        }
        var now = new Date().getTime();
        // use actual time difference
        pool.push(now-last);
        // always compute, occasionally use Math.random
        var rnd = (Math.random() * 0xffffffff)|0;
        if (now % 2)
            pool[pool.length-1] += rnd;
        last = now;
        to = setTimeout(step, 100+Math.random()*512); // use hypothetical time difference
    }

    var to = null;

    /* starts accumulating */
    function start() {
        if (to) return;
        to = setTimeout(step, 100+Math.random()*512);
        if (console.log)
            console.log("bcrypt-isaac: collecting entropy...");
        // install collectors
        if (typeof window !== 'undefined' && window && window.addEventListener)
            window.addEventListener("load", loadCollector, false),
            window.addEventListener("mousemove", mouseCollector, false),
            window.addEventListener("touchmove", touchCollector, false);
        else if (typeof document !== 'undefined' && document && document.attachEvent)
            document.attachEvent("onload", loadCollector),
            document.attachEvent("onmousemove", mouseCollector);
    }

    /* stops accumulating */
    function stop() {
        if (!to) return;
        clearTimeout(to); to = null;
        // uninstall collectors
        if (typeof window !== 'undefined' && window && window.removeEventListener)
            window.removeEventListener("load", loadCollector, false),
            window.removeEventListener("mousemove", mouseCollector, false),
            window.removeEventListener("touchmove", touchCollector, false);
        else if (typeof document !== 'undefined' && document && document.detachEvent)
            document.detachEvent("onload", loadCollector),
            document.detachEvent("onmousemove", mouseCollector);
    }

    /* fetches the randomness pool */
    function fetch() {
        // add overall time difference
        pool.push((new Date().getTime()-time)|0);
        var res = pool;
        init();
        if (console.log)
            console.log("bcrypt-isaac: using "+res.length+"/256 samples of entropy");
        // console.log(res);
        return res;
    }

    /* adds the current time to the top of the pool */
    function addTime() {
        pool[pool.length-1] += new Date().getTime() - time;
    }

    /* page load collector */
    function loadCollector() {
        if (!to || pool.length >= 255)
            return;
        pool.push(0);
        addTime();
    }

    /* mouse events collector */
    function mouseCollector(ev) {
        if (!to || pool.length >= 255)
            return;
        try {
            var x = ev.x || ev.clientX || ev.offsetX || 0,
                y = ev.y || ev.clientY || ev.offsetY || 0;
            if (x != 0 || y != 0)
                pool[pool.length-1] += ((x-mouseCollector.last[0]) ^ (y-mouseCollector.last[1])),
                addTime(),
                mouseCollector.last = [x,y];
        } catch (e) {}
    }
    mouseCollector.last = [0,0];

    /* touch events collector */
    function touchCollector(ev) {
        if (!to || pool.length >= 255)
            return;
        try {
            var touch = ev.touches[0] || ev.changedTouches[0];
            var x = touch.pageX || touch.clientX || 0,
                y = touch.pageY || touch.clientY || 0;
            if (x != 0 || y != 0)
                pool[pool.length-1] += (x-touchCollector.last[0]) ^ (y-touchCollector.last[1]),
                addTime(),
                touchCollector.last = [x,y];
        } catch (e) {}
    }
    touchCollector.last = [0,0];

    init();
    return {
        "start": start,
        "stop": stop,
        "fetch": fetch
    }

})();
