// ---------------------------------------------------------------------------
// - Serial.cpp                                                              -
// - standard object library - serializable object 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-2012 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Set.hpp"
#include "Byte.hpp"
#include "Cons.hpp"
#include "List.hpp"
#include "Real.hpp"
#include "Regex.hpp"
#include "Plist.hpp"
#include "Stdsid.hxx"
#include "Vector.hpp"
#include "Strvec.hpp"
#include "Strfifo.hpp"
#include "Relatif.hpp"
#include "Boolean.hpp"
#include "Character.hpp"
#include "NameTable.hpp"
#include "QuarkZone.hpp"
#include "PrintTable.hpp"
#include "InputStream.hpp"
#include "cmem.hpp"

namespace afnix {

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

  // the maximum number of serial codes
  static const long SERIAL_CBKMAX = 256;
  // the array of serial callback
  static Serial::t_genser* p_sercbk = nilp;

  // the serial callback deallocator
  static void del_serial_cbk (void) {
    delete p_sercbk;
    p_sercbk = nilp;
  }

  // the serial callback allocator
  static void new_serial_cbk (void) {
    if (p_sercbk == nilp) {
      p_sercbk = new Serial::t_genser[SERIAL_CBKMAX];
      for (long i = 0; i < SERIAL_CBKMAX; i++) p_sercbk[i] = nilp;
      c_gcleanup (del_serial_cbk);
    }
  }

  // add a new serial callback
  static void add_serial_cbk (const t_byte sid, Serial::t_genser cbk) {
    new_serial_cbk ();
    if ((sid == 0x00) || (p_sercbk[sid] != nilp))
      throw Exception ("serial-errror", "cannot add callback");
    p_sercbk[sid] = cbk;
  }

  // get a serial object by sid
  static Serial* get_serial_object (const t_byte sid) {
    if ((p_sercbk == nilp) || (p_sercbk[sid] == nilp)) 
      throw Exception ("serial-error", "cannot find object to deserialize");
    Serial::t_genser cbk = p_sercbk[sid];
    return (cbk ());
  }

  // -------------------------------------------------------------------------
  // - public section                                                        -
  // -------------------------------------------------------------------------

  // register a deserialize callback

  t_byte Serial::setsid (const t_byte sid, t_genser cbk) {
    add_serial_cbk (sid, cbk);
    return sid;
  }

  // return the a standard object by serial id

  Serial* Serial::getserial (const t_byte sid) {
    switch (sid) {
    case SERIAL_NILP_ID:
      return nilp;
      break;
    case SERIAL_BOOL_ID:
      return new Boolean;
      break;
    case SERIAL_BYTE_ID:
      return new Byte;
      break;
    case SERIAL_INTG_ID:
      return new Integer;
      break;
    case SERIAL_RELT_ID:
      return new Relatif;
      break;
    case SERIAL_REAL_ID:
      return new Real;
      break;
    case SERIAL_CHAR_ID:
      return new Character;
      break;
    case SERIAL_STRG_ID:
      return new String;
      break;
    case SERIAL_REGX_ID:
      return new Regex;
      break;
    case SERIAL_CONS_ID:
      return new Cons;
      break;
    case SERIAL_VECT_ID:
      return new Vector;
      break;
    case SERIAL_OSET_ID:
      return new Set;
      break;
    case SERIAL_NTBL_ID:
      return new NameTable;
      break;
    case SERIAL_STRV_ID:
      return new Strvec;
      break;
    case SERIAL_PROP_ID:
      return new Property;
      break;
    case SERIAL_PLST_ID:
      return new Plist;
      break;
    case SERIAL_LIST_ID:
      return new List;
      break;
    case SERIAL_STRF_ID:
      return new Strfifo;
      break;
    case SERIAL_PTBL_ID:
      return new PrintTable;
      break;
    default:
      break;
    }
    // check if we can get a callback
    return get_serial_object (sid);
  }

  // check if a nil serial id is present

  bool Serial::isnilid (InputStream& is) {
    is.wrlock ();
    try {
      t_byte sid = is.read ();
      is.pushback ((char) sid);
      is.unlock ();
      return (sid == SERIAL_NILP_ID);
    } catch (...) {
      is.unlock ();
      throw;
    }
  }

  // write a nil id to an output stream

  void Serial::wrnilid (OutputStream& os) {
    os.write ((char) SERIAL_NILP_ID);
  }

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

  // return the object serial code

  t_byte Serial::serialid (void) const {
    throw Exception ("serial-error", "cannot get serial id for", repr ());
  }

  // serialize an object to an output stream

  void Serial::wrstream (OutputStream& os) const {
    throw Exception ("serial-error", "cannot serialize object", repr ());
  }

  // deserialize an object from an input stream

  void Serial::rdstream (InputStream& is) {
    throw Exception ("serial-error", "cannot deserialize object", repr ());
  }

  // serialize an object with it serial id

  void Serial::serialize (OutputStream& os) const {
    // write the serial id
    os.write ((char) serialid ());
    // serialize the object
    wrstream (os);
  }

  // return an object by deserialization

  Object* Serial::deserialize (InputStream& is) {
    // get a new object by serial id
    t_byte   sid = is.read ();
    Serial* sobj = Serial::getserial (sid);
    if (sobj == nilp) return nilp;
    // read in the object
    sobj->rdstream (is);
    return sobj;
  }

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

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 4;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the object supported quarks
  static const long QUARK_RSRLZ = zone.intern ("read-serial");
  static const long QUARK_WSRLZ = zone.intern ("write-serial");
  static const long QUARK_SERLZ = zone.intern ("serialize");
  static const long QUARK_DERLZ = zone.intern ("deserialize");

  // return true if the given quark is defined

  bool Serial::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* Serial::apply (Runnable* robj, Nameset* nset, const long quark,
			 Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_RSRLZ) {
	Object* obj = argv->get (0);
	InputStream* is = dynamic_cast <InputStream*> (obj);
	if (is == nilp) {
	  throw Exception ("type-error", "invalid object with read-serial",
			   Object::repr (obj));
	}
	rdstream (*is);
	return nilp;
      }
      if (quark == QUARK_DERLZ) {
	Object* obj = argv->get (0);
	InputStream* is = dynamic_cast <InputStream*> (obj);
	if (is == nilp) {
	  throw Exception ("type-error", "invalid object with deserialize",
			   Object::repr (obj));
	}
	return deserialize (*is);
      }
      if (quark == QUARK_WSRLZ) {
	Object* obj = argv->get (0);
	OutputStream* os = dynamic_cast <OutputStream*> (obj);
	if (os == nilp) {
	  throw Exception ("type-error", "invalid object with write-serial",
			   Object::repr (obj));
	}
	wrstream (*os);
	return nilp;
      }
      if (quark == QUARK_SERLZ) {
	Object* obj = argv->get (0);
	OutputStream* os = dynamic_cast <OutputStream*> (obj);
	if (os == nilp) {
	  throw Exception ("type-error", "invalid object with serialize",
			   Object::repr (obj));
	}
	serialize (*os);
	return nilp;
      }
    }
    // call the object method
    return Object::apply(robj, nset, quark, argv);
  }
}
