// Buffer for packing data in byte-aligned way -*- c++ -*-

#ifndef BYTEBUFFER_H_
# define BYTEBUFFER_H_
# ifdef __GNUC__
#  pragma interface
# endif // __GNUC__

/** @file ByteBuffer.h
 * Encoding and decoding data in byte strings
 */

/* Copyright © 2002 Marko Mäkelä (msmakela@tcs.hut.fi).

   This file is part of MARIA, a reachability analyzer and model checker
   for high-level Petri nets.

   MARIA is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   MARIA is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   The GNU General Public License is often shipped with GNU software, and
   is generally kept in a file called COPYING or LICENSE.  If you do not
   have a copy of the license, write to the Free Software Foundation,
   59 Temple Place, Suite 330, Boston, MA 02111 USA. */

# include <string.h>
# include <assert.h>

/** Buffer for packing bytes */
class BytePacker
{
public:
  /** Constructor */
  BytePacker () :
    myBuf (0), myLength (0), myAllocated (0) {}
private:
  /** Copy constructor */
  explicit BytePacker (const class BytePacker& old);
  /** Assignment operator */
  class BytePacker& operator= (const class BytePacker& old);
public:
  /** Destructor */
  ~BytePacker () { delete[] myBuf; }

  /** Get the buffer */
  const unsigned char* getBuf () const { return myBuf; }
  /** Get the buffer */
  unsigned char* getBuf () { return myBuf; }
  /** Get the length of the buffer in bytes */
  unsigned getLength () const { return myLength; }
  /** Set the length of the buffer in bytes */
  void setLength (unsigned length) {
    assert (length <= myAllocated); myLength = length;
  }
  /** Get the allocated length of the buffer in bytes */
  unsigned getAllocated () const { return myAllocated; }

  /** Empty the buffer */
  void clear () { myLength = 0; }

  /** Determine how many bytes storage a number takes
   * @param num		Number to be encoded
   * @return		number of bytes required for encoding num
   */
  static unsigned size (unsigned num) {
    if (num < 128)
      return 1;
    else if ((num -= 128) < (1 << 14))
      return 2;
    else if ((num -= (1 << 14)) < (1 << 30))
      return 4;
    else
      return assert (false), 0;
  }

  /** Append an unsigned integer to the buffer
   * @param num		Number to be appended
   */
  void append (unsigned num) {
    // variable-length code:
    // 0..127			0xxxxxxx
    // 128..16511		10xxxxxxxxxxxxxx
    // 16512..1073758335	11xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    unsigned char* s;
    if (num < 128)
      *extend (1) = num;
    else if ((num -= 128) < (1 << 14))
      *(s = extend (2)) = 0x80 | (num >> 8), s[1] = num;
    else if ((num -= (1 << 14)) < (1 << 30)) {
      s = extend (4);
      s[3] = num;
      s[2] = num >>= 8;
      s[1] = num >>= 8;
      s[0] = (num >> 8) | 0xc0;
    }
    else
      assert (false);
  }

  /** Append a string of bytes to the buffer
   * @param buf		the byte string
   * @param len		length of the string in bytes
   */
  void append (const void* buf, unsigned len) {
    memcpy (extend (len), buf, len);
  }

  /** Extend the allocation of the buffer, so that the unused space
   * is at least as long as specified
   * @param len		minimum length of unused space
   */
  void allocate (unsigned len) {
    if (myLength + len < myAllocated)
      return;
    if (!myAllocated) {
      assert (!myBuf && !myLength);
      for (myAllocated = 1; myAllocated < len; myAllocated <<= 1);
      myBuf = new unsigned char[myAllocated];
    }
    else {
      assert (myAllocated && myBuf);
      for (; myAllocated < myLength + len; myAllocated <<= 1);
      unsigned char* buf = new unsigned char[myAllocated];
      memcpy (buf, myBuf, myLength);
      delete[] myBuf;
      myBuf = buf;
    }
  }

  /** Extend the buffer by the specified number of bytes
   * @param len		length to be added to the buffer
   * @return		pointer to the beginning of the added area
   */
  unsigned char* extend (unsigned len) {
    allocate (len);
    unsigned char* s = myBuf + myLength;
    myLength += len;
    return s;
  }

private:
  /** The buffer */
  unsigned char* myBuf;
  /** Length of the buffer in bytes */
  unsigned myLength;
  /** Allocated length of the buffer in bytes */
  unsigned myAllocated;
};

/** Buffer for unpacking bytes from a read-only buffer */
class ByteUnpacker
{
public:
  /** Constructor for read-only access
   * @param buffer	a previously encoded string (read-only)
   */
  explicit ByteUnpacker (const void* buffer) :
    buf (static_cast<const unsigned char*>(buffer)) {}
private:
  /** Copy constructor */
  explicit ByteUnpacker (const class ByteUnpacker& old);
  /** Assignment operator */
  class ByteUnpacker& operator= (const class ByteUnpacker& old);
public:
  /** Destructor */
  ~ByteUnpacker () {}

  /** Extract an unsigned integer from the buffer */
  unsigned extract () {
    const unsigned char* s = buf;
    switch (*s & 0xc0) {
    default:
      // 0..127
      buf++;
      return *s;
    case 0x80:
      // 128..16511
      buf += 2;
      return 128 + (((unsigned (*s) & 0x3f) << 8) | s[1]);
    case 0xc0:
      // 16512..1073758335
      buf += 4;
      return 16512 + ((unsigned (*s & 0x3f) << 24) |
		      (unsigned (s[1]) << 16) |
		      (unsigned (s[2]) << 8) |
		      s[3]);
    }
  }
  /** Extract an unsigned integer from a bounded buffer
   * @param p		the buffer whose cursor this is
   * @param i		(output) the extracted unsigned integer
   * @return		true if enough data was available; false at EOF
   */
  bool extract (const class BytePacker& p,
		unsigned& i) {
    if (!ensureData (p, 1))
      return false;
    switch (*buf & 0xc0) {
    default:
      // 0..127
      i = *buf++;
      return true;
    case 0x80:
      // 128..16511
      if (!ensureData (p, 2))
	return false;
      i = 128 + (((unsigned (*buf) & 0x3f) << 8) | buf[1]);
      buf += 2;
      return true;
    case 0xc0:
      // 16512..1073758335
      if (!ensureData (p, 4))
	return false;
      i = 16512 + ((unsigned (*buf & 0x3f) << 24) |
		   (unsigned (buf[1]) << 16) |
		   (unsigned (buf[2]) << 8) |
		   buf[3]);
      buf += 4;
      return true;
    }
  }

  /** Ensure that enough data is available to be extracted
   * @param p		the buffer whose cursor this is
   * @param numBytes	number of bytes needed to extract
   * @return		true if the data is available; false otherwise
   */
  bool ensureData (const class BytePacker& p,
		   unsigned numBytes) const {
    assert (buf >= p.getBuf ());
    assert (buf <= p.getBuf () + p.getLength ());
    return buf + numBytes <= p.getBuf () + p.getLength ();
  }

  /** Extract a string of bytes from the buffer
   * @param b		(output) the byte string, previously allocated
   * @param len		length of the string in bytes
   */
  void extract (void* b, unsigned len) {
    memcpy (b, buf, len);
    buf += len;
  }

  /** The extraction buffer */
  const unsigned char* buf;
};

#endif // BYTEBUFFER_H_
