EventEmitter = (require 'events').EventEmitter
Addr64 = (require '../log/Addr64.coffee').Addr64
DataIterator = (require '../log/DataIterator.coffee').DataIterator
LoggableToWrite = (require '../log/LoggableToWrite.coffee').LoggableToWrite
LightOutputStream =
  (require '../database/impl/iterate/LightOutputStream.coffee').
  LightOutputStream
RandomAccessLoggable =
  (require '../log/RandomAccessLoggable.coffee').RandomAccessLoggable
CompressedUnsignedLongByteIterable =
  (require '../log/iterate/CompressedUnsignedLongByteIterable.coffee').
  CompressedUnsignedLongByteIterable
Log = (require '../log/Log.coffee').Log
LogUtil = (require '../log/LogUtil.coffee').LogUtil
ArrayByteIterable =
  (require '../database/impl/iterate/ArrayByteIterable.coffee').
  ArrayByteIterable
FixedLengthByteIterable =
  (require '../log/iterate/FixedLengthByteIterable.coffee').
  FixedLengthByteIterable

# This class provides logical node of List. In fact value and virtual
# addresses are be stored separately.
class ListNode extends EventEmitter
  @NODE_TYPE: 3
  @KEY_TYPE: 5
  @VALUE_TYPE: 7
  # @private
  maxInt32: undefined
  # @private
  nullAddr: undefined
  # @private
  log: undefined
  # @private
  keyAddr: undefined
  # @private
  valueAddr: undefined
  # @private
  nextAddr: undefined
  # @private
  ownAddr: undefined
  # @private
  key: undefined
  # @private
  keyLength: undefined
  # @private
  value: undefined
  # @private
  valueLength: undefined
  # @private
  next: undefined
  # @private
  structureId: undefined
  # @private
  prev: undefined
  # @private
  addrsAreInMemory: undefined

  # Constructor.
  #
  # @param log.
  @create$Log: (log, o) ->
    if !o? then o = new ListNode
    o.maxInt32 = (Math.pow 2, 32) - 1
    o.nullAddr = Addr64.create$int$int o.maxInt32, o.maxInt32
    o.log = log
    o.keyAddr = o.nullAddr
    o.valueAddr = o.nullAddr
    o.nextAddr = o.nullAddr
    o.ownAddr = o.nullAddr
    o.setMaxListeners 0
    o.addrsAreInMemory = true
    return o

  # Constructor.
  #
  # @param log.
  # @param key.
  # @param value.
  # @param next can be undefined.
  @create$Log$ByteIterable$ByteIterable$int$ListNode: (log, key, value, structureId, next, o) ->
    if !o? then o = new ListNode
    o = ListNode.create$Log log, o
    o.key = key
    o.keyLength = key.getLength()
    o.value = value
    o.valueLength = value.getLength()
    o.structureId = structureId
    o.next = next
    o.prev = undefined
    o.addrsAreInMemory = false
    return o

  # Check whether node is uploaded.
  #
  # @return true if node is defined and it's key and value are defined or
  # do not exist.
  nodeIsUploaded: () ->
    return !@addrsAreInMemory &&
    (@next? || @nextAddr.equals$Addr64 @nullAddr) &&
    (@key? || @keyAddr.equals$Addr64 @nullAddr) &&
    (@value? || @valueAddr.equals$Addr64 @nullAddr)

  # Wrap the node into LogGable object.
  #
  # @return loggable.
  toLoggable: () ->
    length =
      (CompressedUnsignedLongByteIterable.getCompressedSize$int @nextAddr.left) +
      (CompressedUnsignedLongByteIterable.getCompressedSize$int @nextAddr.right) +
      (CompressedUnsignedLongByteIterable.getCompressedSize$int @keyAddr.left) +
      (CompressedUnsignedLongByteIterable.getCompressedSize$int @keyAddr.right) +
      (CompressedUnsignedLongByteIterable.getCompressedSize$int @valueAddr.left) +
      (CompressedUnsignedLongByteIterable.getCompressedSize$int @valueAddr.right)
    stream = LightOutputStream.create$int length
    CompressedUnsignedLongByteIterable.
    fillBytes$int$LightOutputStream @nextAddr.left, stream
    CompressedUnsignedLongByteIterable.
    fillBytes$int$LightOutputStream @nextAddr.right, stream
    CompressedUnsignedLongByteIterable.
    fillBytes$int$LightOutputStream @keyAddr.left, stream
    CompressedUnsignedLongByteIterable.
    fillBytes$int$LightOutputStream @keyAddr.right, stream
    CompressedUnsignedLongByteIterable.
    fillBytes$int$LightOutputStream @valueAddr.left, stream
    CompressedUnsignedLongByteIterable.
    fillBytes$int$LightOutputStream @valueAddr.right, stream
    loggable = LoggableToWrite.create$int$ByteIterable$int ListNode.NODE_TYPE,
            stream.asArrayByteIterable(), @structureId
    return loggable

  # Wrap the key that is stored in the node into Logable object.
  #
  # @return loggable.
  keyToLoggable: () ->
    return LoggableToWrite.create$int$ByteIterable$int ListNode.KEY_TYPE, @key,
            @structureId

  # Wrap the value that is stored in the node into Logable object.
  #
  # @return loggable.
  valueToLoggable: () ->
    return LoggableToWrite.create$int$ByteIterable$int ListNode.VALUE_TYPE,
            @value, @structureId

  # Save to Log this node and all the nodes before him (in reversed order) if it
  # is necessary. This function doesn't commit data to Log.
  #
  # @return address of this node in log.
  flush$emit: () ->
    if @next? && @nextAddr.equals$Addr64 @nullAddr
      @nextAddr = @next.flush$emit()
    if @key? && @keyAddr.equals$Addr64 @nullAddr
      @keyAddr = @log.write$Loggable @keyToLoggable()
    if @value? && @valueAddr.equals$Addr64 @nullAddr
      @valueAddr = @log.write$Loggable @valueToLoggable()
    if @ownAddr.equals$Addr64 @nullAddr
      @ownAddr = @log.write$Loggable @toLoggable()
      return @ownAddr

  isEmpty: () ->
    return (!@next? && @nextAddr.equals$Addr64 @nullAddr) &&
    (!@key? && @keyAddr.equals$Addr64 @nullAddr) &&
    (!@value? && @valueAddr.equals$Addr64 @nullAddr)

  # Get node itself - all adresses but not key and value.
  #
  # @return always emits 'getSelf' when done.
  getSelf: () ->
    if @nodeIsUploaded()
      @emit 'getSelf'
    else
      if @ownAddr.equals$Addr64(@nullAddr) ||
      @ownAddr.equals$Addr64 Addr64.create$int$int(-1, -1)
        @emit 'getSelf'
      else
        addrStr = Addr64.addr64ToString$Addr64$int @ownAddr, LogUtil.LOG_NAME_BASE
        @log.on 'read' + addrStr, (loggable) =>
          it = loggable.getData().iterator()
          nums = new Object()
          n = 0
          onRead = (value) =>
            n += 1
            nums[n] = value
            if n == 6
              @nextAddr = Addr64.create$int$int nums[1], nums[2]
              @keyAddr = Addr64.create$int$int nums[3], nums[4]
              @valueAddr = Addr64.create$int$int nums[5], nums[6]
              @addrsAreInMemory = false
              @emit 'getSelf'
            else
              value = CompressedUnsignedLongByteIterable.
              getInt$ByteIterator$emit it
              if value?
                onRead value
          CompressedUnsignedLongByteIterable.EMITTER.on 'getInt', (value) =>
            onRead value
          value = CompressedUnsignedLongByteIterable.getInt$ByteIterator$emit it
          if value?
            onRead value
        @log.read$Addr64$emit @ownAddr

  # Get node key.
  #
  # @return always emits 'getKey' when done.
  getKey: () ->
    if @key?
      @emit 'getKey', @key
    else if @keyAddr.equals$Addr64 @nullAddr
      @emit 'getKey', undefined
    else
      addrStr = Addr64.addr64ToString$Addr64$int @keyAddr, LogUtil.LOG_NAME_BASE
      @log.on 'read' + addrStr, (loggable) =>
        @key = FixedLengthByteIterable.
        create$ByteIterable$int loggable.getData(), loggable.getDataLength()
        @emit 'getKey', @key
      @log.read$Addr64$emit @keyAddr

  # Get node value.
  #
  # @return always emits 'getValue' when done.
  getValue: () ->
    if @value?
      @emit 'getValue', @value
    else if @valueAddr.equals$Addr64 @nullAddr
      @emit 'getValue', undefined
    else
      addrStr = Addr64.addr64ToString$Addr64$int @valueAddr, LogUtil.LOG_NAME_BASE
      @log.on 'read' + addrStr, (loggable) =>
        @value = FixedLengthByteIterable.
        create$ByteIterable$int loggable.getData(), loggable.getDataLength()
        @emit 'getValue', @value
      @log.read$Addr64$emit @valueAddr

  # Get next node.
  #
  # @return next.
  getNext: () ->
    if !@next?
      if !@nextAddr.equals$Addr64 @nullAddr
        node = ListNode.create$Log @log
        node.ownAddr = @nextAddr
        return node
      else
        return undefined
    else
      return @next

  # Get all node data - all addresses, key and value.
  #
  # @return always emits 'getNode' when done. If true, node is got,
  # otherwise node is empty.
  getNode: () ->
    this.once 'getSelf', () =>
      if @isEmpty()
        @emit 'getNode', false, this
      else
        @next = @getNext()
        getKey = false
        getValue = false
        this.once 'getKey', (key) =>
          getKey = true
          if getKey && getValue
            @emit 'getNode', true, this
        this.once 'getValue', (value) =>
          getValue = true
          if getKey && getValue
            @emit 'getNode', true, this
        if !@keyAddr.equals$Addr64 @nullAddr
          @getKey()
        else
          getKey = true
        if !@valueAddr.equals$Addr64 @nullAddr
          @getValue()
        else
          getValue = true
        if getKey && getValue
          @emit 'getNode', true, this
    if @ownAddr?
      @getSelf()
    else
      @emit 'getNode', false, this

exports.ListNode = ListNode