local function map(array, func)
  local new_array = {}
  for index, value in ipairs(array) do new_array[index] = func(value) end
  return new_array
end

local knight = {}
local modules= {}

function knight.module(name)
  if modules[name] then return modules[name] end

  modules[name] = {}
  local module = modules[name]
  local components = {}
  local dependency_sweep
  local dependencies_met
  local get_dependencies

  -- require creates an anonymous component
  modules[name].require = function(dependencies, callback)
    table.insert(components, {
      dependencies=dependencies,
      constructor=callback
    })
    dependency_sweep()
    return module
  end

  modules[name].provide = function(name, value)
    components[name] = {
      dependencies={},
      result=value,
      loaded=true
    }
    dependency_sweep()
    return module
  end

  modules[name].component = function(name, dependencies, constructor)
    components[name] = {
      dependencies=dependencies,
      constructor=constructor
    }
    dependency_sweep()
    return module
  end

  modules[name].create_component = function(name, dependencies)
    return components[name].constructor(unpack(dependencies))
  end

  dependency_sweep = function()
    local found_one = false
    for name, component in pairs(components) do
      local dependencies = component.dependencies

      if not component.loaded and dependencies_met(dependencies) then
        found_one = true
        component.result = component.constructor(unpack(get_dependencies(dependencies)))
        component.loaded = true
      end
    end

    if found_one then dependency_sweep() end
  end

  dependencies_met = function(dependencies)
    for _, name in pairs(dependencies) do
      if not (components[name] and components[name].loaded) then
        return false
      end
    end
    return true
  end

  get_dependencies = function(dependencies)
    return map(dependencies, function(dependency)
      if components[dependency] then
        return components[dependency].result
      end
    end)
  end

  return modules[name]
end

return knight
