fs = require 'fs'
Addr64 = (require '../Addr64.coffee').Addr64
LogUtil = (require '../LogUtil.coffee').LogUtil
ByteIterableBase =
  (require '../../database/impl/iterate/ByteIterableBase.coffee').ByteIterableBase
IllegalArgumentError =
  (require '../../errors/IllegalArgumentError.coffee').IllegalArgumentError
EventEmitter = (require 'events').EventEmitter

# TODO there is some cache in exodus - what is it for?

# This class is used for compressed reading and writing of numbers using
# iterators.
class CompressedUnsignedLongByteIterable extends ByteIterableBase
  # @private
  MAX_DIGIT_NUMBER: 10
  # @private
  l: undefined

  @EMITTER = new EventEmitter()
  @EMITTER.setMaxListeners 0

  # Constructor.
  @create: (o) ->
    if !o? then o = new CompressedUnsignedLongByteIterable
    return o

  # Constructor.
  #
  # @param l.
  @create$int: (l, o) ->
    if !o? then o = new CompressedUnsignedLongByteIterable
    if (l < 0)
      throw new IllegalArgumentError(l.toString())
    o.l = l
    return o

  # Gets new iterable.
  #
  # @param l number.
  # @return new iterable.
  @getIterable$int: (l) ->
    return CompressedUnsignedLongByteIterable.create$int l

  # Calculates the compressed number length.
  #
  # @param l number.
  # @return number length.
  @getCompressedSize$int: (l) ->
    ll = l
    ll >>>= 7
    result = 1
    while ll > 0
      result += 1
      ll >>>= 7
    return result

  # Writes compressed number to stream.
  #
  # @param l number.
  # @param stream the stream to write number in.
  #
  # @return reference to the same stream (just for tests!).
  @fillBytes$int$LightOutputStream: (l, stream) ->
    if (l < 0)
      throw new IllegalArgumentError(l.toString())
    ll = l
    while true
      b = ll & 0x7f
      if ((ll >>>= 7) == 0)
        stream.writeFast$int b | 0x80
        break
      stream.writeFast$int b
    return stream

  # Reads int from iterable. Method contains corruption check: if we have
  # already read MAX_DIGIT_NUMBER and continue reading, it means that info
  # is not a number.
  #
  # @param iterable the iterable to read data from.
  # @return number if it could be read without events, and undefined otherwise.
  # If undefined, the 'getInt' event is emitted with number or undefined
  # if it can't be read.
  @getInt$ByteIterable$emit: (iterable) ->
    CompressedUnsignedLongByteIterable.
    getInt$ByteIterator$emit iterable.getIterator()

  # Reads int from iterable. Method contains corruption check: if we have
  # already read MAX_DIGIT_NUMBER and continue reading, it means that info
  # is not a number.
  #
  # @param iterator the iterator to the place to read data from.
  # @return number if it could be read without events, and undefined otherwise.
  # If undefined, the 'getInt' event is emitted with number or undefined
  # if it can't be read.
  @getInt$ByteIterator$emit: (iterator) ->
    num = 0
    ll = 0
    shift = 0
    valueIsRead = false
    emitGetInt = true
    oldIt = iterator.copy()
    iterator.removeAllListeners()
    if oldIt.addr != undefined
      addrString = Addr64.addr64ToString$Addr64$int(oldIt.addr,
              LogUtil.LOG_NAME_BASE)
    emit = false
    onGetNext = (v) =>
      if v != undefined
        if num > @MAX_DIGIT_NUMBER
          CompressedUnsignedLongByteIterable.EMITTER.emit 'getInt' + addrString,
                  undefined, iterator
          return undefined
        ll += ((v & 0x7f) << shift)
        if (v & 0x80) != 0
          iterator.removeAllListeners 'next'
          if emit
            CompressedUnsignedLongByteIterable.EMITTER.emit 'getInt' +
            addrString, ll, iterator
            return undefined
          return ll
        shift += 7
        num += 1
        onGetNext iterator.next$emit()
    iterator.on 'next', (v) =>
      emit = true
      onGetNext v
    onGetNext iterator.next$emit()

  # This methods are the same as getInt. Used for compatibility.
  @getLong$ByteIterable$emit: (iterable) ->
    CompressedUnsignedLongByteIterable.
    getInt$ByteIterator$emit iterable.getIterator()

  @getLong$ByteIterator$emit: (iterator) ->
    CompressedUnsignedLongByteIterable.
    getInt$ByteIterator$emit iterator

exports.CompressedUnsignedLongByteIterable = CompressedUnsignedLongByteIterable
