local __G_ERROR_TRACK = __G_ERROR_TRACK
local M = {}
local tag = "promise"
local deferred = {}
deferred.__index = deferred
local PENDING = 0
local RESOLVING = 1
local REJECTING = 2
local RESOLVED = 3
local REJECTED = 4
local finish = function(deferred, state)
  state = state or REJECTED
  for i, f in ipairs(deferred.queue) do
    if state == RESOLVED then
      f:resolve(deferred.value)
    else
      f:reject(deferred.value)
    end
  end
  deferred.state = state
end
local isfunction = function(f)
  if type(f) == "table" then
    local mt = getmetatable(f)
    return mt ~= nil and type(mt.__call) == "function"
  end
  return type(f) == "function"
end
local promise = function(deferred, next, success, failure, nonpromisecb)
  if type(deferred) == "table" and type(deferred.value) == "table" and isfunction(next) then
    local called = false
    local ok, err = pcall(next, deferred.value, function(v)
      if called then
        return
      end
      called = true
      deferred.value = v
      success()
    end, function(v)
      if called then
        return
      end
      called = true
      deferred.value = v
      failure()
    end)
    if not ok and not called then
      deferred.value = err
      failure()
    elseif __G_ERROR_TRACK then
      __G_ERROR_TRACK(v)
    else
      print("ERROR", v)
    end
  else
    nonpromisecb()
  end
end

local function fire(deferred)
  local next
  promise(deferred, next, function()
    deferred.state = RESOLVING
    fire(deferred)
  end, function()
    deferred.state = REJECTING
    fire(deferred)
  end, function()
    local ok, v
    if deferred.state == RESOLVING and isfunction(deferred.success) then
      ok, v = pcall(deferred.success, deferred.value)
    elseif deferred.state == REJECTING and isfunction(deferred.failure) then
      ok, v = pcall(deferred.failure, deferred.value)
      if ok then
        deferred.state = RESOLVING
      end
    end
    if ok ~= nil then
      if ok then
        deferred.value = v
      else
        if __G_ERROR_TRACK then
          __G_ERROR_TRACK(v)
        else
          print("ERROR", v)
        end
        deferred.value = v
        return finish(deferred)
      end
    end
    if deferred.value == deferred then
      deferred.value = pcall(error, "resolving promise with itself")
      return finish(deferred)
    else
      promise(deferred, next, function()
        finish(deferred, RESOLVED)
      end, function(state)
        finish(deferred, state)
      end, function()
        finish(deferred, deferred.state == RESOLVING and RESOLVED)
      end)
    end
  end)
end

local resolve = function(deferred, state, value)
  if deferred.state == 0 then
    deferred.value = value
    deferred.state = state
    fire(deferred)
  end
  return deferred
end

function deferred:resolve(value)
  return resolve(self, RESOLVING, value)
end

function deferred:reject(value)
  return resolve(self, REJECTING, value)
end

function M.new(options)
  if isfunction(options) then
    local d = M.new()
    local ok, err = pcall(options, d)
    if not ok then
      d:reject(err)
    elseif __G_ERROR_TRACK then
      __G_ERROR_TRACK(v)
    else
      print("ERROR", v)
    end
    return d
  end
  options = options or {}
  local d
  d = {
    next = function(self, success, failure)
      local next = M.new({
        success = success,
        failure = failure,
        extend = options.extend
      })
      if d.state == RESOLVED then
        next:resolve(d.value)
      elseif d.state == REJECTED then
        next:reject(d.value)
      else
        table.insert(d.queue, next)
      end
      return next
    end,
    state = 0,
    queue = {},
    success = options.success,
    failure = options.failure
  }
  d = setmetatable(d, deferred)
  if isfunction(options.extend) then
    options.extend(d)
  end
  return d
end

function M.all(args)
  local d = M.new()
  if #args == 0 then
    return d:resolve({})
  end
  local method = "resolve"
  local pending = #args
  local results = {}
  local synchronizer = function(i, resolved)
    return function(value)
      results[i] = value
      if not resolved then
        method = "reject"
      end
      pending = pending - 1
      if pending == 0 then
        d[method](d, results)
      end
      return value
    end
  end
  for i = 1, pending do
    args[i]:next(synchronizer(i, true), synchronizer(i, false))
  end
  return d
end

function M.map(args, fn)
  local d = M.new()
  local results = {}
  
  local function donext(i)
    if i > #args then
      d:resolve(results)
    else
      fn(args[i]):next(function(res)
        table.insert(results, res)
        donext(i + 1)
      end, function(err)
        d:reject(err)
      end)
    end
  end
  
  donext(1)
  return d
end

function M.first(args)
  local d = M.new()
  for _, v in ipairs(args) do
    v:next(function(res)
      d:resolve(res)
    end, function(err)
      d:reject(err)
    end)
  end
  return d
end

return M
