// ---------------------------------------------------------------------------
// - PrintTable.cpp                                                          -
// - standard object library - printable table class definition              -
// ---------------------------------------------------------------------------
// - 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 "Vector.hpp"
#include "Integer.hpp"
#include "Boolean.hpp"
#include "Runnable.hpp"
#include "Character.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
#include "PrintTable.hpp"

namespace afnix {

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

  // this procedure compute the maximum of two integers
  static inline long max (const long x, const long y) {
    return (x < y) ? y : x;
  }

  // this procedure format a string according to the desired width, the
  // filling character and direction
  static String fmtstr (const String& value, const long csiz, 
			const t_quad  fill,  const bool cdir, 
			const long    cwth) {
    // format the result string using non combining length
    String result;
    long len = value.ncclen ();
    // check if we process the size or not
    if (csiz == 0) {
      if (len < cwth) {
	if (cdir == false) {
	  result = value.rfill (fill, cwth);
	} else {
	  result = value.lfill (fill, cwth);
	}
      } else {
	result = value;
      }	     
    } else {
      if (len == csiz) result = value;
      if (len < csiz) {
	if (cdir == false) {
	  result = value.rfill (fill, csiz);
	} else {
	  result = value.lfill (fill, csiz);
	}
      }
      if (len > csiz) {
	if (cdir == false) {
	  result = value.lsubstr (csiz);
	} else {
	  result = value.rsubstr (len - csiz);
	}
      }
    }
    return result;
  }

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

  // create a default print table

  PrintTable::PrintTable (void) {
    d_size = 16;
    d_cols = 1;
    d_rows = 0;
    // initialize the data table
    p_head = new String[d_cols];
    p_data = new String*[d_size];
    for (long i = 0; i < d_size; i++) p_data[i] = nilp;
    // initialize the format size and column width
    p_csiz = new long[d_cols];
    p_fill = new t_quad[d_cols];
    p_cdir = new bool[d_cols];
    p_cwth = new long[d_cols];
    for (long i = 0; i < d_cols; i++) {
      p_csiz[i] = 0;
      p_fill[i] = blkq;
      p_cdir[i] = false;
      p_cwth[i] = 0;
    }
  }

  // create a print table with a number of columns

  PrintTable::PrintTable (const long cols) {
    d_size = 16;
    d_cols = cols;
    d_rows = 0;
    // initialize the data table
    p_head = new String[d_cols];
    p_data = new String*[d_size];
    for (long i = 0; i < d_size; i++) p_data[i] = nilp;
    // initialize the format size and column width
    p_csiz = new long[d_cols];
    p_fill = new t_quad[d_cols];
    p_cdir = new bool[d_cols];
    p_cwth = new long[d_cols];
    for (long i = 0; i < d_cols; i++) {
      p_csiz[i] = 0;
      p_fill[i] = blkq;
      p_cdir[i] = false;
      p_cwth[i] = 0;
    }
  }

  // create a print table with a number of columns and rows

  PrintTable::PrintTable (const long cols, const long rows) {
    d_size = rows;
    d_cols = cols;
    d_rows = 0;
    // initialize the data table
    p_head = new String[d_cols];
    p_data = new String*[d_size];
    for (long i = 0; i < d_size; i++) p_data[i] = nilp;
    // initialize the format size and column width
    p_csiz = new long[d_cols];
    p_fill = new t_quad[d_cols];
    p_cdir = new bool[d_cols];
    p_cwth = new long[d_cols];
    for (long i = 0; i < d_cols; i++) {
      p_csiz[i] = 0;
      p_fill[i] = blkq;
      p_cdir[i] = false;
      p_cwth[i] = 0;
    }
  }

  // destroy this print table

  PrintTable::~PrintTable (void) {
    for (long i = 0; i < d_rows; i++) delete [] p_data[i];
    delete [] p_head;
    delete [] p_data;
    delete [] p_csiz;
    delete [] p_fill;
    delete [] p_cdir;
    delete [] p_cwth;
  }

  // return the class name

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

  // return true if the header is defined

  bool PrintTable::ishead (void) const {
    rdlock ();
    try {
      bool result = false;
      for (long i = 0; i < d_cols; i++) result |= !p_head[i].isnil ();
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the number of rows

  long PrintTable::getrows (void) const {
    rdlock ();
    long result = d_rows;
    unlock ();
    return result;
  }

  // return the number of columns

  long PrintTable::getcols (void) const {
    rdlock ();
    long result = d_cols;
    unlock ();
    return result;
  }

  // set the column size

  void PrintTable::setsize (const long col, const long size) {
    wrlock ();
    // check for column and size
    if ((col < 0) || (col >= d_cols)) {
      unlock ();
      throw Exception ("table-error", "invalid column index");
    }
    if (size < 0) {
      unlock ();
      throw Exception ("table-error", "invalid column size");
    }
    p_csiz[col] = size;
    unlock ();
  }

  // return the column size

  long PrintTable::getsize (const long col) const {
    rdlock ();
    if ((col < 0) || (col >= d_cols)) {
      unlock ();
      throw Exception ("table-error", "invalid column index");
    }
    long result = p_csiz[col];
    unlock ();
    return result;
  }

  // set the column filling character

  void PrintTable::setfill (const long col, const t_quad fill) {
    wrlock ();
    // check for column and size
    if ((col < 0) || (col >= d_cols)) {
      unlock ();
      throw Exception ("table-error", "invalid column index");
    }
    p_fill[col] = fill;
    unlock ();
  }

  // return the column fill character

  t_quad PrintTable::getfill (const long col) const {
    rdlock ();
    if ((col < 0) || (col >= d_cols)) {
      unlock ();
      throw Exception ("table-error", "invalid column index");
    }
    t_quad result = p_fill[col];
    unlock ();
    return result;
  }

  // set the column direction

  void PrintTable::setcdir (const long col, const bool cdir) {
    wrlock ();
    // check for column and size
    if ((col < 0) || (col >= d_cols)) {
      unlock ();
      throw Exception ("table-error", "invalid column index");
    }
    p_cdir[col] = cdir;
    unlock ();
  }

  // return the column direction flag

  bool PrintTable::getcdir (const long col) const {
    rdlock ();
    if ((col < 0) || (col >= d_cols)) {
      unlock ();
      throw Exception ("table-error", "invalid column index");
    }
    bool result = p_cdir[col];
    unlock ();
    return result;
  }

  // add a new row and return the row index

  long PrintTable::add (void) {
    wrlock ();
    // check if we need to resize
    if ((d_rows + 1) >= d_size) resize (d_size * 2);
    long result = d_rows;
    p_data[d_rows++] = new String[d_cols];
    unlock ();
    return result;
  }

  // set a head element by column

  void PrintTable::sethead (const long col, const String& val) {
    wrlock ();
    try {
      // check for valid column
      if ((col < 0) || (col >= d_cols)) {
	throw Exception ("table-error", "invalid column index");
      }
      // udpate the header
      p_head[col] = val;
      // update the maximum width
      p_cwth[col] = max (p_cwth[col], val.length ());
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a head element by column

  String PrintTable::gethead (const long col) const {
    rdlock ();
    try {
      // check for valid row and column
      if ((col < 0) || (col >= d_cols)) {
	throw Exception ("table-error", "invalid column index");
      }
      String result = p_head[col];
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set a data element at row and column

  void PrintTable::set (const long row, const long col, const String& val) {
    wrlock ();
    // check for valid row and column
    if ((row < 0) || (row >= d_rows) || (col < 0) || (col >= d_cols)) {
      unlock ();
      throw Exception ("table-error", "invalid row or column index");
    }
    // udpate the table
    String* line = p_data[row];
    line[col] = val;
    // update the maximum width
    p_cwth[col] = max (p_cwth[col], val.length ());
    unlock ();
  }

  // set a data element at row and column

  void PrintTable::set (const long row, const long col, const long val) {
    wrlock ();
    try {
      Integer ival = val;
      String  sval = ival.tostring ();
      set (row, col, sval);
      unlock ();
    } catch(...) {
      unlock ();
      throw;
    }
  }

  // set a data element with a literal object

  void PrintTable::set (const long row, const long col, Literal* obj) {
    if (obj == nilp) return;
    set (row, col, obj->tostring ());
  }

  // get a table element by row and column

  String PrintTable::get (const long row, const long col) const {
    rdlock ();
    // check for valid row and column
    if ((row < 0) || (row >= d_rows) || (col < 0) || (col >= d_cols)) {
      unlock ();
      throw Exception ("table-error", "invalid row or column index");
    }
    String* line = p_data[row];
    String result = line[col];
    unlock ();
    return result;
  }

  // get a formatted row suitable for dumping

  String PrintTable::dump (const long row) const {
    rdlock ();
    // check for valid row and column
    if ((row < 0) || (row >= d_rows)) {
      unlock ();
      throw Exception ("table-error", "invalid row index");
    }
    // get the line and preapre for result
    String* line = p_data[row];
    String result;
    for (long i = 0; i < d_cols; i++) {
      String data = line[i].toliteral ();
      long   cwth = p_cwth[i] + 2;
      result = result + fmtstr (data, 0, ' ', false, cwth);
      if (i < (d_cols - 1)) result = result + ' ';
    }
    unlock ();
    return result;
  }

  // dump the table into a buffer

  void PrintTable::dump (Buffer& buffer) const {
    rdlock ();
    for (long i = 0; i < d_rows; i++) {
      buffer.add (dump (i));
      buffer.add (eolc);
    }
    unlock ();
  }

  // dump the table for an output stream

  void PrintTable::dump (Output& os) const {
    rdlock ();
    for (long i = 0; i < d_rows; i++)  os.writeln (dump (i));
    unlock ();
  }
  
  // get a formatted head suitable for printing

  String PrintTable::fmhead (void) const {
    rdlock ();
    try {
      String result;
      for (long i = 0; i < d_cols; i++) {
	result = result + fmtstr (p_head[i], p_csiz[i], 
				  p_fill[i], p_cdir[i], p_cwth[i]);
	if (i < (d_cols - 1)) result = result + ' ';
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a formatted row suitable for printing

  String PrintTable::format (const long row) const {
    rdlock ();
    // check for valid row and column
    if ((row < 0) || (row >= d_rows)) {
      unlock ();
      throw Exception ("table-error", "invalid row index");
    }
    // get the line and prepare for result
    String* line = p_data[row];
    String result;
    for (long i = 0; i < d_cols; i++) {
      result = result + fmtstr (line[i], p_csiz[i], p_fill[i], p_cdir[i],
				p_cwth[i]);
      if (i < (d_cols - 1)) result = result + ' ';
    }
    unlock ();
    return result;
  }

  // format the table into a buffer

  void PrintTable::format (Buffer& buffer) const {
    rdlock ();
    // add the head if defined
    if (ishead () == true) {
      buffer.add (fmhead ());
      buffer.add (eolc);
    }
    // add the data
    for (long i = 0; i < d_rows; i++) {
      buffer.add (format (i));
      buffer.add (eolc);
    }
    unlock ();
  }

  // format the table for an output stream

  void PrintTable::format (Output& os) const {
    rdlock ();
    // add the if defined
    if (ishead () == true) os.writeln (fmhead ());
    // add the data
    for (long i = 0; i < d_rows; i++)  os.writeln (format (i));
    unlock ();
  }

  // resize this print table

  void PrintTable::resize (const long size) {
    wrlock ();
    // check for valid size
    if (size <= d_size) {
      unlock ();
      return;
    }
    // create a new table
    String** data = new String* [size];
    for (long i = 0;      i < d_rows; i++) data[i] = p_data[i];
    for (long i = d_rows; i < size;   i++) data[i] = nilp;
    // update table and remove old one
    delete [] p_data;
    p_data = data;
    d_size = size;
    unlock ();
  }

  // merge a print table into this one

  void PrintTable::merge (const PrintTable& ptbl) {
    wrlock ();
    try {
      // make sure e have enough columns
      long cols = ptbl.getcols ();
      if (d_cols < cols) {
	throw Exception ("merge-error", "print table is too large");
      }
      // get the number of rows and iterate
      long rows = ptbl.getrows ();
      for (long row = 0; row < rows; row++) {
	long idx = add ();
	for (long col = 0; col < cols; col++) {
	  // get the table value
	  String value = ptbl.get (row, col);
	  // merge it locally
	  set (idx, col, value);
	}
      }
      // done
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

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

  // the object supported quarks
  static const long QUARK_ADD     = zone.intern ("add");
  static const long QUARK_GET     = zone.intern ("get");
  static const long QUARK_SET     = zone.intern ("set");
  static const long QUARK_ADDH    = zone.intern ("add-head");
  static const long QUARK_GETH    = zone.intern ("get-head");
  static const long QUARK_SETH    = zone.intern ("set-head");
  static const long QUARK_DUMP    = zone.intern ("dump");
  static const long QUARK_HEADP   = zone.intern ("head-p");
  static const long QUARK_MERGE   = zone.intern ("merge");
  static const long QUARK_FORMAT  = zone.intern ("format");
  static const long QUARK_GETCOLS = zone.intern ("get-columns");
  static const long QUARK_GETROWS = zone.intern ("get-rows");
  static const long QUARK_SETSIZE = zone.intern ("set-column-size");
  static const long QUARK_SETFILL = zone.intern ("set-column-fill");
  static const long QUARK_GETSIZE = zone.intern ("get-column-size");
  static const long QUARK_GETFILL = zone.intern ("get-column-fill");
  static const long QUARK_SETCDIR = zone.intern ("set-column-direction");
  static const long QUARK_GETCDIR = zone.intern ("get-column-direction");

  // create a new object in a generic way

  Object* PrintTable::mknew (Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();
    // check for 0 argument
    if (argc == 0) return new PrintTable;
    // check for 1 argument
    if (argc == 1) {
      long cols = argv->getint (0);
      return new PrintTable (cols);
    }
    // check for 2 arguments
    if (argc == 2) {
      long cols = argv->getint (0);
      long rows = argv->getint (1);
      return new PrintTable (cols, rows);
    }
    throw Exception ("argument-error", "invavlid argument for print table");
  }

  // return true if the given quark is defined

  bool PrintTable::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 an object method with a set of arguments and a quark
  
  Object* PrintTable::apply (Runnable* robj, Nameset* nset, const long quark,
			     Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // dispatch generic quark
    if (quark == QUARK_ADDH) {
      wrlock ();
      try {
	if (argc != d_cols) {
	  throw Exception ("argument-error", 
			   "invalid number of arguments with add-header");
	}
	for (long i = 0; i < argc; i++) {
	  Object*  obj = argv->get (i);
	  String* sobj = dynamic_cast <String*> (obj);
	  if (sobj == nilp) {
	    throw Exception ("type-error", "invalid object for add-header",
			     Object::repr (obj));
	  }
	  sethead (i, *sobj);
	}
	unlock ();
	return nilp;
      } catch (...) {
	unlock ();
	throw;
      }
    }
    if (quark == QUARK_ADD) {
      if (argc == 0) return new Integer (add ());
      wrlock ();
      // get the new row
      long row = -1;
      try {
	row = add ();
      } catch (...) {
	unlock ();
	throw;
      }
      // loop in the arguments
      try {
	if (argc != d_cols) {
	  throw Exception ("argument-error", 
			   "invalid number of arguments with add");
	}
	for (long i = 0; i < argc; i++) {
	  Object*   obj = argv->get (i);
	  Literal* lobj = dynamic_cast <Literal*> (obj);
	  if (lobj == nilp) {
	    throw Exception ("type-error", "invalid object for add",
			     Object::repr (obj));
	  }
	  set (row, i, lobj);
	}
	unlock ();
	return new Integer (row);
      } catch (...) {
	if (row >= 0) delete [] p_data[row];
	d_rows--;
	unlock ();
	throw;
      }
    }

    // dispatch 0 argument
    if (argc == 0) {
      if (quark == QUARK_HEADP)   return new Boolean (ishead  ());
      if (quark == QUARK_GETROWS) return new Integer (getrows ());
      if (quark == QUARK_GETCOLS) return new Integer (getcols ());
      if (quark == QUARK_DUMP) {
	Output* os = robj->getos ();
	dump (*os);
	return nilp;
      }
      if (quark == QUARK_FORMAT) {
	Output* os = robj->getos ();
	format (*os);
	return nilp;
      }
    }
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_GETSIZE) {
	long col = argv->getint (0);
	return new Integer (getsize (col));
      }
      if (quark == QUARK_GETFILL) {
	long col = argv->getint (0);
	return new Character (getfill (col));
      }
      if (quark == QUARK_GETCDIR) {
	long col = argv->getint (0);
	return new Boolean (getcdir (col));
      }
      if (quark == QUARK_GETH) {
	long col = argv->getint (0);
	return new String (gethead (col));
      }
      if (quark == QUARK_MERGE) {
	Object* obj = argv->get (0);
	PrintTable* pobj = dynamic_cast <PrintTable*> (obj);
	if (pobj == nilp) {
	  throw Exception ("type-error", "invalid object with merge",
			   Object::repr (obj));
	}
	merge (*pobj);
	return nilp;
      }
      if (quark == QUARK_DUMP) {
	Object* obj = argv->get (0);
	// check for integer
	Integer* ival = dynamic_cast <Integer*> (obj);
	if (ival != nilp) {
	  return new String (dump (ival->tointeger ()));
	}
	// check for buffer
	Buffer* buffer = dynamic_cast <Buffer*> (obj);
	if (buffer != nilp) {
	  dump (*buffer);
	  return nilp;
	}
	// check for output stream
	Output* os = dynamic_cast <Output*> (obj);
	if (os != nilp) {
	  dump (*os);
	  return nilp;
	}
	throw Exception ("type-error", "invalid object with format",
			 Object::repr (obj));
      }
      if (quark == QUARK_FORMAT) {
	Object* obj = argv->get (0);
	// check for integer
	Integer* ival = dynamic_cast <Integer*> (obj);
	if (ival != nilp) {
	  return new String (format (ival->tointeger ()));
	}
	// check for buffer
	Buffer* buffer = dynamic_cast <Buffer*> (obj);
	if (buffer != nilp) {
	  format (*buffer);
	  return nilp;
	}
	// check for output stream
	Output* os = dynamic_cast <Output*> (obj);
	if (os != nilp) {
	  format (*os);
	  return nilp;
	}
	throw Exception ("type-error", "invalid object with format",
			 Object::repr (obj));
      }
    }
    // dispatch 2 arguments
    if (argc == 2) {
      if (quark == QUARK_GET) {
	long row = argv->getint (0);
	long col = argv->getint (1);
	return new String (get (row, col));
      }
      if (quark == QUARK_SETH) {
	long   col = argv->getint (0);
	String val = argv->getstring (1);
	sethead (col, val);
	return nilp;
      }
      if (quark == QUARK_SETSIZE) {
	long col  = argv->getint (0);
	long size = argv->getint (1);
	setsize (col, size);
	return nilp;
      }
      if (quark == QUARK_SETFILL) {
	long   col  = argv->getint  (0);
	t_quad fill = argv->getchar (1);
	setsize (col, fill);
	return nilp;
      }
      if (quark == QUARK_SETCDIR) {
	long col  = argv->getint  (0);
	bool cdir = argv->getbool (1);
	setcdir (col, cdir);
	return nilp;
      }
    }
    // dispatch 3 arguments
    if (argc == 3) {
      if (quark == QUARK_SET) {
	long   row = argv->getint (0);
	long   col = argv->getint (1);
	String val = argv->getstring (2);
	set (row, col, val);
	return nilp;
      }
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }
}
