/* Implementation of HTML Timers (setInterval/setTimeout) based on sleep.
 * @license MIT
 *
 * Copyright 2012 Kevin Locke <kevin@kevinlocke.name>
 * Modified by UnityBase team - added priority to realise the setImmediate 
 */
/*jslint bitwise: true, evil: true */

/**
 * Adds methods to implement the HTML5 WindowTimers interface on a given
 * object.
 *
 * Adds the following methods:
 *
 *  - clearInterval
 *  - clearTimeout
 *  - setInterval
 *  - setTimeout<
 *
 * See http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html
 * for the complete specification of these methods.
 *
 * @module WindowTimer
 */
var WindowTimer = {};

/**
 * @method makeWindowTimer
 *
 * @param {Object} target Object to which the methods should be added.
 * @param {Function} sleep A function which sleeps for a specified number of
 *     milliseconds.
 * @return {Function} The function which runs the scheduled timers.
 */
function makeWindowTimer(target, sleep) {
    "use strict";
 
    var counter = 1,
        inCallback = false,
        // Map handle -> timer
        timersByHandle = {},
        // Min-heap of timers by time then handle, index 0 unused
        timersByTime = [ null ];
 
    /** Compares timers based on scheduled time and handle. */
    function timerCompare(t1, t2) {
        // Note:  Only need less-than for our uses
        return t1.priority < t2.priority ? -1 : (t1.priority === t2.priority ?
            (t1.time < t2.time ? -1 :
                (t1.time === t2.time && t1.handle < t2.handle ? -1 : 0)) : 0);
    }
 
    /** Fix the heap invariant which may be violated at a given index */
    function heapFixDown(heap, i, lesscmp) {
        var j, tmp;
 
        j = i * 2;
        while (j < heap.length) {
            if (j + 1 < heap.length &&
                    lesscmp(heap[j + 1], heap[j]) < 0) {
                j = j + 1;
            }
 
            if (lesscmp(heap[i], heap[j]) < 0) {
                break;
            }
 
            tmp = heap[j];
            heap[j] = heap[i];
            heap[i] = tmp;
            i = j;
            j = i * 2;
        }
    }
 
    /** Fix the heap invariant which may be violated at a given index */
    function heapFixUp(heap, i, lesscmp) {
        var j, tmp;
        while (i > 1) {
            j = i >> 1;     // Integer div by 2
 
            if (lesscmp(heap[j], heap[i]) < 0) {
                break;
            }
 
            tmp = heap[j];
            heap[j] = heap[i];
            heap[i] = tmp;
            i = j;
        }
    }
 
    /** Remove the timer element from the heap */
    function heapPop(heap, lesscmp, timer) {
        for (let index = 1; index < heap.length - 1; index++) {
            if (heap[index] && heap[index].handle === timer.handle) {
                heap[index] = heap[heap.length - 1];
            }
        }
        //heap[1] = heap[heap.length - 1];
        heap.pop();
        heapFixDown(heap, 1, lesscmp);
    }
 
    /** Create a timer and schedule code to run at a given time */
    function addTimer(code, delay, repeat, argsIfFn, priority) {
        var handle, timer;
 
        if (typeof code !== "function") {
            code = String(code);
            argsIfFn = null;
        }

        delay = Number(delay) || 0;
        if (inCallback) {
            delay = Math.max(delay, 4);
        }
        // Note:  Must set handle after argument conversion to properly
        // handle conformance test in HTML5 spec.
        handle = counter;
        counter += 1;
 
        timer = {
            args: argsIfFn,
            cancel: false,
            code: code,
            handle: handle,
            repeat: repeat ? Math.max(delay, 4) : 0,
            time: new Date().getTime() + delay,
            priority: priority || 0
        };

        timersByHandle[handle] = timer;
        timersByTime.push(timer);
        heapFixUp(timersByTime, timersByTime.length - 1, timerCompare);
 
        return handle;
    }
 
    /** Cancel an existing timer */
    function cancelTimer(handle, repeat) {
        var timer;
 
        if (timersByHandle.hasOwnProperty(handle)) {
            timer = timersByHandle[handle];
            if (repeat === (timer.repeat > 0)) {
                timer.cancel = true;
            }
        }
    }
 
    function clearInterval(handle) {
        cancelTimer(handle, true);
    }
    target.clearInterval = clearInterval;
 
    function clearTimeout(handle) {
        cancelTimer(handle, false);
    }
    target.clearTimeout = clearTimeout;
 
    function setInterval(code, delay) {
        return addTimer(
            code,
            delay,
            true,
            Array.prototype.slice.call(arguments, 2)
        );
    }
    target.setInterval = setInterval;
 
    function setTimeout(code, delay) {
        return addTimer(
            code,
            delay,
            false,
            Array.prototype.slice.call(arguments, 2),
            0
        );
    }
    target.setTimeout = setTimeout;

    function setTimeout(code, delay) {
        return addTimer(
            code,
            delay,
            false,
            Array.prototype.slice.call(arguments, 2),
            0
        );
    }
    target.setTimeout = setTimeout;

    function setTimeoutWithPriority(code, delay, priority) {
        return addTimer(
            code,
            delay,
            false,
            Array.prototype.slice.call(arguments, 3),
            priority
        );
    }
    timerLoop.setTimeoutWithPriority = setTimeoutWithPriority;

    function timerLoop(nonblocking) {
        // on the way out, don't bother. it won't get fired anyway.
        if (process._exiting)
            return;

        var now, timer;
 
        // Note: index 0 unused in timersByTime
        while (timersByTime.length > 1) {
            timer = timersByTime[1];
 
            if (timer.cancel) {
                delete timersByHandle[timer.handle];
                heapPop(timersByTime, timerCompare, timer);
            } else {
                now = new Date().getTime();
                if (timer.time <= now) {
                    inCallback = true;
                    try {
                        if (typeof timer.code === "function") {
                            timer.code.apply(undefined, timer.args);
                        } else {
                            eval(timer.code);
                        }
                    } finally {
                        inCallback = false;
                    }
 
                    if (timer.repeat > 0 && !timer.cancel) {
                        timer.time += timer.repeat;
                        heapFixDown(timersByTime, 1, timerCompare);
                    } else {
                        delete timersByHandle[timer.handle];
                        heapPop(timersByTime, timerCompare, timer);
                    }
                } else if (!nonblocking) {
                    sleep(timer.time - now);
                } else {
                    return true;
                }
            }
        }
 
        return false;
    };
    return timerLoop;
}
 
if (typeof exports === "object") {
    exports.makeWindowTimer = makeWindowTimer;
}
 