// ---------------------------------------------------------------------------
// - Buffer.cpp                                                              -
// - standard object library - character buffer class implementation         -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2008 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Byte.hpp"
#include "Ascii.hpp"
#include "Vector.hpp"
#include "Buffer.hpp"
#include "Unicode.hpp"
#include "Integer.hpp"
#include "Boolean.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
#include "ccnv.hpp"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // default buffer size
  const long BUFFER_SIZE = 1024;

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a new buffer class with a default size of 1024 characters

  Buffer::Buffer (void) {
    p_buffer = new char [BUFFER_SIZE];
    d_size   = BUFFER_SIZE;
    d_length = 0;
    d_rflg   = true;
    d_emod   = System::UTF8;
  }

  // create a new buffer with a predefined size

  Buffer::Buffer (const long size) {
    d_size   = (size <= 0) ? BUFFER_SIZE : size;
    p_buffer = new char[d_size];
    d_length = 0;
    d_rflg   = true;
    d_emod   = System::UTF8;
  }

  // create a new buffer by mode

  Buffer::Buffer (const System::t_emod emod) {
    d_size   = BUFFER_SIZE;
    p_buffer = new char[d_size];
    d_length = 0;
    d_rflg   = true;
    d_emod   = emod;
  }

  // create a new buffer and initialize it with a c string

  Buffer::Buffer (const char* value) {
    d_size   = BUFFER_SIZE;
    p_buffer = new char[d_size];
    d_length = 0;
    d_rflg   = true;
    d_emod   = System::UTF8;
    add (value, Ascii::strlen (value));
  }

  // create a new buffer and initialize it with a string

  Buffer::Buffer (const String& value) {
    d_size   = BUFFER_SIZE;
    p_buffer = new char[d_size];
    d_length = 0;
    d_rflg   = true;
    d_emod   = System::UTF8;
    add (value);
  }

  // copy construct a buffer

  Buffer::Buffer (const Buffer& that) {
    that.rdlock ();
    d_size   = that.d_size;
    d_length = that.d_length;
    d_rflg   = that.d_rflg;
    d_emod   = that.d_emod;
    p_buffer = new char[d_size];
    add (that.p_buffer, that.d_length);
    that.unlock ();
  }

  // destroy this buffer
  
  Buffer::~Buffer (void) {
    delete [] p_buffer;
  }
  
  // return the class name

  String Buffer::repr (void) const {
    return "Buffer";
  }
  
  // reset this buffer but do not change the size
  
  void Buffer::reset (void) {
    wrlock ();
    d_length = 0;
    unlock ();
  }

  // set the buffer encoding mode

  void Buffer::setemod (const System::t_emod emod) {
    wrlock ();
    d_emod = emod;
    unlock ();
  }

  // get the buffer encoding

  System::t_emod Buffer::getemod (void) const {
    rdlock ();
    System::t_emod result = d_emod;
    unlock ();
    return result;
  }

  // set the resize flag

  void Buffer::setrflg (const bool rflg) {
    wrlock ();
    d_rflg = rflg;
    unlock ();
  }

  // return the resize flag

  bool Buffer::getrflg (void) const {
    rdlock ();
    bool result = d_rflg;
    unlock ();
    return result;
  }

  // return true if the buffer is empty

  bool Buffer::empty (void) const {
    rdlock ();
    bool result = (length () == 0);
    unlock ();
    return result;
  }

  // return true if the buffer is full

  bool Buffer::full (void) const {
    rdlock ();
    bool result = d_rflg ? false : (length () == d_size);
    unlock ();
    return result;
  }

  // add a character in this buffer
  
  void Buffer::add (const char value) {
    wrlock ();
    // first check if we are at the buffer end
    if (d_length >= d_size) {
      if (d_rflg == true) {
	long size = d_size * 2;
	char* buf = new char[size];
	for (long i = 0; i < d_length; i++) buf[i] = p_buffer[i];
	delete [] p_buffer;
	d_size   = size;
	p_buffer = buf;
      } else {
	unlock ();
	throw Exception ("buffer-error", "buffer is full");
      }
    }
    p_buffer[d_length++] = value;
    unlock ();
  }

  // add a unicode character in the buffer

  void Buffer::add (const t_quad value) {
    wrlock ();
    try {
      // get the character encoding
      char* sbuf = Unicode::encode (value);
      long  size = Ascii::strlen (sbuf);
      // add the coding in the buffer
      add (sbuf, size);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a c-string in this buffer

  void Buffer::add (const char* s) {
    wrlock ();
    try {
      long size = Ascii::strlen (s);
      add (s, size);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a c-string in this buffer
  
  void Buffer::add (const char* s, const long size) {
    if ((s == nilp) || (size == 0)) return;
    wrlock ();
    try {
      for (long i = 0; i < size; i++) add (s[i]);
    } catch (...) {
      unlock ();
      throw;
    }
    unlock ();
  }

  // add a string in this buffer
  
  void Buffer::add (const String& value) {
    wrlock ();
    try {
      char* sbuf = value.encode  ();
      long  size = Ascii::strlen (sbuf);
      add (sbuf, size);
      delete [] sbuf;
    } catch (...) {
      unlock ();
      throw;
    }
    unlock ();
  }

  // add a buffer object to this buffer

  void Buffer::add (const Buffer& buffer) {
    // do not add yourself
    if (this == &buffer) return;
    // lock and add
    wrlock ();
    buffer.rdlock ();
    try {
      add (buffer.p_buffer, buffer.d_length);
    } catch (...) {
      buffer.unlock ();
      unlock ();
      throw;
    }
    buffer.unlock ();
    unlock ();
  }

  // get the next character but do not remove it

  char Buffer::get (void) const {
    rdlock ();
    char result = (d_length == 0) ? nilc : p_buffer[0];
    unlock ();
    return result;
  }
  
  // read a character in this buffer
  
  char Buffer::read (void) {
    wrlock ();
    // check for no character
    if (d_length == 0) {
      unlock ();
      return nilc;
    }
    // get value and shift
    char value = p_buffer[0];
    for (long i = 0; i < d_length-1; i++) p_buffer[i] = p_buffer[i+1];
    d_length--;
    unlock ();
    return value;
  }

  // read a line from this buffer

  String Buffer::readln (void) {
    wrlock ();
    try {
      // create a buffer to accumulate characters
      Buffer buf = d_emod;
      bool   flg = false;
      // read the character in the buffer
      while (empty () == false) {
	char c = read ();
	if (c == crlc) {
	  flg = true;
	  continue;
	}
	if (c == eolc) break;
	if (flg == true) {
	  buf.add (crlc);
	  flg = false;
	}
	buf.add (c);
      }
      unlock ();
      return buf.tostring ();
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // pushback a character in this buffer
  
  void Buffer::pushback (const char value) {
    wrlock ();
    // check if we are full
    if (d_length == d_size) {
      long size = d_size * 2;
      char* buf = new char[size];
      for (long i = 0; i < d_length; i++) buf[i] = p_buffer[i];
      d_size = size;
      delete [] p_buffer;
      p_buffer = buf;
    }
    // shift the buffer by one
    for (long i = d_length; i > 0; i--) p_buffer[i] = p_buffer[i-1];
    p_buffer[0] = value;
    d_length++;
    unlock ();
  }

  // pushback a  c-string to this buffer

  void Buffer::pushback (const char* s) {
    wrlock ();
    try {
      long size = Ascii::strlen (s);
      pushback (s, size);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // pushback a c-string in this buffer
  
  void Buffer::pushback (const char* s, const long size) {
    if ((s == nilp) || (size == 0)) return;
    wrlock ();
    long len = size - 1;
    for (long i = len; i >= 0; i--) pushback (s[i]);
    unlock ();
  }
  
  // pushback a string in this buffer
  
  void Buffer::pushback (const String& value) {
    wrlock ();
    try {
      char* sbuf = value.encode  ();
      long  size = Ascii::strlen (sbuf);
      pushback (sbuf, size);
      delete [] sbuf;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // pushback a buffer in this buffer
  
  void Buffer::pushback (const Buffer& buffer) {
    // do not pushback yourself
    if (this == &buffer) return;
    // lock and push
    wrlock ();
    buffer.rdlock ();
    try {
      pushback (buffer.p_buffer, buffer.d_length);
    } catch (...) {
      buffer.unlock ();
      unlock ();
      throw;
    }
    buffer.unlock ();
    unlock ();
  }
  
  // return the length of this buffer
  
  long Buffer::length (void) const {
    rdlock ();
    long result = d_length;
    unlock ();
    return result;
  }

  // return the corresponding string accumulated in this buffer
  
  String Buffer::tostring (void) const {
    rdlock ();
    try {
      t_quad* sbuf = nilp;
      switch (d_emod) {
      case System::BYTE:
	sbuf = Unicode::strdup (p_buffer, d_length);
	break;
      case System::UTF8:
	sbuf = Unicode::decode (p_buffer, d_length);
	break;
      }
      String result = sbuf;
      delete [] sbuf;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // map this buffer to an anonymous data structure

  long Buffer::tomap (void* data, const long size) {
    rdlock ();
    long result = (size < d_length) ? size : d_length;
    char* ptr   = reinterpret_cast <char*> (data);
    for (long i = 0; i < result; i++) ptr[i] = p_buffer[i];
    unlock ();
    return result;
  }

  // get a signed word in big endian format from this buffer

  t_word Buffer::getword (void) {
    wrlock ();
    if (length () < 2) {
      unlock ();
      throw Exception ("buffer-error", "not enough character for getword");
    }
    // extract the char buffer
    t_byte buf[2];
    for (long i = 0; i < 2; i++) buf[i] = read ();
    // convert it in host format
    t_word result = c_wntoh (buf);
    unlock ();
    return result;
  }

  // get a signed quad in big endian format from this buffer

  t_quad Buffer::getquad (void) {
    wrlock ();
    if (length () < 4) {
      unlock ();
      throw Exception ("buffer-error", "not enough character for getquad");
    }
    // extract the char buffer
    t_byte buf[4];
    for (long i = 0; i < 4; i++) buf[i] = read ();
    // convert it in host format
    t_quad result = c_qntoh (buf);
    unlock ();
    return result;
  }

  // get an octa in big endian format from this buffer

  t_octa Buffer::getocta (void) {
    wrlock ();
    if (length () < 8) {
      unlock ();
      throw Exception ("buffer-error", "not enough character for getquad");
    }
    // extract the char buffer
    t_byte buf[8];
    for (long i = 0; i < 8; i++) buf[i] = read ();
    // convert it in host format
    t_octa result = c_ontoh (buf);
    unlock ();
    return result;
  }

  // write the buffer content to an output stream
  
  void Buffer::write (Output& os) const {
    wrlock ();
    os.write (p_buffer, d_length);
    unlock ();
  }

  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 15;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);
  
  // the object supported quarks
  static const long QUARK_ADD      = zone.intern ("add");
  static const long QUARK_GET      = zone.intern ("get");
  static const long QUARK_READ     = zone.intern ("read");
  static const long QUARK_RESET    = zone.intern ("reset");
  static const long QUARK_WRITE    = zone.intern ("write");
  static const long QUARK_LENGTH   = zone.intern ("length");
  static const long QUARK_ISFULL   = zone.intern ("full-p");
  static const long QUARK_EMPTYP   = zone.intern ("empty-p");
  static const long QUARK_GETWORD  = zone.intern ("get-word");
  static const long QUARK_GETQUAD  = zone.intern ("get-quad");
  static const long QUARK_GETOCTA  = zone.intern ("get-octa");
  static const long QUARK_SETRFLG  = zone.intern ("set-resize");
  static const long QUARK_TOSTRING = zone.intern ("to-string");
  static const long QUARK_PUSHBACK = zone.intern ("pushback");
  static const long QUARK_ISRESIZE = zone.intern ("resize-p");

  // create a new object in a generic way

  Object* Buffer::mknew (Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();
    // create an empty buffer with 0 arguments
    Buffer* result = new Buffer;
    // loop with literal objects
    for (long i = 0; i < argc; i++) {
      Object*   obj = argv->get (i);
      Literal* lobj = dynamic_cast <Literal*> (obj);
      if (lobj == nilp)
	throw Exception ("type-error", "invalid object with buffer",
			 Object::repr (obj));
      result->add (lobj->tostring ());
    }
    return result;
  }

  // return true if the given quark is defined

  bool Buffer::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (zone.exists (quark) == true) {
      unlock ();
      return true;
    }      
    bool result = hflg ? Object::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }

  // apply this object with a set of arguments and a quark

  Object* Buffer::apply (Runnable* robj, Nameset* nset, const long quark,
			 Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // dispatch 0 argument
    if (argc == 0) {
      if (quark == QUARK_GET)      return new Byte    (get      ());
      if (quark == QUARK_READ)     return new Byte    (read     ());
      if (quark == QUARK_LENGTH)   return new Integer (length   ());
      if (quark == QUARK_ISFULL)   return new Boolean (full     ());
      if (quark == QUARK_EMPTYP)   return new Boolean (empty    ());
      if (quark == QUARK_GETWORD)  return new Integer (getword  ());
      if (quark == QUARK_GETQUAD)  return new Integer (getquad  ());
      if (quark == QUARK_GETOCTA)  return new Integer (getocta  ());
      if (quark == QUARK_TOSTRING) return new String  (tostring ());
      if (quark == QUARK_ISRESIZE) return new Boolean (getrflg  ());
      if (quark == QUARK_RESET) {
	reset ();
	return nilp;
      }
    }

    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_SETRFLG) {
	bool rflg = argv->getbool (0);
	setrflg (rflg);
	return nilp;
      }
      if (quark == QUARK_ADD) {
	Object* obj = argv->get (0);
	// check for a byte
	Byte* bobj = dynamic_cast<Byte*> (obj);
	if (bobj != nilp) {
	  add ((char) bobj->tobyte ());
	  return nilp;
	}
	// check for a literal
	Literal* lobj = dynamic_cast<Literal*> (obj);
	if (lobj != nilp) {
	  add (lobj->tostring ());
	  return nilp;
	}
	// check for a buffer
	Buffer* uobj = dynamic_cast<Buffer*> (obj);
	if (uobj != nilp) {
	  add (*uobj);
	  return nilp;
	}
	throw Exception ("type-error", "invalid object to add in buffer");
      }
      if (quark == QUARK_PUSHBACK) {
	Object* obj = argv->get (0);
	// check for a byte
	Byte* bobj = dynamic_cast<Byte*> (obj);
	if (bobj != nilp) {
	  pushback ((char) bobj->tobyte ());
	  return nilp;
	}
	// check for a literal
	Literal* lobj = dynamic_cast<Literal*> (obj);
	if (lobj != nilp) {
	  pushback (lobj->tostring ());
	  return nilp;
	}
	// check for a buffer
	Buffer* uobj = dynamic_cast<Buffer*> (obj);
	if (uobj != nilp) {
	  pushback (*uobj);
	  return nilp;
	}
	throw Exception ("type-error", "invalid object to pushback in buffer");
      }
      if (quark == QUARK_WRITE) {
	Output* os = dynamic_cast <Output*> (argv->get (0));
	if (os == nilp) 
	  throw Exception ("type-error", "output object expected with write");
	write (*os);
	return nilp;
      }
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }
}
