var isPromise = require('is-promise')

var nextTick
if (typeof setImediate === 'function') nextTick = setImediate
else if (typeof process === 'object' && process && process.nextTick) nextTick = process.nextTick
else nextTick = function (cb) { setTimeout(cb, 0) }

var extensions = []

module.exports = Promise
function Promise(fn) {
  if (!(this instanceof Promise)) {
    return fn ? new Promise(fn) : defer()
  }
  if (typeof fn !== 'function') {
    throw new TypeError('fn is not a function')
  }

  var state = {
    isResolved: false,
    isSettled: false,
    isFulfilled: false,
    value: null,
    waiting: [],
    running: false
  }

  function _resolve(val) {
    resolve(state, val);
  }
  function _reject(err) {
    reject(state, err);
  }
  this.then = function _then(onFulfilled, onRejected) {
    return then(state, onFulfilled, onRejected);
  }

  _resolve.fulfill = deprecate(_resolve, 'resolver.fulfill(x)', 'resolve(x)')
  _resolve.reject = deprecate(_reject, 'resolver.reject', 'reject(x)')

  try {
    fn(_resolve, _reject)
  } catch (ex) {
    _reject(ex)
  }
}

function resolve(promiseState, value) {
  if (promiseState.isResolved) return
  if (isPromise(value)) {
    assimilate(promiseState, value)
  } else {
    settle(promiseState, true, value)
  }
}

function reject(promiseState, reason) {
  if (promiseState.isResolved) return
  settle(promiseState, false, reason)
}

function then(promiseState, onFulfilled, onRejected) {
  return new Promise(function (resolve, reject) {
    function done(next, skipTimeout) {
      var callback = promiseState.isFulfilled ? onFulfilled : onRejected
      if (typeof callback === 'function') {
        function timeoutDone() {
          var val
          try {
            val = callback(promiseState.value)
          } catch (ex) {
            reject(ex)
            return next(true)
          }
          resolve(val)
          next(true)
        }
        if (skipTimeout) timeoutDone()
        else nextTick(timeoutDone)
      } else if (promiseState.isFulfilled) {
        resolve(promiseState.value)
        next(skipTimeout)
      } else {
        reject(promiseState.value)
        next(skipTimeout)
      }
    }
    promiseState.waiting.push(done)
    if (promiseState.isSettled && !promiseState.running) processQueue(promiseState)
  })
}

function processQueue(promiseState) {
  function next(skipTimeout) {
    if (promiseState.waiting.length) {
      promiseState.running = true
      promiseState.waiting.shift()(next, skipTimeout)
    } else {
      promiseState.running = false
    }
  }
  next(false)
}

function settle(promiseState, isFulfilled, value) {
  if (promiseState.isSettled) return

  promiseState.isResolved = promiseState.isSettled = true
  promiseState.value = value
  promiseState.isFulfilled = isFulfilled

  processQueue(promiseState)
}

function assimilate(promiseState, thenable) {
  try {
    promiseState.isResolved = true
    thenable.then(function (res) {
      if (isPromise(res)) {
        assimilate(promiseState, res)
      } else {
        settle(promiseState, true, res)
      }
    }, function (err) {
      settle(promiseState, false, err)
    })
  } catch (ex) {
    settle(promiseState, false, ex)
  }
}

Promise.use = function (extension) {
  extensions.push(extension)
}


function deprecate(method, name, alternative) {
  return function () {
    var err = new Error(name + ' is deprecated use ' + alternative)
    if (typeof console !== 'undefined' && console && typeof console.warn === 'function') {
      console.warn(name + ' is deprecated use ' + alternative)
      if (err.stack) console.warn(err.stack)
    } else {
      nextTick(function () {
        throw err
      })
    }
    method.apply(this, arguments)
  }
}
function defer() {
  var err = new Error('promise.defer() is deprecated')
  if (typeof console !== 'undefined' && console && typeof console.warn === 'function') {
    console.warn('promise.defer() is deprecated')
    if (err.stack) console.warn(err.stack)
  } else {
    nextTick(function () {
      throw err
    })
  }
  var resolver
  var promise = new Promise(function (res) { resolver = res })
  return {resolver: resolver, promise: promise}
}