module.exports = (racer) ->
  {Model} = racer

  CollectionProto = (@name, @ns) ->
    @listeners = []
    @hooks = []
    return

  CollectionProto.prototype.on = (mutator, path, cb) ->
    if arguments.length == 2
      cb = path
      @listeners.push [mutator, "#{@ns}.*", cb]
    else
      @listeners.push [mutator, "#{@ns}.*.#{path}", cb]

  CollectionProto.prototype.onLocal = (mutator, path, cb) ->
    if arguments.length == 2
      cb = path
      pattern = "#{@ns}.*"
    else if path == ''
      pattern = "#{@ns}.*"
    else
      pattern = "#{@ns}.*.#{path}"

    callback = ->
      passed = arguments[arguments.length-1]
      return if passed?.$remote
      cb.apply this, arguments
    @listeners.push [mutator, pattern, callback]

  if racer.util.isServer
    CollectionProto.prototype.after = (type, pattern, cb) ->
      @hooks.push [type, cb]

  # The rest of these prototype methods are inherited as static methods of ModelCollection
  CollectionProto.prototype.at = (path) ->
    @nsModel.at path

  CollectionProto.prototype.atOrFetchById = (id, cb) ->
    $doc = @nsModel.at id
    if $doc.get()?
      return cb null, $doc
    $doc.fetch (err) ->
      cb err, $doc

  CollectionProto.prototype.get = (path) ->
    @nsModel.get path

  CollectionProto.prototype.getCopy = (path) ->
    @nsModel.getCopy path

  CollectionProto.prototype.getDeepCopy = (path) ->
    @nsModel.getDeepCopy path

  CollectionProto.prototype.query = (query) ->
    @model.query @ns, query

  CollectionProto.prototype.filter = ->
    throw new Error('Unimplemented')

  CollectionProto.prototype.add = ->
    @nsModel.add arguments...

  CollectionProto.prototype.set = ->
    @nsModel.set arguments...

  CollectionProto.prototype.setNull = ->
    @nsModel.setNull arguments...

  CollectionProto.prototype.push = ->
    @nsModel.push arguments...

  CollectionProto.prototype.remove = ->
    @nsModel.remove arguments...

  CollectionProto.prototype.move = ->
    @nsModel.move arguments...

  CollectionProto.prototype.del = ->
    @nsModel.del arguments...

  CollectionProto.prototype.indexByKey = (indexByKey) ->
    index = {}
    docs = @nsModel.get()
    for id, doc of docs when doc
      index[doc[indexByKey]] = doc
    return index

  CollectionProto.prototype.debounce = (Collection, name, listeners, time = 500) ->
    debouncers = @debouncers ||= {}
    listeners.forEach ({event, path, initial, update}) ->
      Collection.onLocal event, path, ->
        #TODO: assuming first argument of callback is a unique id of collection item
        debounceId = name + '.' + arguments[0]
        debounce = debouncers[debounceId] ||= {}
        args = [debounce].concat([].splice.call(arguments, 0))
        if debounce.__timeout__
          #we want to run update if debounce already exists
          update.apply(@, args)
          clearTimeout(debounce.__timeout__)
          debounce.__timeout__ = setTimeout(( -> delete debouncers[debounceId]), time)
        else
          #we want to run initial if it doesn't
          initial.apply(@, args)
          debounce.__timeout__ = setTimeout(( -> delete debouncers[debounceId]), time)

  CollectionProto.prototype.init = (model) ->
    @model = model
    @debouncers = {}
    @nsModel = model.scope @ns

    # Setup listeners
    @listeners.forEach ([type, pattern, cb]) ->
      model.on type, pattern, cb.bind({model})

    # Setup hooks
    if racer.isServer
      store = model.store
      @hooks.forEach([type, pattern, cb]) =>
        if typeof pattern is 'function'
          store.hook type, @ns, cb.bind({store})
        store.hook type, @ns + '.*.' + pattern, cb.bind({store})

    return

  COLLECTIONS = {}

  Model.INITS.push (model) ->
    for name, Collection of COLLECTIONS
      model[name] = new Collection model
    return

  Model.collection = (name, ns) ->
    Collection = COLLECTIONS[name]
    return Collection.prototype if Collection

    Collection = (model) ->
      @init model
      return
    Collection.prototype = new CollectionProto(name, ns)
    COLLECTIONS[name] = Collection
    return Collection.prototype

  return
