// $Id: relation.cpp,v 1.73 1995-12-07 11:13:59 guy Exp $
// relation.cpp
// fv 12/8/93
// relation class for isam files

#include <ctype.h>
#include <stdlib.h>

#include <applicat.h>
#include <config.h>
#include <expr.h>
#include <extcdecl.h>
#include <prefix.h>
#include <relation.h>
#include <sheet.h>
#include <sort.h>
#include <tabutil.h>
#include <utility.h>
#include <xvtility.h>

#include <codeb.h>

// *** check if not already defined
#define NOTFOUND (-1)

// *** maximum number of elements in a cursor working page
#define CMAXELPAGE 8000


#define print_name(out, f) out << (f.num() == LF_TABCOM ? "%" : "") << f.name()

extern int get_error(int);

HIDDEN const char* field_type_str(TFieldtypes f)
{
  const char* c = "";
  switch(f)
  {
  case _charfld: 
    c = "Carattere"; break;
  case _realfld: 
    c = "Reale"; break;
  case _datefld: 
    c = "Data"; break;
  case _boolfld: 
    c = "Logico"; break;
    //  case _wordfld: 
    //  case _intzerofld:
    //  case _longzerofld:
    //  case _intfld: 
    //  case _longfld: 
  default: 
    c = "Intero"; break;
  }
  return c;
}

///////////////////////////////////////////////////////////
// TRelationdef
///////////////////////////////////////////////////////////

class TRelationdef : public TObject
{
  // @DPRIV
  friend class TRelation;

  const TRelation* _rel;          // Relazione padre
  int        _num;                // Posizione file
  int        _numto;              // Posizione padre
  int        _alias;              // Alias
  byte       _key;                // Chiave
  TArray     _fields;             // Campi di join
  TArray     _exprs;              // Condizioni di uguaglianza
  TArray     _altexprs;           // Condizioni di uguaglianza alternative
  TBit_array _forced;
  bool       _first_match  : 1;   // primo match (ed esiste)
  bool       _allow_lock   : 1;   // ?????
  bool       _write_enable : 1;

public: // TObject
  virtual void print_on(ostream& out) const;

public:
  int num() const { return _num; }
  int link() const { return _numto; }
  int alias() const { return _alias; }
  bool write_enable() const { return _write_enable; }
  void write_enable(bool we) { _write_enable = we; }
  TRectype& load_rec(TRectype& r, const TBaseisamfile& from) const;
  const char* evaluate_expr(int j, const TLocalisamfile& to);

  void print_on(TToken_string& out) const;

  TRelationdef(const TRelation* rel, int file, byte key,
               int linkto, const char* relexprs, int alias,
               bool allow_lock);
  virtual ~TRelationdef() {}
};


TRelationdef::TRelationdef(const TRelation* rel, int idx_file, byte key, 
                           int idx_to, const char* relexprs, int alias,
                           bool allow_lock)
: _num(idx_file), _key(key), _numto(idx_to),
  _rel(rel), _fields(4), _exprs(4), _alias(alias),
  _first_match(FALSE), _allow_lock(allow_lock),
  _write_enable(FALSE)
{
  TToken_string rels(relexprs);
  int i = 0;
  TString80 r,s;
  
  for (const char* g = rels.get(); g; g = rels.get(), i++)
  {
    r = g;
    int   eq = r.find('=');

    CHECKS(eq > 0, "Ahoo! E l'uguale 'ndo sta? ", (const char*)g);

    s = r.left(eq);     // Parte a sinistra dell' =

#ifdef DBG
    const char* n = s;
    const int p = s.find('[');
    if (p > 0) n = s.left(p);
    if (rel->file(_num).curr().exist(n) == FALSE)
    {
      yesnofatal_box("Errore di JOIN: '%s' non e' un campo del file %d",
                     n, rel->file(_num).num());
      continue;
    }
#endif   

    if (r[eq+1] == '=')
    {
      _forced.set(i);
      eq++;
    }
    
    _fields.add(new TFieldref(s, 0));                                   
    
    s = r.mid(eq+1);
    const int par = s.find('(');
    if (par > 0)
    {
      _exprs.add(new TExpression(s.left(par), _strexpr), i);
      _altexprs.add(new TExpression(s.sub(par+1, s.len()-1), _strexpr), i);
    }
    else 
      _exprs.add(new TExpression(s, _strexpr), i);
  } 
}


void TRelationdef::print_on(ostream& out) const
{
  const TLocalisamfile& f = _rel->file(_num);
  
  out << "JOIN "; 
  print_name(out, f);

  if (_numto > 0)
  {
    out << " TO ";
    const int alias = _rel->reldef(_numto-1).alias();
    if (alias > 0) out << alias << '@';
    else 
    {
      const TLocalisamfile& t = _rel->file(_numto);
      print_name(out, t);
    }
  }

  if (_key > 1) out << " KEY " << (int)_key;

  if (_alias > 0) out << " ALIAS " << _alias;

  for (int i = 0; i < _fields.items(); i++)
  {
    if (i == 0) out << " INTO";
    out << ' ' << _fields[i] << '=';
    if (_forced[i]) out << '=';
    out << _exprs[i];
    if (_altexprs.objptr(i))
      out << '(' << _altexprs[i] << ')';
  }
}


void TRelationdef::print_on(TToken_string& out) const
{
  const TLocalisamfile& f = _rel->file(_num);

  out = "";
  print_name(out, f);
  
  // add description 
  const char* name = f.name();
  if (f.tab())
    name = TDir::tab_des(name);
  else 
    name = prefix().description(name);
  out.add(name);
  
  out << '|';

  if (_numto > 0)
  {
    const int alias = _rel->reldef(_numto-1).alias();
    if (alias > 0) 
      out << alias << '@';
    else
    {
      const TLocalisamfile& t = _rel->file(_numto);
      print_name(out, t);
    }
  } else out << ' ';
  
  out.add(_key);
  out.add(_alias);
  out << '|';
  for (int i = 0; i < _fields.items(); i++)
  {          
    if (i) out << ' ';
    out << _fields[i] << '='; 
    if (_forced[i]) out << '=';
    out << _exprs[i];
    if (_altexprs.objptr(i))
      out << '(' << _altexprs[i] << ')';
  }
}


const char* TRelationdef::evaluate_expr(int j, const TLocalisamfile& to)
{             
  TExpression& expr = (TExpression&)_exprs[j];
  for (int k = 0; k < expr.numvar(); k++)
    expr.setvar(k, to.get(expr.varname(k)));
  
  const char* val = expr;
  
  if (*val == '\0' && _altexprs.objptr(j))
  {
    TExpression& altexpr = (TExpression&)_altexprs[j];
    for (int k = 0; k < expr.numvar(); k++)
      altexpr.setvar(k, to.get(altexpr.varname(k)));
    val = altexpr;
  }

  return val;
}  

///////////////////////////////////////////////////////////
// TRelation
///////////////////////////////////////////////////////////

TRelation::TRelation(int logicnum)
: _files(4), _reldefs(4), _errors(NOERR)
{
  TLocalisamfile* f = new TLocalisamfile(logicnum, FALSE);
  _files.add(f);
}

TRelation::TRelation(const char* tabname)
: _files(4), _reldefs(4), _errors(NOERR)
{
  TTable* t = new TTable(tabname, FALSE);
  _files.add(t);
}

TRelation::TRelation(TLocalisamfile* l) 
: _files(4), _reldefs(4), _errors(NOERR)
{
  _files.add(l);
}

TRelation::~TRelation()
{}

void TRelation::print_on(ostream& out) const
{
  const TLocalisamfile& f = file();

  out << "USE "; print_name(out, f);

  const int k = f.getkey();
  if (k > 1) out << " KEY " << k;
  out << endl;

  for (int r = 0; r < _reldefs.items(); r++)
    out << _reldefs[r] << endl;
}

void TRelation::restore_status()
{
  _status.restart();
  for (int i = 0; i < _files.items(); i++)
  {
    const int err   = _status.get_int();
    const TRecnotype recno = _status.get_long();
    const int key   = _status.get_int();
    
    file(i).setkey(key);
    if (recno >= 0l) file(i).readat(recno);  
    else file(i).curr().zero();
    file(i).setstatus(err);
  }
  for (i = 0; i < _reldefs.items(); i++)
  {
    const bool first_match = _status.get_int() ? TRUE : FALSE;
    reldef(i)._first_match = first_match;
  }
}

void TRelation::save_status()
{
  _status.cut(0);
  for (int i = 0; i < _files.items(); i++)
  {
    const int err = file(i).status();
    const TRecnotype recno = file(i).eof() ? -1l : file(i).recno();
    const int key = file(i).getkey();
    _status.add (err);
    _status.add (recno);
    _status.add (key);
  }
  for (i = 0; i < _reldefs.items(); i++)
  {
    const bool first_match = reldef(i)._first_match;
    _status.add(format("%d",first_match));
  }
}

int TRelation::log2ind(int log) const
{
  // returns _files index of logical number or NOTFOUND if not present

  if (log <= 0) 
    return alias2ind(-log);
  
  const int nf = _files.items();
  for (int i = 0; i < nf; i++)
    if (file(i).num() == log) 
      return i;
  
  return NOTFOUND;
}

int TRelation::alias2ind(int alias) const
{
  if (alias <= 0) return 0;
  
  for (int i = 0; i < _reldefs.items(); i++)
  {
    const TRelationdef& r = (const TRelationdef&)_reldefs[i];
    if (r.alias() == alias) return r.num();
  }
  
  return NOTFOUND;
}

int TRelation::name2ind(const char* name) const
{
  const int num = name2log(name);
  const int ind = log2ind(num);
  return ind;
}

TLocalisamfile& TRelation::lfile(int logicnum) const
{
  const int idx = log2ind(logicnum);
  CHECKD(idx != NOTFOUND, "File not found n. ", logicnum);
  return (TLocalisamfile&)_files[idx];
}

TLocalisamfile& TRelation::lfile(const char* name) const
{
  const int idx = name2ind(name);
  CHECKS(idx != NOTFOUND, "File or Table not found:", name);
  return (TLocalisamfile&)_files[idx];
}

void TRelation::write_enable(int logicnum, const bool on)

{
  if (logicnum == 0)     
  {
    for (int i = 0; i < _reldefs.items(); i++)
      reldef(i).write_enable(on);
  }
  else
  {
    const int idx = log2ind(logicnum);
    CHECKD(idx > 0, "File not found n. ", logicnum);
    reldef(idx-1).write_enable(on);
  }
}

void TRelation::write_enable(const char* name, const bool on)

{
  const int idx = name2ind(name);
  CHECKS(idx > 0, "File or Table not found:", name);
  reldef(idx-1).write_enable(on);
}


bool TRelation::add(TLocalisamfile* f, const char* relexprs, int key,
                    int linkto, int alias, bool allow_lock)
{
  const int idxto = log2ind(linkto);
  if (idxto == NOTFOUND)
    fatal_box("Can't join file %d to %d", f->num(), linkto);
  
  const int idx = _files.add(f);
  
  CHECK(relexprs && *relexprs, "Mancano le espressioni di collegamento");
  
  TRelationdef* r = new TRelationdef(this, idx, key, idxto,
                                     relexprs, alias, allow_lock);
  _reldefs.add(r);

  return _errors;
}


bool TRelation::add(int logicnum, const char* relexprs, int key,
                    int linkto, int alias, bool allow_lock)
{
  TLocalisamfile* f = new TLocalisamfile(logicnum, FALSE);
  return add(f, relexprs, key, linkto, alias, allow_lock);
}

bool TRelation::add(const char* tabname, const char* relexprs, int key,
                    int linkto, int alias, bool allow_lock)
{
  TTable* t = new TTable(tabname, FALSE);
  return add(t, relexprs, key, linkto, alias, allow_lock);
}

void TRelation::replace(TLocalisamfile* f, int index)
{
  const TLocalisamfile* p = (const TLocalisamfile*)_files.objptr(index);
  CHECK(p && p->num() == f->num(), "Can't replace a file with different logic number");
  _files.add(f, index);
} 

TRectype& TRelationdef::load_rec(TRectype& r, const TBaseisamfile& from) const
{
  r.zero();

  for (int j = 0 ; j < _fields.items(); j++) // for each field
  {
    TFieldref& s = (TFieldref&) _fields[j];
    s.write(s.read(from.curr()),r);
  }

  return r;
}


void TRelation::zero()
{
  for (int i = 0; i < _files.items(); i++)
    file(i).zero();
}

int TRelation::position_rels(TIsamop op, TReclock lockop,
                             TDate& atdate, int first)
{
  _errors = NOERR;

  // workhorse: position files for each active relation
  for (int i = first; i < _reldefs.items(); i++)
  {
    TRelationdef& rd     = reldef(i);
    TLocalisamfile& from = file(rd.num());
    TLocalisamfile& to   = file(rd.link());
    TReclock lck = rd._allow_lock ? lockop : _nolock;

    if (to.curr().empty())
    { from.zero(); continue; }

    from.setkey(rd._key);
    from.curr().zero();

    // build record
    if (rd._fields.items() && rd._exprs.items())
    {
      for (int j = 0 ; j < rd._fields.items(); j++) // for each field
      {
        /*      
           TExpression& expr = (TExpression&)rd._exprs[j];
           for (int k = 0; k < expr.numvar(); k++)
           expr.setvar(k, to.get(expr.varname(k)));
           */     
        const char* expr = rd.evaluate_expr(j, to);
        TFieldref& s = (TFieldref&) rd._fields[j];
        s.write(expr, from.curr());
      } // for each field
    }

    // read record: if not found, zero current record
    const TRectype rec(from.curr());
    from.read(op, lck, atdate);
    if (from.bad())
    {
      rd._first_match = (from.curr() == rec);
      if (rd._first_match)
      {
        bool eq = TRUE;

        for (int kk = 0; eq && kk < rd._fields.items(); kk++)
        {
          if (rd._forced[kk])
          {
            TFieldref& fl = (TFieldref&)rd._fields[kk];
            const TString f_fr(fl.read(from.curr()));
            eq = (f_fr == rd.evaluate_expr(kk, to));
          }
        }
        if (eq) from.setstatus(NOERR);
        else    { rd._first_match = FALSE; from.curr().zero(); }
      }
      else from.curr().zero();
    }
    else rd._first_match = TRUE;
  } // for each relation
  return _errors;
}

bool TRelation::next_match(int logicnum, const char* fieldlist, int nkey)
{
  if (logicnum == file().num())
  {
    next();
    return file().good();
  }
  
  const int i = log2ind(logicnum);
  CHECKD(i != NOTFOUND,"Nonexistent file referenced in relation ",logicnum);
  
  for (int j = 0; j < _reldefs.items(); j++)
    if (reldef(j).num() == i) break;
  
  TLocalisamfile& from = file(i);
  TLocalisamfile& to = file(reldef(j).link());

  reldef(j)._first_match = FALSE;

  if (from.bad())
    return FALSE; // && vaffanculo();

  const TRecnotype last = from.recno();
  
  bool ok = TRUE; // Corrispondenza trovata ?
  
  if (fieldlist == NULL)
  {
    TRectype rec(from.curr());
    reldef(j).load_rec(rec, from);
    from.setkey(reldef(j)._key);
    from.next();
    
    ok = (from.good() && from.curr() == rec);     
    for (int kk = 0; ok && kk < reldef(j)._fields.items(); kk++)
    {
      if (reldef(j)._forced[kk])
      {
        TFieldref& fl = (TFieldref&)reldef(j)._fields[kk];
        const TString f_fr(fl.read(from.curr()));
        const TString f_to(fl.read(to.curr()));
        
        ok = (f_fr == f_to);
      }
    }
    if (ok) from.setstatus(NOERR);
  }
  else
  {
    if (nkey > 0)
      from.setkey(nkey);
    TToken_string fields(fieldlist);
    TToken_string old(80);
    for (const char* f = fields.get(0); f; f = fields.get())
      old.add(from.curr().get(f));
    
    from.next();
    
    old.restart();


    for (f = fields.get(0); f && ok; f = fields.get())
    {
      const TFixed_string v(from.curr().get(f));
      const char* o = old.get();
      if (v != o)
        ok = FALSE;
    }
  }
  
  if (!ok)
  {
    from.readat(last);
    return FALSE;
  }

  // Riposiziona gli eventuali files successivi
  position_rels(_isequal, _nolock, (TDate&)botime, j+1);
  reldef(j)._first_match = FALSE;

  return ok;
}

bool TRelation::is_first_match(int logicnum)
  // TRUE se c'e' un record ed e' il primo match (non si e' mai fatta
  // position_rels)
{
  const int i = log2ind(logicnum);
  CHECKD(i != NOTFOUND,"Nonexistent file referenced in relation ",logicnum);
  
  for (int j = 0; j < _reldefs.items(); j++)
    if (reldef(j).num() == i) return reldef(j)._first_match;
  const int err = file().status();
  return err == NOERR;
}


#ifdef DBG

bool TRelation::isconsistent(bool reset)
{
  const int MAXREL = 24;
  int bad = 0;
  TRecnotype recnos[MAXREL];
  
  for (int i = 0; i < _files.items() && i < MAXREL; i++)
  {
    // must be file OK, non-empty record
    bad |= file(i).good() ||  file(i).curr().empty();
    recnos[i] = file(i).recno();
  }
  
  // if the above hold, check consistency
  if (bad)
    return _errors = bad;
  
  position_rels(_isequal, _nolock, (TDate&)botime);
  for (i = 0; i < _files.items(); i++)
    if (file(i).recno() != recnos[i])
    {
      bad = -1;
      break;
    }
  
  if (reset == FALSE)
    // leave as before
    for(i = 0; i < _files.items(); i++)
      file(i).readat(recnos[i]);
  
  return _errors = bad;
}

#endif


int TRelation::write(bool force, TDate& atdate)
{
  _errors = file(0).write(atdate);
  if (_errors != NOERR)
    return _errors;
  
  for (int i = 0; i < _reldefs.items(); i++)
  {
    TRelationdef& rd   = reldef(i);
    TLocalisamfile& lf = file(rd.num());

    if (!rd.write_enable() || lf.curr().empty()) 
      continue;

    int res = lf.write(atdate);
    if (force && res == _isreinsert)
      res = lf.rewrite(atdate);
    if (_errors == NOERR) 
      _errors = res;
  }

  return _errors;
}

int TRelation::rewrite(bool force, TDate& atdate)
{
  _errors = file(0).rewrite(atdate);                // Riscrive testata
  if (force && _errors == _iskeynotfound)           // Se non la trova ...
    _errors = file(0).write(atdate);                // ... forza la scrittura

  for (int i = 0; i < _reldefs.items(); i++)
  {
    TRelationdef& rd     = reldef(i);
    TLocalisamfile& lf = file(rd.num());

    if (!rd.write_enable() || lf.curr().empty()) 
      continue;
    
    int res = lf.rewrite(atdate);
    if (force && res == _iskeynotfound)
      res = lf.write(atdate);
    if (_errors == NOERR) _errors = res;
  }

  return _errors;
}

int TRelation::remove(TDate& atdate)
{
  const int res = file(0).remove(atdate);
  if (_errors == NOERR && res != _iskeynotfound) _errors = res;
  for (int i = 0; i < _reldefs.items(); i++)
  {
    TRelationdef& rd     = reldef(i);
    const int log                        = rd.num();

    if (!rd.write_enable() ||
        (file(log).curr()).empty()) continue;

    const int res = file(log).remove(atdate);
    if (_errors == NOERR && res != _iskeynotfound) _errors = res;
  }
  return _errors;
}


///////////////////////////////////////////////////////////
// TCursor
///////////////////////////////////////////////////////////


HIDDEN bool __evalcondition(const TRelation* r,TExpression* cond)

{
  for (int i = 0; i < cond->numvar(); i++)
  {
    const char* s = cond->varname(i);
    TFieldref f(s,0);
    
    cond->setvar(i, f.read(r));
  }
  return (bool) *cond;
}

FILE* TCursor::open_index(bool create)
{
#if XVT_OS == XVT_OS_SCOUNIX
  const char* const r = "r";
  const char* const w = "w";
#else
  const char* const r = "rb";
  const char* const w = "wb";
#endif

  if (create && _indexname.empty()) _indexname.temp("ci$$");
  FILE* f = fopen(_indexname, create ? w : r);
  if (f == NULL)
    fatal_box("Can't use cursor index for file %d: '%s'\n", 
              file().filehnd()->ln, (const char*)_indexname);

  return f;
}

TRecnotype TCursor::buildcursor(TRecnotype rp)
{
  
  TRecnotype oldrecno=0,pos,ap = 0;
  int   junk, l, pagecnt = 0;
  const bool filtered = has_filter();

  FILE* _f = open_index(TRUE);
  
  if (DB_reccount(file().filehnd()->fhnd) == 0)
  {
    fclose(_f);
    return 0;
  }
  
  fseek(_f, 0L, SEEK_SET);

  l = strlen(to());
  junk=DB_index_seek(file().filehnd()->fhnd, (char*)(const char*) from());
  if (junk < 0) junk=get_error(junk);
  if (junk == _iseof) return 0;

  TRecnotype* page = new TRecnotype [CMAXELPAGE];   
  pos = DB_index_recno(file().filehnd()->fhnd);
  _pos=-1;           
  
  while (TRUE)
  {              
    if (DB_index_eof(file().filehnd()->fhnd)) break;    
    const char* s0 = DB_index_getkey(file().filehnd()->fhnd);
    const TRecnotype recno = DB_index_recno(file().filehnd()->fhnd);
    if (l && (strncmp(to(), s0, l) < 0)) break;
    if (recno == oldrecno) break; // means that no more keys are available  
    oldrecno=recno;
    if (pagecnt == CMAXELPAGE)
    {     
      if (filtered) pagecnt = filtercursor(pagecnt,page);
      fwrite(page,sizeof(TRecnotype),pagecnt,_f);
      for (int i= 0; i< pagecnt; i++)
        if (page[i] == rp)
        {
          _pos = ap + i;
          break;                                        
        }
      ap += pagecnt;
      pagecnt = 0;
    }
    page[pagecnt++] = recno;
    DB_index_next(file().filehnd()->fhnd);
    int rt=get_error(-1);
    if (rt != NOERR)
      fatal_box("Can't read index n. %d - file n. %d",DB_tagget(file().filehnd()->fhnd),file().filehnd()->ln);
  } // while
  if (pagecnt)
  {
    if (filtered) pagecnt = filtercursor(pagecnt, page);
    fwrite(page, sizeof(TRecnotype), pagecnt, _f);
    for (int i = 0; i < pagecnt; i++)
      if (page[i] == rp)
      {
        _pos = ap + i;
        break;
      }
    ap += pagecnt;
  }
  if (_pos == -1) pos=0;
  delete page;
  fclose (_f);
  return ap;
}


int TCursor::filtercursor(int pagecnt, TRecnotype* page)

{
  int np=0, handle=file().filehnd()->fhnd;
  TRectype& rec=file().curr();
  for (int i=0; i< pagecnt; i++)
  {
    file().readat(rec,page[i]);
    if (update_relation()) _if->update();
    if ((_filterfunction ? _filterfunction(_if) : TRUE ) &&
        (_fexpr ? __evalcondition(_if, _fexpr) : TRUE))
    {
      if (np < i) page[np] = page[i];
      np++;
    }
  }                                               
  return np;
}


bool TCursor::ok() const

{
  if (file().bad()) return FALSE;

  const TRectype& rec = file().curr();
  TString       key(rec.key(_nkey)), kf(from()), kt(to());
  if (file().tab())
  {
    key.ltrim(3);
    kf.ltrim(3);
    kt.ltrim(3);
  }
  
  if (key < kf || (kt.not_empty() && kt < key.left(kt.len())))
    return FALSE;                  
  if (_filter_update || _filterfunction_update) _if->update();
  if ((_filterfunction ? _filterfunction(_if) : TRUE ) &&
      (_fexpr ? __evalcondition(_if, _fexpr) : TRUE))
    return TRUE;
  return FALSE;
}            

bool TCursor::changed()

{
  isdef* fh = file().filehnd();

  if (_frozen && _lastrec > 0L) return _filename != fh->d->SysName;

  const TRecnotype eod = DB_reccount(fh->fhnd);

  if (_lastrec != eod ||
      (_lastkrec != DB_changed(fh->fhnd)) ||
      (_filename != fh->d->SysName))
  {
    //    _lastrec = eod;
    //    _filename = fh->d->SysName;
    //    _lastkrec = DB_changed(fh->fhnd);
    return TRUE;
  }
  else return FALSE;
}

TRecnotype TCursor::update()

{
  main_app().begin_wait();

  file().setkey(_nkey);   
  if (file().curr().empty()) file().curr().zero();
  file().read(_isgteq);
  const TRecnotype totrec = buildcursor(file().recno());

  main_app().end_wait();

  isdef* fh = file().filehnd();
  const TRecnotype eod = DB_reccount(fh->fhnd);

  _lastrec = eod;
  _filename = fh->d->SysName;
  _lastkrec = DB_changed(fh->fhnd);

  return totrec;
}


void TCursor::filter(const char* fil, const TRectype *from,
                     const TRectype* to)

{
  CHECK(!_frozen, "Impossibile filtrare un cursore congelato");

  TString kf(_keyfrom), kto(_keyto);

  const bool filterchanged = (fil != NULL) && (_filter != fil);

  if (file().tab())
  {
    TTable& f = (TTable&) file();
    kf = kto = f.name();
  }
  if (from != NULL)
  {
    kf = from->key(_nkey);
    int p;
    while ((p = kf.find('~')) != -1) kf[p] = ' ';
  }
  if (to != NULL)
  {
    kto = to->key(_nkey);
    int p;
    while ((p = kto.find('~')) != -1) kto[p] = ' ';
  }
  
  if (filterchanged || (_keyfrom != kf) || (_keyto != kto))
  {
    _pos = 0;
    _totrec = 0;
    _lastrec = 0;
    if (filterchanged)
    {
      _filter = fil;
      if (_fexpr) delete _fexpr;
      TTypeexp type = (_filter.find('"') != -1) ? _strexpr : _numexpr;
      if (_filter.not_empty())
      {
        _fexpr = new TExpression(_filter, type);
        if (_fexpr->type() == _numexpr)
          for (int i = 0 ; i < _fexpr->numvar(); i++)
            if (file().curr().type(_fexpr->varname(i)) == _alfafld)
            {
              _fexpr->set_type(_strexpr);
              break;
            }
      }
      else _fexpr = NULL;
    }
    _keyfrom = kf;
    _keyto = kto;
  }
}

void TCursor::setkey(int nkey)

{
  if (nkey != _nkey)
  {
    _lastrec = 0L;
    _nkey = nkey;
    file().setkey(_nkey);
    filter(NULL);
  }
}


TRecnotype TCursor::read(TIsamop op, TReclock lockop, TDate& atdate)
{
  TRecnotype *page;
  int pagecnt;

  file().setkey(_nkey);
  const bool approx = (op == _isgteq);

  _if->file().read(op, lockop, atdate);
  if (approx)
  {
    while (_if->file().good() && !ok())
      _if->file().next();
  }
  const TRecnotype curpos = file().recno();

  if (changed())
    _totrec = update();
  if (approx && _if->file().status() == _iseof) 
  { 
    _pos = _totrec - 1;
    return _pos;
  }

  FILE* _f = open_index();

  if (fseek(_f, 0L, SEEK_SET) != 0)
    fatal_box("Can't repos cursor : File %d\n", file().filehnd()->ln);

  page = new TRecnotype [CMAXELPAGE];
  _pos = -1;

  for (TRecnotype max = _totrec; _pos == -1 && max > 0; max -= pagecnt)
  {
    pagecnt = (max < CMAXELPAGE) ? (int)max : CMAXELPAGE;
    fread(page, sizeof(TRecnotype), pagecnt, _f);
    for (int i = 0; i < pagecnt; i++)
      if (page[i] == curpos)
      {
        _pos = _totrec - max + i;
        break;
        
      }
  }
  if (_pos == -1) _pos = 0;
  delete page;
  fclose(_f);

  readrec();
  return _pos;
}


TCursor::TCursor(TRelation* r, const char* fil, int nkey, 
                 const TRectype *from, const TRectype* to)
: _if(r), _nkey(nkey), _frozen(FALSE), _filterfunction(NULL), _fexpr(NULL), _filter_update(FALSE), _filterfunction_update(FALSE)
{
  file().setkey(_nkey);
  _pos = 0;
  _totrec = 0;
  _lastrec = 0;
  _lastkrec = 0;
  filter(fil, from, to);
}


TCursor::~TCursor()

{
  if (_indexname.not_empty())
    ::remove(_indexname);
  if (_fexpr) 
    delete _fexpr;
}


TRecnotype TCursor::readrec()

{
  TRecnotype& nrec = file().filehnd()->RecNo;

  if (_pos == items())
  {
    file().setstatus(_iseof);
    curr().zero();
    return nrec = 0L;
  }
  file().setstatus(NOERR);

  FILE* _f = open_index();

  if (fseek(_f, _pos * sizeof(TRecnotype), SEEK_SET) != 0)
    fatal_box("Can't seek position %ld in cursor n. %d\n", _pos, file().filehnd()->ln);
  
  if (fread(&nrec, sizeof(TRecnotype), 1, _f) != 1)
    fatal_box("Can't read position %ld in cursor n. %d\n", _pos, file().filehnd()->ln);

  fclose(_f);

  curr().setdirty();
  file().readat(nrec);
  repos();
  return nrec;
}

int TCursor::lock(TReclock l)
{
  int rt=NOERR; 
  switch(l)
  {
  case _lock:
    rt=DB_lock_rec(file().filehnd()->fhnd,_pos);
    break;
  case _unlock:
    rt=DB_unlock(file().filehnd()->fhnd);
    break;                             
  default:
    break;                      
  }                   
  if (rt != NOERR) rt=get_error(rt);
  return(rt);
}

TRecnotype TCursor::operator =(const TRecnotype pos)
{
  if (changed())
    _totrec = update();
  CHECKD(pos >= 0 && pos <= _totrec, "Bad cursor position : ", pos);
  _pos = pos;
  readrec();
  return _pos;
}

TRecnotype TCursor::operator +=(const TRecnotype npos)
{
  if (changed())
    _totrec = update();

  _pos += npos;
  if (_pos > _totrec) _pos = _totrec; 
  else
    if (_pos < 0) _pos = 0;
  readrec();
  
  return _pos;
}

TRecnotype TCursor::items()
{
  if (changed())
    _totrec = update();
  return _totrec;
}

bool TCursor::next_match(int lognum, const char* fl, int nk)
{
  if (lognum == 0 || lognum == file().num())
  {++(*this); return file().good(); }
  else return _if->next_match(lognum, fl, nk);
}

bool TCursor::is_first_match(int ln)
{
  return (ln == 0 || ln == file().num()) ?
    (_pos == 0 && file().good()) : (_if->is_first_match(ln));
}


///////////////////////////////////////////////////////////
// TSorted_cursor
///////////////////////////////////////////////////////////

typedef struct {
  char       f[512];
  TRecnotype p;
} El_To_Sort;

bool  TSorted_cursor::check_expr(TString& s)
// Controlla la validita' dell'espressione ritornando un bool
// Modifica l'espressione in modo da avere una valido parametro per TFieldref!
// Es. UPPER(20->RAGSOC[1,40]+) ==> 20->RAGSOC[1,40]+
// N.B. : la direzione dell'ordinamento va messa indicata sempre dopo la definizione del campo, mai
// dopo l'operatore UPPER(). E' a cura delle altre funzioni esterne che utilizzano la check_expr() 
// discriminare il segno + o - che sia
{
    int p=s.find('(');
    int p1=s.find(')');
    int rt=TRUE;
    
    if (p>-1 && p1>-1)
      if (s.find("UPPER") == -1) 
      {
        error_box("Only UPPER() operator is allowed");
        rt=FALSE;
      }
      else             
      {
        s=s.mid(6,s.len()-7);
        p=s.find('(');
        p1=s.find(')');
        if (p>-1 || p1 >-1)
        {
          error_box("Too many parentheses!");
          rt=FALSE;
        }
      }
    else
    if ((p>-1 && p1==-1) || (p==-1 && p1>-1))
    {
      error_box("Unbalanced parentheses in expression");
      rt=FALSE;
    }
  return rt;
}

bool  TSorted_cursor::is_upper(TString& s)
// Controlla se nell'espressione esiste l'operatore UPPER()
{
  bool rt=TRUE;
  if (s.find("UPPER")>-1)
    rt=check_expr(s);
  else
    rt=FALSE;
  return rt;
}

TRecnotype  TSorted_cursor::buildcursor(TRecnotype rp)
{  
  TRecnotype oldrecno=0,pos,ap = 0;
  int   abspos=0,junk, l, pagecnt = 0;
  const bool filtered = has_filter();
  TString s;
  El_To_Sort * Element;
  
  _sort = new TSort(sizeof(El_To_Sort));
  _order_expr.restart();
  while ((s=_order_expr.get()).not_empty())
  {
    check_expr(s); // Toglie l'eventuale operatore UPPER(), in modo che l'ultimo carattere
                   // indichi eventualmente la direzione dell'ordinamento
    char versus = (s.right(1)=="-") ? 'd' : 'a';  
    if (s.right(1) == "-" || s.right(1) == "+")
      s.cut(s.len()-1);
    TFieldref  f(s,0);
    // Il controllo del file e'  automatico in f.len()
    int flen = f.len(relation()->curr());
    _sort->addsortkey(abspos+f.from(),flen,versus); 
    CHECKS(flen!=0,"Field can not have null length: ",(const char *) s);
    int lf = (f.file()!=0 ? f.file() : file().num());
    TRectype r(lf);
    abspos+=r.length(f.name());
    CHECKD(abspos<=512,"Cannot exceed 512 bytes-key %d",abspos);
  } 
  
  _sort->init();      
  
  FILE* _f = open_index(TRUE); 
  
  if (DB_reccount(file().filehnd()->fhnd) == 0)
  {
    fclose(_f);
    return 0;
  }
  
  fseek(_f, 0L, SEEK_SET);

  l = strlen(to());
  junk=DB_index_seek(file().filehnd()->fhnd, (char*)(const char*) from());
  if (junk < 0) junk=get_error(junk);
  if (junk == _iseof) return 0;

  TRecnotype* page = new TRecnotype [CMAXELPAGE];   
  pos = DB_index_recno(file().filehnd()->fhnd);
  TCursor::pos()=-1;           
  
  while (TRUE)
  {              
    if (DB_index_eof(file().filehnd()->fhnd)) break;    
    const char* s0 = DB_index_getkey(file().filehnd()->fhnd);
    const TRecnotype recno = DB_index_recno(file().filehnd()->fhnd);
    if (l && (strncmp(to(), s0, l) < 0)) break;
    if (recno == oldrecno) break; // means that no more keys are available  
    oldrecno=recno;
    if (pagecnt == CMAXELPAGE)
    {     
      pagecnt = filtercursor(pagecnt,page);
      for (int i= 0; i< pagecnt; i++)
        if (page[i] == rp)
        {
          TCursor::pos() = ap + i;
          break;                                        
        }
      ap += pagecnt;
      pagecnt = 0;
    }
    page[pagecnt++] = recno;  // lasciare cosi' altrimenti la readat legge due volte lo stesso record
    DB_index_next(file().filehnd()->fhnd);
    int rt=get_error(-1);
    if (rt != NOERR)
      fatal_box("Can't read index n. %d - file n. %d",DB_tagget(file().filehnd()->fhnd),file().filehnd()->ln);
  } // while
  if (pagecnt)
  {
    pagecnt = filtercursor(pagecnt, page);
    for (int i = 0; i < pagecnt; i++)
      if (page[i] == rp)
      {
        TCursor::pos() = ap + i;
        break;
      }
    ap += pagecnt;
  }          
  _sort->endsort();
  pagecnt=0;
  while ((Element=(El_To_Sort *)_sort->retrieve()) != NULL)
  {     
      page[pagecnt++]=Element->p;
      if (pagecnt==CMAXELPAGE)   
      {
      fwrite(page,sizeof(TRecnotype),pagecnt,_f);  
      pagecnt=0;
    }
  }
  if (pagecnt)
    fwrite(page,sizeof(TRecnotype),pagecnt,_f);  
  if (TCursor::pos() == -1) pos=0;
  delete page; 
  if (_sort) delete _sort;
  fclose (_f);
  return ap;
}

int         TSorted_cursor::filtercursor(int pagecnt, TRecnotype* page)
{     
  
  int np=0, handle=file().filehnd()->fhnd;
  TRectype& rec=file().curr();
  TString s;
  El_To_Sort Element;
  
  for (int i=0; i< pagecnt; i++)
  {
    file().readat(rec,page[i]);
    if (update_relation()) relation()->update();
    if ((filterfunction() ? filterfunction()(relation()) : TRUE ) &&
        (expression() ? __evalcondition(relation(), expression()) : TRUE))
    {
      if (np < i) page[np] = page[i];
      np++;       
      
      _order_expr.restart();
      strcpy(Element.f,"");
      while ((s=_order_expr.get()).not_empty())
      {  
        bool is_up=is_upper(s);     
        int p=s.find('[');// Qui estrae il nome del campo:
        if (p>-1)         // rimane solo l'indicazione del file (eventuale) 
          s.cut(p);       // ed il nome del campo: 
        else              // UPPER(20->RAGSOC[1,30]+) ===>> 20->RAGSOC
        if (s.right(1) == "-" || s.right(1) == "+")
          s.cut(s.len()-1);
        TFieldref f(s,0);  
        TString sf=f.read(relation());
        TFieldtypes fld_type =  file(f.file()).curr().type(f.name());
        if (fld_type == _datefld) // Se il campo e' di tipo data, la converte in ANSI!
        {
          TDate d(sf);
          sf=d.string(ANSI);
        }
        if (is_up) sf.upper();
        TString fmt;  
        if (fld_type == _alfafld || fld_type == _datefld)
          fmt.format("%%-%ds",f.len(rec));
        else  
          fmt.format("%%%ds",f.len(rec));
        strcat(Element.f,(const char*)sf.format((const char *)fmt,(const char *)sf));
      }
      Element.p=page[i];
      _sort->sort((char *) &Element);
    }
  }                                               
  
  return np;
}

bool        TSorted_cursor::changed()
{  
  bool rt;
  
  if (_is_valid_expr)
  {
    rt = TCursor::changed();
    if (!rt) rt=_is_changed_expr;
    _is_changed_expr = FALSE;
  } else         
    {
      error_box("Can't perform changed() while expression ain't valid!");
      rt=FALSE;
    }
  return rt;
}


void        TSorted_cursor::change_order(const char* order_expr)
{          
  if (order_expr && *order_expr && order_expr != _order_expr)
  {
    TString s;
     _order_expr = order_expr;
    _order_expr.restart();
    while ((s=_order_expr.get()).not_empty() && (_is_valid_expr=check_expr(s))) ;
    if (_is_valid_expr)
      _is_changed_expr=TRUE;
  }
}

TSorted_cursor::TSorted_cursor(TRelation *f, const char * order_expr, const char * filter, int key, const TRectype* from, const TRectype* to)
: TCursor(f,filter,key,from,to)
{ 
  change_order(order_expr);
}

TSorted_cursor::~TSorted_cursor()
{
}



///////////////////////////////////////////////////////////
// TFieldRef
///////////////////////////////////////////////////////////

int name2log(const char* name)
{
  int log = 0;
  
  if (name)
    while (*name == ' ') name++;
  
  if (name && *name)
  {
    if (isdigit(*name) || *name == '-')
    {
      log = atoi(name);
      if (strchr(name, '@')) log = -log;
    }
    else
    {         
      const int len = strlen(name);
      if (len == 3 || len == 4)
        log = TTable::name2log(name);
    }
    
    CHECKS(log != 0, "Non e' un file ne' una tabella: ", name);
  }

  return log;
}


TFieldref::TFieldref() : _fileid(0), _name(0) {}


TFieldref::TFieldref(const TString& s, short defid)
{
  operator=(s);
  if (_fileid == 0) _fileid = defid;
}


// A Fieldref should have the following format (only NAME is mandatory):
// FILE->NAME[FROM,TO]
TFieldref& TFieldref::operator =(const TString& s)
{
  int pos = s.find("->");
  if (pos > 0)
  {
    _id = s.left(pos); _id.strip(" ");
    _fileid = name2log(_id);
    pos += 2;
  } 
  else 
  {
    _id.cut(0);
    _fileid = pos = 0;
  }  
  
  int par = s.find('[', pos);
  _name = s.sub(pos, par); _name.strip(" ");
  
  if (par > 0)
  {
    pos = par+1;
    set_from(atoi(s.mid(pos)));
    par = s.find(',', pos);
    if (par > 0) set_to(atoi(s.mid(par+1))); 
    else         set_to(from()+1);
  }
  else
  {
    set_from(0);
    set_to(-1);
  }
  
  return *this;
}

void TFieldref::set_file(int f)
{
  _id.cut(0);
  if (f != 0)
  {
    _fileid = f;
    _id << abs(f);
    if (f < 0) _id << '@';
  }  
  else _fileid = 0;
}

void TFieldref::print_on(ostream& out) const
{
  if (_id.not_empty()) out << _id << "->";
  out << _name;
  if (_from > 0 || _to > 0)
  {
    out << '[' << (_from+1);
    out << ',' << _to << ']';
  }
}

const char* TFieldref::read(const TRectype& rec) const
{
  static TString256 buffer;

  if (_fileid >= CNF_GENERAL)
  {
    TToken_string s(_name, '.');
    TConfig c(_fileid - CNF_STUDIO, s.get());
    buffer = c.get(s.get());
  }
  else
  {
    buffer = rec.get(_name);
    if (_from > 0 || _to > 0)
    {
      const int l = buffer.len();
      if (_to < l && _to > 0) buffer.cut(_to);
      if (_from > 0) buffer.ltrim(_from);
    } 
  }
  return buffer;
}

const char* TFieldref::read(const TRelation* r) const
{ 
  CHECK(r != NULL, "Can't read field from NULL relation");
  const TRectype& rec = _id.empty() ? r->curr() : r->lfile(_id).curr();
  const char *s = read(rec);
  return s;
}

void TFieldref::write(const char* val, TRelation* r) const
{
  CHECK(r != NULL, "Can't write field on NULL relation");
  TRectype &rec = _id.empty() ? r->curr() : r->lfile(_id).curr();
  write(val, rec);
}


void TFieldref::write(const char* val, TRectype& rec) const
{
  if (_fileid >= CNF_GENERAL) return;
  if (_from > 0)
  {
    TString256 campo(rec.get(_name));
    campo.overwrite(val, _from);
    rec.put(_name, campo);
  }
  else  
    rec.put(_name, val);
}

int TFieldref::len(TRectype &rec) const
{
  if (_to >= 0) 
    return _to - _from;
  if (_fileid == 0) 
    return rec.length(_name) - _from;
  
  const TRectype r(_fileid);
  const int len = r.length(_name);
  return  len - _from;
}

///////////////////////////////////////////////////////////
// TRecord_Array
///////////////////////////////////////////////////////////

TRecord_array::TRecord_array(const TRectype& r, const char* numfield, int first)
: _file(r.num()), _num(numfield), _offset(first - 1)
{ 
  read(r); 
} 

TRecord_array::TRecord_array(int logicnum, const char* numfield, int first)
             : _file(logicnum), _num(numfield), _offset(first - 1)
{
  set_key(new TRectype(logicnum));
} 

TRecord_array::TRecord_array(const TRecord_array& a) 
: TArray(a), _file(a._file), _offset(a._offset), _num(a._num) 
{}

void TRecord_array::set_key(TRectype* r)
{                                                                     
  CHECK(r != NULL, "TRecord_array can't have a null key");
  CHECK(r->num() == _file, "Bad key record");

  add(r, 0);                              // Setta il nuovo campo chiave
  if (rows() > 0)
  {
    const RecDes* recd = r->rec_des();    // Descrizione del record della testata
    const KeyDes& kd = recd->Ky[0];       // Elenco dei campi della chiave 1
    // Copia tutti i campi chiave, tranne l'ultimo, in tutti i records
    for (int i = kd.NkFields-2; i >= 0; i--)
    {                        
      const int nf = kd.FieldSeq[i] % MaxFields;
      const RecFieldDes& rf = recd->Fd[nf];  
      const TString& val = r->get(rf.Name);
      renum_key(rf.Name, val);
    }
  }
}

const TRectype& TRecord_array::key() const 
{
  TRectype* r = (TRectype*)objptr(0);
  CHECK(r, "TRecord_array lost its key");
  return *r; 
}

bool TRecord_array::exist(int n) const
{ 
  const int i = n > 0 ? n - _offset : -1;
  TRectype* r = (TRectype*)objptr(i);
  return r != NULL;
}  


TRectype& TRecord_array::row(int n, bool create)
{                    
  const int i = n >= 0 ? n - _offset : -1;
  TRectype* r = (TRectype*)objptr(i);
  if (r == NULL && create)
  {
    r = (TRectype*)key().dup();                 // Crea nuovo record copiando la chiave
    n = add(r, i) + _offset;                    // Riassegna n se era negativo!
    char str[16]; sprintf(str, "%d", n);
    r->renum_key(_num, str);                    // Aggiorna campo numero riga
  }
  CHECKD(r && n > 0, "Bad record number ", n);  
  return *r;
}

bool TRecord_array::renum_key(const char* field, const TString& num)
{             
  const TString& curr = key().get(field);
  if (curr == num)
    return FALSE;
  
  for (int i = last(); i >= 0; i--)
  {
    TRectype* r = (TRectype*)objptr(i);
    if (r != NULL)
      r->renum_key(field, num);
  }  
  
  return TRUE; 
}

bool TRecord_array::renum_key(const char* field, long num)
{   
  CHECKS(num > 0, "Null key value for field: ", field);
  TString16 n; n << num;
  return renum_key(field, n);
}


int TRecord_array::rec2row(const TRectype& r) const
{
  CHECK(r.num() == _file, "Incompatible record");
  const int n = atoi(r.get(_num)) - _offset;    // Non e' detto che sia un int!
  CHECKD(n >= 0 && n < 30000, "Bad line number in record ", n + _offset);
  return n;
}

int TRecord_array::add_row(TRectype* r)
{               
  const int nr = rec2row(*r);
  add(r, nr);
  if (nr == 0 && rows() > 0)                        // Se ho cambiato il record campione
  {                                                 // e ci sono altre righe ...
    for (int f = r->items()-1; f >= 0; f--)         
    {                                               
      const char* fn = r->fieldname(f);
      const TString& v = r->get(fn);
      if (v.not_empty()) renum_key(fn, v);          // ... aggiorna tutte le righe in base
    }                                               // ai campi non vuoti del campione
  }
  return nr;
}

bool TRecord_array::destroy_row(int r, bool pack)
{                       
  CHECKD(r > _offset, "Can't destroy row ", r);
  const int index = r - _offset;
  const bool ok = destroy(index, pack);
  
  if (ok && pack)
  {
    for (int i = size()-1; i > index; i--)
    {
      TRectype* r = (TRectype*)objptr(i);
      if (r != NULL)
      {
        TString16 n; n << i+_offset;
        r->renum_key(_num, n);
      }  
    }  
  }
  return ok;
} 

void TRecord_array::destroy_rows()
{                      
  for (int i = last(); i > 0; i = pred(i))
    destroy(i);
} 

// Confronta i campi della chiave 1 scartando l'ultimo
bool TRecord_array::good(const TRectype& rec) const
{                                
  const bool yes = key().same_key(rec, 1, 1);
  return yes;
}

int TRecord_array::read(TRectype* filter)
{            
  CHECKD(filter->num() == _file, "Bad key record ", filter->num());
  CHECKS(filter->get(_num).empty(), "You can't specify in the filter the field ", (const char*)_num);

  destroy();
  int err = NOERR;
  if (filter != NULL && !filter->empty())
  {
    TLocalisamfile f(_file);

    set_key(filter);    
    TRectype* rec = (TRectype*)filter->dup();
    err = rec->read(f, _isgteq);
    for (int e = err; e == NOERR && good(*rec); e = rec->next(f))
    {
      add_row(rec);
      rec = (TRectype*)(key().dup());
    }  
    delete rec;
  }
  return err;  
}

int TRecord_array::read(const TRectype& filter)
{ 
  TRectype* f = (TRectype*)filter.dup();
  return read(f);  
}

int TRecord_array::remove_from(int pos) const
{                 
  int err = NOERR;

  TRectype* rec = (TRectype*)key().dup();
  CHECK(!rec->empty(), "Can't use empty key");

  TLocalisamfile f(_file);
  rec->put(_num, pos);
  for (int e = rec->read(f, _isgteq); e == NOERR && good(*rec); e = rec->next(f))
  {                     
    const int found = rec->get_int(_num);
    if (found >= pos)
    {
      err = rec->remove(f);             
      if (err != NOERR)
        break;
      pos = found+1;  
    }
    else
      break;    
  } 
  delete rec;

  return err;
}


int TRecord_array::write(bool re) const
{          
  const int EOR = 32000;              // End of records on file
  int last_on_file = 0;               // Last record found on file
  int err = NOERR;
    
  TLocalisamfile f(_file);
  const int u = last();
  for (int i = 1; i <= u; i++)
  {
    const TRectype* r = (TRectype*)objptr(i);

    if (r != NULL) 
    {               
      if (re)
      {
        err = r->rewrite(f);     
        if (err == _iskeynotfound || err == _iseof || err == _isemptyfile)
          err = r->write(f);           
        if (err != NOERR) 
          break;
      }
      else
      {
        err = r->write(f);     
        if (err == _isreinsert)
        {
          err = r->rewrite(f);           
          re = TRUE;
        }  
        if (err != NOERR) 
          break;
      }    
    }
    else
    {                    
      const int pos = i+_offset;
      if (pos > last_on_file)
      {
        TRectype* rec = (TRectype*)key().dup();
        CHECK(!rec->empty(), "TRecord_array has an empty key");
        rec->put(_num, pos);
        err = rec->read(f, _isgteq);
        if (err == NOERR && good(*rec))       // La riga c'era ma ora non piu'
        {               
          last_on_file = atoi(rec->get(_num));
          if (last_on_file == pos)
          {
            err = (rec->remove(f));             
            if (err != NOERR) 
            {         
              delete rec;   
              break;
            }  
          }
        } 
        else
          last_on_file = EOR; 
        delete rec;   
      }  
    }
  }

  // Cancella eventuali residui successivi
  if (err == NOERR && last_on_file != EOR)
    remove_from(i + _offset);
  
  return err;
}

int TRecord_array::remove() const
{
  return remove_from(1 + _offset);
}

///////////////////////////////////////////////////////////
// TRelation_description
///////////////////////////////////////////////////////////


void TRelation_description::init_files_array()
{
  const TLocalisamfile& f = _rel->file();
  TToken_string s(128);
  print_name(s, f);                // Logic number
  
  const char* name = f.name();
  if (f.tab())
    name = TDir::tab_des(name);
  else 
    name = prefix().description(name);
  s.add(name);                     // Description 
  s.add(" ");                      // No join 
  s.add(f.getkey());               // Key
  s.add(" | ");                    // No Alias nor expression
  
  _files.destroy();
  _files.add(s);                   // Main file
  for (int i = 0; i < _rel->items(); i++)
  {
    _rel->reldef(i).print_on(s);
    _files.add(s);
  }
}


void TRelation_description::read_rel()
{
  // scan files and build description arrays
  init_files_array();
  
  _fields.destroy();  
  for (int i = 0; i < _files.items(); i++)
  { 
    TToken_string& tt = (TToken_string&)_files[i];  
    TFilename descfname; descfname << DESCDIR << "/d";
    
    const char* tn = tt.get(0);                   // Codice del file o tabella

    int which_file = atoi(tn);                    // Non spostare questa riga (tn puo' cambiare!)
    if (which_file == 0)
      which_file = TTable::name2log(tn);          // Numero logico del file

    if (tn[0] == '%' || tn[0] == '$')  
      descfname << (tn+1);
    else 
      descfname << tn;
    descfname << ".des";
    
    TConfig conf(descfname, DESCPAR);
    // new record descriptor for _fields array   
    TString_array* rdesc = new TString_array;
    
    TTrec trec; trec.get(which_file);
    TToken_string ttmp(64);
    
    for (int f = 0; f < trec.fields(); f++)
    {  
      ttmp = trec.fielddef(f);  
      const TString16 name(ttmp.get(0));   
      if (!name.blank())
      { 
        TString80 dfld(conf.get(name, NULL, -1, "Missing description"));    
        if (!dfld.blank() && dfld[0] != '#')
        {
          ttmp.add(dfld,4); 
          // contiene: nome campo, tipo, lunghezza, decimali, descrizione 
          rdesc->add(ttmp); 
        }
      }
    }                           
    
    _fields.add(rdesc, i);  
  }                        
}

void TRelation_description::change_relation(TRelation& r, TString_array& a)
{
  _rel = &r;
  read_rel();
  if (a.items() > 0)
    _files = a;
}

void TRelation_description::print_on(ostream& out) const
{
  for (int i = 0; i < _files.items(); i++)
  {        
    TToken_string& r = ((TRelation_description*)this)->_files.row(i);
    TString16 cod(r.get(4));
    if (atoi(cod) == 0) 
      cod = r.get(0);
    else 
      cod << '@';
    out << "  " << cod << "->* ";
    out << "\"" << r.get(1) << "\"\n";
  }
}


bool TRelation_description::choose_file(int file)
{                                                
  TArray_sheet sht(-1,-1,0,0,"Selezione archivio", "Codice|Descrizione archivio@70");
  TToken_string tt(80);
  
  int sel = 0;
  
  for (int i = 0; i < _files.items(); i++)
  {
    TToken_string& tf = _files.row(i);
    tt = tf.get(4);
    
    int num = atoi(tt);
    if (num == 0)
    {
      tt = tf.get(0);  
      num = atoi(tt);
      if (sel == 0 && file > 0 && num == file)
        sel = i;
    }  
    else
    {
      if (sel == 0 && file < 0 && num == -file)
        sel = i;
      tt << '@';
    }  
    
    tt.add(tf.get(1));
    sht.add(tt);
  }
  
  sht.select(sel);
  
  if (sht.run() == K_ENTER)
  {
    _cur_file = (int)sht.selected();
    _cfile    = _files.row(_cur_file);
    return TRUE;
  }
  return FALSE;
}

bool TRelation_description::choose_field(const char* fld)                         
{              
  TArray_sheet sht(-1,-1,0,0,"Selezione campo", 
                   "Campo@10|Descrizione@50|Tipo@10|Dim.");
  TString_array& fd = (TString_array&)_fields[_cur_file];
  TToken_string tt(80);

  int sel = 0;
  for (int i = 0; i < fd.items(); i++)
  {
    TToken_string& tf = fd.row(i);
    tt = tf.get(0);
    if (sel == 0 && tt == fld) 
      sel = i;
    tt.add(tf.get(4));
    tt.add(field_type_str((TFieldtypes)tf.get_int(1)));
    tt.add(tf.get(2));
    sht.add(tt);
  }
  
  sht.select(sel);
  if (sht.run() == K_ENTER)
  {
    _cur_field = (int)sht.selected();
    _cfield    = fd.row(_cur_field);
    return TRUE;
  }
  return FALSE;                                          
}

bool TRelation_description::build_menu(const char* title)              
{                                                       
  return FALSE;
}

bool TRelation_description::remove_menu()       
{                                        
  return FALSE;
}

bool TRelation_description::set_cur_file(int id)
{
  const int n = _rel->log2ind(id);
  if (n >= 0 && n != _cur_file)
  {
    _cur_file = n;
    _cfile = _files.row(n);
  }
  return n >= 0;
}

int TRelation_description::file_num()
{                        
  int n = -_cfile.get_int(4);
  if (n == 0) 
    n = _cfile.get_int(0);
  return n;
}

const char* TRelation_description::file_desc()
{
  return _cfile.get(1);
}                                             

void TRelation_description::file_desc(const char* desc)
{
  _cfile.add(desc, 1);
  _files.row(_cur_file) = _cfile;
}                                             


const char* TRelation_description::field_desc()
{
  return _cfield.get(4);
}

const char* TRelation_description::field_name()
{                                              
  return _cfield.get(0);
}

int TRelation_description::field_len()
{                                     
  return _cfield.get_int(2);
}

TFieldtypes TRelation_description::field_type()
{                                              
  return (TFieldtypes)_cfield.get_int(1);
}

bool TRelation_description::set_field_description(const char* field, const char* des)
{
  TString_array& fld = (TString_array&)_fields[_cur_file];
  
  for (int i = 0; i < fld.items(); i++)
  {
    TToken_string& tt  = fld.row(i);
    if (strcmp(tt.get(0),field) == 0)
    {
      if (des && *des) 
        tt.add(des, 4);  
      else 
      {
        fld.destroy(i);
        fld.pack();
      } 
      return TRUE;
    }
  } 
  
  if (des && *des)
  {
    TToken_string tt(80); 
    tt.add(field);
    tt.add(des, 4);
    fld.add(tt);
    return TRUE;
  }  
  
  return FALSE;
}

const char* TRelation_description::get_field_description(const char* field)
{
  TString_array& fld = (TString_array&)_fields[_cur_file];
  
  for (int i = 0; i < fld.items(); i++)
  {
    TToken_string& tt  = fld.row(i);
    if (strcmp(tt.get(0),field) == 0)
      return tt.get(4);
  } 
  return "";
}


TRelation_description::TRelation_description(TRelation& r) 
: _rel(&r), _menu(FALSE), _cur_file(0), _cur_field(0),
  _cfile(80), _cfield(80)
{ 
  read_rel();
  if (_files.items() > 0) 
  {
    _cfile  = _files.row(0);
    _cfield = "";
  }
}

TRelation_description::~TRelation_description()
{ 
  if (_menu) remove_menu(); 
}  


// *** EOF relation.cpp

