fs = require 'fs'
ByteIterable = require('../../ByteIterable.coffee').ByteIterable
ByteIterableUtil = require('./ByteIterableUtil.coffee').ByteIterableUtil

# This absract class implements common methods for iterables.
class ByteIterableBase extends ByteIterable
  # @private
  bytes: undefined
  # @private
  length: undefined
  # @private
  @singleBytes: undefined

  # @private
  # Function that returns array of arrays with single bytes from 0 to 255.
  @SINGLE_BYTES: () ->
    if !ByteIterableBase.singleBytes?
      ByteIterableBase.singleBytes = new Array 256
      for i in [0..ByteIterableBase.singleBytes.length - 1]
        ByteIterableBase.singleBytes[i] = new Buffer([i])
    return ByteIterableBase.singleBytes

  # Compares two iterables.
  #
  # @param o the second iterable.
  # @return number (see ByteIterableUtil.compare$ByteIterable$ByteIterable).
  compareTo$ByteIterable$emit: (o) ->
    return ByteIterableUtil.compare$ByteIterable$ByteIterable this, o

  # Gets iterator.
  #
  # @return iterator.
  iterator: () ->
    if (@bytes == null)
      return @getIterator()
    bytes = @bytes
    length = @length
    res =
      i: 0
      hasNext: () ->
        return i < length
      next$emit: () ->
        result = bytes[i]
        i += 1
        return result
      skip$int: (len) ->
        result =  Math.min(length - i, len)
        i += result
        return result
    return res

  getBytesUnsafe: () ->
    if (@bytes == undefined)
      @fillBytes()
    return @bytes

  getLength: () ->
    if (@length == undefined)
      @fillBytes()
    return @length

  # Compares two iterables.
  #
  # @param obj any object.
  # @return true if objects are equal and false otherwise.
  equals$Object: (obj) ->
    return obj instanceof ByteIterable && @compareTo(obj) == 0

  # For tests.
  #
  # @return hascode that contains all the bytes.
  hashCode: () ->
    a = @getBytesUnsafe()
    if !(a?)
      return 0
    result = 1
    length = @getLength()
    for i in [0..length - 1]
      result = 31 * result + a[i]
    return result

  toString: () ->
    a = @getBytesUnsafe()
    if (a == undefined)
      return "null"
    length = @getLength()
    if (length <= 0 || length == undefined)
      return "[]"
    s = '['
    i = 0
    while true
      s += a[i++]
      if i == length
        return s + ']'
      s += ', '

  # All five functions below fill bytes of the some ByteIterable to this
  # ByteIterable or to LightOutputStream.
  fillBytes: () ->
    @fillBytes$ByteIterator @getIterator()

  # @param bi ByteIterable that is written to stream.
  # @param output stream.
  @fillBytes$ByteIterable$LightOutputStream: (bi, output) ->
    it = bi.iterator()
    if it.hasNext$emit()
      ByteIterableBase.fillBytes$ByteIterator$LightOutpustStream it, output

  # @param bi ByteIterator of data that is written to stream.
  # @param output stream.
  @fillBytes$ByteIterator$LightOutputStream: (it, output) ->
    while it.hasNext$emit()
      output.writeFast$int it.next$emit()

  # @param bi ByteIterator of data that is written to this ByteIterable.
  fillBytes$ByteIterator: (it) ->
    if (!it.hasNext$emit())
      @bytes = ByteIterableBase.EMPTY.getBytesUnsafe()
      @length = 0
    else
      @fillBytes$int$ByteIterator it.next$emit(), it

  # @param firstByte the first value to be written.
  # @param bi ByteIterator of data that is written to this ByteIterable.
  fillBytes$int$ByteIterator: (firstByte, it) ->
    if !(it.hasNext$emit())
      @bytes = ByteIterableBase.SINGLE_BYTES()[firstByte & 0xff]
      @length = 1
    else
      output = (require './LightOutputStream.coffee').LightOutputStream.create()
      output.writeFast$int firstByte
      ByteIterableBase.fillBytes$ByteIterator$LightOutputStream it, output
      @bytes = output.getBufferBytes()
      @length = output.getBufferLength()

exports.ByteIterableBase = ByteIterableBase