EntityIterator     = require "./iterators/entity_iterator"
ObjectIterator     = require "./iterators/object_iterator"
ArrayIterator      = require "./iterators/array_iterator"
NullIterator       = require "./iterators/null_iterator"
IteratorsIterator  = require "./iterators/iterators_iterator"

null_iterator      = new NullIterator()

# The base class of API model.
# 
# Provides facilities for traversing, and adding external behaviour
# 
class Entity

  # Part of mixins implementation taken from http://arcturo.github.io/library/coffeescript/03_classes.html
  # 
  @extend: (obj) ->
    for key, value of obj when key not in ['extended', 'included']
      @[key] = value

    obj.extended?.apply(@)
    this

  # Part of mixins implementation taken from http://arcturo.github.io/library/coffeescript/03_classes.html
  # 
  @include: (obj) ->
    for key, value of obj when key not in ['extended', 'included']
      @::[key] = value

    obj.included?.apply(@)
    this

  @accessors: (properties..., templates) ->
    camelize = (string) ->
      string.replace /(?:^|[-_])(\w)/g, (_, c) ->
        (if c then c.toUpperCase() else "")
    
    singularize = (string) ->
      string.replace /s$/, ""

    propertyToMethod = (prefix, property) ->
      prefix + camelize(property.replace(/^_+/, ""))

    propertyToSingularizedMethod = (prefix, property) ->
      singularize(propertyToMethod(prefix,property))

    proto = @::
    templates.forEach (template) ->
        switch template
          when "get"
            properties.forEach (prop) ->
              methodName = propertyToMethod("get", prop)
              proto[methodName] = -> 
                @[prop] 

          when "set"
            properties.forEach (prop) ->
              methodName = propertyToMethod("set", prop)
              proto[methodName] = (value) -> 
                if value instanceof Entity
                  value.setParent(@)                
                @[prop] = value

          when "get-key"
            properties.forEach (prop) ->
              methodName = propertyToSingularizedMethod("get", prop)
              proto[methodName] = (k) -> 
                m = @[prop] or {}
                m[k]

          when "put-key"
            properties.forEach (prop) ->
              methodName = propertyToSingularizedMethod("put", prop)
              proto[methodName] = (k, v) -> 
                if v instanceof Entity
                  v.setParent(@)
                m = @[prop] ?= {}
                m[k] = v

          when "delete-key"
            properties.forEach (prop) ->
              methodName = propertyToSingularizedMethod("delete", prop)
              proto[methodName] = (k) ->
                m = @[prop]
                v = m[k]
                if v instanceof Entity
                  v.setParent(undefined)
                delete m[k]
                v

          when "put-keys"
            properties.forEach (prop) ->
              methodName = propertyToMethod("put", prop)
              proto[methodName] = (obj) ->
                for k, v of obj
                  if v instanceof Entity
                    v.setParent(@)
                  m = @[prop]
                  m[k] = v
                prop

  @children: (children...) ->
    isArray = (array) ->
      not (not array or (not array.length or array.length is 0) or typeof array isnt "object" or not array.constructor or array.nodeType or array.item)
    
    iteratorFor = (object) ->
      if object instanceof Entity
        new EntityIterator(object)
      else if isArray(object)
        new ArrayIterator(object)
      else if typeof object is "object"
        new ObjectIterator(object)
      else
        null_iterator
  
    @::getChildrenIterator = ->
      if children.length == 0
        null_iterator
      else if children.length == 1
        iteratorFor(@[children[0]])
      else
        subIterators = []
        for child in children
          if @[child]
            subIterators.push(iteratorFor(@[child]))

        new IteratorsIterator(subIterators)


  @accessors "title", "description",  [ "get", "set" ]
  @accessors "annotations",  ["get-key", "put-key", "put-keys", "delete-key"]
  @accessors "locals",       ["get-key", "put-key", "put-keys", "delete-key"]

  @entity: (name) ->
    @::getEntityType = ->
      name

    @::isInstanceOfEntity = (type) ->
      type is name or super(name)

  constructor: (options) ->
    for k, v of options
      @[k] = v
    
  getEntityType: () ->
    "Entity"
  
  isInstanceOfEntity: (type) ->
    type is "Entity"

  # Create a composite traversable structure
  #

  getParent: ->
    @parent

  setParent: (parent) ->
    @parent = parent

  isRoot: () ->
    !! @parent

  getAncestors: ->
    ancestors = []
    e = @
    while parent = e.getParent()
      ancestors.unshift(parent)
      e = parent
    ancestors

  getChildrenIterator: ->
    null_iterator

  isLeaf: () ->
    not @getChildrenIterator().hasNext()

module.exports = Entity
