# Component Definition Helpers
#
# We have customized the core Backbone.extend process to use a slightly
# different syntax, which allows us to intercept the component definition at
# various points, and maintain information about classes being defined, and
# the relationships between inherited classes, etc.
#
# Under the hood it isn't much more than Backbone.View.extend(@proto)  
#
# Luca provides a self-documenting component generation language which
# allows you to build the @proto property in a way which captures the intent
# of the interface being described.  
#
# Example:
#   myForm = MyApp.register    'MyForm'
# 
#   myForm.extends             'Luca.components.FormView'
#
#   myForm.triggers            'some:custom:hook'
#
#   myForm.publicMethods
#     publicMethod: ()-> ...
#
#   myForm.classMethods 
#     classMethod: ()-> ...
#
# This gives us the ability to inspect our component registry at run time,
# auto-generate nice documentation, build development tools, etc.

class ComponentDefinition
  constructor:(componentName, @autoRegister=true)->
    @namespace = Luca.util.namespace()
    @componentId = @componentName = componentName
    @superClassName = 'Luca.View' 
    @properties ||= {}
    @_classProperties ||= {}

    if componentName.match(/\./)
      @namespaced = true
      parts = componentName.split('.')
      @componentId = parts.pop()
      @namespace = parts.join('.')

      # automatically add the namespace to the namespace registry
      Luca.registry.addNamespace( parts.join('.') )

    Luca.define.__definitions.push(@)

  @create: (componentName, autoRegister=Luca.config.autoRegister)->
    new ComponentDefinition(componentName, autoRegister)

  isValid: ()->
    return false unless _.isObject(@properties) 
    return false unless Luca.util.resolve(@superClassName)?
    return false unless @componentName?
    true

  isDefined: ()->
    @defined is true

  isOpen: ()->
    !!(@isValid() and not @isDefined())

  meta: (key, value)->
    metaKey = @namespace + '.' + @componentId
    metaKey = metaKey.replace(/^\./,'')
    data = Luca.registry.addMetaData(metaKey, key, value)

    @properties.componentMetaData = ()->
      Luca.registry.getMetaDataFor(metaKey)

  # allow for specifying the namespace
  in: (@namespace)-> @

  # allow for multiple ways of saying the same thing for readability purposes
  from: (@superClassName)-> 
    @
  extends: (@superClassName)-> 
    @
  extend: (@superClassName)-> 
    @

  aliases: (@_aliases...)->
    @meta("aliases", @_aliases)
    @

  replaces: (replaces...)->
    @_aliases ||= []
    for old in replaces
      @_aliases.push(old)
      Luca.util.deprecateComponent(old, @componentName)

  triggers: (hooks...)->
    _.defaults(@properties ||= {}, hooks: []) 
    for hook in hooks
      @properties.hooks.push(hook) 

    @properties.hooks = _.uniq(@properties.hooks)

    @meta("hooks", @properties.hooks)
    @

  includes: (includes...)->
    _.defaults(@properties ||= {}, include: []) 
    for include in includes
      @properties.include.push(include) 
    @properties.include = _.uniq(@properties.include)
    @meta("includes", @properties.include)
    @

  mixesIn: (concerns...)->
    _.defaults(@properties ||= {}, concerns: []) 
    for concern in concerns
      @properties.concerns.push(concern) 
    @properties.concerns = _.uniq(@properties.concerns)

    @meta("concerns", @properties.concerns)
    @

  contains: (components...)->
    _.defaults(@properties, components: [])
    @properties.components = components 
    @

  validatesConfigurationWith:(validationConfiguration={})->
    @meta "configuration validations", validationConfiguration
    @properties.validatable = true
    @

  beforeDefinition: (callback)->
    @_classProperties.beforeDefinition = callback  
    @

  afterDefinition: (callback)->
    @_classProperties.afterDefinition = callback  
    @

  classConfiguration: (properties={})->
    @meta("class configuration", _.keys(properties))
    _.defaults((@_classProperties||={}), properties)
    @

  configuration: (properties={})->
    @meta("public configuration", _.keys(properties) )
    _.defaults((@properties||={}), properties)
    @

  publicConfiguration: (properties={})->
    @meta("public configuration", _.keys(properties) )
    _.defaults((@properties||={}), properties)
    @

  privateConfiguration: (properties={})->
    @meta("private configuration", _.keys(properties) )
    _.defaults((@properties||={}), properties)
    @

  classInterface: (properties={})->
    @meta("class interface", _.keys(properties))
    _.defaults((@_classProperties||={}), properties)
    @

  methods: (properties={})->
    @meta("public interface", _.keys(properties) )
    _.defaults((@properties||={}), properties)
    @

  public: (properties={})->
    @meta("public interface", _.keys(properties) )
    _.defaults((@properties||={}), properties)
    @

  publicInterface: (properties={})->
    @meta("public interface", _.keys(properties) )
    _.defaults((@properties||={}), properties)
    @

  private: (properties={})->
    @meta("private interface", _.keys(properties) )
    _.defaults((@properties||={}), properties)
    @

  privateInterface: (properties={})->
    @meta("private interface", _.keys(properties) )
    _.defaults((@properties||={}), properties)
    @

  # This is the end of the chain. It MUST be called
  # in order for the component definition to be complete. 
  definePrototype: (properties={})->
    _.defaults((@properties||={}), properties)

    at = if @namespaced
      Luca.util.resolve(@namespace, (window || global))
    else
      (window||global)

    # automatically create the namespace
    if @namespaced and not at?
      eval("(window||global).#{ @namespace } = {}")
      at = Luca.util.resolve(@namespace,(window || global))

    @meta("super class name", @superClassName )
    @meta("display name", @componentName)

    @properties.displayName = @componentName
    
    @properties.componentMetaData = ()->
      Luca.registry.getMetaDataFor(@displayName)

    @_classProperties?.beforeDefinition?(@)

    definition = at[@componentId] = Luca.extend(@superClassName,@componentName, @properties)

    if _.isArray( @properties?.include )
      for include in @properties.include
        include = Luca.util.resolve(include) if _.isString(include)
        if not include?
          console.log "Attempt to include module failed. #{ include } not defined."
        _.extend(definition::, include)

    if @_aliases?
      for alias in @_aliases
        eval("#{ alias } = definition;")

    if @autoRegister is true 
      componentType = "view" if Luca.isViewPrototype( definition )

      if Luca.isCollectionPrototype( definition )
        Luca.Collection.namespaces ||= []
        Luca.Collection.namespaces.push( @namespace )
        componentType = "collection" 

      componentType = "model" if Luca.isModelPrototype( definition )

      # automatically register this with the component registry
      type_alias = _.string.underscored(@componentId)
      Luca.registerComponent( type_alias, @componentName, componentType)

    @defined = true

    unless _.isEmpty(@_classProperties)
      _.extend(definition, @_classProperties)



    definition?.afterDefinition?.call(definition, @)

    definition


# Aliases for the mixin definition
cd = ComponentDefinition::

cd.concerns = cd.behavesAs = cd.uses = cd.mixesIn 

# Aliases for the final call on the define proxy
cd.register = cd.defines = cd.defaults = cd.exports = cd.defaultProperties = cd.definePrototype

cd.defaultsTo = cd.enhance = cd.with = cd.definePrototype

cd.publicMethods = cd.publicInterface
cd.privateMethods = cd.privateInterface
cd.classProperites = cd.classMethods = cd.classInterface

_.extend (Luca.define = ComponentDefinition.create),
  __definitions: []
  incomplete: ()->
    _( Luca.define.__definitions ).select (definition)-> 
      definition.isOpen()
  close: ()->
    for open in Luca.define.incomplete()
      open.register() if open.isValid()
    Luca.define.__definitions.length = 0
  findDefinition: (componentName)->
    _( Luca.define.__definitions ).detect (definition)->
      definition.componentName is componentName

Luca.register = (componentName)->
  new ComponentDefinition(componentName, true)

_.mixin def: Luca.define

# The last method of the ComponentDefinition chain is always going to result in
# a call to Luca.extend.  Luca.extend wraps the call to Luca.View.extend,
# or Backbone.Collection.extend, and accepts the names of the extending,
# and extended classes as strings.  This allows us to maintain information
# and references to the classes and their prototypes, mainly for the purposes
# of introspection and development tools
Luca.extend = (superClassName, childName, properties={})->
  superClassName  = Luca.util.checkDeprecationStatusOf(superClassName)
  superClass      = Luca.util.resolve( superClassName, (window || global) )

  unless _.isFunction(superClass?.extend)
    throw "Error defining #{ childName }. #{ superClassName } is not a valid component to extend from"

  superMetaData = Luca.registry.getMetaDataFor(superClassName)
  superMetaData.descendants().push(childName)

  properties.displayName = childName

  properties._superClass = ()->
    superClass.displayName ||= superClassName
    superClass

  properties._super = (method, context=@, args=[])->
    # TODO: debug this better
    # protect against a stack too deep error in weird cases
    @_superClass().prototype[method]?.apply(context, args)

  definition = superClass.extend(properties)

  if _.isArray( properties?.include )
    for include in properties.include
      include = Luca.util.resolve(include) if _.isString(include)
      _.extend(definition::, include)

  definition