fs = require 'fs'
# This is hack used to resolve cyclic reference.
ArrayByteIterableFile = (require './ArrayByteIterable.coffee')

# TODO many-many tests!!!
# TODO verify interface.
# This class allows writing various data to buffer.
class LightOutputStream
  NULL_STRING_UTF_VALUE: 0xFF
  @DEFAULT_INIT_SIZE: 8
  # @private
  len: undefined
  # @private
  buf: undefined

  # Constructor.
  @create: (o) ->
    return @create$int LightOutputStream.DEFAULT_INIT_SIZE, o

  # Constructor.
  #
  # @param initialSize the size of buffer to start with.
  @create$int: (initialSize, o) ->
    return @create$Buffer new Buffer initialSize, o

  # Constructor.
  #
  # @param buffer.
  @create$Buffer: (buffer, o) ->
    if !o? then o = new LightOutputStream
    o.buf = buffer
    o.len = 0
    return o

  # @return ArrayByteIterable to work with.
  asArrayByteIterable: () ->
    return ArrayByteIterableFile.ArrayByteIterable.create$Buffer$int @buf, @len

  clear: () ->
    @len = 0

  # --- begin ByteArrayOutputStream compatible methods ---

  size: () ->
    return @len

  # Write.
  #
  # @param b number.
  write$int: (b) ->
    @writeFast$int b

  # Write.
  #
  # @param fromBuf buffer.
  write$Buffer: (fromBuf) ->
    @ensureCapacity$int @len + fromBuf.length
    fromBuf.copy @buf, @len, 0, fromBuf.length
    @len += fromBuf.length

  # Write.
  #
  # @param fromBuf buffer.
  # @param offset.
  # @param length.
  write$Buffer$int$int: (fromBuf, offset, length) ->
    @ensureCapacity$int @len + fromBuf.length
    fromBuf.copy @buf, @len, offset, length
    @len += fromBuf.length

  toString: () ->
    return @buf.toString()

  # --- end ByteArrayOutputStream compatible methods ---

  # Write number.
  #
  # @param val.
  writeByte$int: (val) ->
    @writeFast$int val ^ 0x80

  # Write number.
  #
  # @param b.
  writeFast$int: (b) ->
    if @len >= @buf.length
      @ensureCapacity$int @len + 1
    @buf[@len++] = b

  # Write boolean.
  #
  # @param val.
  writeBoolean$boolean: (val) ->
    @writeFast$int val? 1: 0

  # Write number.
  #
  # @param val.
  writeShort$int: (val) ->
    @writeUnsignedShort$int val ^ 0x8000

  # Write number.
  #
  # @param val.
  writeInt$int: (val) ->
    @writeUnsignedInt$int val ^ 0x80000000

  # Write number.
  #
  # @param val.
  writeLong$int: (val) ->
    @writeUnsignedLong$int val ^ 0x8000000000000000

  # Write number.
  #
  # @param val.
  writeFloat$double: (val) ->
  # really?
    @writeUnsignedInt$int val

  # Write number.
  #
  # @param val.
  writeDouble$double: (val) ->
  # is this true?
    @writeUnsignedLong$int val

  # Write String.
  #
  # @param val.
  writeString$String: (val) ->
    if val?
      if val.length != 0
        utfLength = @getUtfByteLength$String val
        @ensureCapacity$int len + utfLength
        @utfCharsToBytes$String$int$Buffer$int$int val, 0, @getBufferBytes(),
                @getBufferLength(), val.length
        len += utfLength
    else
      @writeFast$int @NULL_STRING_UTF_VALUE
    @writeFast$int 0 # terminator

  # will next three methods work? is the question...
  # Write number.
  #
  # @param val.
  writeUnsignedShort$int: (val) ->
    @writeFast$int val >>> 8
    @writeFast$int val

  # Write number.
  #
  # @param val.
  writeUnsignedInt$int: (val) ->
    @writeFast$int val >>> 24
    @writeFast$int val >>> 16
    @writeFast$int val >>> 8
    @writeFast$int val

  # @private
  # Write number.
  #
  # @param val.
  writeUnsignedLong$int: (val) ->
    @writeFast$int val >>> 56
    @writeFast$int val >>> 48
    @writeFast$int val >>> 40
    @writeFast$int val >>> 32
    @writeFast$int val >>> 24
    @writeFast$int val >>> 16
    @writeFast$int val >>> 8
    @writeFast$int val

  getBufferBytes: () ->
    return @buf

  getBufferLength: () ->
    return @len

  # Resize buffer if necessary so that capacity will be enough.
  #
  # @param requiredCapacity.
  ensureCapacity$int: (requiredCapacity) ->
    bufLen = @buf.length
    if bufLen < requiredCapacity
      nextAllowedCapacity = Math.round((bufLen << 3) / 5)
      if nextAllowedCapacity > requiredCapacity
        requiredCapacity = nextAllowedCapacity
      newBuf = new Buffer requiredCapacity
      @buf.copy newBuf, 0, 0, @buf.length
      @buf = newBuf

  # Get string length in utf.
  #
  # @param chars the string.
  # @retuan length.
  @getUtfByteLength$String: (chars) ->
    length = chars.length
    len = 0
    for i in [0..length - 1]
      c = chars[i]
      if c >= 0x0001 && c <= 0x007F
        len += 1
      else if c > 0x07FF
        len += 3
      else
        len += 2
    return len

  # Write string as utf in buffer.
  #
  # @param chars the string.
  # @param bytes the Buffer to write this string in.
  # @param charLength the length of string.
  @utfCharsToBytes$String$int$Buffer$int$int: (chars, bytes, charLength) ->
    LightOutputStream.utfCharsToBytes$String$int$Buffer$int$int chars, 0,
            bytes, 0, charLength

  # Write string as utf in buffer.
  #
  # @param chars the string.
  # @param charOffset offset inside this string.
  # @param bytes the Buffer to write this string in.
  # @param byteOffset offset inside Buffer.
  # @param charLength the length of string.
  @utfCharsToBytes$String$int$Buffer$int$int: (chars, charOffset, bytes, byteOffset, charLength) ->
    if charOffset == undefined
      charOffset = 0
    if byteOffset == undefined
      byteOffset = 0
    charLength += charOffset
    for i in [charOffset..charLength - 1]
      c = chars[i]
      if c >= 0x0001 && c <= 0x007F
        bytes[byteOffset++] = c
      else if c > 0x07FF
        bytes[byteOffset++] = (0xE0 | ((c >> 12) & 0x0F))
        bytes[byteOffset++] = (0x80 | ((c >> 6) & 0x3F))
        bytes[byteOffset++] = (0x80 | ((c >> 0) & 0x3F))
      else
        bytes[byteOffset++] = (0xC0 | ((c >> 6) & 0x1F))
        bytes[byteOffset++] = (0x80 | ((c >> 0) & 0x3F))

exports.LightOutputStream = LightOutputStream
