#include "../xvaga/incstr.h"

#include <applicat.h>
#include <codeb.h>
#include <colors.h>
#include <dongle.h>
#include <modaut.h>
#include <progind.h>
#include <recset.h>
#include <relation.h>
#include <utility.h>
#include <xml.h>

#include <statbar.h>

///////////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////////

static bool is_numeric(const char* str)
{
  if (str == NULL || *str == '\0' || (*str == '0' && isdigit(str[1])))
    return false;    // Se comincia per zero va preservato!
  if (*str == '-')
  {
    str++;
    if (*str <= ' ')
      return false;
  }
  while (*str)
  {
    if (strchr("0123456789.,", *str) == NULL)
      return false;
    str++;
  }
  return true;
}

static void num_reformat(TString& val)
{
	xvt_str_number_format(val.get_buffer(), val.size());
  const int comma = val.find(',');
  const int point = val.find('.');
  if (comma >= 0 && comma < point) 
    val.strip(","); else
  if (point >= 0 && point < comma) 
    val.strip(".");
}

///////////////////////////////////////////////////////////
// TRecordset
///////////////////////////////////////////////////////////

const TString& TRecordset::query_text() const
{
  return EMPTY_STRING;
}

const TToken_string& TRecordset::sheet_head() const
{
  TToken_string head;
  TToken_string tablefield(32, '.');
	const unsigned int cols = columns();

  for (unsigned int c = 0; c < cols; c++)
  {
    const TRecordset_column_info& ci = column_info(c);
    tablefield = ci._name;
    int maxlen = 0;
    FOR_EACH_TOKEN(tablefield, tok)
    {
      if (maxlen == 0)
        head.add(tok);
      else
        head << '\n' << tok;
      const int len = strlen(tok);
      if (len > maxlen)
        maxlen = len;
    }
    head << '@' << max(ci._width, maxlen);
    switch (ci._type)
    {
    case _boolfld: head << 'C'; break;
    case _wordfld:
    case _intfld:
    case _longfld:
    case _realfld: head << 'R'; break;
    default: break;
    }
  }

  // Creo la stringa temporanea solo ora, altrimenti puo' essere sovrascritta!
  TToken_string& h = get_tmp_string();
  h = head;
  return h;
}


bool TRecordset::save_as_html(const char* path)
{
  TProgind pi(items(), TR("Esportazione in corso..."), true, true);
  ofstream out(path);
  out << "<html>" << endl;
  save_html_head(out, main_app().title());
  out << "<body>" << endl;

  TString qry; parsed_text(qry);
  if (qry.full())
  {
    for (int i = qry.find('\n'); i > 0; i = qry.find('\n', i+1))
      qry.insert("<br/>", i+1);
    out << "<p><b>" << qry << "</b></p>" << endl;
  }

  out << "<table border=\"1\">";
  out << " <caption>" << main_app().title() << "</caption>" << endl;

  const unsigned int cols = columns();
  if (cols > 0)
  {
    out << " <thead>" << endl;
    for (unsigned int c = 0; c < cols; c++)
    {
      const TRecordset_column_info& ci = column_info(c);
      out << "  <col ";
      switch (ci._type)
      {
      case _intfld :
      case _longfld:
      case _realfld: out << "align=\"right\""; break;
      case _boolfld: out << "align=\"center\""; break;
      default      : out << "style=\"mso-number-format:\\@\""; break;
      }
      out << " />" << endl;
    }

    TXmlItem tr; tr.SetTag("tr"); 
    tr.SetColorAttr("bgcolor", BTN_BACK_COLOR);
    tr.Write(out, 2);
    out << endl;

    for (unsigned int c = 0; c < cols; c++)
    {
      const TRecordset_column_info& ci = column_info(c);
      TToken_string header(ci._name, '\n');
      TString str;
      FOR_EACH_TOKEN(header, tok)
      {
        if (str.not_empty())
          str << "<br/>";
        str << tok;
      }
      out << "   <th>" << str << "</th>" << endl;
    }
    out << "  </tr>" << endl;
    out << " </thead>" << endl;
  }

  out << " <tbody>" << endl;
  TString val;
  for (bool ok = move_first(); ok; ok = move_next())
  {
    if (!pi.addstatus(1))
      break;

    out << "  <tr>" << endl;
    for (unsigned int c = 0; c < cols; c++)
    {
      const TRecordset_column_info& ci = column_info(c);
      out << "   <td>";
      switch (ci._type)
      {
      case _intfld:
      case _longfld:
        {
          const long r = get(c).as_int();
          val.cut(0);
          if (r != 0)
            val << r;
        }
        break;
      case _realfld:
        {
          const real r = get(c).as_real();
          if (r.is_zero())
            val.cut(0);
          else
            val = r.stringe();
        }
        break;
      default: 
        get(c).as_string(val); 
        break;
      }
      if (val.full())
      {
        val.rtrim();
        out << val;
      }
      out << "</td>" << endl;
    }
    out << "  </tr>" << endl;
  }
  out << " </tbody>" << endl;
  out << "</table>" << endl;
  out << "</body>" << endl;
  out << "</html>" << endl;

  return !pi.iscancelled();
}

bool TRecordset::save_as_text(const char* path)
{
  TProgind pi(items(), TR("Esportazione in corso..."), true, true);

  ofstream out(path);
  TString val;

	const unsigned int cols = columns();
  for (bool ok = move_first(); ok; ok = move_next())
  {
    for (unsigned int c = 0; ; c++)
    {
      const TVariant& var = get(c);
      if (var.is_null() && c >= cols)
				break;

      if (c > 0)
        out << '\t';
      if (!var.is_empty())
      {
        var.as_string(val);
        val.rtrim();
        if (val.find('\n') >= 0 || val.find('\t') >= 0)
          out << unesc(val); // Evitiamo doppi separatori di campo e record
        else
        {
          if (var.type() == _realfld || (cols == 0 && is_numeric(val)))
            num_reformat(val);
          out << val;
        }
      }
    }
    out << endl;
    if (!pi.addstatus(1))
      break;
  }
 
  return !pi.iscancelled();
}

bool TRecordset::save_as_campo(const char* path)
{
  TProgind pi(items(), TR("Esportazione in corso..."), true, true);
  ofstream out(path);

  out << "[Head]" << endl;
  out << "Version=0";

	const unsigned int cols = columns();
  for (unsigned int c = 0; c < cols; c++)
  {
    const TRecordset_column_info& ci = column_info(c);
    if ((c % 8) == 0)
      out << endl << "Fields=";
    else
      out << '|';
    out << ci._name;
  }
  out << endl << endl << "[Data]" << endl;

  TString val;
  for (bool ok = move_first(); ok; ok = move_next())
  {
    for (unsigned int c = 0; ; c++)
    {
      const TVariant& var = get(c);
      if (var.is_null() && c >= cols)
				break;

      if (c > 0)
        out << '|';
      if (!var.is_empty())
      {
        var.as_string(val);
        if (var.type() == _alfafld)
        {
          val.rtrim();
          val.replace('|', SAFE_PIPE_CHR);  // Evitiamo doppi separatori di campo
          if (val.find('\n') >= 0)          // Evitiamo doppi separatori di record  
            out << unesc(val);
          else
            out << val;
        }
        else
          out << val;
      }
    }
    out << endl;
    if (!pi.addstatus(1))
      break;
  }
  return !pi.iscancelled();
}

bool TRecordset::save_as_dbf(const char* table, int mode)
{
  char volume[_MAX_DRIVE];
  char dirname[_MAX_PATH];
  char name[_MAX_FNAME];
  char ext[_MAX_EXT];
  xvt_fsys_parse_pathname (table, volume, dirname, name, ext, NULL);
  
  const int logicnum = table2logic(name);
 
  if (mode == 0x0)
    mode = 0x1;
  if (mode & 0x4)
  {
    if (logicnum >= LF_USER && *dirname == '\0')
    {
      TSystemisamfile isam(logicnum);
      if (isam.zap() != NOERR)
        return error_box(TR("Impossibile cancellare il file '%s'"), table);
    }
    else
    {
      TFilename n;
      n = volume; n.add(dirname); n.add(name); n.ext("*");
      TString_array files; list_files(n, files);
      FOR_EACH_ARRAY_ROW(files, f, row)
        xvt_fsys_remove_file(*row);
    }
    mode = 0x1;
  }

  TLocalisamfile* pisam = NULL;
  if (logicnum >= LF_USER)
  {
    if (*dirname)
      pisam = new TIsamtempfile(logicnum, table);
    else
      pisam = new TLocalisamfile(logicnum);
  }
  if (pisam == NULL)
    return error_box("Impossibile creare il file %s", table);

  TLocalisamfile& isam = *pisam;

  TProgind pi(items(), TR("Esportazione in corso..."));
  TRectype& rec = isam.curr();

  TString_array names;
  int valid = 0;
  for (unsigned int j = 0; j < columns(); j++)
  {
    const TVariant& var = get(j);
    const TString& name = column_info(j)._name;
    if (rec.exist(name))
    {
      names.add(name);
      valid++;
    }
    else
      names.add(EMPTY_STRING);
  }

  bool ok = true;
  for (bool go = move_first(); go; go = move_next())
  {
    pi.addstatus(1);
    rec.zero();
    FOR_EACH_ARRAY_ROW(names, j, name) if (name->not_empty())
      rec.put(*name, get(j).as_string());

    int err = NOERR;
    bool to_be_written = true;

    // Devo solo aggiornare parte dei campi?
    if ((mode & 0x2) && valid < rec.items())
    {
      err = isam.read(_isequal, _lock);
      if (err != NOERR)
        rec.zero();
      FOR_EACH_ARRAY_ROW(names, j, name) if (name->not_empty())
        rec.put(*name, get(j).as_string());
      if (err == NOERR)
        to_be_written = isam.rewrite() != NOERR;
    }
    if (to_be_written)
    {
      err = (mode & 0x1) ? isam.write() : isam.rewrite();
      if (err != NOERR && (mode & 0x3) != 0)
        err = (mode & 0x1) ? isam.rewrite() : isam.write();
    }
    if (err != NOERR)
    {
      ok = error_box(FR("Errore %d durante la scrittura del record %s"), err, rec.build_key());
      break;
    }
  }

  return ok;
}

bool TRecordset::save_as(const char* path, TRecordsetExportFormat fmt, int mode)
{
  if (fmt == fmt_unknown)
  {
    TString ext;
    xvt_fsys_parse_pathname(path, NULL, NULL, NULL, ext.get_buffer(), NULL);
    ext.lower();
    if (ext == "htm" || ext == "html" || ext == "xml" || ext == "xls")
      fmt = fmt_html;
  }
  bool ok = false;
  switch (fmt)
  {
  case fmt_html : ok = save_as_html(path);  break;
  case fmt_campo: ok = save_as_campo(path); break;
  case fmt_dbf  : ok = save_as_dbf(path, mode);   break;
  default       : ok = save_as_text(path);  break;
  }

  return ok;
}

int TRecordset::find_column(const char* column_name) const
{
	int i;
  for (i = columns()-1; i >= 0; i--)
  {
    const TRecordset_column_info& info = column_info(i);
    if (info._name == column_name)
      break;
  }
  return i;
}

TVariant& TRecordset::get_tmp_var() const
{
  static TArray _page;      // Variants to be returned by get
  static int _next_var = 0; // Index of next variant to be returned

  if (_next_var >= 32)
    _next_var = 0;
  TVariant* var = (TVariant*)_page.objptr(_next_var);
  if (var == NULL)
  {
    var = new TVariant;
    _page.add(var, _next_var);
  }
  _next_var++;
  return *var;
}

const TVariant& TRecordset::get(const char* column_name) const
{
  if (*column_name == '#')
    return get_var(column_name);

  char* colon = (char*)strchr(column_name, ':');  //antica porcata
  if (colon != NULL)
  {
    *colon = '\0';
    const int i = find_column(column_name);
    *colon = ':';
    if (i >= 0)
    {
      const TString& str = get(i).as_string();
      TString subfield; subfield << (colon+1) << '=';
      int s = str.find(subfield);
      if (s == 0 || (s > 0 && str[s-1] < ' '))
      {
        s += subfield.len();
        const int e = str.find('\n', s);
        TVariant& var = get_tmp_var();
        var.set(str.sub(s, e));
        return var;
      }
    }
  }
  else
  {
    const int i = find_column(column_name);
    if (i >= 0)
      return get(i);
  }

  return NULL_VARIANT;
}

const TVariant& TRecordset::get_var(const char* name) const
{
	const TVariant* var = (const TVariant*)_var.objptr(name);

  // Non so che variabile sia: provo con quelle di sistema
  if (var == NULL && *name == '#')
  {
    const TFixed_string n(name);
  // Se mi accorgo che posso e voglio accedere ad un campo del recordset padre
    if (_parentset != NULL && n.starts_with("#PARENT.")) 
      var = &_parentset->get(name+8); // Attenzione! E' giusto usare get() e non get_var()
    else
  {
      if (n.starts_with("#RECORD."))
      {
        const TFixed_string fn(name + 8);

        TVariant& v = get_tmp_var();
        if (fn == "NUMBER")
          v.set(current_row()+1); else
        if (fn == "LAST")
          v.set(items());
        var = &v;
      }
    }
  }
  return var != NULL ? *var : NULL_VARIANT;
}

bool TRecordset::set_var(const char* name, const TVariant& var, bool create)
{
  bool ok = false;
  TVariant* old = (TVariant*)_var.objptr(name);
  if (old != NULL)
  {
    *old = var;
    ok = true;
  }
  else
  {
    if (create)
    {
      const TFixed_string n(name);
      if (n.starts_with("#PARENT.") || n.starts_with("#RECORD."))
      {
        // Aggiungo solo il nome alla lista: niente valore!
        _varnames.add(name);
      }
      else
      {
      _var.add(name, var);
      _varnames.add(name);
      }
      ok = true;
    }
  }
  if (ok)
    requery();
  return ok;
}

bool is_var_separator(char c)
{
  if (isspace(c))
    return true;
  return strchr("<=>+-*/(,.", c) != NULL;
}

// Cerca le variabili nel testo SQL:
// Una variabile comincia per # ed e' composta da soli caratteri alfanumerici.
// Prima del simbolo # e dopo il nome della variabile deve esserci un separatore o blank
void TRecordset::find_and_reset_vars()
{
  _var.destroy();
  _varnames.destroy();
  
  const TString& sql = query_text();
  int diesis = sql.find('#'); // cerco il primo #
  for ( ; diesis > 0; diesis = sql.find('#', diesis+1)) // Cerco tutti i #
  {
    if (is_var_separator(sql[diesis-1])) // Controllo che ci sia un separatore prima del #
    {
      int i = diesis+1;
      for ( ; sql[i] && (isalnum(sql[i]) || strchr("@_.#", sql[i]) != NULL); i++);
      if (i > diesis+1)
      {
        const TString& name = sql.sub(diesis, i);
        set_var(name, NULL_VARIANT, true);
				diesis = i;		//ricomincia a cercare variabili dopo la fine della variabile corrente;senza questa istruzione non possono funzionare cose tip #PARENT.#PARENT.33.CAMPO
      }
    }
  }
}

void TRecordset::parsed_text(TString& sql) const
{
  sql = query_text();
  const bool is_isam = sql.starts_with("US");
  const bool is_sql = !is_isam;
  const char* apici = is_isam ? "\"" : "'";

  const bool vars = ((TRecordset*)this)->ask_variables(false);
  if (vars) // Se ci sono variabili faccio le sostituzioni
  {
    const TString_array& names = variables();
    TString s;
    FOR_EACH_ARRAY_ROW(names, i, name) // Scandisco tutte le variabili
    {
      TVariant var = get_var(*name);
      int pos = sql.find(*name);
      for ( ; pos > 0; pos = sql.find(*name, pos+1))
      {
        const TString& after = sql.mid(pos+name->len());
        sql.cut(pos);
        
        if (var.type() == _datefld)
          s.format("%ld", var.as_date().date2ansi());
        else
        {
          s = var.as_string();
          if (is_sql) // Raddoppia gli apici in SQL
          {
            for (int i = 0; s[i]; i++)
            {
              if (s[i] == '\'')
                s.insert("'", i++);
            }
          }
        }
        if ((var.is_string() && s[0] != *apici && sql.right(1) != apici) || var.is_null())
        {
          s.insert(apici);
          s << apici;
        }
        sql << s << after;
      }
    }
  }
}

bool ask_variable(const char* name, TVariant& var)
{
  TMask m(TR("Richiesta variabile"), 1, 52, 4);
  m.add_static(-1, 0, name, 1, 0);
  m.add_string(101, 0, "", 1, 1, 80, "", 50);
  m.add_button(DLG_OK, 0, "", -12, -1, 10, 2);
  m.add_button(DLG_CANCEL, 0, "", -22, -1, 10, 2);
  m.set(101, var.as_string());
  const bool ok = m.run() == K_ENTER;
  if (ok)
  {
    const TString& str = m.get(101);
    if (is_numeric(str))
      var = real(str);
    else
      var = str;
  }
  return ok;
}

bool TRecordset::ask_variables(bool all)
{
  const bool ok = variables().items() > 0;
  if (ok) // Se ci sono variabili faccio le sostituzioni
  {
    FOR_EACH_ARRAY_ROW(_varnames, i, name)
    {
      TVariant var = get_var(*name);
      if (var.is_null() || all) 
      {
        ask_variable(*name, var);
        if (var.is_null())
          var.set(""); // Mi serve assolutamente un valore!
        set_var(*name, var);
      }
    }
  }
  return ok;
}

const TString& TRecordset::driver_version() const
{ return EMPTY_STRING; }

TRecordset::TRecordset() : _parentset(NULL)
{ }

///////////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////////

static void sort_files(TString_array& files)
{
  TFilename path;

  // Trasforma i path completi in nomi senza estensione
  FOR_EACH_ARRAY_ROW(files, i, row)
  {
    path = *row; 
    path = path.name(); 
    path.ext("");
    path.lower();
    *row = path;
  }
  files.sort();  // Ordina alfabeticamente

  // Rimuove i files doppi proveninenti da Campo e Custom
  for (int j = files.last(); j > 0; j--)
  {
    if (files.row(j) == files.row(j-1))
      files.destroy(j);
  }
  files.pack();
}

static bool get_xml_attr(const TString& line, const char* attr, TString& value)
{
  TString80 str; str << ' ' << attr << "=\"";
  const int pos = line.find(str);
  if (pos >= 0)
  {
    const int apicia = line.find('"', pos)+1;
    const int apicic = line.find('"', apicia);
    if (apicic > apicia)
    {
      value = line.sub(apicia, apicic);
      return true;
    }
  }
	value.cut(0);
  return false;
}

static bool get_xml_child(const TString& line, const char* tag, TString& value)
{
  TString80 str; str << '<' << tag << '>';
  const int pos = line.find(str);
  if (pos >= 0)
  {
    const int apicia = line.find('>', pos)+1;
    const int apicic = line.find('<', apicia);
    if (apicic > apicia)
    {
      value = line.sub(apicia, apicic);
      return true;
    }
  }
	value.cut(0);
  return false;
}

bool list_custom_files(const char* ext, const char* classe, TString_array& files)
{
  TString_array lista;
  TFilename path;
  TWait_cursor hourglass;
	TFilename name = path.name();
	const bool wild = (name.find('*') >= 0) || (name.find('?') >= 0);

	if (!wild)
		name = "*";

  // Leggo i files in custom
  if (main_app().has_module(RSAUT))
  {
    TFilename custom = firm2dir(-1);
    custom.add("custom");
    if (!custom.exist())
      xvt_fsys_mkdir(custom);

    path = custom;
    path.add(name);
    path.ext(ext);
	}
  list_files(path, lista);
  path = name; path.ext(ext);  // Leggo i files in campo
	list_files(path, lista);
	sort_files(lista);           // Ordino i files e rimuovo i doppioni

  TString8 acqua;
  TString stringona, desc;

  FOR_EACH_ARRAY_ROW(lista, i, row)
  {
    path = *row; path.ext(ext);
    bool ok = path.custom_path();
    if (ok)
    { 
      TScanner scan(path);
      stringona.cut(0);
      for (int i = 0; i < 4 && scan.good(); i++) // Leggo solo le prime righe
        stringona << scan.line();
  
      get_xml_attr(stringona, "class", acqua);
      if (classe && *classe)
        ok = acqua == classe;
   
      if (ok)
      {
        get_xml_child(stringona, "description", desc);
        TToken_string* riga = new TToken_string;
        riga->add(*row);
        riga->add(acqua);
        riga->add(desc); 
        riga->add(path.find("custom") > 0 ? "X" : "");
        files.add(riga);
      }
    }
  }
  return !files.empty();
}

bool select_custom_file(TFilename& path, const char* ext, const char* classe)
{
  TArray_sheet sheet(-1, -1, 78, 20, TR("Selezione"), HR("Nome@16|Classe@8|Descrizione@50|Custom"));
  TString_array& files = sheet.rows_array();
  TFilename first = path.name(); first.ext(""); // Riga su cui posizionarsi
  bool ok = list_custom_files(ext, classe, files);

  if (ok)
  {
    if (first.full()) // Cerco la prima riga da selezionare se possibile
    {
      FOR_EACH_ARRAY_ROW(files, i, row)
      {
        if (first.compare(row->get(0), -1, true) == 0) // Ho trovato la selezione corrente
        {
          sheet.select(i);
          break;
        }
      }
    }

    ok = sheet.run() == K_ENTER;
    if (ok)
    {
      path = sheet.row(-1).get(0);
      path.ext(ext);
      ok = path.custom_path();
    }
  }

  return ok;
}

///////////////////////////////////////////////////////////
// TCursor_parser
///////////////////////////////////////////////////////////

class TCursor_parser
{
  istream& _instr;
  TArray& _column;

  TString _pushed;
  TString _token;
  
  TRelation* _relation;
  TCursor* _cursor;

protected:
  const TString& pop();
  const TString& line();
  void push();
  void add_column_info(const char* table, const TRectype& rec);

  void parse_sortexpr(TToken_string& se);
  void parse_filter(TToken_string& filter);
  void parse_select(TToken_string& filter);
  void parse_region(TRectype& rec);
  void parse_join_param(TRelation* rel, const TString& j, int to);
  void parse_join();
  void parse_sortedjoin();

public:
  TRelation* get_relation() { return _relation; }
  TCursor* get_cursor() { return _cursor; }

  TCursor_parser(istream& instr, TArray& column);
};

const TString& TCursor_parser::pop()
{
  if (_pushed.not_empty())
  {
    _token = _pushed;
    _pushed.cut(0);
  }
  else
  {
    _token.cut(0);  
    eatwhite(_instr);
    char instring = '\0';

    while (_instr)
    {
      char c; 
      _instr.get(c);
      if (c == EOF)
        break;
      if (instring > ' ') // Sono dentro ad una stringa
      {
        if (c == '\n')
          break;
        if (c == instring)
          instring = '\0';
        _token << c;
      }
      else
      {
        if (c == '"' || c == '\'')
        {
          instring = c;
          _token << c;
        }
        else
        {
          if (isspace(c))
            break;
          _token << c;
        }
      }
    }
  }
  return _token;
}

const TString& TCursor_parser::line()
{
  if (_pushed.not_empty())
    return pop();
  char* buff = _token.get_buffer(256);
  _instr.getline(buff, _token.size());
  return _token;
}

void TCursor_parser::push()
{
  CHECK(_pushed.empty(), "Repushing?");
  _pushed = _token;
}

void TCursor_parser::add_column_info(const char* table, const TRectype& rec)
{
  for (int i = 0; i < rec.items(); i++)
  {
    TRecordset_column_info* info = new TRecordset_column_info;
    const char* name = rec.fieldname(i);
    info->_name << table << '.' << name;
    info->_type = rec.type(name);
    switch (info->_type)
    {
    case _datefld: info->_width = 10; break;
    case _memofld: info->_width = 50; break;
    default      : info->_width = rec.length(name); break;
    }
    _column.add(info);
  }
}

void TCursor_parser::parse_sortexpr(TToken_string& se)
{
  const char sep = se.separator();
  se.separator(' ');
  _instr.getline(se.get_buffer(), se.size());
  se.strip_double_spaces();
  se.replace(' ', sep);
  se.separator(sep);

  // Trasforma i nomi dei files in numeri se necessario
  if (se.find('.') > 0)
  {
    TToken_string fld(16, '.');
    TString16 name;
    for (int i = 0; se.get(i, fld); i++)
    {
      if (!isdigit(fld[0]) && fld.find('.') > 0)
      {
        fld.get(0, name);
        const int num = ::table2logic(name);
        if (num != 0)
        {
          fld.add(num, 0);
          se.add(fld, i);
        }
      }
    }
  }
}

void TCursor_parser::parse_filter(TToken_string& filter)
{
  const TString& str = pop();
  while (str.find('=') > 0)
  {
    filter.add(str);
    pop();
  }
  push();
}

void TCursor_parser::parse_select(TToken_string& filter)
{
  filter = line();
  filter.trim();
}


void TCursor_parser::parse_region(TRectype& rec)
{
  TString16 field;
  TString80 value;
  while (true)
  {
    const TString& ass = pop();
    const int equal = ass.find('=');
    if (equal > 0)
    {
      field = ass.left(equal);
      value = ass.mid(equal+1);
      value.trim();
      if (value[0] == '"' || value[0] == '\'')
      {
        value.rtrim(1);
        value.ltrim(1);
      }
      rec.put(field, value);       
    }
    else
      break;
  }
  push();
}


void TCursor_parser::parse_join_param(TRelation* rel, const TString& j, int to)
{
  int key = 1;
  const TString& tok = pop();
  if (tok.starts_with("KE"))
  {
    pop();
    key = atoi(tok);
  }
  else 
    push();

  int alias = 0;
  pop();
  if (tok.starts_with("AL"))
  {
    pop();
    alias = atoi(tok);
  }
  else 
    push();

  TToken_string exp(80);
  pop();
  if (tok == "INTO")
  {
    parse_filter(exp);
  }
  if (exp.empty())
    yesnofatal_box("JOIN senza espressioni INTO");

  const int logicnum = table2logic(j);
  switch (logicnum)
  {
  case LF_TABGEN:
  case LF_TABCOM:
  case LF_TAB:
  case LF_TABMOD:
    rel->add(j, exp, key, to, alias);          // join table
    break;
  default:
    rel->add(logicnum, exp, key, to, alias);   // join file
    break;
  } 

  TString16 tabname;
  if (alias > 0)
    tabname << alias << '@'; 
  else
    tabname = j;
  const TRectype& rec = rel->curr(logicnum);
  add_column_info(tabname, rec);    
}

void TCursor_parser::parse_join()
{
  const TString j = pop();           // File or table

  int to = 0;
  const TString& tok = pop();
  if (tok == "TO")         // TO keyword
  {
    pop();
    to = table2logic(tok);
  }
  else 
    push();
  
  parse_join_param(_relation, j, to);
}

void TCursor_parser::parse_sortedjoin()
{
  TToken_string filter,sortexp;
  const TString j = pop();           // File or table
  const TString& tok = pop(); 
  if (tok == "BY" )
    parse_sortexpr(sortexp);
  else 
    push();
 
  pop();
  if (tok.starts_with("FI") || tok.starts_with("SE")) 
  {
    parse_select(filter);
  } 
  else 
    push();
  
  TRelation* sortrel = new TRelation(table2logic(j)); 
  while (true)
  {
    pop();
    if (tok.empty() || tok.starts_with("JO"))
    {
      break;
    }

    if (tok.starts_with("US"))         // USING keyword
    {
      const TString subj = pop();      // File or table
      parse_join_param(sortrel, subj, table2logic(j));
    } 
  } 

  int to = 0;
  pop();
  if (tok == "TO")         // TO keyword
  {
    pop();
    to = table2logic(tok);
  }
  else 
    push();

  int key = 1;
  pop();
  if (tok.starts_with("KE"))
  {
    pop();
    key = atoi(tok);
  }
  else 
    push();

  int alias = 0;
  pop();
  if (tok.starts_with("AL"))
  {
    pop();
    alias = atoi(tok);
  }
  else 
    push();

  TToken_string exp(80);
  if (pop() == "INTO")
  {
    pop();
    while (tok.find('=') > 0)
    {
      exp.add(tok);
      pop();
    }
  }
  push();

  TSortedfile *sf= new TSortedfile(atoi(j),sortrel,sortexp,filter,key);
  _relation->add((TLocalisamfile *)sf, exp, key, to, alias, false);         // join table

  TString16 tabname = j;
  if (alias > 0)
    tabname.cut(0) << alias << '@'; 
  add_column_info(tabname, sf->curr());    
}

TCursor_parser::TCursor_parser(istream& instr, TArray& col) 
                : _instr(instr), _column(col), _relation(NULL), _cursor(NULL)
{
  _column.destroy();
  const TString& tok = pop();
  if (!tok.starts_with("US"))
    push();

  pop();
  if (tok.blank())
    return;

  TString table(tok); table.upper();
  if (table.ends_with(".DBF"))
  {
    TFilename dbf = table;
    if (table[0] == '$' || table[0] == '%')
    {
      if (table[0] == '%')
        dbf = firm2dir(0);
      else
        dbf = firm2dir(prefix().get_codditta());
      dbf.add(table.mid(1));
    }
    else
    {
      if (dbf.is_relative_path())
      {
        if (!dbf.custom_path())
        {
          dbf.tempdir();
          dbf.add(table);
        }
      }
    }
    dbf.lower();
    TExternisamfile* eif = new TExternisamfile(dbf, true);
    _relation = new TRelation(eif);
  }
  else
  {
    int logicnum = table2logic(tok);
    if (logicnum == LF_MAG && tok == "MAG")  // Faccio prevalere la tabella MAG sul file MAG
      logicnum = LF_TAB;

    if (logicnum == LF_TAB || logicnum == LF_TABCOM || logicnum == LF_TABMOD)
      _relation = new TRelation(tok);
    else
      _relation = new TRelation(logicnum);
  }
  pop();
	
  add_column_info(table, _relation->curr());

  int key = 1;  // key number
  if (tok.starts_with("KE"))
  {
    pop();
    key = atoi(tok);
  }
  else
    push();

  pop();
  
	TToken_string filter;

	if (tok.starts_with("FI") || tok.starts_with("SE"))
    parse_select(filter);
  else
    push();

  pop();
  if (tok.starts_with("BY"))  // "sort BY": user-defined sort
  { 
    TToken_string ordexpr(256);
    parse_sortexpr(ordexpr);
    _cursor = new TSorted_cursor(_relation, ordexpr, "", key);
  }
  else
    push();


  if (_cursor == NULL)
    _cursor = new TCursor(_relation, "", key);

  _relation->lfile().zero();  // Azzera correttamente tabelle normali e di modulo!
  TRectype rec_start(_relation->curr());
  TRectype rec_stop(rec_start);

  TString_array fields;
	pop();
	while (tok.starts_with("DI"))
  {
		pop();
		pop();
		TString field;

		if (tok.find(".") < 0)
			field <<  table << ".";
		field << tok;
		fields.add(field);
		pop();
  }
	push();
  while (true)
  {
    pop();
    if (tok.starts_with("FR"))
      parse_region(rec_start); else
    if (tok.starts_with("TO"))
      parse_region(rec_stop); else
    if (tok.starts_with("JO"))
      parse_join(); else
    if (tok.starts_with("SO"))
      parse_sortedjoin(); 
    else
      break;
  }
  push(); 

  if (!rec_start.empty() || !rec_stop.empty())
    _cursor->setregion(rec_start, rec_stop);
	if (!filter.empty())
		_cursor->setfilter(filter);
  if (fields.items() > 0)
  {
    FOR_EACH_ARRAY_ITEM_BACK(_column, i, obj)
    {
      TRecordset_column_info* info = (TRecordset_column_info*)obj;
			
			if (fields.find(info->_name) == -1)
				_column.destroy(i, true);
    }
  }
  if (_relation->items() == 0) // Non ci sono anche tabelle collegate
  {
    FOR_EACH_ARRAY_ITEM(_column, i, obj)
    {
      TRecordset_column_info* info = (TRecordset_column_info*)obj;
      const int arrow = info->_name.find('.');
      if (arrow > 0)
        info->_name = info->_name.mid(arrow+1);
    }
  }
}

///////////////////////////////////////////////////////////
// TISAM_recordset
///////////////////////////////////////////////////////////

const TVariant& TISAM_recordset::get_field(int logic, const char* fldname) const
{
  const int idx = relation()->log2ind(logic);
  if (idx < 0)
    return NULL_VARIANT;

  TString80 name = fldname;
  TString16 subfield;
  int from = 1, to = 0;

  const int open_bracket = name.find('[');
  if (open_bracket > 0)
  {
    TToken_string range(name.mid(open_bracket+1), ',');
    from = range.get_int();
    to   = range.get_int();
    name.cut(open_bracket);
  }

  const int colon = name.find(':');

  if (colon > 0)
  {
    subfield = name.mid(colon+1);
    name.cut(colon);
  }

  const TRectype& rec = relation()->file(idx).curr();
  const TFieldtypes ft = rec.type(name);

  if (ft == _nullfld)
  {
    switch (logic) // Proviamo la magia dei campi virtuali
    {
    case LF_DOC     : subfield = name; name = "G1";  break; 
    case LF_RIGHEDOC: subfield = name; name = "RG1"; break;
    default: return NULL_VARIANT;
    }
  }

  TVariant& var = get_tmp_var();
  switch (ft)
  {
  case _boolfld: var.set(rec.get_bool(name)); break;
  case _datefld: var.set(rec.get_date(name)); break;
  case _realfld: var.set(rec.get_real(name)); break;
  case _intfld : 
  case _longfld:
  case _wordfld: var.set(rec.get_long(name)); break;
  case _intzerofld : // Non usare il convertitore degll interi
  case _longzerofld: // in modo da preservare gli zeri iniziali
  default          : var.set(rec.get(name)); break;
  }

  if (subfield.not_empty())
  {
    subfield << '=';
    const TString& str = var.as_string();
    int s = str.find(subfield);
    if (s == 0 || (s > 0 && str[s-1] < ' '))
    {
      s += subfield.len();
      const int e = str.find('\n', s);
      var.set(str.sub(s, e));
    }
    else
      var.set_null();
  }

  if (to >= from && !var.is_empty())
    var.set(var.as_string().sub(from-1, to));

  return var;
}

const TVariant& TISAM_recordset::get(size_t c) const
{
  const TRecordset_column_info* info = (const TRecordset_column_info*)_column.objptr(c);
  if (info != NULL)
  {
    int logic = 0;
    const char* field = info->_name;
    const int dot = info->_name.find('.');
    if (dot > 0)
    {
      logic = table2logic(info->_name.left(dot));
      field += dot+1;
    }
    return get_field(logic, field);
  }
  return NULL_VARIANT;
}

const TVariant& TISAM_recordset::get(const char* name) const
{
  if (*name == '#')
    return get_var(name);

  const TFixed_string fldname(name);
  
  int table_end = fldname.find('.');
  int field_start = table_end+1;
  if (table_end < 0)
  {
    table_end = fldname.find('-');
    if (table_end > 0)
      field_start = table_end+2;
  }
  int logic = 0;
  const char* field = name;
  if (table_end > 0)
  {
    const TString& table = fldname.left(table_end);
    logic = table2logic(table);
    field += field_start;
  }
  return get_field(logic, field);
}

const TRecordset_column_info& TISAM_recordset::column_info(size_t i) const
{
  return (const TRecordset_column_info&)_column[i];
}

TCursor* TISAM_recordset::cursor() const
{
  if (_cursor == NULL && _use.full())
  {
    TString use; parsed_text(use);
    TPerformance_profiler prof("ISAM query");
    TISAM_recordset* my = (TISAM_recordset*)this;
#ifdef LINUX
		string s(use.get_buffer());
    istringstream instr(s);
#else
    istrstream instr(use.get_buffer(), use.len()+1);  //"barata" x aggiungere il carattere finale
#endif
    TCursor_parser parser(instr, my->_column);

    my->_relation = parser.get_relation();
    my->_cursor = parser.get_cursor();

    if (_cursor != NULL)
    {
      set_custom_filter(*_cursor);
      const TRecnotype items = _cursor->items();

      _cursor->freeze();
			if (items > 0)
				*_cursor = 0L;
    }
  }
  return _cursor;
}

TRelation* TISAM_recordset::relation() const
{
  cursor();
  return _relation;
}

TRecnotype TISAM_recordset::current_row() const
{
  TCursor* c = cursor();
  return c != NULL ? c->pos() : -1;
}

TRecnotype TISAM_recordset::items() const 
{
  TCursor* c = cursor();
  return c != NULL ? c->items() : 0;
}

unsigned int TISAM_recordset::columns() const 
{ 
  cursor();
  return _column.items(); 
}


bool TISAM_recordset::move_to(TRecnotype pos)
{
  TCursor* c = cursor();
  bool ok = c != NULL && pos >= 0;
  if (ok)
  {
    *c = pos;
    ok = pos >= 0 && pos < c->items();
  }
  return ok;
}

void TISAM_recordset::reset()
{
  _column.destroy();
  requery();
}

void TISAM_recordset::requery()
{
  if (_cursor != NULL)
  {
    delete _cursor; 
    _cursor = NULL;
  }
  if (_relation != NULL)
  {
    delete _relation; 
    _relation = NULL;
  }
}

void TISAM_recordset::set(const char* use)
{
  reset();
  _use = use;
  find_and_reset_vars(); 
}

const TString& TISAM_recordset::driver_version() const
{
  TString& tmp = get_tmp_string(20);
  DB_version(tmp.get_buffer(), tmp.size());
  return tmp;
}

TISAM_recordset::TISAM_recordset(const char* use) 
               : _relation(NULL), _cursor(NULL)
{ 
  set(use); 
}

TISAM_recordset::~TISAM_recordset()
{
  requery();
}

///////////////////////////////////////////////////////////
// TRecordset_sheet
///////////////////////////////////////////////////////////

void TRecordset_sheet::get_row(long r, TToken_string& row)
{
  row.separator('\t');
  row.cut(0);
  if (_query.move_to(r))
  {
    TString str;
    unsigned int cols = _query.sheet_head().items();
    if (cols == 0)
      cols = _query.columns();
    for (unsigned int c = 0; c < cols; c++)
    {
      _query.get(c).as_string(str);
      row.add(str);
    }
  }
}

long TRecordset_sheet::get_items() const
{
  return _query.items();
}

TRecordset_sheet::TRecordset_sheet(TRecordset& query, const char* title, byte buttons) 
                : TSheet(-1, -1, -2, -4, title, query.sheet_head(), buttons), _query(query)
{
}