/*
MIT Licence
Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola
https://github.com/YuzuJS/setImmediate/blob/f1ccbfdf09cb93aadf77c4aa749ea554503b9234/LICENSE.txt
*/

var nextHandle = 1; // Spec says greater than zero
var tasksByHandle = {};
var currentlyRunningATask = false;
var doc = global.document;
var registerImmediate;

export function setImmediate(callback) {
  // Callback can either be a function or a string
  if (typeof callback !== "function") {
    callback = new Function("" + callback);
  }
  // Copy function arguments
  var args = new Array(arguments.length - 1);
  for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i + 1];
  }
  // Store and register the task
  var task = { callback: callback, args: args };
  tasksByHandle[nextHandle] = task;
  registerImmediate(nextHandle);
  return nextHandle++;
}

export function clearImmediate(handle) {
    delete tasksByHandle[handle];
}

function run(task) {
    var callback = task.callback;
    var args = task.args;
    switch (args.length) {
    case 0:
        callback();
        break;
    case 1:
        callback(args[0]);
        break;
    case 2:
        callback(args[0], args[1]);
        break;
    case 3:
        callback(args[0], args[1], args[2]);
        break;
    default:
        callback.apply(undefined, args);
        break;
    }
}

function runIfPresent(handle) {
    // From the spec: "Wait until any invocations of this algorithm started before this one have completed."
    // So if we're currently running a task, we'll need to delay this invocation.
    if (currentlyRunningATask) {
        // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a
        // "too much recursion" error.
        setTimeout(runIfPresent, 0, handle);
    } else {
        var task = tasksByHandle[handle];
        if (task) {
            currentlyRunningATask = true;
            try {
                run(task);
            } finally {
                clearImmediate(handle);
                currentlyRunningATask = false;
            }
        }
    }
}

function installNextTickImplementation() {
    registerImmediate = function(handle) {
        process.nextTick(function () { runIfPresent(handle); });
    };
}

function canUsePostMessage() {
    // The test against `importScripts` prevents this implementation from being installed inside a web worker,
    // where `global.postMessage` means something completely different and can't be used for this purpose.
    if (global.postMessage && !global.importScripts) {
        var postMessageIsAsynchronous = true;
        var oldOnMessage = global.onmessage;
        global.onmessage = function() {
            postMessageIsAsynchronous = false;
        };
        global.postMessage("", "*");
        global.onmessage = oldOnMessage;
        return postMessageIsAsynchronous;
    }
}

function installPostMessageImplementation() {
    // Installs an event handler on `global` for the `message` event: see
    // * https://developer.mozilla.org/en/DOM/window.postMessage
    // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages

    var messagePrefix = "setImmediate$" + Math.random() + "$";
    var onGlobalMessage = function(event) {
        if (event.source === global &&
            typeof event.data === "string" &&
            event.data.indexOf(messagePrefix) === 0) {
            runIfPresent(+event.data.slice(messagePrefix.length));
        }
    };

    if (global.addEventListener) {
        global.addEventListener("message", onGlobalMessage, false);
    } else {
        global.attachEvent("onmessage", onGlobalMessage);
    }

    registerImmediate = function(handle) {
        global.postMessage(messagePrefix + handle, "*");
    };
}

function installMessageChannelImplementation() {
    var channel = new MessageChannel();
    channel.port1.onmessage = function(event) {
        var handle = event.data;
        runIfPresent(handle);
    };

    registerImmediate = function(handle) {
        channel.port2.postMessage(handle);
    };
}

function installReadyStateChangeImplementation() {
    var html = doc.documentElement;
    registerImmediate = function(handle) {
        // Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
        // into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
        var script = doc.createElement("script");
        script.onreadystatechange = function () {
            runIfPresent(handle);
            script.onreadystatechange = null;
            html.removeChild(script);
            script = null;
        };
        html.appendChild(script);
    };
}

function installSetTimeoutImplementation() {
    registerImmediate = function(handle) {
        setTimeout(runIfPresent, 0, handle);
    };
}

// If supported, we should attach to the prototype of global, since that is where setTimeout et al. live.
var attachTo = Object.getPrototypeOf && Object.getPrototypeOf(global);
attachTo = attachTo && attachTo.setTimeout ? attachTo : global;

// Don't get fooled by e.g. browserify environments.
if ({}.toString.call(global.process) === "[object process]") {
    // For Node.js before 0.9
    installNextTickImplementation();

} else if (canUsePostMessage()) {
    // For non-IE10 modern browsers
    installPostMessageImplementation();

} else if (global.MessageChannel) {
    // For web workers, where supported
    installMessageChannelImplementation();

} else if (doc && "onreadystatechange" in doc.createElement("script")) {
    // For IE 6–8
    installReadyStateChangeImplementation();

} else {
    // For older browsers
    installSetTimeoutImplementation();
}
export default {
  setTimeout: setTimeout,
  clearTimeout: clearTimeout
}
