
#
# @nodoc
# An object that holds all applied operations.
#
# @note The HistoryBuffer is commonly abbreviated to HB.
#
class HistoryBuffer

  #
  # Creates an empty HB.
  # @param {Object} user_id Creator of the HB.
  #
  constructor: (@user_id)->
    @operation_counter = {}
    @buffer = {}
    @change_listeners = []
    @garbage = [] # Will be cleaned on next call of garbageCollector
    @trash = [] # Is deleted. Wait until it is not used anymore.
    @performGarbageCollection = true
    @garbageCollectTimeout = 30000
    @reserved_identifier_counter = 0
    setTimeout @emptyGarbage, @garbageCollectTimeout

  resetUserId: (id)->
    own = @buffer[@user_id]
    if own?
      for o_name,o of own
        o.uid.creator = id
      if @buffer[id]?
        throw new Error "You are re-assigning an old user id - this is not (yet) possible!"
      @buffer[id] = own
      delete @buffer[@user_id]

    @operation_counter[id] = @operation_counter[@user_id]
    delete @operation_counter[@user_id]
    @user_id = id

  emptyGarbage: ()=>
    for o in @garbage
      #if @getOperationCounter(o.uid.creator) > o.uid.op_number
      o.cleanup?()

    @garbage = @trash
    @trash = []
    if @garbageCollectTimeout isnt -1
      @garbageCollectTimeoutId = setTimeout @emptyGarbage, @garbageCollectTimeout
    undefined

  #
  # Get the user id with wich the History Buffer was initialized.
  #
  getUserId: ()->
    @user_id

  addToGarbageCollector: ()->
    if @performGarbageCollection
      for o in arguments
        if o?
          @garbage.push o

  stopGarbageCollection: ()->
    @performGarbageCollection = false
    @setManualGarbageCollect()
    @garbage = []
    @trash = []

  setManualGarbageCollect: ()->
    @garbageCollectTimeout = -1
    clearTimeout @garbageCollectTimeoutId
    @garbageCollectTimeoutId = undefined

  setGarbageCollectTimeout: (@garbageCollectTimeout)->

  #
  # I propose to use it in your Framework, to create something like a root element.
  # An operation with this identifier is not propagated to other clients.
  # This is why everybode must create the same operation with this uid.
  #
  getReservedUniqueIdentifier: ()->
    {
      creator : '_'
      op_number : "_#{@reserved_identifier_counter++}"
      doSync: false
    }

  #
  # Get the operation counter that describes the current state of the document.
  #
  getOperationCounter: (user_id)->
    if not user_id?
      res = {}
      for user,ctn of @operation_counter
        res[user] = ctn
      res
    else
      @operation_counter[user_id]

  isExpectedOperation: (o)->
    @operation_counter[o.uid.creator] ?= 0
    o.uid.op_number <= @operation_counter[o.uid.creator]
    true #TODO: !! this could break stuff. But I dunno why

  #
  # Encode this operation in such a way that it can be parsed by remote peers.
  # TODO: Make this more efficient!
  _encode: (state_vector={})->
    json = []
    unknown = (user, o_number)->
      if (not user?) or (not o_number?)
        throw new Error "dah!"
      not state_vector[user]? or state_vector[user] <= o_number

    for u_name,user of @buffer
      # TODO next, if @state_vector[user] <= state_vector[user]
      for o_number,o of user
        if o.uid.doSync and unknown(u_name, o_number)
          # its necessary to send it, and not known in state_vector
          o_json = o._encode()
          if o.next_cl? # applies for all ops but the most right delimiter!
            # search for the next _known_ operation. (When state_vector is {} then this is the Delimiter)
            o_next = o.next_cl
            while o_next.next_cl? and unknown(o_next.uid.creator, o_next.uid.op_number)
              o_next = o_next.next_cl
            o_json.next = o_next.getUid()
          else if o.prev_cl? # most right delimiter only!
            # same as the above with prev.
            o_prev = o.prev_cl
            while o_prev.prev_cl? and unknown(o_prev.uid.creator, o_prev.uid.op_number)
              o_prev = o_prev.prev_cl
            o_json.prev = o_prev.getUid()
          json.push o_json

    json

  #
  # Get the number of operations that were created by a user.
  # Accordingly you will get the next operation number that is expected from that user.
  # This will increment the operation counter.
  #
  getNextOperationIdentifier: (user_id)->
    if not user_id?
      user_id = @user_id
    if not @operation_counter[user_id]?
      @operation_counter[user_id] = 0
    uid =
      'creator' : user_id
      'op_number' : @operation_counter[user_id]
      'doSync' : true
    @operation_counter[user_id]++
    uid

  #
  # Retrieve an operation from a unique id.
  #
  # when uid has a "sub" property, the value of it will be applied
  # on the operations retrieveSub method (which must! be defined)
  #
  getOperation: (uid)->
    if uid.uid?
      uid = uid.uid
    o = @buffer[uid.creator]?[uid.op_number]
    if uid.sub? and o?
      o.retrieveSub uid.sub
    else
      o

  #
  # Add an operation to the HB. Note that this will not link it against
  # other operations (it wont executed)
  #
  addOperation: (o)->
    if not @buffer[o.uid.creator]?
      @buffer[o.uid.creator] = {}
    if @buffer[o.uid.creator][o.uid.op_number]?
      throw new Error "You must not overwrite operations!"
    if (o.uid.op_number.constructor isnt String) and (not @isExpectedOperation(o)) and (not o.fromHB?) # you already do this in the engine, so delete it here!
      throw new Error "this operation was not expected!"
    @addToCounter(o)
    @buffer[o.uid.creator][o.uid.op_number] = o
    o

  removeOperation: (o)->
    delete @buffer[o.uid.creator]?[o.uid.op_number]

  # When the HB determines inconsistencies, then the invokeSync
  # handler wil be called, which should somehow invoke the sync with another collaborator.
  # The parameter of the sync handler is the user_id with wich an inconsistency was determined
  setInvokeSyncHandler: (f)->
    @invokeSync = f

  # empty per default # TODO: do i need this?
  invokeSync: ()->

  # after you received the HB of another user (in the sync process),
  # you renew your own state_vector to the state_vector of the other user
  renewStateVector: (state_vector)->
    for user,state of state_vector
      if (not @operation_counter[user]?) or (@operation_counter[user] < state_vector[user])
        @operation_counter[user] = state_vector[user]

  #
  # Increment the operation_counter that defines the current state of the Engine.
  #
  addToCounter: (o)->
    if not @operation_counter[o.uid.creator]?
      @operation_counter[o.uid.creator] = 0
    if typeof o.uid.op_number is 'number' and o.uid.creator isnt @getUserId()
      # TODO: check if operations are send in order
      if o.uid.op_number is @operation_counter[o.uid.creator]
        @operation_counter[o.uid.creator]++
      else
        @invokeSync o.uid.creator

    #if @operation_counter[o.uid.creator] isnt (o.uid.op_number + 1)
      #console.log (@operation_counter[o.uid.creator] - (o.uid.op_number + 1))
      #console.log o
      #throw new Error "You don't receive operations in the proper order. Try counting like this 0,1,2,3,4,.. ;)"

module.exports = HistoryBuffer
