#include <xinclude.h>

#include <applicat.h>
#include <automask.h>
#include <defmask.h>
#include <extcdecl.h>
#include <modaut.h>
#include <odbcrset.h>
#include <prefix.h>
#include <relation.h>
#include <sheet.h>
#include <tree.h>
#include <treectrl.h>
#include <utility.h>
#include <xml.h>

#include "../sqlite/sqlite3.h"
#include "ba8200.h"

///////////////////////////////////////////////////////////
// TRelation_node & TRelation_tree
///////////////////////////////////////////////////////////

// Informazioni relative ad un nodo della relazione:
class TRelation_node : public TObject
{
  const TRelation_node* _father; // Puntatore all'eventuale padre 
  int _logicnum;                 // Numero logico  
  TString16 _name, _alias;       // Nome ed Alias
  TString_array _join_fields;    // Elenco dei campi di join

public:
  bool description(TString& str) const;
  int num() const { return _logicnum; }
  const TString& name() const { return _name; }
  const TString& alias() const { return _alias; }
  const TString& id() const { return _alias.not_empty() ? _alias : _name; } 
  const TRelation_node* father() const { return _father; }

  void set_num(int num);
  void set_alias(const char* alias) { _alias = alias; }

  TString_array& join() { return _join_fields; }
  
  TRelation_node(const TRelation_node* father, int ln, const char* alias);
  virtual ~TRelation_node() {}
};

bool TRelation_node::description(TString& str) const
{
  const bool ok = _logicnum >= LF_USER;
  if (ok)
  {
    const FileDes& fd = prefix().get_filedes(_logicnum);
    str = _name;
    if (_alias.not_empty())
      str << " ALIAS " << _alias;
    str << " (" << fd.Des << ')';
  }
  return ok;
}

void TRelation_node::set_num(int num)
{
  _logicnum = num;
  const FileDes& fd = prefix().get_filedes(_logicnum);
  const TFilename name = fd.SysName;
  _name = name.name(); _name.upper();
}

TRelation_node::TRelation_node(const TRelation_node* father, int ln, const char* alias)
              : _father(father)
{ 
  set_num(ln);
  set_alias(alias);
}

// Albero di una relazione
class TRelation_tree : public TObject_tree
{
protected:
  virtual bool get_description(TString& str) const;

public:
  bool find_id(const TString& id);
};

bool TRelation_tree::get_description(TString& str) const
{
  const TRelation_node* rn = (const TRelation_node*)curr_node();
  if (rn != NULL)
    return rn->description(str);
  return false;
}

static bool find_id_callback(TTree& tree, void* jolly, word flags)
{
  const TRelation_node* n = (const TRelation_node*)tree.curr_node();
  if (n != NULL)
  {
    const TString& id = *(const TString*)jolly;
    return n->id() == id;
  }
  return false;
}

bool TRelation_tree::find_id(const TString& id)
{
  bool ok = goto_root();
  if (ok)
  {
    scan_depth_first(find_id_callback, (void*)&id);
    const TRelation_node* n = (const TRelation_node*)curr_node();
    ok = n != NULL && n->id() == id;
  }
  return ok;
}

///////////////////////////////////////////////////////////
// TTable_mask
///////////////////////////////////////////////////////////

// Maschera per inserire/editare un nodo dell'albero
class TTable_mask : public TAutomask
{
protected:
  virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly);

protected:
  int father_logicnum() const;
  const char* find_linked_field(int logicnum, const RecFieldDes& fd) const;

public:
  int son_logicnum() const;
  void mask2node(TRelation_node& node);

  TTable_mask();
  ~TTable_mask();
};

int TTable_mask::father_logicnum() const
{
  return table2logic(get(F_FATHER));
}

int TTable_mask::son_logicnum() const
{
  return table2logic(get(F_SON));
}

// Dato il numero logico di una tabella ed un campo (di un'altra tabella)  
// cerca il campo piu' simile all'interno del tracciato record
const char* TTable_mask::find_linked_field(int logicnum, const RecFieldDes& fd) const
{
  const RecDes& rd = prefix().get_recdes(logicnum);
  int nBest = -1;
  double dBest = 0.0;
  for (int i = 0; i < rd.NFields; i++)
  {
    const RecFieldDes& field = rd.Fd[i];
    if (field.TypeF == fd.TypeF && field.Len == fd.Len)
    {
      const double fuzzy = xvt_str_fuzzy_compare(field.Name, fd.Name);
      if (fuzzy > dBest)
      {
        nBest = i;
        dBest = fuzzy;
        if (dBest == 1.0)
          break;
      }
    }
  }
  return nBest >= 0 ? rd.Fd[nBest].Name : "";
}

bool TTable_mask::on_field_event(TOperable_field& o, TField_event e, long jolly)
{
  switch (o.dlg())
  {
  case F_SON:
    if (e == fe_modify)
    {
      const int logicnum = son_logicnum();
      const int fathernum = father_logicnum();

      TSheet_field& sheet = sfield(F_SHEET);
      sheet.destroy();
      if (logicnum >= LF_USER && fathernum >= LF_USER)
      {
        const RecDes& rd = prefix().get_recdes(logicnum);
        const KeyDes& kd = rd.Ky[0];
        TToken_string tok;
        for (int j = 0; j < kd.NkFields; j++)
        {
          const int n = kd.FieldSeq[j] % MaxFields;
          tok = rd.Fd[n].Name;
          tok.add(find_linked_field(fathernum, rd.Fd[n]));
          sheet.rows_array().add(tok);
        }
      }       
      sheet.force_update();
    }
    break;
  case F_SHEET:
    if (e == se_query_add || e == se_query_del)
      return false;
    break;
  case F_FLD_TO:
    if (e == fe_button)
    {
      const int logicnum = son_logicnum();
      if (logicnum >= LF_USER)
      {
        TArray_sheet sheet(-1, -1, 24, 20, TR("Selezione"), HR("Nome@12|Lunghezza"));
        const RecDes& rd = prefix().get_recdes(logicnum);
        TToken_string row;
        for (int i = 0; i < rd.NFields; i++)
        {
          row = rd.Fd[i].Name;
          row.add(rd.Fd[i].Len);
          sheet.add(row);
        }
        sheet.rows_array().sort();
        if (sheet.run() == K_ENTER)
          o.set(sheet.row(-1).get(0));
      }
    }
    break;
  default:
    break;
  }

  return true;
}

// Trasferisce le informazioni dalla maschera ad un nodo della tabella
void TTable_mask::mask2node(TRelation_node& son)
{
  son.set_num(son_logicnum());
  son.set_alias(get(F_SON_ALIAS));

  if (son.father() != NULL)
  {
    TString_array& join = son.join();
    join.destroy();
    
    TSheet_field& sheet = sfield(F_SHEET);
    FOR_EACH_SHEET_ROW(sheet, i, row)
    {
      TToken_string* newrow = new TToken_string(50, '=');
      newrow->add(row->get(0)); 
      newrow->add(row->get()); 
      join.add(newrow);
    }
  }
}

TTable_mask::TTable_mask() : TAutomask("ba8200b")
{ 
  FileDes dir;
  CGetFile(LF_DIR, &dir, _nolock, NORDIR);
  const int nfiles = (int)dir.EOD;

  TList_sheet& sht = *efield(F_SON).sheet();
  TToken_string tt(80);
  TFilename n;
  for (int logic = LF_USER; logic < nfiles; logic++)
  {     
    const FileDes& fd = prefix().get_filedes(logic);
    n = fd.SysName; n = n.name(); n.upper();
    tt = n;
    tt.add(logic);
    tt.add(fd.Des);
    sht.rows_array().add(tt);
  }
  sht.rows_array().sort();
}

TTable_mask::~TTable_mask()
{ }

///////////////////////////////////////////////////////////
// TQuery_mask
///////////////////////////////////////////////////////////

// Maschera principale della costruzione della query
class TQuery_mask : public TAutomask
{
  TRelation_tree _tree; // Albero della relazione
  int _curr_num;        // Numero del file corrente
  bool _dragster;       // Operazione di trascinamento in corso

  TFilename _curr_query;// Nome completo della query in editazione
  bool _is_dirty;       // Maschera cambiata dall'ultimo caricamento
  bool _sql_dirty;      // Query modificata manualmente

protected:
  virtual void handler(WINDOW win, EVENT* ep);
  virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly);

protected:
  bool add_file_to_tree();
  void add_field_to_sheet(const char* fld, bool update);
  void add_field_to_sheet(bool update = true);
  void add_asterisk_to_sheet();
  bool ask_vars(const char* maskname, TRecordset& recset) const;
  TRecordset* new_recordset() const;
  bool edit_file_in_tree();
  
  static int sort_fields(const TObject** r1, const TObject** r2);
  void fill_fields();
  void enable_sql_button();
  void enable_field_buttons();
  void move_curr_field(int dir);

  void tree2sql(TString& from, TString& where);
  void tree2isam(TString_array& a);
  void sheet2sql();
  void sheet2isam();
  
  bool select_query();
  bool load_tables_tree(TXmlItem& tables);
  bool load_fields_sheet(TXmlItem& fields);
  void global_reset();

  bool save_tables_tree(TXmlItem& xml);
  bool save_fields_sheet(TXmlItem& xml);

public:
  void edit_query();
  void save_as(TRecordsetExportFormat fmt);

  bool load_query();
  bool save_query();
  bool save_if_needed();
  bool delete_query();

  bool get_qry_path(TFilename& path) const;

  TRelation_node* curr_node();
  TQuery_mask();
};

// Nodo corrente dell'albero
TRelation_node* TQuery_mask::curr_node() 
{ 
  TTree_field& tf = tfield(F_TABLES);
  tf.goto_selected();
  TRelation_node* n = (TRelation_node*)_tree.curr_node();
  return n;
}

// Aggiunge interattivamente un nodo all'albero
bool TQuery_mask::add_file_to_tree()
{
  TTable_mask msk;
 
  const TRelation_node* father = curr_node();
  TString fatherid; 
  if (father != NULL)
  {
    _tree.curr_id(fatherid);
    msk.set(F_FATHER, father->name());
    msk.set(F_FATHER_ALIAS, father->alias());
  }
  msk.disable(DLG_DELREC);

  bool ok = msk.run() == K_ENTER;
  if (ok)
  {
    TRelation_node* son = new TRelation_node(father, msk.son_logicnum(), msk.get(F_SON_ALIAS));
    msk.mask2node(*son);
    if (fatherid.not_empty())
      _tree.goto_node(fatherid);
    _tree.add_son(son);
    _tree.expand_all();
    tfield(F_TABLES).win().force_update();

    _curr_num = son->num();
    fill_fields();
  }

  return ok;
}

// Modifica interattivamente il nodo corrente dell'albero
bool TQuery_mask::edit_file_in_tree()
{
  TRelation_node* son = curr_node();
  if (son == NULL)
    return false; 

  TString sonid; _tree.curr_id(sonid);

  const TRelation_node* father = NULL;
  if (_tree.goto_father())
    father = (const TRelation_node*)_tree.curr_node();

  TTable_mask msk;
  if (father != NULL)
  {
    msk.set(F_FATHER, father->name());
    msk.set(F_FATHER_ALIAS, father->alias());

    TString_array& arr = son->join();
    FOR_EACH_ARRAY_ROW(arr, i, row)
    {
      TToken_string& newrow = msk.sfield(F_SHEET).row(-1);
      FOR_EACH_TOKEN((*row), tok)
      {
        TToken_string str(tok, '.');
        str.strip_spaces();
        if (str.items() > 1)
          newrow.add(str.get(1));
        else
          newrow.add(str);
      }
    }
  }
  msk.set(F_SON, son->name());
  msk.set(F_SON_ALIAS, son->alias());
   
  bool update = true;
  switch (msk.run())
  {
  case K_ENTER:
    msk.mask2node(*son); 
    break;
  case K_DEL:
    if (curr_node() != NULL)
    {
      if (yesno_box(TR("Eliminare anche le colonne associate della query?")))
      {
        TSheet_field& sheet = sfield(F_SHEET);
        FOR_EACH_SHEET_ROW_BACK(sheet, r, row)
        {
          const TString& id = row->get(0);
          if (id == son->id())
            sheet.destroy(r);
        }
      }
      _tree.goto_node(sonid);
      _tree.kill_node();
      _tree.goto_root();
    }
    break;
  default:
    update = false;
    break;
  }

  if (update)
  {
    TTree_field& tf = tfield(F_TABLES);          
    tf.win().force_update();
    
    _curr_num = 0;
    if (tf.select_current())
    {
      const TRelation_node* curr = curr_node();
      if (curr != NULL)
        _curr_num = curr->num();
    }
    fill_fields();
  }

  return update;  // ????
}

// Ordina per importanza i campi di un tracciato record
int TQuery_mask::sort_fields(const TObject** r1, const TObject** r2)
{
  TToken_string& s1 = (TToken_string&)**r1;
  TToken_string& s2 = (TToken_string&)**r2;
  const int w1 = s1.get_int(4);
  const int w2 = s2.get_int(4);
  int cmp = w2-w1;
  if (cmp == 0)
    cmp = strcmp(s1.get(0), s2.get(0));
  return cmp;
}

// Riempie la lista dei campi del file corrente
void TQuery_mask::fill_fields()
{
  TSheet_field& sht = sfield(F_FIELDS);
  sht.destroy();
  if (_curr_num >= LF_USER)
  {
    TRelation rel(_curr_num);
    TRelation_description reldes(rel);

    const RecDes& rd = prefix().get_recdes(_curr_num);
    for (int i = 0; i < rd.NFields; i++)
    {
      TToken_string& row = sht.row(-1);
      const RecFieldDes& fd = rd.Fd[i];
      row = fd.Name;
      row.add(fd.Len);
        
      int weight = 0;
      for (int k = 0; k < rd.NKeys; k++)
      {
        const KeyDes& kd = rd.Ky[k];
        for (int j = 0; j < kd.NkFields; j++)
        {
          const int n = kd.FieldSeq[j] % MaxFields;
          if (n == i)
          {
            if (weight == 0)
            {
              weight = (rd.NKeys-k)*100 + kd.NkFields-j;
              row.add(k+1);
            }
            else
              row << ' ' << (k+1);
          }
        }
      }
      row.add(reldes.get_field_description(fd.Name), 3);
      row.add(weight, 4);
    }
    sht.rows_array().TArray::sort(sort_fields);
  }
  sht.force_update();
  enable_field_buttons();
}

// Aggiunge un cmapo allo spreadsheet F_SHEET
void TQuery_mask::add_field_to_sheet(const char* fld, bool update)
{
  const TRelation_node* n = curr_node();
  if (n != NULL)
  {
    TSheet_field& ff = sfield(F_SHEET);
    TToken_string& row = ff.row(-1);
    row = n->id();
    row.add(fld);
    _is_dirty = true;
    if (update)
    {
      ff.force_update();
      enable_sql_button();
    }
  }
}

// Aggiunge il campo selezionato nello spredasheet F_FIELDS allo spreadsheet F_SHEET
void TQuery_mask::add_field_to_sheet(bool update)
{
  TSheet_field& sf = sfield(F_FIELDS);
  const int r = sf.selected();
  if (r >= 0)
  {
    TToken_string& rowsel = sf.row(r);
    add_field_to_sheet(rowsel.get(0), update);
  }
}

// Aggiunge tutte le colonne allo sheet
void TQuery_mask::add_asterisk_to_sheet()
{
  const TRelation_node* n = curr_node();
  if (n != NULL)
  {
    if (yesno_box("Si desisdera aggiungere tutti i campi singolarmente?"))
    {
      TSheet_field& sf = sfield(F_FIELDS);
      const int nLast = sf.items()-1;
      for (int i = 0; i <= nLast; i++)
      {
        sf.select(i);
        add_field_to_sheet(i == nLast);
      }
      sf.select(0);
    }
    else
      add_field_to_sheet("*", true);
  }
}

// Decide se attivare o meno il bottone SQL
void TQuery_mask::enable_sql_button()
{
  const bool yes = sfield(F_SHEET).items() > 0;
  enable(F_GENSQL, yes);
}

// Decide se attivare o meno i bottoni di aggiunta campi
void TQuery_mask::enable_field_buttons()
{
  const TSheet_field& sf = sfield(F_FIELDS);
  const bool ok = sf.items() > 0;
  enable(-1, ok);
}

void TQuery_mask::move_curr_field(int dir)
{
  TSheet_field& s = sfield(F_SHEET);
  const int sel = s.selected();
  if (sel >= 0 && sel < s.items())
  {
    const int des = sel+dir;
    if (des >= 0 && des < s.items())
    {
      s.rows_array().swap(sel, des);
      s.select(des);
      s.force_update();
    }
  }
}

static bool sql_tree_handler(TTree& tree, void* jolly, word flags)
{
  TRelation_node* rn = (TRelation_node*)tree.curr_node();
  TPointer_array& arr = *(TPointer_array*)jolly;
  TToken_string& from = *(TToken_string*)arr.objptr(0);
  TString& where = *(TString*)arr.objptr(1);
  TString_array& join = rn->join();
  TString str;

  if (from.get_pos(rn->id()) < 0)
  {
    from.add(rn->name());
    if (rn->alias().not_empty())
      from << " AS " << rn->alias();

    FOR_EACH_ARRAY_ROW(join, i, row)
    {
      if (where.find(*row) < 0)
      {
        if (where.not_empty())
          where << "AND";
        where << '(' << rn->id() << '.' << row->get(0) << '=';
        str = row->get();
        if (isalpha(str[0]))
          where << rn->father()->id() << '.';
        where << str << ')';
      }
    }
  }
  
  return false;  // Don't stop search
}

// Riempie una stringa SQL con la relazione tra le tabelle
void TQuery_mask::tree2sql(TString& from, TString& where)
{
  if (_tree.goto_root())
  {
    TPointer_array a;
    a.add(&from);
    a.add(&where);
    _tree.scan_depth_first(sql_tree_handler, &a);
  }
}

static bool isam_tree_handler(TTree& tree, void* jolly, word flags)
{
  TRelation_node* rn = (TRelation_node*)tree.curr_node();
  TString_array* a = (TString_array*)jolly;
  TToken_string row;
  if (a->items() == 0)
  {
    row << "USE " << rn->name();
  }
  else
  {
    row << "JOIN " << rn->name();
    if (rn->father()->name() != a->row(0).mid(4))
      row << " TO " << rn->father()->name();
    if (!rn->alias().blank())
      row << " ALIAS " << rn->alias();
    row << " INTO ";
    FOR_EACH_ARRAY_ROW(rn->join(), i, r)
      row << *r << ' ';
  }
  a->add(row);
  return false;
}

// Riempie una stringa ISAM con la relazione tra le tabelle
void TQuery_mask::tree2isam(TString_array& a)
{
  if (_tree.goto_root())
    _tree.scan_depth_first(isam_tree_handler, &a);
}

inline bool tok_get_bool(TToken_string& tok, int pos)
{
  const char* str = tok.get(pos);
  return str && *str > ' ';
}

static void add_where_clause(TString& where, const char* field, const char* cmp, const char* expr)
{
  if (where.not_empty())
    where << "AND";
  where << '(' << field << cmp << expr << ')';
}

// Genera una query SQL a partire dallo spreadsheet F_SHEET
void TQuery_mask::sheet2sql()
{
  const TSheet_field& sheet = sfield(F_SHEET);
  TToken_string select(50, ',');

  _tree.goto_root();
  const bool multiple = _tree.has_son();

  TToken_string from(50, ','), groupby(50, ','), orderby(50, ','); 
  TString where, expr_from, expr_to;

  TString field;
  FOR_EACH_SHEET_ROW(sheet, i, row)
  {
    field = row->get(1);
    if (!field.blank() && !tok_get_bool(*row, 2)) // Campo valido e visibile
    {
      if (multiple)
      {
        const TString& tab = row->get(0);
        if (!tab.blank())
        {
          field.insert(".");
          field.insert(tab); // Table name
        }
      }
      select.add(field);           // Column name only

      if (tok_get_bool(*row, 3))   // Sort
        orderby.add(field);
      if (tok_get_bool(*row, 4))   // Group
        groupby.add(field);
      
      expr_from = row->get(5); expr_from.trim();
      expr_to = row->get(6); expr_to.trim();
      if (expr_from.not_empty() || expr_to.not_empty())
      {
        if (expr_from.not_empty() && isalpha(expr_from[0]))
        {
          expr_from.insert("'");
          expr_from << '\'';
        }
        if (expr_to.not_empty() && isalpha(expr_to[0]))
        {
          expr_to.insert("'");
          expr_to << '\'';
        }

        if (expr_from == expr_to)
        {
          add_where_clause(where, field, "=", expr_from);
        }
        else
        {
          if (expr_from.not_empty())
            add_where_clause(where, field, ">=", expr_from);
          if (expr_to.not_empty())
            add_where_clause(where, field, "<=", expr_to);
        }
      }
    }

  }

  tree2sql(from, where);

  TString sql;
  sql << "SELECT " << select << '\n';
  sql << "FROM " << from << '\n';
  if (!where.blank())
    sql << "WHERE " << where << '\n';
  if (groupby.not_empty())
    sql << "GROUP BY " << groupby << '\n';
  if (orderby.not_empty())
    sql << "ORDER BY " << orderby << '\n';
  sql << ";";

  set(F_SQL, sql, true);
  _sql_dirty = false;
  TEdit_field& fs = efield(F_SQL);
  fs.set_focusdirty(false); // Evita di scatenare eventi inutili
}

// Genera una query ISAM a partire dallo spreadsheet F_SHEET
void TQuery_mask::sheet2isam()
{
  TString_array rel;
  tree2isam(rel);

  TString use;
  FOR_EACH_ARRAY_ROW(rel, i, row)
    use << *row << '\n';

  set(F_SQL, use, true);
  _sql_dirty = false;
  TEdit_field& fs = efield(F_SQL);
  fs.set_focusdirty(false); // Evita di scatenare eventi inutili
}

bool TQuery_mask::ask_vars(const char* maskname, TRecordset& recset) const 
{
  if (recset.variables().items() == 0)
    return true;

  TFilename fname = maskname; fname.ext("msk");
  KEY key = K_QUIT;
  if (!fname.custom_path())
    return recset.ask_variables(true);

  TMask m(maskname);
  TVariant var;
  for (int i = m.fields()-1; i >= 0; i--)
  {
    TMask_field& f = m.fld(i);
    const TFieldref* ref = f.field();
    if (ref != NULL)
    {
      TString name = ref->name();
      if (name[0] != '#')
        name.insert("#");
      const TVariant& var = recset.get_var(name);
      if (!var.is_null())
        f.set(var.as_string());
    }
  }
  key = m.run();
  const bool ok = key != K_QUIT && key != K_ESC;
  if (ok)
  {
    // Rendi visibili tutte le variabili utente al report
    for (int i = m.fields()-1; i >= 0; i--)
    {
      TMask_field& f = m.fld(i);
      const TFieldref* ref = f.field();
      if (ref != NULL)
      {
        switch (f.class_id())
        {
        case CLASS_CURRENCY_FIELD:
        case CLASS_REAL_FIELD: 
          var = real(f.get()); 
          break;
        case CLASS_DATE_FIELD: 
          var = TDate(f.get()); 
          break;
        default: 
          var = f.get(); 
          break;
        }       
        TString name = ref->name();
        if (name[0] != '#')
          name.insert("#");
        recset.set_var(name, var, true);
      }
    }
  }
  return ok;
}

TRecordset* TQuery_mask::new_recordset() const
{
  const TString& sql = get(F_SQL);
  TRecordset* rex = create_recordset(sql);

  if (rex != NULL)
  {
    if (!ask_vars(get(F_CODICE), *rex))
    {
      delete rex;
      rex = NULL;
    }

    if (rex != NULL && rex->items() == 0)
    {
      warning_box(TR("Nessuna riga risultato"));
      delete rex;
      rex = NULL;
    }
  }

  return rex;
}

void TQuery_mask::edit_query()
{
  TRecordset* rex = new_recordset();
  if (rex != NULL)
  {
    TRecordset_sheet sht(*rex);
    sht.run();
    delete rex;
  }
}

void TQuery_mask::save_as(TRecordsetExportFormat fmt)
{
  TRecordset* rex = new_recordset();
  if (rex == NULL)
    return;

  if (fmt == fmt_dbf)
  {
    TTable_mask tm;
    TList_sheet& sht = *tm.efield(F_SON).sheet();
    if (sht.run() == K_ENTER)
    {
      const TString& table = tm.get(F_SON);
      const KEY k = yesnocancel_box(FR("Si desidera azzerare il file %s prima dell'esportazione?"), 
                                    (const char*)table);
      if (k != K_ESC)
        rex->save_as(table, fmt_dbf, k == K_YES ? 0x5 : 0x3);
    }
    return;
  }


  xvt_fsys_save_dir();
  TFilename path; path.tempdir();

  const char* ext = "txt";
  switch (fmt)
  {
  case fmt_html: ext = "html"; break;
  case fmt_silk: ext = "xls"; break;
  default: break;
  }

  FILE_SPEC fs;
/*  xvt_fsys_convert_str_to_dir(path, &fs.dir);
  strcpy(fs.name, get(F_CODICE));
  strcpy(fs.type, ext);
  strcpy(fs.creator, "AGA"); */
  if (field(F_CODICE).empty())
    path.add("query"); 
  else
    path.add(get(F_CODICE)); 
  path.ext(ext);
  xvt_fsys_convert_str_to_fspec(path, &fs);
  
  xvt_fsys_save_dir();
  const bool good = xvt_dm_post_file_save(&fs, TR("Esportazione")) == FL_OK;
  xvt_fsys_restore_dir();

  if (good)
  {
    xvt_fsys_convert_fspec_to_str(&fs, path.get_buffer(), path.size());
    if (rex->save_as(path, fmt))
      xvt_sys_goto_url(path, "open");
  }

  delete rex;
}

bool TQuery_mask::get_qry_path(TFilename& path) const
{
  const TString& name = get(F_CODICE);
  const bool ok = name.full();
  if (ok)
  {
    path = name;
    if (!path.is_absolute_path())
    {
      path = firm2dir(-1);
      path.add("custom");
      if (!path.exist())
        xvt_fsys_mkdir(path);
      path.add(name);
    }
    path.ext("qry");
  }
  return ok;
}

bool TQuery_mask::select_query()
{
  TFilename path;
  const bool ok = select_custom_file(path, "qry");
  if (ok)
  {
    path = path.name(); path.ext("");
    set(F_CODICE, path);
  }
  return ok;
}

static bool xml_save_tree_handler(TTree& tree, void* jolly, word flags)
{
  TXmlItem* rel = (TXmlItem*)jolly;
  TRelation_node& node = (TRelation_node&)*tree.curr_node();

  TXmlItem& son = rel->AddChild("table");
  TString4 num; num << node.num();
  son.SetAttr("Num", num);
  son.SetAttr("Name", node.name());
  if (node.alias().not_empty())
    son.SetAttr("Alias", node.alias());
  if (node.father() != NULL)
  {
    son.SetAttr("Father", node.father()->id());
    FOR_EACH_ARRAY_ROW(node.join(), i, row)
    {
      if (i > 0)
        son << "AND";
      son << "(" << *row << ")";
    }
  }
  return false;
}

bool TQuery_mask::save_tables_tree(TXmlItem& xml)
{
  const bool ok = _tree.goto_root();
  if (ok)
  {
    TXmlItem& rel = xml.AddChild("tables");
    _tree.scan_depth_first(xml_save_tree_handler, &rel);
  }
  return ok;
}

bool TQuery_mask::save_fields_sheet(TXmlItem& xml)
{
  TSheet_field& sf = sfield(F_SHEET);
  const bool ok = sf.items() > 0;
  if (ok)
  {
    TXmlItem& fields = xml.AddChild("fields");
    FOR_EACH_SHEET_ROW(sf, i, row)
    {
      TXmlItem& field = fields.AddChild("field");
      field.SetAttr("Table", row->get(0));
      field.SetAttr("Name", row->get(1));
      field.SetAttr("Hidden", tok_get_bool(*row,2));
      field.SetAttr("Sort", tok_get_bool(*row,3));
      field.SetAttr("Group", tok_get_bool(*row,4));
      const char* str = row->get(5);
      if (str && *str > ' ')
        field.SetAttr("ExprFrom", str);
      str = row->get(6);
      if (str && *str > ' ')
        field.SetAttr("ExprTo", str);
    }
  }
  return ok;
}

bool TQuery_mask::save_query()
{
  bool ok = _curr_query.not_empty();
  if (!ok)
    ok = get_qry_path(_curr_query);
  if (!ok)
    return field(F_CODICE).on_key(K_ENTER); // Segnala errore

  char name[_MAX_FNAME];
  xvt_fsys_parse_pathname (_curr_query, NULL, NULL, name, NULL, NULL);
  ok = *name > ' ';
  if (ok)
  {
    TXmlItem xml;
    xml.SetTag("query");
    xml.SetAttr("Name", name);
    xml.AddChild("description") << get(F_DESCR);
    save_tables_tree(xml);
    save_fields_sheet(xml);
    xml.AddChild("sql") << get(F_SQL);
    xml.Save(_curr_query);
    _is_dirty = false;
  }
  
  return ok;
}

bool TQuery_mask::save_if_needed()
{
  if (!_is_dirty || !field(DLG_SAVEREC).active())
    return true;
  if (!yesno_box(TR("Si desidera registrare la query?")))
    return false;

  return save_query();
}

///////////////////////////////////////////////////////////
// Caricamento da file xml
///////////////////////////////////////////////////////////

// Carica l'albero della relazione
bool TQuery_mask::load_tables_tree(TXmlItem& tables)
{
  for (int i = 0; i < tables.GetChildren(); i++)
  {
    const TXmlItem& table = *tables.GetChild(i);
    const int num = atoi(table.GetAttr("Num"));
    if (num >= LF_USER)
    {
      _tree.find_id(table.GetAttr("Father"));
      const TRelation_node* father = (const TRelation_node*)_tree.curr_node();
      TRelation_node* son = new TRelation_node(father, num, table.GetAttr("Alias"));
      if (father != NULL)
      {
        TString expr; table.GetEnclosedText(expr);
        int i = expr.find('(');
        while (i >= 0)
        {
          const int j = expr.find(')', i+1);
          TToken_string* eq = new TToken_string(expr.sub(i+1, j), '=');
          son->join().add(eq);
          i = expr.find('(', j+1);
        }
      }
      _tree.add_son(son);
    }
  }

  const bool ok = _tree.goto_root();
  if (ok)
  {
    const TRelation_node* rn = (TRelation_node*)_tree.curr_node();
    _curr_num = rn->num();
    fill_fields();
    _tree.expand_all(); 
    tfield(F_TABLES).win().force_update();
  }
  enable_field_buttons();

  return ok;
}

// Carica l'elenco dei campi (o colonne)
bool TQuery_mask::load_fields_sheet(TXmlItem& xml)
{
  TSheet_field& sheet = sfield(F_SHEET);
  TXmlItem* fields = xml.FindFirst("fields");
  const bool ok = fields != NULL;
  if (ok)
  {
    for (int i = 0; i < fields->GetChildren(); i++)
    {
      const TXmlItem& field = *fields->GetChild(i);
      TToken_string& row = sheet.row(-1);
      row.add(field.GetAttr("Table"),0);
      row.add(field.GetAttr("Name"),1);
      row.add(field.GetBoolAttr("Hidden") ? "X" : "",2);
      row.add(field.GetBoolAttr("Sort") ? "X" : "",3);
      row.add(field.GetBoolAttr("Group") ? "X" : "",4);
      row.add(field.GetAttr("ExprFrom"),5);
      row.add(field.GetAttr("ExprTo"),6);
    }
    sheet.force_update();
  }
  enable_sql_button();
  return ok;
}

// Azzera tutto, ma proprio tutto
void TQuery_mask::global_reset()
{
  if (_tree.goto_root())
  {
    _tree.kill_node();
    TTree_field& tf = tfield(F_TABLES);
    tf.select_current();
    tf.win().force_update();
  }
  reset();
  _is_dirty = _sql_dirty = false;
}

// Caica l'intera query
bool TQuery_mask::load_query()
{
  TFilename path; get_qry_path(path);
  bool ok = path.exist();
  if (ok)
  {
    TXmlItem xml;
    ok = xml.Load(path);
    if (ok)
    {
      _curr_query = path;
      global_reset();

      path = path.name(); path.ext("");
      set(F_CODICE, path);

      const TXmlItem* desc = xml.FindFirst("description");
      if (desc != NULL)
      {
        TString str; desc->GetEnclosedText(str);
        set(F_DESCR, str);
      }

      TXmlItem* tables = xml.FindFirst("tables");
      if (tables != NULL)
        load_tables_tree(*tables);

      load_fields_sheet(xml);

      const TXmlItem* sql = xml.FindFirst("sql");
      if (sql != NULL)
      {
        TString str; sql->GetEnclosedText(str);
        set(F_SQL, str, true);         // Aggiorna anche stato bottoni di esportazione
        TEdit_field& sf = efield(F_SQL);
        sf.set_dirty(false); // Evita falsi allarmi di registrazione
        _sql_dirty = !sf.empty();
      }
      _is_dirty = false;               // Resetta definitivamente il dirty 
    }
  }
  return ok;
}

bool TQuery_mask::delete_query()
{
  TFilename path; get_qry_path(path);
  const bool ok = yesno_box(FR("Si desidera eliminare il file %s"), (const char*)path);
  if (ok)
  {
    ::remove(path);
    global_reset();
  }
  return ok;
}

// Gestione eventi standard
bool TQuery_mask::on_field_event(TOperable_field& o, TField_event e, long jolly)
{
  switch (o.dlg())
  {
  case F_CODICE:
    if (e == fe_button)
    {
      if (select_query())
        e = fe_modify;
    }
    if (e == fe_init || e == fe_modify)
    {
      save_if_needed();
      load_query();
    }
    break;
  case F_TABLES:
    if (e == fe_modify)
    {
      const TRelation_node* rn = (TRelation_node*)_tree.curr_node();
      if (rn && rn->num() != _curr_num)
      {
        _curr_num = rn->num();
        fill_fields();
      }
    }
    break;
  case F_FIELDS:
    if (e == se_query_modify || e == se_query_add || e == se_query_del)
      return false;
    enable_field_buttons();
    break;
  case F_ADDFILE:
    if (e == fe_button)
      add_file_to_tree();
    break;
  case F_EDITFILE:
    if (e == fe_button)
      edit_file_in_tree();
    break;
  case DLG_USER:
    if (e == fe_button)
      add_field_to_sheet();
    break;
  case F_ASTERISK:
    if (e == fe_button)
      add_asterisk_to_sheet();
    break;
  case F_GENSQL:
  case F_GENISAM:
    if (e == fe_button)
    {
      next_page(1001);
      bool ok = true;
      if (_sql_dirty && !field(F_SQL).empty())
        ok = yesno_box(TR("Attenzione: la query verra' rigenerata\n"
                          "annullando eventuali modifiche manuali.\n"
                          "Si desidera proseguire?"));
      if (ok)
      {
        if (o.dlg() == F_GENSQL)
          sheet2sql();
        else
          sheet2isam();
      }
    }
    break;
  case F_EDITQUERY:
    if (e == fe_button)
      edit_query();
    break;
  case F_SQL:
    if (e == fe_init || e == fe_modify)
    {
      const bool on = !o.empty();
      enable(F_EXPORT_DBF, on && is_power_station());
      if (e == fe_modify)
      {
        _is_dirty = true;
        _sql_dirty = !o.empty();
      }
    }
    break;
  case F_EXPORT_HTML:
    if (e == fe_button)
      save_as(fmt_html);
    break;
  case F_EXPORT_EXCEL:
    if (e == fe_button)
      save_as(fmt_silk);
    break;
  case F_EXPORT_TXT:
    if (e == fe_button)
      save_as(fmt_text);
    break;
  case F_EXPORT_CAMPO:
    if (e == fe_button)
      save_as(fmt_campo);
    break;
  case F_EXPORT_DBF:
    if (e == fe_button)
      save_as(fmt_dbf);
    break;
  case F_SHEET:
    enable_sql_button();
    break;
  case F_MOVEUP:
    if (e == fe_button)
      move_curr_field(-1);
    break;
  case F_MOVEDN:
    if (e == fe_button)
      move_curr_field(+1);
    break;
  case DLG_NEWREC:
    if (e == fe_button)
    {
      save_if_needed();
      global_reset();
      next_page(1000);
    }
    break;
  case DLG_FINDREC:
    if (e == fe_button)
      send_key(K_F9, F_CODICE);
  case DLG_SAVEREC:
    if (e == fe_button)
    {
      get_qry_path(_curr_query);
      save_query();
      next_page(1000);
    }
    break;
  case DLG_DELREC:
    if (e == fe_button && jolly == 0)  // Elimina della Toolbar 
    {
      delete_query();
      next_page(1000);
      return false; // Do not exit!
    }
    break;
  case DLG_QUIT:
    save_if_needed();
    break;
  default:
    break;
  }
  return true;
}

// Gestione eventi di trascinamento
void TQuery_mask::handler(WINDOW wnd, EVENT* ep)
{
  switch (ep->type)
  {
  case E_MOUSE_DOWN:
    if (ep->v.mouse.button == 0 && wnd == page_win(0))
    {
      const TSheet_field& sf = sfield(F_FIELDS);
      RCT rct; sf.get_rect(rct);
      _dragster = xvt_rect_has_point(&rct, ep->v.mouse.where) != 0;
    }
    else
      _dragster = false;
    if (_dragster)
    {
      XinCursor hand = xi_get_pref(XI_PREF_HAND_CURSOR_RID);
      xvt_win_set_cursor(wnd, (CURSOR)hand);
    }
    else
      xvt_win_set_cursor(wnd, CURSOR_ARROW);
    break;
  case E_MOUSE_UP:
    if (ep->v.mouse.button == 0 && _dragster)
    {
      TSheet_field& ff = sfield(F_SHEET);
      RCT rct; ff.get_rect(rct);
      if (xvt_rect_has_point(&rct, ep->v.mouse.where))
        add_field_to_sheet();
      xvt_win_set_cursor(wnd, CURSOR_ARROW);
    }
    _dragster = false;
    break;
  default:
    break;
  }
  TAutomask::handler(wnd, ep);
}

TQuery_mask::TQuery_mask() : TAutomask("ba8200a"), _curr_num(0), _is_dirty(false)
{ 
  RCT rcts, rctf, rctt;

  TSheet_field& sheet = sfield(F_SHEET);
  sheet.get_rect(rcts);

  // Allarga a dritta lo spreadsheet coi noi dei campi
  TSheet_field& fields = sfield(F_FIELDS);
  fields.get_rect(rctf);
  rctf.right = rcts.right;
  fields.set_rect(rctf);

  // Allarga a mancina l'albero della relazione
  TTree_field& trf = tfield(F_TABLES);
  trf.get_rect(rctt);
  rctt.top -= 4;
  rctt.left = rcts.left+4;
  rctt.right -= 32; // Toglie scrollbar
  rctt.bottom = rctf.bottom - 32;
  trf.set_rect(rctt);

  trf.set_tree(&_tree); // Associa l'albero al campo della maschera
}

///////////////////////////////////////////////////////////
// TSQL_recordset_app
///////////////////////////////////////////////////////////

class TSQL_recordset_app : public TSkeleton_application
{
  TQuery_mask* _msk;

public:
  virtual bool create();
  virtual void main_loop();
  virtual bool destroy();
};

bool TSQL_recordset_app::create()
{
  if (!has_module(RSAUT))
    return error_box(TR("Modulo non autorizzato"));

  _msk = new TQuery_mask;
  xvt_sys_sleep(500);  // Lasciamo il tempo di leggere il titolo

	if (argc() > 2)
	{
		_msk->set(F_CODICE, argv(2));
		_msk->load_query();
				
		if (argc() > 3)
    {
			switch (argv(3)[0])
			{
			case 'H':
				_msk->save_as(fmt_html);
				break;
			case 'T':
				_msk->save_as(fmt_text);
				break;
			case 'X':
				_msk->save_as(fmt_silk);
				break;
			case 'C':
				_msk->save_as(fmt_campo);
				break;
			default :
				_msk->save_as(fmt_html);
				break;
			}
    }
		else
			_msk->edit_query();

    return FALSE;
	}

  return TSkeleton_application::create();
}

void TSQL_recordset_app::main_loop()
{
  if (argc() > 2)
  {
    _msk->set(F_CODICE, argv(2)); // Carico la query da riga di comando
    _msk->disable(DLG_SAVEREC);   // Non permetto modifiche di alcun genere
    _msk->disable(DLG_NEWREC);
    _msk->disable(DLG_DELREC);
    _msk->disable(DLG_FINDREC);
  }
  _msk->run();
}

bool TSQL_recordset_app::destroy()
{
  if (_msk != NULL)
  {
    delete _msk;
    _msk = NULL;
  }
  return true;
}

int ba8200(int argc, char* argv[])
{     
  TSQL_recordset_app app;
  app.run(argc, argv, TR("Query Generator"));
  return 0;
}