-configure = ($dependencies) ->
-  IOC = {}
-  IOC.configure = configure
+configure = (configuration) ->
 
-  constructDependencies = (props = {}) ->
-    $ = Object.create props
+  class Configuration
+    constructor: (properties = {}) ->
+      @[key] = val for own key,val of properties
 
-    $.createPromise ?= (resolver) ->
-      require("when").promise resolver
+    require: (name) ->
+      require name
 
-    $.asyncMap ?= (array, mapFn, done) ->
-      require("async").map array, mapFn, done
+    createPromise: (resolver) ->
+      @require("when").promise resolver
 
-    $.asyncWaterfall ?= (series, done) ->
-      require("async").waterfall series, done
+    asyncMap: (array, mapFn, done) ->
+      @require("async").map array, mapFn, done
 
-    return $
+    asyncWaterfall: (series, done) ->
+      @require("async").waterfall series, done
 
-  $ = constructDependencies $dependencies
+  class CustomError extends Error
+    name: undefined
 
-  class IOC.ComponentDependenciesNotDefined extends Error
-    constructor: (props = {}) ->
+    constructor: (properties = {}) ->
+      @[key] = val for own key,val of properties
+
       @name = @constructor.name
-      @componentName = props.componentName
-      @dependenciesNotDefined = props.dependenciesNotDefined
-      super @getMessage()
       Error.captureStackTrace @, @constructor
 
-    getMessage: ->
+  class ComponentDependenciesUndefined extends CustomError
+    componentName: undefined
+    undefinedDependencies: undefined
+
+    Object.defineProperty @prototype, "message", get: ->
       "Component '#{@componentName}' " +
-      "has undefined dependencies: [#{@dependenciesNotDefined}]"
+      "has undefined dependencies: [#{@undefinedDependencies}]"
 
-  class IOC.ComponentNotAcyclic extends Error
-    constructor: (props = {}) ->
-      @name = @constructor.name
-      @componentName = props.componentName
-      @cycles = props.cycles
-      @message = @getMessage()
-      Error.captureStackTrace @, @constructor
+  class ComponentCyclic extends CustomError
+    componentName: undefined
+    cycles: undefined
 
-    getMessage: ->
-      message = "Component '#{@componentName}' has cycles:\n"
-      @cycles.forEach (cycle) ->
-        message += "*  #{cycle.join(' > ')}"
-      message
+    inspectCycles: ->
+      mapFn = (cycle) -> "* #{cycle.join(" > ")}"
+      @cycles.map(mapFn).join("\n")
 
-  class IOC.Dictionary
-    constructor: (props = {}) ->
-      @definitions = props.definitions ?= {}
+    Object.defineProperty @prototype, "message", get: ->
+      "Component '#{@componentName}' has cycles:\n" +
+      @inspectCycles()
+
+  class ComponentUndefined extends CustomError
+    componentName: undefined
+
+    Object.defineProperty @prototype, "message", get: ->
+      "Component not defined: '#{@componentName}'"
+
+  class Dictionary
+    @definitions: undefined
+
+    constructor: (properties = {}) ->
+      @[key] = val for own key,val of properties
+      @definitions ?= {}
 
     set: (name, value) ->
       @definitions[name] = value

        @resolveDependencies.bind(this, container),
        @applyFactory.bind this

      @asyncWaterfall series, done


  return IOC

module.exports = configure()

    isUndefined: (name) ->
      @definitions[name] == undefined

  class Components extends Dictionary

    getUndefinedDependencies: (name) ->
      @get(name).dependencies.filter @isUndefined.bind(@)

    getCycles: (name, stack = []) ->
      return stack.concat(name) if stack.indexOf(name) isnt -1
      return [] unless @isDefined(name)

      reduceDependencyCycles = (memo, dependency) =>
        cycles = @getCycles dependency, stack
        memo.push cycles if cycles.length isnt 0
        memo

      stack = stack.concat name
      dependencies = @get(name).dependencies
      dependencies.reduce reduceDependencyCycles, []

    assertDefined: (name) ->
      return if @isDefined(name)
      throw new ComponentUndefined
        componentName: name

    assertAcyclic: (name) ->
      cycles = @getCycles name
      return if cycles.length is 0
      throw new ComponentCyclic
        componentName: name
        cycles: cycles

    assertDependenciesDefined: (name) ->
      undefinedDependencies = @getUndefinedDependencies name
      return if undefinedDependencies.length is 0
      throw new ComponentDependenciesUndefined
        componentName: name
        undefinedDependencies: undefinedDependencies

    validate: (name) ->
      @assertDefined name
      @assertAcyclic name
      @assertDependenciesDefined name

  class Promises extends Dictionary

  class Container
    components: undefined
    promises: undefined

    constructor: (properties = {}) ->
      @[key] = val for own key,val of properties

      @components ?= new Components
      @promises ?= new Promises

    createComponentPromise: (name) ->
      $.createPromise (resolve, reject) =>
        @components.get(name).resolve this, (error, result) ->
          return reject error if error?
          return resolve result

    ensureComponentPromiseSet: (name) ->
      return if @promises.isDefined name
      @promises.set name, @createComponentPromise(name)
      @promises.get name

    resolveComponent: (name, done) ->
      try
        @components.validate(name) unless @promises.isDefined(name)
        @ensureComponentPromiseSet(name).then done.bind(null, null), done
      catch error then done error

  class Component
    dependencies: undefined
    factory: undefined

    constructor: (properties = {}) ->
      @[key] = val for own key,val of properties

      @dependencies ?= []
      @factory ?= null

    resolveDependencies: (container, done) ->
      resolveComponent = container.resolveComponent.bind container
      $.asyncMap @dependencies, resolveComponent, done

    applyFactory: (args, done) ->
      @factory args..., done
