/**
 * Promise polyfill v1.0.9
 * requires setImmediate
 *
 * © 2014–2015 Dmitry Korobkin
 * Released under the MIT license
 * github.com/Octane/Promise
 */
(function (global) {'use strict';

    var STATUS = '[[PromiseStatus]]';
    var VALUE = '[[PromiseValue]]';
    var ON_FUlFILLED = '[[OnFulfilled]]';
    var ON_REJECTED = '[[OnRejected]]';
    var ORIGINAL_ERROR = '[[OriginalError]]';
    var PENDING = 'pending';
    var INTERNAL_PENDING = 'internal pending';
    var FULFILLED = 'fulfilled';
    var REJECTED = 'rejected';
    var NOT_ARRAY = 'not an array.';
    var REQUIRES_NEW = 'constructor Promise requires "new".';
    var CHAINING_CYCLE = 'then() cannot return same Promise that it resolves.';

    var setImmediate = global.setImmediate || require('timers').setImmediate;
    var isArray = Array.isArray || function (anything) {
        return Object.prototype.toString.call(anything) == '[object Array]';
    };

    function InternalError(originalError) {
        this[ORIGINAL_ERROR] = originalError;
    }

    function isInternalError(anything) {
        return anything instanceof InternalError;
    }

    function isObject(anything) {
        //Object.create(null) instanceof Object → false
        return Object(anything) === anything;
    }

    function isCallable(anything) {
        return typeof anything == 'function';
    }

    function isPromise(anything) {
        return anything instanceof Promise;
    }

    function identity(value) {
        return value;
    }

    function thrower(reason) {
        throw reason;
    }

    function enqueue(promise, onFulfilled, onRejected) {
        if (!promise[ON_FUlFILLED]) {
            promise[ON_FUlFILLED] = [];
            promise[ON_REJECTED] = [];
        }
        promise[ON_FUlFILLED].push(onFulfilled);
        promise[ON_REJECTED].push(onRejected);
    }

    function clearAllQueues(promise) {
        delete promise[ON_FUlFILLED];
        delete promise[ON_REJECTED];
    }

    function callEach(queue) {
        var i;
        var length = queue.length;
        for (i = 0; i < length; i++) {
            queue[i]();
        }
    }

    function call(resolve, reject, value) {
        var anything = toPromise(value);
        if (isPromise(anything)) {
            anything.then(resolve, reject);
        } else if (isInternalError(anything)) {
            reject(anything[ORIGINAL_ERROR]);
        } else {
            resolve(value);
        }
    }

    function toPromise(anything) {
        var then;
        if (isPromise(anything)) {
            return anything;
        }
        if(isObject(anything)) {
            try {
                then = anything.then;
            } catch (error) {
                return new InternalError(error);
            }
            if (isCallable(then)) {
                return new Promise(function (resolve, reject) {
                    setImmediate(function () {
                        try {
                            then.call(anything, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    });
                });
            }
        }
        return null;
    }

    function resolvePromise(promise, resolver) {
        function resolve(value) {
            if (promise[STATUS] == PENDING) {
                fulfillPromise(promise, value);
            }
        }
        function reject(reason) {
            if (promise[STATUS] == PENDING) {
                rejectPromise(promise, reason);
            }
        }
        try {
            resolver(resolve, reject);
        } catch(error) {
            reject(error);
        }
    }

    function fulfillPromise(promise, value) {
        var queue;
        var anything = toPromise(value);
        if (isPromise(anything)) {
            promise[STATUS] = INTERNAL_PENDING;
            anything.then(
                function (value) {
                    fulfillPromise(promise, value);
                },
                function (reason) {
                    rejectPromise(promise, reason);
                }
            );
        } else if (isInternalError(anything)) {
            rejectPromise(promise, anything[ORIGINAL_ERROR]);
        } else {
            promise[STATUS] = FULFILLED;
            promise[VALUE] = value;
            queue = promise[ON_FUlFILLED];
            if (queue && queue.length) {
                clearAllQueues(promise);
                callEach(queue);
            }
        }
    }

    function rejectPromise(promise, reason) {
        var queue = promise[ON_REJECTED];
        promise[STATUS] = REJECTED;
        promise[VALUE] = reason;
        if (queue && queue.length) {
            clearAllQueues(promise);
            callEach(queue);
        }
    }

    function Promise(resolver) {
        var promise = this;
        if (!isPromise(promise)) {
            throw new TypeError(REQUIRES_NEW);
        }
        promise[STATUS] = PENDING;
        promise[VALUE] = undefined;
        resolvePromise(promise, resolver);
    }

    Promise.prototype = {

        constructor: Promise,

        then: function (onFulfilled, onRejected) {
            var promise = this;
            var nextPromise;
            onFulfilled = isCallable(onFulfilled) ? onFulfilled : identity;
            onRejected = isCallable(onRejected) ? onRejected : thrower;
            nextPromise = new Promise(function (resolve, reject) {
                function tryCall(func) {
                    var value;
                    try {
                        value = func(promise[VALUE]);
                    } catch (error) {
                        reject(error);
                        return;
                    }
                    if (value === nextPromise) {
                        reject(new TypeError(CHAINING_CYCLE));
                    } else {
                        call(resolve, reject, value);
                    }
                }
                function asyncOnFulfilled() {
                    setImmediate(tryCall, onFulfilled);
                }
                function asyncOnRejected() {
                    setImmediate(tryCall, onRejected);
                }
                switch (promise[STATUS]) {
                    case FULFILLED:
                        asyncOnFulfilled();
                        break;
                    case REJECTED:
                        asyncOnRejected();
                        break;
                    default:
                        enqueue(promise, asyncOnFulfilled, asyncOnRejected);
                }
            });
            return nextPromise;
        },

        'catch': function (onRejected) {
            return this.then(identity, onRejected);
        }

    };

    Promise.resolve = function (value) {
        var anything = toPromise(value);
        if (isPromise(anything)) {
            return anything;
        }
        return new Promise(function (resolve, reject) {
            if (isInternalError(anything)) {
                reject(anything[ORIGINAL_ERROR]);
            } else {
                resolve(value);
            }
        });
    };

    Promise.reject = function (reason) {
        return new Promise(function (resolve, reject) {
            reject(reason);
        });
    };

    Promise.race = function (values) {
        return new Promise(function (resolve, reject) {
            var i;
            var length;
            if (isArray(values)) {
                length = values.length;
                for (i = 0; i < length; i++) {
                    call(resolve, reject, values[i]);
                }
            } else {
                reject(new TypeError(NOT_ARRAY));
            }
        });
    };

    Promise.all = function (values) {
        return new Promise(function (resolve, reject) {
            var fulfilledCount = 0;
            var promiseCount = 0;
            var anything;
            var length;
            var value;
            var i;
            if (isArray(values)) {
                values = values.slice(0);
                length = values.length;
                for (i = 0; i < length; i++) {
                    value = values[i];
                    anything = toPromise(value);
                    if (isPromise(anything)) {
                        promiseCount++;
                        anything.then(
                            function (index) {
                                return function (value) {
                                    values[index] = value;
                                    fulfilledCount++;
                                    if (fulfilledCount == promiseCount) {
                                        resolve(values);
                                    }
                                };
                            }(i),
                            reject
                        );
                    } else if (isInternalError(anything)) {
                        reject(anything[ORIGINAL_ERROR]);
                    } else {
                        //[1, , 3] → [1, undefined, 3]
                        values[i] = value;
                    }
                }
                if (!promiseCount) {
                    resolve(values);
                }
            } else {
                reject(new TypeError(NOT_ARRAY));
            }
        });
    };

    if (typeof module != 'undefined' && module.exports) {
        module.exports = global.Promise || Promise;
    } else if (!global.Promise) {
        global.Promise = Promise;
    }

}(this));
