'use strict';
var $ = require('./$'),
    LIBRARY = require('./$.library'),
    global = require('./$.global'),
    ctx = require('./$.ctx'),
    classof = require('./$.classof'),
    $def = require('./$.def'),
    isObject = require('./$.is-object'),
    anObject = require('./$.an-object'),
    aFunction = require('./$.a-function'),
    strictNew = require('./$.strict-new'),
    forOf = require('./$.for-of'),
    setProto = require('./$.set-proto').set,
    same = require('./$.same'),
    species = require('./$.species'),
    SPECIES = require('./$.wks')('species'),
    RECORD = require('./$.uid')('record'),
    asap = require('./$.microtask'),
    PROMISE = 'Promise',
    process = global.process,
    isNode = classof(process) == 'process',
    P = global[PROMISE],
    Wrapper;

var testResolve = function testResolve(sub) {
  var test = new P(function () {});
  if (sub) test.constructor = Object;
  return P.resolve(test) === test;
};

var useNative = (function () {
  var works = false;
  function P2(x) {
    var self = new P(x);
    setProto(self, P2.prototype);
    return self;
  }
  try {
    works = P && P.resolve && testResolve();
    setProto(P2, P);
    P2.prototype = $.create(P.prototype, { constructor: { value: P2 } });
    // actual Firefox has broken subclass support, test that
    if (!(P2.resolve(5).then(function () {}) instanceof P2)) {
      works = false;
    }
    // actual V8 bug, https://code.google.com/p/v8/issues/detail?id=4162
    if (works && require('./$.support-desc')) {
      var thenableThenGotten = false;
      P.resolve($.setDesc({}, 'then', {
        get: function get() {
          thenableThenGotten = true;
        }
      }));
      works = thenableThenGotten;
    }
  } catch (e) {
    works = false;
  }
  return works;
})();

// helpers
var isPromise = function isPromise(it) {
  return isObject(it) && (useNative ? classof(it) == 'Promise' : RECORD in it);
};
var sameConstructor = function sameConstructor(a, b) {
  // library wrapper special case
  if (LIBRARY && a === P && b === Wrapper) return true;
  return same(a, b);
};
var getConstructor = function getConstructor(C) {
  var S = anObject(C)[SPECIES];
  return S != undefined ? S : C;
};
var isThenable = function isThenable(it) {
  var then;
  return isObject(it) && typeof (then = it.then) == 'function' ? then : false;
};
var notify = function notify(record, isReject) {
  if (record.n) return;
  record.n = true;
  var chain = record.c;
  asap(function () {
    var value = record.v,
        ok = record.s == 1,
        i = 0;
    var run = function run(react) {
      var cb = ok ? react.ok : react.fail,
          ret,
          then;
      try {
        if (cb) {
          if (!ok) record.h = true;
          ret = cb === true ? value : cb(value);
          if (ret === react.P) {
            react.rej(TypeError('Promise-chain cycle'));
          } else if (then = isThenable(ret)) {
            then.call(ret, react.res, react.rej);
          } else react.res(ret);
        } else react.rej(value);
      } catch (err) {
        react.rej(err);
      }
    };
    while (chain.length > i) run(chain[i++]); // variable length - can't use forEach
    chain.length = 0;
    record.n = false;
    if (isReject) setTimeout(function () {
      var promise = record.p,
          handler,
          console;
      if (isUnhandled(promise)) {
        if (isNode) {
          process.emit('unhandledRejection', value, promise);
        } else if (handler = global.onunhandledrejection) {
          handler({ promise: promise, reason: value });
        } else if ((console = global.console) && console.error) {
          console.error('Unhandled promise rejection', value);
        }
      }record.a = undefined;
    }, 1);
  });
};
var isUnhandled = function isUnhandled(promise) {
  var record = promise[RECORD],
      chain = record.a || record.c,
      i = 0,
      react;
  if (record.h) return false;
  while (chain.length > i) {
    react = chain[i++];
    if (react.fail || !isUnhandled(react.P)) return false;
  }return true;
};
var $reject = function $reject(value) {
  var record = this;
  if (record.d) return;
  record.d = true;
  record = record.r || record; // unwrap
  record.v = value;
  record.s = 2;
  record.a = record.c.slice();
  notify(record, true);
};
var $resolve = function $resolve(value) {
  var record = this,
      then;
  if (record.d) return;
  record.d = true;
  record = record.r || record; // unwrap
  try {
    if (then = isThenable(value)) {
      asap(function () {
        var wrapper = { r: record, d: false }; // wrap
        try {
          then.call(value, ctx($resolve, wrapper, 1), ctx($reject, wrapper, 1));
        } catch (e) {
          $reject.call(wrapper, e);
        }
      });
    } else {
      record.v = value;
      record.s = 1;
      notify(record, false);
    }
  } catch (e) {
    $reject.call({ r: record, d: false }, e); // wrap
  }
};

// constructor polyfill
if (!useNative) {
  // 25.4.3.1 Promise(executor)
  P = function Promise(executor) {
    aFunction(executor);
    var record = {
      p: strictNew(this, P, PROMISE), // <- promise
      c: [], // <- awaiting reactions
      a: undefined, // <- checked in isUnhandled reactions
      s: 0, // <- state
      d: false, // <- done
      v: undefined, // <- value
      h: false, // <- handled rejection
      n: false // <- notify
    };
    this[RECORD] = record;
    try {
      executor(ctx($resolve, record, 1), ctx($reject, record, 1));
    } catch (err) {
      $reject.call(record, err);
    }
  };
  require('./$.mix')(P.prototype, {
    // 25.4.5.3 Promise.prototype.then(onFulfilled, onRejected)
    then: function then(onFulfilled, onRejected) {
      var S = anObject(anObject(this).constructor)[SPECIES];
      var react = {
        ok: typeof onFulfilled == 'function' ? onFulfilled : true,
        fail: typeof onRejected == 'function' ? onRejected : false
      };
      var promise = react.P = new (S != undefined ? S : P)(function (res, rej) {
        react.res = res;
        react.rej = rej;
      });
      aFunction(react.res);
      aFunction(react.rej);
      var record = this[RECORD];
      record.c.push(react);
      if (record.a) record.a.push(react);
      if (record.s) notify(record, false);
      return promise;
    },
    // 25.4.5.1 Promise.prototype.catch(onRejected)
    'catch': function _catch(onRejected) {
      return this.then(undefined, onRejected);
    }
  });
}

// export
$def($def.G + $def.W + $def.F * !useNative, { Promise: P });
require('./$.tag')(P, PROMISE);
species(P);
species(Wrapper = require('./$.core')[PROMISE]);

// statics
$def($def.S + $def.F * !useNative, PROMISE, {
  // 25.4.4.5 Promise.reject(r)
  reject: function reject(r) {
    return new this(function (res, rej) {
      rej(r);
    });
  }
});
$def($def.S + $def.F * (!useNative || testResolve(true)), PROMISE, {
  // 25.4.4.6 Promise.resolve(x)
  resolve: function resolve(x) {
    return isPromise(x) && sameConstructor(x.constructor, this) ? x : new this(function (res) {
      res(x);
    });
  }
});
$def($def.S + $def.F * !(useNative && require('./$.iter-detect')(function (iter) {
  P.all(iter)['catch'](function () {});
})), PROMISE, {
  // 25.4.4.1 Promise.all(iterable)
  all: function all(iterable) {
    var C = getConstructor(this),
        values = [];
    return new C(function (res, rej) {
      forOf(iterable, false, values.push, values);
      var remaining = values.length,
          results = Array(remaining);
      if (remaining) $.each.call(values, function (promise, index) {
        C.resolve(promise).then(function (value) {
          results[index] = value;
          --remaining || res(results);
        }, rej);
      });else res(results);
    });
  },
  // 25.4.4.4 Promise.race(iterable)
  race: function race(iterable) {
    var C = getConstructor(this);
    return new C(function (res, rej) {
      forOf(iterable, false, function (promise) {
        C.resolve(promise).then(res, rej);
      });
    });
  }
});

//# sourceMappingURL=es6.promise-compiled.js.map