// ---------------------------------------------------------------------------
// - Record.cpp                                                              -
// - afnix:sps module - record 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 "Cons.hpp"
#include "Spssid.hxx"
#include "Record.hpp"
#include "Method.hpp"
#include "Sorter.hpp"
#include "Boolean.hpp"
#include "Integer.hpp"
#include "Importer.hpp"
#include "Runnable.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"

namespace afnix {

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

  // this procedure returns a new record object for deserialization
  static Serial* mksob (void) {
    return new Record;
  }
  // register this cell serial id
  static const t_byte SERIAL_ID = Serial::setsid (SERIAL_RECD_ID, mksob);
 
  // this function compare an object with another one in ascent mode
  static bool qsort_cmplth (Object* ref, Object* slv) {
    if (ref == nilp) return true;
    Object*   obj = ref->oper (Object::LTH, slv);
    Boolean* bobj = dynamic_cast <Boolean*> (obj);
    bool result   = (bobj == nilp) ? false : bobj->toboolean ();
    Object::cref (obj);
    return result;
  }
  
  // this function compare an object with another one in descent mode
  static bool qsort_cmpgth (Object* ref, Object* slv) {
    if ((ref == nilp) && (slv == nilp)) return true;
    if ((ref == nilp) && (slv != nilp)) return false;
    Object*   obj = ref->oper (Object::GTH, slv);
    Boolean* bobj = dynamic_cast <Boolean*> (obj);
    bool result   = (bobj == nilp) ? false : bobj->toboolean ();
    Object::cref (obj);
    return result;
  }

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

  // create a nil record

  Record::Record (void) {
    d_quark = 0;
    reset ();
  }

  // create a new record by name

  Record::Record (const String& name) {
    d_quark = name.toquark ();
    reset ();
  }

  // copy construct this record

  Record::Record (const Record& that) {
    that.rdlock ();
    try {
      d_quark   = that.d_quark;
      d_trnum   = that.d_trnum;
      long rlen = that.length ();
      for (long i = 0; i < rlen; i++) {
	Cell* cell = that.get (i);
	if (cell == nilp) continue;
	add (new Cell (*cell));
      }
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }

  // return the object name

  String Record::repr (void) const {
    return "Record";
  }

  // return a clone of this object

  Object* Record::clone (void) const {
    return new Record (*this);
  }

  // assign a  record to this one

  Record& Record::operator = (const Record& that) {
    if (this == &that) return *this;
    wrlock ();
    that.rdlock ();
    try {
      d_quark   = that.d_quark;
      d_trnum   = that.d_trnum;
      long rlen = that.length ();
      for (long i = 0; i < rlen; i++) {
	Cell* cell = that.get (i);
	if (cell == nilp) continue;
	add (new Cell (*cell));
      }
      that.unlock ();
      unlock ();
    } catch (...) {
      that.unlock ();
      unlock ();
      throw;
    }
    return *this;
  }

  // return the record serial id

  t_byte Record::serialid (void) const {
    return SERIAL_RECD_ID;
  }

  // serialize a record

  void Record::wrstream (Output& os) const {
    rdlock ();
    // save the record name
    const String& name = String::qmap (d_quark);
    name.wrstream (os);
    // save the transaction number
    Integer trnum  (d_trnum);
    trnum.wrstream (os);
    // save the vector
    d_vcell.wrstream (os);
    unlock ();
  }

  // deserialize this record

  void Record::rdstream (Input& is) {
    wrlock ();
    // get the record name
    String name;
    name.rdstream (is);
    d_quark = name.toquark ();
    // et the transaction number
    Integer trnum;
    trnum.rdstream (is);
    d_trnum = trnum.tointeger ();
    // get the vector
    d_vcell.rdstream (is);
    unlock ();
  }

  // reset this record

  void Record::reset (void) {
    wrlock ();
    try {
      d_trnum = -1;
      d_vcell.reset ();
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the record name

  String Record::getname (void) const {
    rdlock ();
    String result = String::qmap (d_quark);
    unlock ();
    return result;
  }

  // set the record name

  void Record::setname (const String& name) {
    wrlock ();
    d_quark = name.toquark ();
    unlock ();
  }

  // set the cell name by index

  void Record::setname (const long index, const String& name) {
    wrlock ();
    try {
    Cell* cell = dynamic_cast <Cell*> (d_vcell.get (index));
    if (cell != nilp) cell->setname (name);
    unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set the transaction number

  void Record::setrnum (const long trnum) {
    wrlock ();
    d_trnum = trnum < 0 ? d_trnum = -1 : trnum;
    unlock ();
  }

  // return the transaction number

  long Record::getrnum (void) const {
    rdlock ();
    long result = d_trnum;
    unlock ();
    return result;
  }

  // return the length of the record list

  long Record::length (void) const {
    rdlock ();
    long result = d_vcell.length ();
    unlock ();
    return result;
  }

  // add a cell in this record

  void Record::add (Cell* cell) {
    wrlock ();
    try {
      Cell* data = (cell == nilp) ? new Cell : cell;
      d_vcell.append (data);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a literal in this record

  void Record::add (Literal* lobj) {
    wrlock ();
    Cell* cell = new Cell (lobj);
    try {
      add (cell);
      unlock ();
    } catch (...) {
      delete cell;
      unlock ();
      throw;
    }
  }

  // add a literal in this record

  void Record::add (const String& name, Literal* lobj) {
    wrlock ();
    Cell* cell = new Cell (name, lobj);
    try {
      add (cell);
      unlock ();
    } catch (...) {
      delete cell;
      unlock ();
      throw;
    }
  }

  // add an object to this record

  void Record::add (Object* object) {
    if (object == nilp) {
      add (new Cell);
      return;
    }
    // check for a cell
    Cell* cell = dynamic_cast <Cell*> (object);
    if (cell != nilp) {
      add (cell);
      return;
    }
    // check for a literal
    Literal* lobj = dynamic_cast <Literal*> (object);
    if (lobj != nilp) {
      add (lobj);
      return;
    }
    // check for a cons cell - which contains the name and literal value
    Cons* cons = dynamic_cast <Cons*> (object);
    if (cons != nilp) {
      // check for 2 elements
      if (cons->length () != 2) {
	throw Exception ("cons-error", "invalid cons cell to add in record");
      }
      // check the car
      Object* car  = cons->getcar ();
      Object* scst = (car == nilp) ? nilp : car->mini ();
      String* sobj = (car == nilp) ? nilp : dynamic_cast <String*> (scst);
      if (sobj == nilp) {
	throw Exception ("type-error", "invalid object in cons cell to add",
			 Object::repr (car));
      }
      // check the cadr
      Object* cadr = cons->getcadr ();
      Object* lcst = (car == nilp) ? nilp : cadr->mini ();
      lobj = dynamic_cast <Literal*> (lcst);
      if (sobj == nilp) {
	throw Exception ("type-error", "invalid object in cons cell to add",
			 Object::repr (cadr));
      }
      // add the the literal by name
      add (*sobj, lobj);
      return;
    }
    // invalid object
    throw Exception ("type-error", "invalid object to add in record",
		     Object::repr (object));
  }

  // add a vector of objects

  void Record::add (const Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();
    if (argc == 0) return;
    wrlock ();
    try {
      for (long i = 0; i < argc; i++) add (argv->get (i));
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a cell by index

  Cell* Record::get (const long index) const {
    rdlock ();
    Cell* result = dynamic_cast <Cell*> (d_vcell.get (index));
    unlock ();
    return result;
  }

  // map a cell literal by index

  Literal* Record::map (const long index) const {
    rdlock ();
    try {
      Cell* cell = get (index);
      Literal* lobj = (cell == nilp) ? nilp : cell->get ();
      unlock ();
      return lobj;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set a cell in this record by index

  void Record::set (const long index, Cell* cell) {
    wrlock ();
    try {
      // add cell if necessary
      long rlen = length ();
      if (index >= rlen) {
	long delta = index - rlen + 1;
	for (long i = 0; i < delta; i++) add (new Cell);
      }
      // set the cell
      d_vcell.set (index, cell);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set a literal in this record by index

  void Record::set (const long index, Literal* lobj) {
    wrlock ();
    Cell* cell = new Cell (lobj);
    // update the record
    try {
      // add cell if necessary
      long rlen = length ();
      if (index >= rlen) {
	long delta = index - rlen + 1;
	for (long i = 0; i < delta; i++) add (new Cell);
      }
      // add the cell
      d_vcell.set (index, cell);
    } catch (...) {
      delete cell;
      unlock ();
      throw;
    }
    unlock ();
  }

  // set an object in this record by index

  void Record::set (const long index, Object* object) {
    if (object == nilp) return;
    // check for a cell
    Cell* cell = dynamic_cast <Cell*> (object);
    if (cell != nilp) {
      set (index, cell);
      return;
    }
    // check for a literal
    Literal* lobj = dynamic_cast <Literal*> (object);
    if (lobj != nilp) {
      set (index, lobj);
      return;
    }
    throw Exception ("type-error", "invalid object to set in record",
		     Object::repr (object));
  }

  // find a cell by quark

  Cell* Record::find (const long quark) const {
    rdlock ();
    long rlen = d_vcell.length ();
    for (long i = 0; i < rlen; i++) {
      Cell* cell = dynamic_cast <Cell*> (d_vcell.get (i));
      if (cell == nilp) continue;
      if (*cell == quark) {
	unlock ();
	return cell;
      }
    }
    unlock ();
    return nilp;
  }

  // find a cell by name

  Cell* Record::find (const String& name) const {
    long quark = name.toquark ();
    return find (quark);
  }

  // find a cell by quark or throw an exception

  Cell* Record::lookup (const long quark) const {
    Cell* cell = find (quark);
    if (cell == nilp) {
      throw Exception ("lookup-error", "cannot find cell", 
		       String::qmap (quark));
    }
    return cell;
  }

  
  // find a cell by name or throw an exception

  Cell* Record::lookup (const String& name) const {
    long quark = name.toquark ();
    return lookup (quark);
  }

  // get a cell index by quark

  long Record::getindex (const long quark) const {
    rdlock ();
    long rlen = d_vcell.length ();
    for (long i = 0; i < rlen; i++) {
      Cell* cell = dynamic_cast <Cell*> (d_vcell.get (i));
      if (cell == nilp) continue;
      if (*cell == quark) {
	unlock ();
	return i;
      }
    }
    unlock ();
    throw Exception ("index-error", "cannot find cell by index",
		     String::qmap (quark));
  }

  // get a cell index by name

  long Record::getindex (const String& name) const {
    long quark = name.toquark ();
    return getindex (quark);
  }

  // check if the a literal match a cell by index

  bool Record::isequal (const long index, const Literal& lobj) const {
    rdlock ();
    try {
      // get the cell by index
      Cell* cell = get (index);
      // check the literal
      bool result = (cell == nilp) ? false : (*cell == lobj);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // import data in this record

  void Record::import (Input* is) {
    if (is == nilp) return;
    // protect us during the importation
    Object::iref (this);
    // create a new importer
    Recording ri (this);
    try {
      ri.import (is);
      Object::tref (this);
    } catch (...) {
      Object::tref (this);
      throw;
    }
  }

  // sort a record by ascending or descending mode

  void Record::sort (const bool mode) {
    wrlock ();
    // build the sorter object
    Sorter sorter;
    if (mode == true) 
      sorter.setcmpf (qsort_cmplth);
    else
      sorter.setcmpf (qsort_cmpgth);
    // sort the vector
    try {
      sorter.qsort (&d_vcell);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

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

  // the record supported quarks
  static const long QUARK_ADD      = zone.intern ("add");
  static const long QUARK_GET      = zone.intern ("get");
  static const long QUARK_MAP      = zone.intern ("map");
  static const long QUARK_SET      = zone.intern ("set");
  static const long QUARK_FIND     = zone.intern ("find");
  static const long QUARK_SORT     = zone.intern ("sort");
  static const long QUARK_RESET    = zone.intern ("reset");
  static const long QUARK_LOOKUP   = zone.intern ("lookup");
  static const long QUARK_LENGTH   = zone.intern ("length");
  static const long QUARK_GETNAME  = zone.intern ("get-name");
  static const long QUARK_SETNAME  = zone.intern ("set-name");
  static const long QUARK_GETINDEX = zone.intern ("get-index");
  static const long QUARK_SETRNUM  = zone.intern ("set-transaction-number");
  static const long QUARK_GETRNUM  = zone.intern ("get-transaction-number");

  // create a new object in a generic way

  Object* Record::mknew (Vector* argv) {
    // get number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();
    // check for 0 argument
    if (argc == 0) return new Record;
    // check for 1 argument
    if (argc == 1) {
      String name = argv->getstring (0);
      return new Record (name);
    }
    throw Exception ("argument-error", "too many argument with record");
  }

  // return true if the given quark is defined

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

  // evaluate this object object by quark
  
  Object* Record::eval (Runnable* robj, Nameset* nset, const long quark) {
    rdlock ();
    try {
      // find the cell by quark
      Cell* cell = find (quark);
      if (cell != nilp) {
	Object* result = cell->get ();
	robj->post (result);
	unlock ();
	return result;
      }
      // make it a method
      unlock ();
      return new Method (quark, this);
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

  Object* Record::apply (Runnable* robj, Nameset* nset, const long quark,
			 Vector* argv) {
    // check for generic quark
    if (quark == QUARK_ADD) {
      add (argv);
      return nilp;
    }

    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // dispatch 0 argument
    if (argc == 0) {
      if (quark == QUARK_LENGTH ) return new Integer (length  ());
      if (quark == QUARK_GETNAME) return new String  (getname ());
      if (quark == QUARK_GETRNUM) return new Integer (getrnum ());
      if (quark == QUARK_RESET) {
	reset ();
	return nilp;
      }
      if (quark == QUARK_SORT) {
	sort (true);
	return nilp;
      }
    }
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_SETNAME) {
	String name = argv->getstring (0);
	setname (name);
	return nilp;
      }
      if (quark == QUARK_SETRNUM) {
	long trnum = argv->getint (0);
	setrnum (trnum);
	return nilp;
      }
      if (quark == QUARK_SORT) {
	bool mode = argv->getbool (0);
	sort (mode);
	return nilp;
      }
      if (quark == QUARK_FIND) {
	String name = argv->getstring (0);
	rdlock ();
	Object* result = find (name);
	robj->post (result);
	unlock ();
	return result;
      }
      if (quark == QUARK_LOOKUP) {
	String name = argv->getstring (0);
	rdlock ();
	Object* result = lookup (name);
	robj->post (result);
	unlock ();
	return result;
      }
      if (quark == QUARK_GETINDEX) {
	String name = argv->getstring (0);
	long result = getindex (name);
	return new Integer (result);
      }
      if (quark == QUARK_GET) {
	long idx = argv->getint (0);
	rdlock ();
	try {
	  Object* result = get (idx);
	  robj->post (result);
	  unlock ();
	  return result;
	} catch (...) {
	  unlock ();
	  throw;
	}
      }
      if (quark == QUARK_MAP) {
	long idx = argv->getint (0);
	rdlock ();
	try {
	  Object* result = map (idx);
	  robj->post (result);
	  unlock ();
	  return result;
	} catch (...) {
	  unlock ();
	  throw;
	}
      }
    }
    // dispatch 2 argument
    if (argc == 2) {
      if (quark == QUARK_SET) {
	long    idx = argv->getint (0);
	Object* obj = argv->get (1);
	set (idx, obj);
	return nilp;
      }
      if (quark == QUARK_SETNAME) {
	long    idx = argv->getint    (0);
	String name = argv->getstring (1);
	setname (idx, name);
	return nilp;
      }
    }
    // call the persist method
    return Persist::apply (robj, nset, quark, argv);
  }
}
