ByteIterator = (require '../../database/ByteIterator.coffee').ByteIterator
ByteIterable = (require '../../database/ByteIterable.coffee').ByteIterable
Addr64 = (require '../Addr64.coffee').Addr64
LogUtil = (require '../LogUtil.coffee').LogUtil
ExodusError = (require '../../util/ExodusVersion.coffee').ExodusError

# This class allows working with several iterators (one after another).
class CompoundByteIteratorBase extends ByteIterator
  # @private
  private: undefined
  # @private
  hasNext: undefined
  # @private
  hasNextValid: undefined

  # @protected
  # Constructor.
  #
  # @param current the base iterator.
  @create$ByteIterator: (current, o) ->
    if !o? then o = new CompoundByteIteratorBase
    o.current = current
    return o

  # @protected
  # Constructor.
  @create: (o) ->
    return CompoundByteIteratorBase.
    create$ByteIterator ByteIterable.EMPTY_ITERATOR, o

  # Check whether there is next element.
  #
  # @return true or false.
  hasNext$emit: () ->
    onHasNextImpl = (@hasNext) =>
      @hasNextValid = true
    @once 'hasNextImpl', (hasNext) =>
      onHasNextImpl hasNext
      @emit 'hasNext', hasNext
    if !@hasNextValid
      @hasNext = @hasNextImpl$emit()
      if @hasNext?
        @removeAllListeners 'hasNextImpl'
        onHasNextImpl @hasNext
        return @hasNext
      else
        return undefined
    return @hasNext

  # Skip several elements.
  #
  # @param length the number of elements to skip.
  # @return the number of elements actually skipped.
  skip$int: (length) ->
    skipped = @current.skip$int length
    while true
      @hasNextValid = false
      if skipped >= length || !@hasNext$emit()
        break
      skipped += @current.skip$int(length - skipped)
    return skipped

  # Get next element.
  #
  # @return next element.
  next$emit: () ->
    if !@hasNext$emit()
      @onFail$String "CompoundByteIterator: no more bytes available"
    @current.once 'next', (result) =>
      @hasNextValid = false
      @emit 'next', result
    result = @current.next$emit()
    if result?
      @current.removeAllListeners 'next'
      @hasNextValid = false
      return result
    else
      return undefined

  # @private
  hasNextImpl$emit: () ->
    cycleStep = () =>
      @once 'nextIterator', (nextIterator) =>
        emit = true
        onNextIterator nextIterator
      nextIterator = @nextIterator$emit()
      if nextIterator?
        @removeAllListeners 'nextIterator'
        return onNextIterator nextIterator
      else
        return undefined
    onNextIterator = (nextIterator) =>
      if !nextIterator?
        if emit
          @emit 'hasNextImpl$emit', false
        else
          return false
      @current = nextIterator
      if !@current.hasNext$emit()
        cycleStep()
      if emit
        @emit 'hasNextImpl$emit', true
      else
        return true
    emit = false
    if !@current? || !@current.hasNext$emit()?
      cycleStep()
    return true

  getCurrent: () ->
    return @current

  # @protected
  # Throws error message.
  #
  # @param message.
  onFail$String: (message) ->
    throw new ExodusError(message)

  # TEMPORARY!!!
  # Get buffer with data at current position.
  #
  # @param length the length of data to be read.
  # @return buffer with data or undefined. Behavour is similar to getValue.
  getData$int$emit: (length, cache) ->
    if !@cache? then @cache = cache
    b = new Buffer length
    k = Math.ceil((@offset + length) / @cache.getPageSize())
    buffers = new Object()
    pagesRead = 0

    onGetPage = (addrStr, buffer) =>
      buffers[addrStr] = buffer
      pagesRead += 1
      if pagesRead == k
        for i in [0..k - 1]
          pageAddr = @pageAddr.plus$int(i * @cache.getPageSize())
          addrStr = Addr64.addr64ToString$Addr64$int(pageAddr,
                  LogUtil.LOG_NAME_BASE)
          if buffers[addrStr]?
            buffer = buffers[addrStr]
            if i == 0
              buffer.copy b, 0, @offset, Math.min(@cache.getPageSize(),
                      @offset + length)
            else if i < k - 1
              buffer.copy b, i * @cache.getPageSize() - @offset, 0,
                      @cache.getPageSize()
            else
              buffer.copy b, i * @cache.getPageSize() - @offset, 0,
                      (length + @offset) % @cache.getPageSize()
        @emit 'getData', b

    for i in [0..k - 1]
      pageAddr = @pageAddr.plus$int(i * @cache.getPageSize())
      addrString = Addr64.addr64ToString$Addr64$int(pageAddr,
              LogUtil.LOG_NAME_BASE)
      @cache.once 'getPage' + addrString, (buffer, addrStr) =>
        onGetPage addrStr, buffer
      buffer = @cache.getPage$Addr64$emit pageAddr
      if buffer?
        pagesRead += 1
        if i == 0
          buffer.copy b, 0, @offset, Math.min(@cache.getPageSize(),
                  @offset + length)
        else if i < k - 1
          buffer.copy b, i * @cache.getPageSize() - @offset, 0,
                  @cache.getPageSize()
        else
          buffer.copy b, i * @cache.getPageSize() - @offset, 0,
                  (length + @offset) % @cache.getPageSize()
    if pagesRead == k
      return b
    else
      return undefined

exports.CompoundByteIteratorBase = CompoundByteIteratorBase
