// ---------------------------------------------------------------------------
// - Axs.cpp                                                                 -
// - afnix cross spreadsheet - interpreter 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 "Axs.hpp"
#include "System.hpp"
#include "Integer.hpp"
#include "Function.hpp"
#include "AxsCalls.hpp"
#include "InputFile.hpp"
#include "Exception.hpp"
#include "OutputFile.hpp"
#include "InputMapped.hpp"

namespace afnix {

  // query default prompts
  static const char* DEFAULT_PP = "(axs) ";
  static const char* DEFAULT_SP = "(...) ";
    
  // this function initialize the axs nameset
  static Nameset* initialize_axs (Axs* ctrl) {
    // create the axs nameset
    Nameset* axs = ctrl->mknset ("axs");
    // bind the base axs commands
    axs->symcst ("new",           new Function (axs_new));
    axs->symcst ("open",          new Function (axs_open));
    axs->symcst ("save",          new Function (axs_save));
    axs->symcst ("info",          new Function (axs_info));
    axs->symcst ("sort",          new Function (axs_sort));
    axs->symcst ("create",        new Function (axs_create));
    axs->symcst ("import",        new Function (axs_import));
    axs->symcst ("export",        new Function (axs_export));
    axs->symcst ("select",        new Function (axs_select));
    axs->symcst ("insert",        new Function (axs_insert));
    axs->symcst ("insert-tag",    new Function (axs_addtag));
    axs->symcst ("insert-marker", new Function (axs_addmark));
    axs->symcst ("insert-header", new Function (axs_addhead));
    axs->symcst ("insert-footer", new Function (axs_addfoot));
    axs->symcst ("list",          new Function (axs_flist));
    axs->symcst ("view",          new Function (axs_view));
    return axs;
  }

  // create a default interpreter

  Axs::Axs (void) {
    // initialize the command interpreter
    p_sps = nilp;
    p_sht = nilp;
    if (p_term != nilp) {
      p_term->setpp (DEFAULT_PP);
      p_term->setsp (DEFAULT_SP);
    }
    // bind the axs nameset
    Object::iref (p_cset = initialize_axs (this));
  }

  // create an interpreter with or without a terminal

  Axs::Axs (const bool tflg) : Interp (tflg) {
    // initialize the command interpreter
    p_sps = nilp;
    p_sht = nilp;
    // bind the axs nameset
    Object::iref (p_cset = initialize_axs (this));
  }
  
  // create a stream based interpreter

  Axs::Axs (Input* is,Output* os,Output* es) : Interp (is,os,es) {
    // initialize the command interpreter
    p_sps = nilp;
    p_sht = nilp;
    // bind the axs nameset
    Object::iref (p_cset = initialize_axs (this));
  }
  
  // destroy this interpreter

  Axs::~Axs (void) {
    if (p_cset != nilp) p_cset->reset ();
    Object::dref (p_cset);
    Object::dref (p_sht);
    Object::dref (p_sps);
  }

  // return the class name

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

  // create a new folio
  
  Folio* Axs::newf (void) {
    wrlock ();
    // clean the old folio
    Object::dref (p_sps);
    // create a new folio
    try {
      p_sps = new Folio;
      Object::iref (p_sps);
    } catch (...) {
      unlock ();
      throw;
    }
    unlock ();
    return p_sps;
  }

  // create a new folio by name
  
  Folio* Axs::newf (const String& name) {
    wrlock ();
    // clean the old folio
    Object::dref (p_sps);
    // create a new folio
    try {
      p_sps = new Folio (name);
      Object::iref (p_sps);
    } catch (...) {
      unlock ();
      throw;
    }
    unlock ();
    return p_sps;
  }

  // create a new folio by name and info
  
  Folio* Axs::newf (const String& name, const String& info) {
    wrlock ();
    // clean the old folio
    Object::dref (p_sps);
    // create a new folio
    try {
      p_sps = new Folio (name, info);
      Object::iref (p_sps);
    } catch (...) {
      unlock ();
      throw;
    }
    unlock ();
    return p_sps;
  }

  // open an folio by name
  
  Folio* Axs::open (const String& fname) {
    wrlock ();
    // clean the old folio
    Object::dref (p_sps);
    try {
      InputMapped is (fname);
      p_sps = new Folio (&is);
      Object::iref (p_sps);
    } catch (...) {
      unlock ();
      throw;
    }
    unlock ();
    return p_sps;
  }

  // save a folio by name

  void Axs::save (const String& fname) {
    rdlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    try {
      // try to open the file
      OutputFile os (fname);
      p_sps->write (os);
    } catch (...) {
      unlock ();
      throw;
    }
    unlock ();
  }
  
  // print some information about the folio

  void Axs::info (Output* os) const {
    rdlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    try {
      // create a print table
      PrintTable result (2);
      long row = result.add ();
      result.setsize (0, 40);
      result.set (row, 0, "Object");
      result.set (row, 1, "Value");
      // get folio name
      String name = p_sps->getname ();
      row = result.add ();
      result.set (row, 0, "Folio name");
      result.set (row, 1, name);
      // get folio info
      String info = p_sps->getinfo ();
      row = result.add ();
      result.set (row, 0, "Folio info");
      result.set (row, 1, info);
      // get the folio size
      row = result.add ();
      result.set (row, 0, "Folio length");
      result.set (row, 1, p_sps->length ());
      // get the current sheet
      String tname = (p_sht == nilp) ? "none" : p_sht->getname ();
      row = result.add ();
      result.set (row, 0, "Current sheet");
      result.set (row, 1, tname);
      // print the sheet
      if (os != nilp) result.format (*os);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // print folio sheet contents

  void Axs::flist (Output* os) const {
    rdlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    try {
      // create a print table
      PrintTable result (3);
      long row = result.add ();
      result.set (row, 0, "Sheet index");
      result.set (row, 1, "Sheet name");
      result.set (row, 2, "Sheet length");
      // collect the sheet info
      long len = p_sps->length ();
      for (long i = 0; i < len; i++) {
	Sheet* sht = p_sps->get (i);
	if (sht == nilp) continue;
	Integer ival (i);
	row = result.add ();
	result.set (row, 0, ival.tostring ());
	result.set (row, 1, sht->getname ());
	ival = sht->length ();
	result.set (row, 2, ival.tostring ());
      }
      if (os != nilp) result.format (*os);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // create a new sheet and make it the default

  Sheet* Axs::create (void) {
    wrlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    // remove the old sheet and create a new one
    Object::dref (p_sht);
    Object::iref (p_sht = new Sheet);
    // attach it to the folio
    p_sps->add (p_sht);
    unlock ();
    return p_sht;
  }

  // import data into the default sheet

  void Axs::import (const String& fname) {
    wrlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    if (p_sht == nilp) {
      unlock ();
      throw Exception ("axs-error", "no current sheet is set");
    }
    InputFile* is = nilp;
    try {
      // try to open the file
      Object::iref (is = new InputFile (fname));
      p_sht->import (is);
      unlock ();
    } catch (Exception& e) {
      e.setname (fname);
      Object::dref (is);
      unlock ();
      throw;
    } catch (...) {
      Object::dref (is);
      unlock ();
      throw;
    }
  }

  // select a sheet by index and make it the default one

  Sheet* Axs::select (const long index) {
    wrlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    try {
      Sheet* sht = p_sps->get (index);
      Object::iref (sht);
      Object::dref (p_sht);
      p_sht = sht;
      unlock ();
      return sht;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // select a sheet by tag and make it the default one

  Sheet* Axs::select (const String& tag) {
    wrlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    try {
      Sheet* sht = p_sps->lookup (tag);
      Object::iref (sht);
      Object::dref (p_sht);
      p_sht = sht;
      unlock ();
      return sht;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a tag to the current sheet

  void Axs::addtag (const String& tag) {
    wrlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    if (p_sht == nilp) {
      unlock ();
      throw Exception ("axs-error", "no current sheet is set");
    }
    try {
      p_sht->addtag (tag);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add element marker  to the current sheet

  void Axs::addmark (Vector* elems) {
    wrlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    if (p_sht == nilp) {
      unlock ();
      throw Exception ("axs-error", "no current sheet is set");
    }
    try {
      p_sht->addmark (elems);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add element header to the current sheet

  void Axs::addhead (Vector* elems) {
    wrlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    if (p_sht == nilp) {
      unlock ();
      throw Exception ("axs-error", "no current sheet is set");
    }
    try {
      p_sht->addhead (elems);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add element footer to the current sheet

  void Axs::addfoot (Vector* elems) {
    wrlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    if (p_sht == nilp) {
      unlock ();
      throw Exception ("axs-error", "no current sheet is set");
    }
    try {
      p_sht->addfoot (elems);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add element body to the current sheet

  void Axs::insert (Vector* elems) {
    wrlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    if (p_sht == nilp) {
      unlock ();
      throw Exception ("axs-error", "no current sheet is set");
    }
    try {
      p_sht->adddata (elems);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }


  // view a sheet content

  void Axs::view (Output* os, long max, long start, bool flag) const {
    rdlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    if (p_sht == nilp) {
      unlock ();
      throw Exception ("axs-error", "no current sheet is set");
    }
    try {
      PrintTable* table = p_sht->convert (max, start, flag);
      table->format (*os);
      delete table;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // sort the default sheet by column index and flag

  void Axs::sort (const long col, const bool mode) {
    rdlock ();
    if (p_sps == nilp) {
      unlock ();
      throw Exception ("axs-error", "spreadsheet is not open");
    }
    if (p_sht == nilp) {
      unlock ();
      throw Exception ("axs-error", "no current sheet is set");
    }
    try {
      p_sht->sort (col, mode);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }
}
