//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   16 Mar 06  Brian Frank  Creation
//

**
** Buf is used to model a block of bytes with random access.  Buf is
** typically backed by a block of memory, but can also be backed by
** a file:
**   - `Buf.make`: backed by RAM
**   - `File.open`: backed by random access file
**   - `File.mmap`: backed by memory mapped file
**
** Buf provides an `InStream` and `OutStream` to read and write into
** the buffer using a configurable position accessed via `Buf.pos`
** and `Buf.seek`.
**
** When using an InStream, bytes are read starting at pos where pos
** is advanced after each read.  The end of stream is reached when pos
** reaches size.  When using the OutStream, bytes are written starting at pos
** with pos advanced after each write.  If pos is less then size then
** the existing bytes are rewritten and size is not advanced, otherwise
** the buffer is automatically grown and size is advanced as bytes are
** appended.  It is common to write bytes into the buffer using the
** OutStream, then call `Buf.flip` to prepare the buffer to be used for reading.
**
** Memory bufs may be made immutable by calling `Obj.toImmutable`.  When a
** a buf is made immutable, the original buffer's data is cleared (to avoid
** copying the backing array).  All write operations on an immutable buf will
** raise a 'ReadonlyErr'.  Reads may be performed by acquiring an InStream via
** the `in` method.  However, reads operations which require a mutable Buf
** pos will raise ReadonlyErr too including methods such as `seek` or `read`.
** Use `dup` to copy an immutable buf back into a mutable buf.
**
class Buf
{

//////////////////////////////////////////////////////////////////////////
// Factory
//////////////////////////////////////////////////////////////////////////

  **
  ** Allocate a byte buffer in RAM with the initial given capacity.
  **
  static new make(Int capacity := 1024)

  **
  ** Generate a random series of bytes.
  **
  ** Example:
  **   Buf.random(8).toHex  => "d548b54989028b90"
  **
  static Buf random(Int size)

  **
  ** Buf cannot be subclassed outside of sys since we do
  ** much optimization under the covers in Java and C#.
  **
  internal new internalMake()

//////////////////////////////////////////////////////////////////////////
// Identity
//////////////////////////////////////////////////////////////////////////

  **
  ** Buf equality is based on reference equality using the === operator.
  **
  override Bool equals(Obj? that)

  **
  ** Return string summary of the buffer.
  **
  override Str toStr()

//////////////////////////////////////////////////////////////////////////
// Access
//////////////////////////////////////////////////////////////////////////

  **
  ** Return if size() == 0.
  **
  Bool isEmpty()

  **
  ** Return the total number of bytes in the buffer.  If the size is
  ** set greater than capacity then the buffer's capacity is automatically
  ** grown, otherwise capacity remains the same.  Setting size does not
  ** actually change any bytes in the buffer.  A mmap buffer can never
  ** be increased from its initial size.
  **
  Int size

  **
  ** The number of bytes this buffer can hold without allocating more
  ** memory.  Capacity is always greater or equal to size.  If adding a
  ** large number of bytes, it may be more efficient to manually set
  ** capacity.  See the `trim` method to automatically set capacity to
  ** size.  Throw ArgErr if attempting to set capacity less than size.
  ** This method is ignored on a file buffer, and unsupported on mmap.
  **
  Int capacity

  **
  ** Return the current position for the next read or write.  The
  ** position is always between 0 and `size`.  If pos is less then
  ** size then future writes will rewrite the existing bytes without
  ** growing size.  Change the position with `seek`.
  **
  Int pos()

  **
  ** Return the remaining number of bytes to read: size-pos.
  **
  Int remaining()

  **
  ** Return if more bytes are available to read: remaining() > 0.
  **
  Bool more()

  **
  ** Set the current position to the specified byte offset.  A
  ** negative index may be used to access from the end of the buffer.
  ** For example seek(-1) is translated into seek(size-1).
  ** Return this.
  **
  This seek(Int pos)

  **
  ** Flip a buffer from write-mode to read-mode.  This method sets
  ** total size to current position, and position to 0.  Return this.
  **
  This flip()

  **
  ** Get the byte at the specified absolute index.  A negative index
  ** may be used to access from the end of the buffer.  For example
  ** get(-1)  is translated into get(size()-1).  This method accesses
  ** the buffer absolutely independent of current position.  The get
  ** method is accessed via the [] shortcut operator.  Throw IndexErr
  ** if index out of range.
  **
  @Operator Int get(Int index)

  **
  ** Return a new buffer containing the bytes in the specified absolute
  ** range.  Negative indexes may be used to access from the end of
  ** the buf.  This method accesses the buffer absolutely independent
  ** of current position.  This method is accessed via the [] operator.
  ** Throw IndexErr if range illegal.
  **
  ** Examples:
  **   buf := Buf.make
  **   buf.write(0xaa).write(0xbb).write(0xcc).write(0xdd)
  **   buf[0..2]   => 0x[aabbcc]
  **   buf[3..3]   => 0x[dd]
  **   buf[-2..-1] => 0x[ccdd]
  **   buf[0..<2]  => 0x[aabb]
  **   buf[1..-2]  => 0x[bbcc]
  **
  @Operator Buf getRange(Range range)

  **
  ** Create a new buffer in memory which deeply clones this buffer.
  ** The resulting buf is read/write.
  **
  Buf dup()

//////////////////////////////////////////////////////////////////////////
// Modification
//////////////////////////////////////////////////////////////////////////

  **
  ** Set is used to overwrite the byte at the specified the index.  A
  ** negative index may be used to access an index from the end of the
  ** buffer.  The set method is accessed via the []= shortcut operator.
  ** Return this.  Throw IndexErr if index is out of range.
  **
  @Operator This set(Int index, Int byte)

  **
  ** Read the buffer for a fresh read by reseting the buffer's pos
  ** and size to zero.  The buffer's capacity remains the same.
  ** Return this.
  **
  This clear()

  **
  ** Trim the capacity such that the underlying storage is optimized
  ** for the current size.  Return this.
  **
  This trim()

  **
  ** If this buffer is backed by a file, then close it.  If a memory
  ** buffer then do nothing.  This method is guaranteed to never
  ** throw an IOErr.  Return true if the buffer was closed
  ** successfully or false if closed abnormally.
  **
  Bool close()

  **
  ** Obsolete call to `sync`.  In the future this method may be
  ** relaxed to flush only memory buffers, but not force an fsync.
  **
  @Deprecated { msg = "Use sync" }
  This flush()

  **
  ** If this Buf is backed by a file, then fsync all changes to
  ** the storage device.  Throw IOErr on error.  Return this.
  **
  This sync()

  **
  ** Byte order mode for both OutStream and InStream.
  ** Default is `Endian.big` (network byte order).
  **
  Endian endian

  **
  ** Character set for both the OutStream and InStream.
  **
  Charset charset

  **
  ** Write the specified byte to the end of the buffer using given count.
  **
  ** Examples:
  **   Buf().fill(0xff, 4)  =>  0xffffffff
  **
  This fill(Int byte, Int times)

//////////////////////////////////////////////////////////////////////////
// OutStream
//////////////////////////////////////////////////////////////////////////

  **
  ** Get the OutStream which writes to this buffer.
  ** This method always returns the same instance.
  ** If this buffer is backed by a file, then 'out.close'
  ** will not close the file - you must use `Buf.close`.
  **
  OutStream out()

  **
  ** Convenience for [out.write]`OutStream.write`
  ** Return this.
  **
  This write(Int byte)

  **
  ** Convenience for [out.writeBuf]`OutStream.writeBuf`
  ** Return this.
  **
  This writeBuf(Buf buf, Int n := buf.remaining)

  **
  ** Convenience for [out.writeI2]`OutStream.writeI2`
  ** Return this.
  **
  This writeI2(Int n)

  **
  ** Convenience for [out.writeI4]`OutStream.writeI4`
  ** Return this.
  **
  This writeI4(Int n)

  **
  ** Convenience for [out.writeI8]`OutStream.writeI8`
  ** Return this.
  **
  This writeI8(Int n)

  **
  ** Convenience for [out.writeF4]`OutStream.writeF4`
  ** Return this.
  **
  This writeF4(Float r)

  **
  ** Convenience for [out.writeF8]`OutStream.writeF8`
  ** Return this.
  **
  This writeF8(Float r)

  **
  ** Convenience for [out.writeDecimal]`OutStream.writeDecimal`
  ** Return this.
  **
  This writeDecimal(Decimal d)

  **
  ** Convenience for [out.writeBool]`OutStream.writeBool`
  ** Return this.
  **
  This writeBool(Bool b)

  **
  ** Convenience for [out.writeUtf]`OutStream.writeUtf`
  ** Return this.
  **
  This writeUtf(Str s)

  **
  ** Convenience for [out.writeChar]`OutStream.writeChar`
  ** Return this.
  **
  This writeChar(Int char)

  **
  ** Convenience for [out.writeChars]`OutStream.writeChars`
  ** Return this.
  **
  This writeChars(Str str, Int off := 0, Int len := str.size-off)

  **
  ** Convenience for [out.print]`OutStream.print`
  ** Return this.
  **
  This print(Obj? s)

  **
  ** Convenience for [out.printLine]`OutStream.printLine`
  ** Return this.
  **
  This printLine(Obj? obj := "")

  **
  ** Convenience for [out.writeProps]`OutStream.writeProps`
  ** Return this.
  **
  This writeProps(Str:Str props)

  **
  ** Convenience for [out.writeObj]`OutStream.writeObj`
  ** Return this.
  **
  This writeObj(Obj? obj, [Str:Obj]? options := null)

  **
  ** Convenience for [out.writeXml]`OutStream.writeXml`
  ** Return this.
  **
  This writeXml(Str s, Int flags := 0)

//////////////////////////////////////////////////////////////////////////
// InStream
//////////////////////////////////////////////////////////////////////////

  **
  ** Get the InStream which reads from this buffer.
  ** This method always returns the same instance.
  ** If this buffer is backed by a file, then 'in.close'
  ** will not close the file - you must use `Buf.close`.
  **
  InStream in()

  **
  ** Convenience for [in.read]`InStream.read`
  **
  Int? read()

  **
  ** Convenience for [in.readBuf]`InStream.readBuf`
  **
  Int? readBuf(Buf buf, Int n)

  **
  ** Convenience for [in.unread]`InStream.unread`
  ** Memory backed buffers support a stack based pushback model
  ** like IO streams.  File backed buffers will simply rewrite
  ** the last position in the file.  Return this.
  **
  This unread(Int b)

  **
  ** Convenience for [in.readAllBuf]`InStream.readAllBuf`
  **
  Buf readAllBuf()

  **
  ** Convenience for [in.readBufFully]`InStream.readBufFully`
  **
  Buf readBufFully(Buf? buf, Int n)

  **
  ** Convenience for [in.peek]`InStream.peek`
  **
  Int? peek()

  **
  ** Convenience for [in.readU1]`InStream.readU1`
  **
  Int readU1()

  **
  ** Convenience for [in.readS1]`InStream.readS1`
  **
  Int readS1()

  **
  ** Convenience for [in.readU2]`InStream.readU2`
  **
  Int readU2()

  **
  ** Convenience for [in.readS2]`InStream.readS2`
  **
  Int readS2()

  **
  ** Convenience for [in.readU4]`InStream.readU4`
  **
  Int readU4()

  **
  ** Convenience for [in.readS4]`InStream.readS4`
  **
  Int readS4()

  **
  ** Convenience for [in.readS8]`InStream.readS8`
  **
  Int readS8()

  **
  ** Convenience for [in.readF4]`InStream.readF4`
  **
  Float readF4()

  **
  ** Convenience for [in.readF8]`InStream.readF8`
  **
  Float readF8()

  **
  ** Convenience for [in.readDecimal]`InStream.readDecimal`
  **
  Decimal readDecimal()

  **
  ** Convenience for [in.readBool]`InStream.readBool`
  **
  Bool readBool()

  **
  ** Convenience for [in.readUtf]`InStream.readUtf`
  **
  Str readUtf()

  **
  ** Convenience for [in.readChar]`InStream.readChar`
  **
  Int? readChar()

  **
  ** Convenience for [in.unreadChar]`InStream.unreadChar`
  ** Memory backed buffers support a stack based pushback model
  ** like IO streams.  File backed buffers will simply rewrite
  ** the last position in the file.  Return this.
  **
  This unreadChar(Int b)

  **
  ** Convenience for [in.peekChar]`InStream.peekChar`
  **
  Int? peekChar()

  **
  ** Convenience for [in.readChars]`InStream.readChars`
  **
  Str readChars(Int n)

  **
  ** Convenience for [in.readLine]`InStream.readLine`
  **
  Str? readLine(Int? max := 4096)

  **
  ** Convenience for [in.readStrToken]`InStream.readStrToken`
  **
  Str? readStrToken(Int? max := null, |Int ch->Bool|? c := null)

  **
  ** Convenience for [in.readAllLines]`InStream.readAllLines`
  **
  Str[] readAllLines()

  **
  ** Convenience for [in.eachLine]`InStream.eachLine`
  **
  Void eachLine(|Str line| f)

  **
  ** Convenience for [in.readAllStr]`InStream.readAllStr`
  **
  Str readAllStr(Bool normalizeNewlines := true)

  **
  ** Convenience for [in.readProps]`InStream.readProps`
  **
  Str:Str readProps()

  **
  ** Convenience for [in.readObj]`InStream.readObj`
  **
  Obj? readObj([Str:Obj]? options := null)

//////////////////////////////////////////////////////////////////////////
// Conversions
//////////////////////////////////////////////////////////////////////////

  **
  ** Create an in-memory File instance for this buffer with the given
  ** file URI.  The buffer must be a RAM based buffer which is converted
  ** to an immutable buffer via 'Obj.toImmutable' semantics.  The current
  ** time is used for the file's modified time.
  **
  File toFile(Uri uri)

  **
  ** Encode the buffer contents from 0 to size into a
  ** hexadecimal string.  This method is unsupported for
  ** mmap buffers.
  **
  ** Example:
  **   Buf.make.print("\r\n").toHex   => "0d0a"
  **   Buf.fromHex("0d0a").readAllStr => "\r\n"
  **
  Str toHex()

  **
  ** Decode the specified hexadecimal string into its binary
  ** contents.  Any characters which are not included in the
  ** set "0-9, a-f, A-F" are ignored as long as they appear
  ** between bytes (hi and lo nibbles must be contiguous).
  **
  ** Example:
  **   Buf.make.print("\r\n").toHex   => "0d0a"
  **   Buf.fromHex("0d0a").readAllStr => "\r\n"
  **
  static Buf fromHex(Str s)

  **
  ** Encode the buffer contents from 0 to size to a Base64
  ** string as defined by MIME RFC 2045.  No line breaks are
  ** added.  This method is only supported by memory-backed
  ** buffers; file-backed buffers will throw UnsupportedErr.
  **
  ** Example:
  **   Buf.make.print("Fan").toBase64    => "RmFu"
  **   Buf.fromBase64("RmFu").readAllStr => "Fan"
  **
  Str toBase64()

  **
  ** Encode the buffer contents from 0 to size to a
  ** Uri-safe Base64 string as defined by RFC 4648.
  ** This means '+' is encoded as '-', and '/' is
  ** encoded as '_'. Additionally, no padding is applied.
  ** This method is only supported by memory-backed buffers;
  ** file-backed buffers will throw UnsupportedErr.
  **
  ** Example:
  **   Buf.make.print("safe base64~~").toBase64    => "c2FmZSBiYXNlNjR+fg=="
  **   Buf.make.print("safe base64~~").toBase64Uri => "c2FmZSBiYXNlNjR-fg"
  **
  Str toBase64Uri()

  **
  ** Decode the specified Base64 string into its binary contents.
  ** Both MIME RFC 2045 and URI-safe RFC 4648 encodings are supported.
  ** Any characters which are not included in the Base64 character
  ** set are safely ignored.
  **
  ** Example:
  **   Buf.make.print("Fan").toBase64    => "RmFu"
  **   Buf.fromBase64("RmFu").readAllStr => "Fan"
  **
  static Buf fromBase64(Str s)

  **
  ** Apply the specified message digest algorthm to this buffer's
  ** contents from 0 to size and return the resulting hash.  Digests
  ** are secure one-way hash functions which input an arbitrary sized
  ** buffer and return a fixed sized buffer.  Common algorithms include:
  ** "MD5", "SHA-1", and "SHA-256"; the full list supported is platform
  ** dependent.  On the Java VM, the algorithm maps to those avaialble
  ** via the 'java.security.MessageDigest' API.  Throw ArgErr if the
  ** algorithm is not available.  This method is unsupported for mmap
  ** buffers.
  **
  ** Example:
  **   Buf.make.print("password").print("salt").toDigest("MD5").toHex
  **    =>  "b305cadbb3bce54f3aa59c64fec00dea"
  **
  Buf toDigest(Str algorithm)

  **
  ** Compute a cycle reduancy check code using this buffer's contents
  ** from 0 to size.  The supported algorithm names:
  **    - "CRC-16": also known as CRC-16-ANSI, CRC-16-IBM; used by
  **      USB, ANSI X3.28, and Modbus
  **    - "CRC-32": used by Ethernet, MPEG-2, PKZIP, Gzip, PNG
  **    - "CRC-32-Adler": used by Zlib
  **
  ** Raise ArgErr is algorithm is not available.  This method is
  ** only supported for memory based buffers.
  **
  Int crc(Str algorithm)

  **
  ** Generate an HMAC message authentication as specified by RFC 2104.
  ** This buffer is the data input, 'algorithm' specifies the hash digest,
  ** and 'key' represents the secret key:
  **   - 'H': specified by algorthim parameter - "MD5" or "SHA1"
  **   - 'K': secret key specified by key parameter
  **   - 'B': fixed at 64
  **   - 'text': this instance
  **
  ** The HMAC is computed using:
  **   ipad = the byte 0x36 repeated B times
  **   opad = the byte 0x5C repeated B times
  **   H(K XOR opad, H(K XOR ipad, text))
  **
  ** Throw ArgErr if the algorithm is not available.  This method is
  ** only supported for memory buffers.
  **
  ** Examples:
  **   "hi there".toBuf.hmac("MD5", "secret".toBuf)
  **
  Buf hmac(Str algorithm, Buf key)

  **
  ** Generate a password based cryptographic key.  Supported algoriths:
  **   - "PBKDF2WithHmacSHA1"
  **   - "PBKDF2WithHmacSHA256"
  **
  ** Parameters:
  **   - password: secret used to generate resulting cryptographic key
  **   - salt: cryptographic salt
  **   - iterations: number of iterations (the 'c' term)
  **   - keyLen: desired length of key in bytes (not bits!)
  **
  ** Throw ArgErr if the algorithm is not available.  This method is
  ** only supported for memory buffers.
  **
  static Buf pbk(Str algorithm, Str password, Buf salt, Int iterations, Int keyLen)
}

**************************************************************************
** MemBuf
**************************************************************************

internal class MemBuf : Buf
{
  private new init()
}

**************************************************************************
** ConstBuf
**************************************************************************

internal class ConstBuf : Buf
{
  private new init()
}

**************************************************************************
** FileBuf
**************************************************************************

internal class FileBuf : Buf
{
  private new init()
}

**************************************************************************
** MmapBuf
**************************************************************************

// C# only right now (and not even implemented)
internal class MmapBuf : Buf
{
  private new init()
}

**************************************************************************
** NioBuf
**************************************************************************

// Java only
internal class NioBuf : Buf
{
  private new init()
}