// ---------------------------------------------------------------------------
// - Transfer.cpp                                                            -
// - afnix:sps module - sps objects transfer 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-2019 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Csv.hpp"
#include "Locale.hpp"
#include "Reader.hpp"
#include "Transfer.hpp"
#include "Runnable.hpp"
#include "InputFile.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"

namespace afnix {

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

  // default transfer format
  static const Transfer::t_xfer SPS_XFER_DEF = Transfer::XFER_CSV;
  // default break sequence
  static const String SPS_SBRK_DEF = ",;\t";
  // the csv transfer format
  static const String SPS_XFMT_CSV = "CSV";
  // the sps transfer format
  static const String SPS_XFMT_SPS = "SPS";

  // this procedure map a string to a transfer type
  static Transfer::t_xfer sps_to_xfer (const String& xfmt) {
    // check for nil
    if (xfmt.isnil () == true) return SPS_XFER_DEF;
    // map to format
    String xu = xfmt.toupper ();
    if (xu ==  SPS_XFMT_CSV) return Transfer::XFER_CSV;
    if (xu ==  SPS_XFMT_SPS) return Transfer::XFER_SPS;
    throw Exception ("sps-error", "invalid transfer format", xfmt);
  }
  
  // -------------------------------------------------------------------------
  // - class section (transfer)                                              -
  // -------------------------------------------------------------------------

  // create a default transfer

  Transfer::Transfer (void) {
    d_xfer = SPS_XFER_DEF;
    d_sbrk = SPS_SBRK_DEF;
  }

  // copy construct this transfert

  Transfer::Transfer (const Transfer& that) {
    that.rdlock ();
    try {
      d_xfer = that.d_xfer;
      d_sbrk = that.d_sbrk;
      d_locv = that.d_locv;
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }
  
  // copy move this transfert

  Transfer::Transfer (Transfer&& that) noexcept :
    Object (static_cast<Object&&>(that)) {
    that.wrlock ();
    try {
      d_xfer = that.d_xfer; that.d_xfer = SPS_XFER_DEF;
      d_sbrk = that.d_sbrk; that.d_sbrk = SPS_SBRK_DEF;
      d_locv = that.d_locv; that.d_locv.reset ();
      that.unlock ();
    } catch (...) {
      d_xfer = SPS_XFER_DEF;
      d_sbrk = SPS_SBRK_DEF;
      d_locv.reset ();
      that.unlock ();
    }
  }
  
  // assign a transfer into this one

  Transfer& Transfer::operator = (const Transfer& that) {
    // check for self-assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      d_xfer = that.d_xfer;
      d_sbrk = that.d_sbrk;
      d_locv = that.d_locv;
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      that.unlock ();
      throw;
    }
  }

  // move a transfert into this one

  Transfer& Transfer::operator = (Transfer&& that) noexcept {
    // check for self-move
    if (this == &that) return *this;
    // lock and move
    wrlock ();;
    that.wrlock ();
    try {
      // assign base object
      Object::operator = (static_cast<Object&&>(that));
      // assign locally
      d_xfer = that.d_xfer; that.d_xfer = SPS_XFER_DEF;
      d_sbrk = that.d_sbrk; that.d_sbrk = SPS_SBRK_DEF;
      d_locv = that.d_locv; that.d_locv.reset ();
      unlock ();
      that.unlock ();
    } catch (...) {
      d_xfer = SPS_XFER_DEF;
      d_sbrk = SPS_SBRK_DEF;
      d_locv.reset ();
      unlock ();
      that.unlock ();
    }
    return *this;
  }
  
  // create a transfer by format and break sequence

  Transfer::Transfer (const String& xfmt, const String& sbrk) {
    setxfmt (xfmt);
    setsbrk (sbrk);
  }

  // set the transfer format

  void Transfer::setxfmt (const String& xfmt) {
    wrlock ();
    try {
      d_xfer = sps_to_xfer (xfmt);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // set the break sequence

  void Transfer::setsbrk (const String& sbrk) {
    wrlock ();
    try {
      d_sbrk = (sbrk.isnil () == true) ? SPS_SBRK_DEF : sbrk;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a locale definition

  void Transfer::addldef (const String& ldef) {
    wrlock ();
    try {
      // create a new locale object
      Locale lo (ldef);
      // get the index and check
      long lidx = lo.getlidx ();
      if (lidx < 0) {
	throw Exception ("transfer-error",
			 "missing index in locale descriptor");
      }
      d_locv.add (lidx, lo.clone ());
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // import stream data into the sheet

  void Transfer::import (InputStream* is) {
    // check for nil
    if (is == nullptr) return;
    // lock and import
    wrlock ();
    Form* form = nullptr;
    Former* rd = nullptr;
    try {
      // protect the stream
      Object::iref (is);
      // get the former reader
      switch (d_xfer) {
      case Transfer::XFER_CSV:
	rd = new Csv (is, d_sbrk, d_locv);
	break;
      case Transfer::XFER_SPS:
	rd = new Reader (is);
	break;
      }
      // parse with the reader
      while (true) {
	form = rd->parse ();
	if (form == nullptr) break;
	import (form);
	Object::cref (form); form = nullptr;
      }
      delete rd;
      Object::tref (is);
    } catch (Exception& e) {
      if (form == nullptr) {
	e.setlnum (rd->getlnum ());
      } else {
	e.setlnum (form->getlnum ());
	Object::cref (form);
      }
      delete rd;
      unlock ();
      throw;
    } catch (...) {
      delete rd;
      Object::tref (is);
      Object::cref (form);
      unlock ();
      throw;
    }
  }
  
  // import data from a file

  void Transfer::import (const String& name) {
    wrlock ();
    InputStream* is = nullptr;
    try {
      Object::iref (is = new InputFile (name));
      import (is);
      Object::dref (is);
      unlock ();
    } catch (...) {
      Object::dref (is);
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - object section (transfer)                                             -
  // -------------------------------------------------------------------------

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

  // the object supported quarks
  static const long QUARK_TIMPORT = tzon.intern ("import");
  static const long QUARK_SETXFMT = tzon.intern ("set-transfert-format");
  static const long QUARK_SETSBRK = tzon.intern ("set-break-sequence");
  static const long QUARK_ADDLDEF = tzon.intern ("add-locale");

  // return true if the given quark is defined

  bool Transfer::isquark (const long quark, const bool hflg) const {
    rdlock ();
    try {
      if (tzon.exists (quark) == true) {
	unlock ();
	return true;
      }
      bool result = hflg ? Object::isquark (quark, hflg) : false;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // apply this object with a set of arguments and a quark

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

    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_SETXFMT) {
	String xfmt = argv->getstring (0);
	setxfmt (xfmt);
	return nullptr;
      }
      if (quark == QUARK_SETSBRK) {
	String sbrk = argv->getstring (0);
	setsbrk (sbrk);
	return nullptr;
      }
      if (quark == QUARK_ADDLDEF) {
	String ldef = argv->getstring (0);
	addldef (ldef);
	return nullptr;
      }
      if (quark == QUARK_TIMPORT) {
	Object* obj = argv->get (0);
	// check for an input stream
	InputStream* is = dynamic_cast <InputStream*> (obj);
	if (is != nullptr) {
	  import (is);
	  return nullptr;
	}
	// check for a string
	String* sobj = dynamic_cast <String*> (obj);
	if (sobj != nullptr) {
	  import (*sobj);
	  return nullptr;
	}
	// check for a cons cell
	Cons* cons = dynamic_cast <Cons*> (obj);
	if (cons != nullptr) {
	  import (cons);
	  return nullptr;
	}
	throw Exception ("type-error", "invalid object for import ",
			 Object::repr (obj));
      }
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }

  // -------------------------------------------------------------------------
  // - class section (record transfer)                                       -
  // -------------------------------------------------------------------------

  // create a default record transfer

  RecordTransfer::RecordTransfer (void) {
    p_trcd = nullptr;
    setrcd (new Record);
  }

  // create a record transfer with a record

  RecordTransfer::RecordTransfer (Record* rcd) {
    p_trcd = nullptr;
    setrcd (rcd);
  }

  // create a record transfer  by format

  RecordTransfer::RecordTransfer (const String& xfmt,
				  const String& sbrk) : Transfer (xfmt, sbrk) {
    p_trcd = nullptr;
    setrcd (new Record);
  }
  
  // create a record transfer with a record and format

  RecordTransfer::RecordTransfer (Record* rcd, const String& xfmt,
				  const String& sbrk) : Transfer (xfmt, sbrk) {
    p_trcd = nullptr;
    setrcd (rcd);
  }

  // destroy this record transfer

  RecordTransfer::~RecordTransfer (void) {
    Object::dref (p_trcd);
  }

  // return the object name

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

  // import a form into the record

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

  // return the transfer record

  Record* RecordTransfer::getrcd (void) const {
    rdlock ();
    try {
      Record* result = p_trcd;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set the record to import

  void RecordTransfer::setrcd (Record* rcd) {
    wrlock ();
    try {
      Object::dref (p_trcd);
      Object::iref (p_trcd = rcd);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

  // the quark zone
  static const long QUARK_RZON_LENGTH = 2;
  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");

  // create a new object in a generic way

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

  // return true if the given quark is defined

  bool RecordTransfer::isquark (const long quark, const bool hflg) const {
    rdlock ();
    try {
      if (rzon.exists (quark) == true) {
	unlock ();
	return true;
      }
      bool result = hflg ? Transfer::isquark (quark, hflg) : false;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // apply this object with a set of arguments and a quark

  Object* RecordTransfer::apply (Runnable* robj, Nameset* nset,
				 const long quark, Vector* argv) {
    // get the number of arguments
    long argc = (argv == nullptr) ? 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 == nullptr) 
	  throw Exception ("type-error", "invalid object for set-record ",
			   Object::repr (obj));
	setrcd (rcd);
	return nullptr;
      }
    }
    // call the transfer method
    return Transfer::apply (robj, nset, quark, argv);
  }

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

  // create a default sheet transfer

  SheetTransfer::SheetTransfer (void) {
    p_tsht = nullptr;
    setsht (new Sheet);
  }

  // create a sheet transfer with a sheet

  SheetTransfer::SheetTransfer (Sheet* sht) {
    p_tsht = nullptr;
    setsht (sht);
  }

  // create a sheet transfer by format

  SheetTransfer::SheetTransfer (const String& xfmt,
				const String& sbrk) : Transfer (xfmt, sbrk) {
    p_tsht = nullptr;
    setsht (new Sheet);
  }

  
  // create a sheet transfer with a sheet and format

  SheetTransfer::SheetTransfer (Sheet* sht, const String& xfmt,
				const String& sbrk) : Transfer (xfmt, sbrk) {
    p_tsht = nullptr;
    setsht (sht);
  }

  // destroy this sheet importer

  SheetTransfer::~SheetTransfer (void) {
    Object::dref (p_tsht);
  }

  // return the object name

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

  // import a form into the sheet

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

  // get the transfer sheet

  Sheet* SheetTransfer::getsht (void) const {
    rdlock ();
    try {
      Sheet* result = p_tsht;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set the sheet to import

  void SheetTransfer::setsht (Sheet* sht) {
    wrlock ();
    try {
      Object::iref (sht); Object::dref (p_tsht); p_tsht = sht;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // acquire the transfer sheet - the reference count is not touched
  // when an object is acquired

  Sheet* SheetTransfer::acquire (void) {
    rdlock ();
    try {
      Sheet* result = p_tsht;
      p_tsht = nullptr;
      unlock ();
      return result;
    } 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_ACQUIR = szon.intern ("acquire-sheet");

  // create a new object in a generic way

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

  // return true if the given quark is defined

  bool SheetTransfer::isquark (const long quark, const bool hflg) const {
    rdlock ();
    try {
      if (szon.exists (quark) == true) {
	unlock ();
	return true;
      }
      bool result = hflg ? Transfer::isquark (quark, hflg) : false;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

  Object* SheetTransfer::apply (Runnable* robj, Nameset* nset,
				const long quark, Vector* argv) {
    // get the number of arguments
    long argc = (argv == nullptr) ? 0 : argv->length ();
    
    // dispatch 0 argument
    if (argc == 0) {
      if (quark == QUARK_GETSHT) {
	rdlock ();
	try {
	  Sheet* sht = getsht ();
	  robj->post (sht);
	  unlock ();
	  return sht;
	} catch (...) {
	  unlock ();
	  throw;
	}
      }
      if (quark == QUARK_ACQUIR) {
	wrlock ();
	try {
	  Sheet* sht = acquire ();
	  robj->post (sht);
	  unlock ();
	  return sht;
	} catch (...) {
	  unlock ();
	  throw;
	}
      }
    }
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_SETSHT) {
	Object* obj = argv->get (0);
	Sheet*  sht = dynamic_cast <Sheet*> (obj);
	if (sht == nullptr) 
	  throw Exception ("type-error", "invalid object for set-sheet ",
			   Object::repr (obj));
	setsht (sht);
	return nullptr;
      }
    }
    // call the transfer method
    return Transfer::apply (robj, nset, quark, argv);
  }
}
