fs = require 'fs'
Addr64 = (require './Addr64.coffee').Addr64
LogUtil = (require './LogUtil.coffee').LogUtil
CompoundByteIteratorBase =
  (require './iterate/CompoundByteIteratorBase.coffee').CompoundByteIteratorBase

# TODO Implement many other methods!!!
# This class provides access to data stored in cache. It is lazy - it gets
# information from cache only when getValue or getData are called. Also it is
# changable.
class DataIterator extends CompoundByteIteratorBase
  # @private
  cache: undefined
  # @private
  addr: undefined
  # @private
  pageAddr: undefined
  # @private
  offset: undefined
  # @private
  buffer: undefined

  # Constructor.
  #
  # @param cache the cache where data is stored (or where it can be loaded).
  # @param addr the address in virtual address space.
  @create$LogCache$Addr64: (cache, addr, o) ->
    if !o? then o = new DataIterator
    o.cache = cache
    o.addr = addr
    o.cache.parseAddr$Addr64 o.addr
    o.pageAddr = o.cache.getPageAddr()
    o.offset = o.cache.getPageOffset()
    o.buffer = undefined
    return o

  getAddress: () ->
    return @addr

  # Makes a copy of the current state of iterator.
  #
  # @return new DataIterator
  copy: () ->
    return DataIterator.create$LogCache$Addr64 @cache, @addr

  # Check whether there is next.
  hasNext$emit: () ->
    return @addr.plus$int(1).less$Addr64 @cache.getHighAddress()

  # @private
  # Moves pointer to data to the next byte. This method changes object.
  moveNext: () ->
    @addr = @addr.plus$int 1
    @offset += 1
    if @offset == @cache.getPageSize()
      @pageAddr = @pageAddr.plus$int @offset
      @offset = 0
      @buffer = undefined

  # Move pointer to data to current position plus offset. This method changes
  # object.
  #
  # @param length items to skip (result is undefined for non-positive length).
  # @return how many items were skipped, zero if no elements left.
  skip$int: (length) ->
    if length < 0
      return undefined
    if !(@cache.getHighAddress().less$Addr64 @addr.plus$int(length))
      res = length
    else
      res = @cache.getHighAddress().diff$Addr64 @addr
    @addr = @addr.plus$int res
    @cache.parseAddr$Addr64 @addr
    @pageAddr = @cache.getPageAddr()
    @offset = @cache.getPageOffset()
    @buffer = undefined
    return res

  # Get value of byte at current position and move next.
  #
  # @return If buffer is defined or can be instantly got from cache returns
  # value. Otherwise returns undefined and emits 'getValue' event. If undefined
  # is returned, do not call this method again before event is emitted!
  next$emit: () ->
    if @buffer?
      res = @buffer[@offset]
      @moveNext()
      return res
    else
      addrStr = Addr64.addr64ToString$Addr64$int(@pageAddr,
              LogUtil.LOG_NAME_BASE)
      @cache.once 'getPage' + addrStr, (buffer, addrStr) =>
        @buffer = buffer
        res = @buffer[@offset]
        @moveNext()
        @emit 'next', res
      @buffer = @cache.getPage$Addr64$emit @pageAddr
      if @buffer?
        res = @buffer[@offset]
        @moveNext()
        return res
      else
        return undefined

  getHighAddress: () ->
    return @addr

# This class should be used ONLY when it is known that the data is in one
# buffer and doesn't need to be uploaded from disk. It is EventEmitter to let
# it be used with compressor. Think twice before using it!
# It can be used if you have made a copy of data dorm ReadLogable and want to
# parse it.
class LightReadIterator extends CompoundByteIteratorBase
  # @private
  offset: undefined
  # @private
  buffer: undefined

  # Constructor.
  #
  # @param buffer.
  # @param offset in buffer.
  @create$Buffer$int: (buffer, offset) ->
    @buffer = buffer
    @offset = offset

  # Makes a copy of the current state of iterator.
  #
  # @return new DataIterator
  copy: () ->
    return LightReadIterator.create$Buffer$int @buffer, @offset

  # Check whether there is next.
  hasNext$emit: () ->
    return @offset + 1 < @buffer.length

  # Reads data at current position and moves pointer to data to the next byte.
  # This method changes object.
  next$emit: () ->
    res = @buffer[@offset]
    @offset += 1
    return res

  # Move pointer to data to current position plus offset. This method changes
  # object.
  #
  # @param length items to skip (result is undefined for non-positive length).
  # @return how many items were skipped, zero if no elements left.
  skip$int: (length) ->
    if length < 0
      return undefined
    rest = @buffer.length - @offset
    if rest >= length
      @offset += length
      return length
    else
      @offset += rest
      return rest

  # Get buffer with data at current position.
  #
  # @param length the length of data to be read.
  # @return buffer with data.
  getData$int$emit: (length) ->
    b = new Buffer(length)
    @buffer.copy b, 0, @offset, @offset + length
    return b

exports.DataIterator = DataIterator
exports.LightReadIterator = LightReadIterator
