// ---------------------------------------------------------------------------
// - Importer.cpp                                                            -
// - afnix:sps module - sps objects importer 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-2011 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Reader.hpp"
#include "Importer.hpp"
#include "Runnable.hpp"
#include "QuarkZone.hpp"
#include "InputStream.hpp"

namespace afnix {

  // -------------------------------------------------------------------------
  // - class section (record importer)                                       -
  // -------------------------------------------------------------------------

  // create a default record importer

  Recording::Recording (void) {
    p_ircd = nilp;
    setrcd (new Record);
  }

  // create a record importer with a record

  Recording::Recording (Record* rcd) {
    p_ircd = nilp;
    setrcd (rcd);
  }

  // destroy this record importer

  Recording::~Recording (void) {
    Object::dref (p_ircd);
  }

  // return the object name

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

  // return the importation record

  Record* Recording::getrcd (void) const {
    rdlock ();
    Record* result = p_ircd;
    unlock ();
    return result;
  }

  // set the record to import

  void Recording::setrcd (Record* rcd) {
    wrlock ();
    Object::dref (p_ircd);
    Object::iref (p_ircd = rcd);
    unlock ();
  }

  // import a list of literal into the record

  void Recording::import (Cons* cons) {
    if (cons == nilp) return;
    wrlock ();
    try {
      // iterate in the cons cell
      while (cons != nilp) {
	// get the object and reduce it
	Object* car = cons->getcar ();
	Object* obj = (car == nilp) ? nilp : car->mini ();
	p_ircd->add (obj);
	cons = cons->getcdr ();
      }
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // import stream data into the record

  void Recording::import (InputStream* is) {
    if (is == nilp) return;
    wrlock ();
    // create a new reader
    Reader rd (is);
    wrlock ();
    Form* form = nilp;
    try {
      // parse with the reader
      while (true) {
	form = rd.parse ();
	if (form == nilp) break;
	import (form);
      }
    } catch (Exception& e) {
      if (form == nilp) {
	e.setlnum (rd.getlnum ());
      } else {
	e.setlnum (form->getlnum ());
	Object::cref (form);
      }
      unlock ();
      throw;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - object section (record importer)                                      -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_RZON_LENGTH = 3;
  static QuarkZone  rzon (QUARK_RZON_LENGTH);

  // the object supported quarks
  static const long QUARK_SETRCD  = rzon.intern ("set-record");
  static const long QUARK_GETRCD  = rzon.intern ("get-record");
  static const long QUARK_RIMPORT = rzon.intern ("import");

  // create a new object in a generic way

  Object* Recording::mknew (Vector* argv) {
    // get number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();
    // check for 0 argument
    if (argc == 0) return new Recording;
    // check for 1 argument
    if (argc == 1) {
      Object* obj = argv->get (0);
      Record* rcd = dynamic_cast <Record*> (obj);
      if (rcd != nilp) {
	return new Recording (rcd);
      }
      throw Exception ("type-error", "invalid argument with record import");
    }
    throw Exception ("argument-error", "too many argument with record import");
  }

  // return true if the given quark is defined

  bool Recording::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (rzon.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* Recording::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_GETRCD) {
	rdlock ();
	Record* rcd = getrcd ();
	robj->post (rcd);
	unlock ();
	return rcd;
      }
    }
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_SETRCD) {
	Object* obj = argv->get (0);
	Record* rcd = dynamic_cast <Record*> (obj);
	if (rcd == nilp) 
	  throw Exception ("type-error", "invalid object for set-record ",
			   Object::repr (obj));
	setrcd (rcd);
	return nilp;
      }
      if (quark == QUARK_RIMPORT) {
	Object* obj = argv->get (0);
	// check for an input stream
	InputStream* is = dynamic_cast <InputStream*> (obj);
	if (is != nilp) {
	  import (is);
	  return nilp;
	}
	// check for a cons cell
	Cons* cons = dynamic_cast <Cons*> (obj);
	if (cons != nilp) {
	  import (cons);
	  return nilp;
	}
	throw Exception ("type-error", "invalid object for import ",
			 Object::repr (obj));
      }
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }

  // -------------------------------------------------------------------------
  // - object section (sheet importer)                                       -
  // -------------------------------------------------------------------------

  // create a default sheet importer

  Sheeting::Sheeting (void) {
    p_isht = nilp;
    setsht (new Sheet);
  }

  // create a sheet importer with a sheet

  Sheeting::Sheeting (Sheet* rcd) {
    p_isht = nilp;
    setsht (rcd);
  }

  // destroy this sheet importer

  Sheeting::~Sheeting (void) {
    Object::dref (p_isht);
  }

  // return the object name

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

  // return the importation sheet

  Sheet* Sheeting::getsht (void) const {
    rdlock ();
    Sheet* result = p_isht;
    unlock ();
    return result;
  }

  // set the sheet to import

  void Sheeting::setsht (Sheet* sht) {
    wrlock ();
    Object::dref (p_isht);
    Object::iref (p_isht = sht);
    unlock ();
  }

  // import a list of literal into the sheet

  void Sheeting::import (Cons* cons) {
    if (cons == nilp) return;
    wrlock ();
    Record* rcd = new Record;
    try {
      // iterate in the cons cell
      while (cons != nilp) {
	// get the object and reduce it
	Object* car = cons->getcar ();
	Object* obj = (car == nilp) ? nilp : car->mini ();
	rcd->add (obj);
	cons = cons->getcdr ();
      }
      p_isht->add (rcd);
    } catch (...) {
      Object::cref (rcd);
      unlock ();
      throw;
    }
  }

  // import stream data into the sheet

  void Sheeting::import (InputStream* is) {
    if (is == nilp) return;
    wrlock ();
    // create a new reader
    Reader rd (is);
    wrlock ();
    Form* form = nilp;
    try {
      // parse with the reader
      while (true) {
	form = rd.parse ();
	if (form == nilp) break;
	import (form);
      }
    } catch (Exception& e) {
      if (form == nilp) {
	e.setlnum (rd.getlnum ());
      } else {
	e.setlnum (form->getlnum ());
	Object::cref (form);
      }
      unlock ();
      throw;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - object section (sheet importer)                                       -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_SZON_LENGTH = 3;
  static QuarkZone  szon (QUARK_SZON_LENGTH);

  // the object supported quarks
  static const long QUARK_SETSHT  = szon.intern ("set-sheet");
  static const long QUARK_GETSHT  = szon.intern ("get-sheet");
  static const long QUARK_SIMPORT = szon.intern ("import");

  // create a new object in a generic way

  Object* Sheeting::mknew (Vector* argv) {
    // get number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();
    // check for 0 argument
    if (argc == 0) return new Sheeting;
    // check for 1 argument
    if (argc == 1) {
      Object* obj = argv->get (0);
      Sheet*  sht = dynamic_cast <Sheet*> (obj);
      if (sht != nilp) {
	return new Sheeting (sht);
      }
      throw Exception ("type-error", "invalid argument with sheet import");
    }
    throw Exception ("argument-error", "too many argument with sheet import");
  }

  // return true if the given quark is defined

  bool Sheeting::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (szon.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* Sheeting::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_GETSHT) {
	rdlock ();
	Sheet* sht = getsht ();
	robj->post (sht);
	unlock ();
	return sht;
      }
    }
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_SETSHT) {
	Object* obj = argv->get (0);
	Sheet*  sht = dynamic_cast <Sheet*> (obj);
	if (sht == nilp) 
	  throw Exception ("type-error", "invalid object for set-sheet ",
			   Object::repr (obj));
	setsht (sht);
	return nilp;
      }
      if (quark == QUARK_SIMPORT) {
	Object* obj = argv->get (0);
	// check for an input stream
	InputStream* is = dynamic_cast <InputStream*> (obj);
	if (is != nilp) {
	  import (is);
	  return nilp;
	}
	// check for a cons cell
	Cons* cons = dynamic_cast <Cons*> (obj);
	if (cons != nilp) {
	  import (cons);
	  return nilp;
	}
	throw Exception ("type-error", "invalid object for import ",
			 Object::repr (obj));
      }
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }
}
