// ---------------------------------------------------------------------------
// - Rsamples.cpp                                                            -
// - afnix:mth module - real array samples 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-2013 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Math.hpp"
#include "Mthsid.hxx"
#include "Vector.hpp"
#include "Rsamples.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
 
namespace afnix {

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

  // the default columns size
  static const long RS_COLS_DEF = 1;
  // the default row size
  static const long RS_SIZE_DEF = 1024;
  // the default number precision
  static const long RS_PSIZ_DEF = 0;
  // the default scientific flag
  static const long RS_SFLG_DEF = false;

  // this procedure returns a new rsamples for deserialization
  static Serial* mksob (void) {
    return new Rsamples;
  }
  // register this array serial id
  static const t_byte SERIAL_ID = Serial::setsid (SERIAL_RSPL_ID, mksob);

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

  // create a default array

  Rsamples::Rsamples (void) {
    d_cols = RS_COLS_DEF;
    d_size = 0;
    d_rows = 0;
    d_psiz = RS_PSIZ_DEF;
    d_sflg = RS_SFLG_DEF;
    p_time = nilp;
    p_data = nilp;
  }

  // create an array by columns

  Rsamples::Rsamples (const long cols) {
    if (cols <= 0) {
      throw Exception ("rsample-error", "invalid column size");
    }
    d_cols = cols;
    d_size = 0;
    d_rows = 0;
    d_psiz = RS_PSIZ_DEF;
    d_sflg = RS_SFLG_DEF;
    p_time = nilp;
    p_data = nilp;
  }

  // copy construct this array

  Rsamples::Rsamples (const Rsamples& that) {
    that.rdlock ();
    try {
      // copy the size and allocate
      d_cols = that.d_cols;
      d_size = that.d_size;
      d_rows = that.d_rows;
      d_psiz = that.d_psiz;
      d_sflg = that.d_sflg;
      p_time = (that.p_time == nilp) ? nilp : new t_real[d_size];
      p_data = (d_size == 0) ? nilp : new t_real*[d_size];
      // copy the array
      for (long i = 0; i < d_size; i++) {
	// copy the time stamp
	if (p_time != nilp) p_time[i] = that.p_time[i];
	// copy the samples
	if (that.p_data[i] != nilp) {
	  p_data[i] = new t_real[d_cols];
	  for (t_long j = 0; j < d_cols; j++) p_data[i][j] = that.p_data[i][j];
	} else p_data[i] = nilp;
      }
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }
  
  // destroy this array samples

  Rsamples::~Rsamples (void) {
    for (t_long i = 0; i < d_size; i++) delete [] p_data[i];
    delete [] p_time;
    delete [] p_data;
  }

  // return the class name

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

  // return a clone of this object

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

  // return the array serial code

  t_byte Rsamples::serialid (void) const {
    return SERIAL_RSPL_ID;
  }
  
  // serialize this object
  
  void Rsamples::wrstream (OutputStream& os) const {
    rdlock ();
    try {
      // write the object info
      mth_wrlong (d_cols, os);
      mth_wrlong (d_size, os);
      mth_wrlong (d_rows, os);
      mth_wrlong (d_psiz, os);
      mth_wrbool (d_sflg, os);
      // eventually write the time stamps
      if (p_time != nilp) {
	mth_wrbool (true, os);
	for (long row = 0; row < d_rows; row++) mth_wrreal (p_time[row], os);
      } else {
	mth_wrbool (false, os);
      }
      // write the data
      if (p_data != nilp) {
	mth_wrbool (true, os);	
	for (long row = 0; row < d_rows; row++)
	  for (long col = 0; col < d_cols; col++)
	    mth_wrreal (p_data[row][col], os);
      } else {
	mth_wrbool (false, os);
      }
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // deserialize this object

  void Rsamples::rdstream (InputStream& is) {
    wrlock ();
    try {
      // get the object info
      d_cols = mth_rdlong (is);
      d_size = mth_rdlong (is);
      d_rows = mth_rdlong (is);
      d_psiz = mth_rdlong (is);
      d_sflg = mth_rdbool (is);
      // check for time stamps
      if (mth_rdbool (is) == true) {
	p_time = new t_real[d_size];
	for (long i = 0;      i < d_rows; i++) p_time[i] = mth_rdreal (is);
	for (long i = d_rows; i < d_size; i++) p_time[i] = Math::M_NAN;
      }
      // check for data
      if (mth_rdbool (is) == true) {
	p_data = new t_real*[d_size];
	for (long i = 0; i < d_rows; i++) {
	  p_data[i] = new t_real[d_cols];
	  for (long j = 0; j < d_cols; j++) p_data[i][j] = mth_rdreal (is);
	}
	for (long i = d_rows; i < d_size; i++) p_data[i] = nilp;
      }      
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // assign an array samples to this one

  Rsamples& Rsamples::operator = (const Rsamples& that) {
    // check for self-assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      // delete the old array
      for (long i = 0; i < d_size; i++) delete [] p_data[i];
      delete [] p_data; p_data = nilp;
      // copy the size and allocate
      d_cols = that.d_cols;
      d_size = that.d_size;
      d_rows = that.d_rows;
      d_psiz = that.d_psiz;
      d_sflg = that.d_sflg;
      p_time = (that.p_time == nilp) ? nilp : new t_real[d_size];
      p_data = (d_size == 0) ? nilp : new t_real*[d_size];
      // copy the array
      for (long i = 0; i < d_size; i++) {
	// copy the time stamp
	if (p_time != nilp) p_time[i] = that.p_time[i];
	// copy the samples
	if (that.p_data[i] != nilp) {
	  p_data[i] = new t_real[d_cols];
	  for (t_long j = 0; j < d_cols; j++) p_data[i][j] = that.p_data[i][j];
	} else p_data[i] = nilp;
      }
      // unlock and return
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      that.unlock ();
      throw;
    }
  }

  // clear this array samples

  void Rsamples::clear (void) {
    wrlock ();
    try {
      for (t_long i = 0; i < d_size; i++) delete [] p_data[i];
      delete [] p_time; p_time = nilp;
      delete [] p_data; p_data = nilp;
      d_rows = 0;
      d_size = 0;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the number of rows
  
  long Rsamples::getrows (void) const {
    rdlock ();
    try {
      long result = d_rows;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set the number of colimns

  void Rsamples::setcols (const long cols) {
    wrlock ();
    try {
      // check for valid columns
      if (cols <= 0) {
	throw Exception ("rsample-error", "invalid column size");
      }
      // clear the samples
      clear ();
      // set a new columns
      d_cols = cols;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the number of columns
  
  long Rsamples::getcols (void) const {
    rdlock ();
    try {
      long result = d_cols;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return true if the samples are stamped

  bool Rsamples::stamped (void) const {
    rdlock ();
    try {
      bool result = (p_time != nilp);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a time value by position

  t_real Rsamples::gettime (const long row) const {
    rdlock ();
    try {
      // check for valid position
      if ((row < 0) || (row >= d_rows)) {
	throw Exception ("sample-error", "invalid row position");
      }
      t_real result = (p_time == nilp) ? Math::M_NAN : p_time[row];
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set a sample by position and value

  void Rsamples::set (const long row, const long col, const t_real val) {
    wrlock ();
    try {
      // check for valid position
      if ((row < 0) || (row >= d_rows)) {
	throw Exception ("sample-error", "invalid row position");
      }
      if ((col < 0) || (col >= d_cols)) {
	throw Exception ("sample-error", "invalid column position");
      }
      // set sample value
      p_data[row][col] = val;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a sample by position

  t_real Rsamples::get (const long row, const long col) const {
    rdlock ();
    try {
      // check for valid position
      if ((row < 0) || (row >= d_rows)) {
	throw Exception ("sample-error", "invalid row position");
      }
      if ((col < 0) || (col >= d_cols)) {
	throw Exception ("sample-error", "invalid column position");
      }
      // get sample value
      t_real result = p_data[row][col];
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a new unitialized row

  long Rsamples::newrow (void) {
    wrlock ();
    try {
      // eventually resize the array
      if ((d_rows + 1) > d_size) resize ((d_size == 0) ? 1 : (d_size * 2));
      // save result and allocate
      long result = d_rows;
      p_data[d_rows++] = new t_real[d_cols];
      // initialized as nan
      for (long i = 0; i < d_cols; i++) p_data[result][i] = Math::M_NAN;
      // here it is
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a new uninitialized row with a time stamp

  long Rsamples::newrow (const t_real tval) {
    wrlock ();
    try {
      // allocate the new row
      long result = newrow ();
      // eventually allocate the time stamps
      if (p_time == nilp) {
	p_time = new t_real[d_size];
	for (long i = 0; i < d_size; i++) p_time[i] = Math::M_NAN;
      }
      // set the time stamp
      p_time[result] = tval;
      // unlock and return
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // resize this array samples

  void Rsamples::resize (const long size) {
    wrlock ();
    try {
      // check for resizing
      if (size <= d_size) {
	unlock ();
	return;
      }
      // allocate a new time stamp
      t_real* tptr = (p_time == nilp) ? nilp : new t_real[size];
      // allocate new array
      t_real** data = new t_real*[size];
      // copy the data
      for (long i = 0; i < d_rows; i++) {
	if (tptr != nilp) tptr[i] = p_time[i];
	data[i] = p_data[i];
      }
      for (long i = d_rows; i < size; i++) {
	if (tptr != nilp) tptr[i] = Math::M_NAN;
	data[i] = nilp;
      }
      // clean old and adjust
      delete [] p_time;
      delete [] p_data;
      p_time = tptr;
      p_data = data;
      d_size = size;
      // that's all
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the minimum signed time

  t_real Rsamples::minst (void) const {
    rdlock ();
    try {
      t_real result = Math::M_NAN;
      if (p_time != nilp) {
	for (long i = 0; i < d_rows; i++) {
	  t_real tval = p_time[i];
	  if (Math::isnan (tval) == true) continue;
	  if ((Math::isnan (result) == true) || (tval < result)) result = tval;
	}
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the maximum signed time

  t_real Rsamples::maxst (void) const {
    rdlock ();
    try {
      t_real result = Math::M_NAN;
      if (p_time != nilp) {
	for (long i = 0; i < d_rows; i++) {
	  t_real tval = p_time[i];
	  if (Math::isnan (tval) == true) continue;
	  if ((Math::isnan (result) == true) || (tval > result)) result = tval;
	}
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the minimum column value

  t_real Rsamples::minsc (const long col) const {
    rdlock ();
    try {
      // check for valid column
      if ((col < 0) || (col >= d_cols)) {
	throw Exception ("sample-error", "invalid column position");
      }
      t_real result = Math::M_NAN;
      if (p_data!= nilp) {
	for (long i = 0; i < d_rows; i++) {
	  t_real cval = p_data[i][col];
	  if (Math::isnan  (cval)   == true) continue;
	  if ((Math::isnan (result) == true) || (cval < result)) result = cval;
	}
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the maximum column value

  t_real Rsamples::maxsc (const long col) const {
    rdlock ();
    try {
      // check for valid column
      if ((col < 0) || (col >= d_cols)) {
	throw Exception ("sample-error", "invalid column position");
      }
      t_real result = Math::M_NAN;
      if (p_data != nilp) {
	for (long i = 0; i < d_rows; i++) {
	  t_real cval = p_data[i][col];
	  if (Math::isnan (cval)    == true) continue;
	  if ((Math::isnan (result) == true) || (cval > result)) result = cval;
	}
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // create a print table with the samples
  
  PrintTable* Rsamples::toptbl (const bool tflg) const {
    rdlock ();
    PrintTable* result = nilp;
    try {
      // compute the effective columns
      long ecol = (tflg == true) ? d_cols + 1 : d_cols;
      // create the print table
      result = (d_rows == 0) ? new PrintTable (ecol) :
	new PrintTable (ecol, d_rows);
      result->setpsiz (d_psiz);
      result->setsflg (d_sflg);
      // loop in the data samples
      for (long row = 0; row < d_rows; row++) {
	// create a new row in the table
	if (result->add () != row) {
	  throw Exception ("internal-error", 
			   "inconsistent rsample row index in print-table");
	}
	// set the time if requested
	if (tflg == true) {
	  t_real tval = (p_time == nilp) ? Math::M_NAN : p_time[row];
	  result->set (row, 0, tval);
	  // set the samples
	  for (long col = 0; col < d_cols; col++) {
	    result->set (row, col+1, p_data[row][col]);
	  }
	} else {
	  // set the samples
	  for (long col = 0; col < d_cols; col++) {
	    result->set (row, col, p_data[row][col]);
	  }
	}
      }
      // unlock and return
      unlock ();
      return result;
    } catch (...) {
      delete result;
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - 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_SET     = zone.intern ("set");
  static const long QUARK_GET     = zone.intern ("get");
  static const long QUARK_CLEAR   = zone.intern ("clear");
  static const long QUARK_MINST   = zone.intern ("min-signed-time");
  static const long QUARK_MAXST   = zone.intern ("max-signed-time");
  static const long QUARK_MINSC   = zone.intern ("min-signed-column");
  static const long QUARK_MAXSC   = zone.intern ("max-signed-column");
  static const long QUARK_NEWROW  = zone.intern ("new-row");
  static const long QUARK_RESIZE  = zone.intern ("resize");
  static const long QUARK_TOPTBL  = zone.intern ("to-print-table");
  static const long QUARK_STAMPED = zone.intern ("stamped-p");
  static const long QUARK_GETTIME = zone.intern ("get-time");
  static const long QUARK_GETROWS = zone.intern ("get-rows");
  static const long QUARK_SETCOLS = zone.intern ("set-columns");
  static const long QUARK_GETCOLS = zone.intern ("get-columns");

  // create a new object in a generic way

  Object* Rsamples::mknew (Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();
    
    // check for 0 argument
    if (argc == 0) return new Rsamples;
    // check for 1 argument
    if (argc == 1) {
      long cols = argv->getlong (0);
      return new Rsamples (cols);
    }
    // invalid arguments
    throw Exception ("argument-error", 
		     "invalid arguments with real samples object");
  }

  // return true if the given quark is defined

  bool Rsamples::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* Rsamples::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_MINST)   return new Real    (minst ());
      if (quark == QUARK_MAXST)   return new Real    (maxst ());
      if (quark == QUARK_GETROWS) return new Integer (getrows ());
      if (quark == QUARK_GETCOLS) return new Integer (getcols ());
      if (quark == QUARK_NEWROW)  return new Integer (newrow  ());
      if (quark == QUARK_STAMPED) return new Boolean (stamped ());
      if (quark == QUARK_TOPTBL)  return toptbl (stamped ());
      if (quark == QUARK_CLEAR) {
	clear ();
	return nilp;
      }
    }
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_SETCOLS) {
        long cols = argv->getlong (0);
	setcols (cols);
	return nilp;
      }
      if (quark == QUARK_NEWROW) {
        t_real tval = argv->getreal (0);
	return new Integer (newrow (tval));
      }
      if (quark == QUARK_RESIZE) {
        long size = argv->getlong (0);
	resize (size);
	return nilp;
      }
      if (quark == QUARK_GETTIME) {
	long row = argv->getlong (0);
	return new Real (gettime (row));
      }
      if (quark == QUARK_TOPTBL) {
	bool tflg = argv->getbool (0);
	return toptbl (tflg);
      }
      if (quark == QUARK_MINSC) {
	long col = argv->getlong (0);
	return new Real (minsc (col));
      }
      if (quark == QUARK_MAXSC) {
	long col = argv->getlong (0);
	return new Real (maxsc (col));
      }
    }
    // dispatch 2 arguments
    if (argc == 2) {
      if (quark == QUARK_GET) {
        long row = argv->getlong (0);
        long col = argv->getlong (1);
        return new Real (get (row, col));
      }
    }
    // dispatch 3 arguments
    if (argc == 3) {
      if (quark == QUARK_SET) {
        long   row = argv->getlong (0);
        long   col = argv->getlong (1);
        t_real val = argv->getreal (2);
        set (row, col, val);
	return nilp;
      }
    }
    // call the object
    return Object::apply (robj, nset, quark, argv);
  }
}

