# `pluginClass` must store propery `$el` as jQuery object wrapped on the target DOM-object in his instance.

#= require ./base

# TODO minimize requirements
# requirements stats:
# 4 _.result
# 3 _.extend
# 2 _.isFunction
# 2 _.isObject
# 1 _.isString
# 1 _.isArray
# 1 _.bind
# 1 _.clone
# 1 _.outcasts.delete
# 1 _.string.startsWith

class Ultimate.Plugin
  cid: null
  el: null
  $el: null
  nodes: null
  events: null

  options: null

  constructor: (options) ->
    @cid = _.uniqueId('ultimatePlugin_')
    @_configure options
    @$el = $(@el).first()
    @el = @$el[0]
    @findNodes()
    @initialize? arguments...
    @delegateEvents()

  # jQuery delegate for element lookup, scoped to DOM elements within the
  # current plugin. This should be prefered to global lookups where possible.
  $: (selector) ->
    @$el.find(selector)

  findNodes: (jRoot = @$el, nodes = @nodes) ->
    jNodes = {}
    nodes = if _.isFunction(nodes) then @nodes.call(@) else _.clone(nodes)
    if _.isObject(nodes)
      for nodeName, selector of nodes
        _isObject = _.isObject(selector)
        if _isObject
          nestedNodes = selector
          selector = _.outcasts.delete(nestedNodes, 'selector')
        jNodes[nodeName] = @[nodeName] = jRoot.find(selector)
        if _isObject
          _.extend jNodes, @findNodes(jNodes[nodeName], nestedNodes)
    jNodes

  undelegateEvents: ->
    @$el.unbind ".delegateEvents_#{@cid}"

  # Cached regex to split keys for `delegate`, from backbone.js.
  delegateEventSplitter = /^(\S+)\s*(.*)$/

  # Overload and proxy parent method Backbone.View.delegateEvents() as hook for normalizeEvents().
  delegateEvents: (events) ->
    args = _.toArray(arguments)
    args[0] = @_normalizeEvents(events)
    @_delegateEvents args...

  # delegateEvents() from backbone.js
  _delegateEvents: (events = _.result(@, 'events')) ->
    return  unless events
    @undelegateEvents()
    for key, method of events
      method = @[events[key]]  unless _.isFunction(method)
      throw new Error("Method \"#{events[key]}\" does not exist")  unless method
      [[], eventName, selector] = key.match(delegateEventSplitter)
      method = _.bind(method, @)
      eventName += ".delegateEvents_#{@cid}"
      if selector is ''
        @$el.bind(eventName, method)
      else
        @$el.delegate(selector, eventName, method)

  _normalizeEvents: (events) ->
    events = _.result(@, 'events')  unless events
    if events
      normalizedEvents = {}
      for key, method of events
        [[], eventName, selector] = key.match(delegateEventSplitter)
        selector = _.result(@, selector)
        selector = selector.selector  if selector instanceof jQuery
        if _.isString(selector)
          selector = selector.replace(@$el.selector, '')  if _.string.startsWith(selector, @$el.selector)
          key = "#{eventName} #{selector}"
        normalizedEvents[key] = method
      events = normalizedEvents
    events

  _configure: (options = {}) ->
    @options ||= {}
    _.extend @options, options
    @_reflectOptions()

  _reflectOptions: (options = @options) ->
    @[attr] = value  for attr, value of options  when not _.isUndefined(@[attr])