//      $Id: relation.cpp,v 1.24 1994-11-18 17:01:26 guy Exp $
// relation.cpp
// fv 12/8/93
// relation class for isam files

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

#include <config.h>
#include <expr.h>
#include <extcdecl.h>
#include <relation.h>
#include <tabutil.h>
#include <utility.h>
#include <xvtility.h>

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

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


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

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

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

protected:
  virtual void print_on(ostream& out) const;

public:
  // @FPUB
  int num() const { return _num; }
  int link() const { return _numto; }
  byte 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;

  TRelationdef(const TRelation* rel, int file, byte key,
               int linkto, const char* relexprs, byte alias,
               bool allow_lock, bool write_enable = FALSE);
  virtual ~TRelationdef() {}
};
// @END


HIDDEN ostream& print_name(ostream& out, const TLocalisamfile& f)
{
  const int logicnum = f.num();
  if (logicnum == LF_TABCOM || logicnum == LF_TAB)
  {
    if (logicnum == LF_TABCOM) out << '%';
    out << ((TTable&)f).name();
  }
  else
    out << logicnum;
  return out;
}


TRelationdef::TRelationdef(const TRelation* rel, int idx_file, byte key, 
                           int idx_to, const char* relexprs, byte alias,
                           bool allow_lock, bool write_enable)
: _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(write_enable)
{
  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
    TString80 n(s);
    const int p = n.find('[');
    if (p > 0) n.cut(p);
    if (rel->file(_num).curr().exist(n) == FALSE)
    {
      error_box("'%s' non e' un campo del file %d",
                (const char*)n, rel->file(_num).num());
      continue;
    }
#endif   

    if (r[eq+1] == '=')
    {
      _forced.set(i);
      eq++;
    }
    
    const TString80 xx(s);
    _fields.add(new TFieldref(xx,0));

    _exprs.add(new TExpression(r.mid(eq+1), _strexpr));
  }
}


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

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

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

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

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



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

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

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

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++)
  {
    int err   = _status.get_int();
    int recno = _status.get_int();
    if (recno >= 0l) file(i).readat(recno);  
    else file(i).curr().zero();
    file(i).setstatus(err);
  }
  for (i = 0; i < _reldefs.items(); i++)
  {
    bool first_match = _status.get_int ();
    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();
    _status.add (err);
    _status.add (recno);
  }
  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
  // sets error status

  if (log < 0) return alias2ind(byte(-log));
  
  const int num = _files.items();
  if (log > 0)
  {
    for (int i = 0; i < num; i++)
      if (file(i).num() == log) return i;
  }
  return num ? 0 : NOTFOUND;
}

int TRelation::alias2ind(byte alias) const
{
  if (alias < 1) 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 == - 1)     
  {
    for (int i = 0; i < _reldefs.items(); i++)
      reldef(i).write_enable(on);
  }
  else
  {
    const int idx = log2ind(logicnum);
    CHECKD(idx != NOTFOUND, "File not found n. ", logicnum);
    reldef(idx).write_enable(on);
  }
}

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

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

bool TRelation::add(int logicnum, const char* relexprs, int key,
                    int linkto, byte alias, bool allow_lock)
{
  const int idxto = log2ind(linkto);
  if (idxto == NOTFOUND)
    fatal_box("Can't join file %d to %d", logicnum, linkto);
  
  int idx = log2ind(logicnum);
  TLocalisamfile* f = new TLocalisamfile(logicnum, idx == NOTFOUND);
  idx = _files.add(f);
  
  if (relexprs && *relexprs)
  {
    TRelationdef* r = new TRelationdef(this, idx, key, idxto,
                                       relexprs, alias, allow_lock);
    _reldefs.add(r);
  }
  return _errors;
}

bool TRelation::add(const char* tabname, const char* relexprs, int key,
                    int linkto, byte alias, bool allow_lock)
  
{
  // look for <to> file
  const int idxto = log2ind(linkto);
  if (idxto == NOTFOUND)
    fatal_box("Can't link to file no. %d", linkto);
  
  int idx = name2ind(tabname);
  TTable* f = new TTable(tabname, FALSE);
  idx = _files.add(f);
  
  if (relexprs && *relexprs)
  {
    TRelationdef* r = new TRelationdef(this, idx, key, idxto,
                                       relexprs, alias, allow_lock);
    _reldefs.add(r);
  }
  
  return _errors;
}

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];

        // setvar for every variable
        for (int k = 0; k < expr.numvar(); k++)
          expr.setvar(k, to.get(expr.varname(k)));

        // eval expression and put result in field

        TFieldref& s = (TFieldref&) rd._fields[j];
        s.write(expr, from.curr());
      } // for each field
    }

    // read record: if not found, empty current record
    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()));

            TExpression& expr = (TExpression&)rd._exprs[kk];
            for (int k = 0; k < expr.numvar(); k++)
              expr.setvar(k, to.get(expr.varname(k)));
            eq = (f_fr == (const char*)expr);
          }
        }
        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 = NOERR;
  
  if (file(0).curr().empty())
    return _errors; // *** no error returned **
  // programmer must be aware
  
  if ((_errors = file(0).write(atdate)) != NOERR)
    return _errors;
  
  
  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;

    int res = file(log).write(atdate);
    if (force && res == _isreinsert)
      res = file(log).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);
    const int log                        = rd.num();

    if (!rd.write_enable() ||
        (file(log).curr()).empty()) continue;
    
    int res = file(log).rewrite(atdate);
    if (force && res == _iskeynotfound)
      res = file(log).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 TRectype& r,TExpression* cond)

{
  for (int i = 0; i < cond->numvar(); i++)
  {
    const char* s = cond->varname(i);

    cond->setvar(i, r.get(s));
  }
  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 ap = 0, junkl;
  char s[82];
  int junk, l, pagecnt = 0;
  Page p;
  int  pos;
  const int  kl = file().filehnd()->i.Base[file().filehnd()->i.PN].KeyLen;
  const bool filtered = has_filter();

  FILE* _f = open_index(TRUE);
  
  if (file().filehnd()->i.Base[file().filehnd()->i.PN].PEOD == 0)
  {
    fclose(_f);
    return 0;
  }

  fseek(_f, 0L, SEEK_SET);

  l = strlen(to());
  BTrRead(&file().filehnd()->i, (char*)(const char*) from(), s, &junkl, &junk);
  if (junk == _iseof) return 0;

  TRecnotype* page = new TRecnotype [CMAXELPAGE];

  if (GetAPage(&file().filehnd()->i, &p, file().filehnd()->i.CurPag, &junk) != NOERR)
    fatal_box("Can't read index n. %d - file n. %d", file().filehnd()->i.PN + 1, file().filehnd()->ln);

  pos = file().filehnd()->i.Pos - 1;
  _pos = -1;
  while (TRUE)
  {
    if (pos >= p.PA.PageHeader.NKey)
    {
      if (p.PA.PageHeader.Next == -1L) break;
      file().filehnd()->i.CurPag = p.PA.PageHeader.Next;
      if (GetAPage(&file().filehnd()->i, &p, file().filehnd()->i.CurPag, &junk) != NOERR)
        fatal_box("Can't read index n. %d - file n. %d", file().filehnd()->i.PN + 1, file().filehnd()->ln);
      pos = 0;
    }
    const char* s0 = p.PA.AreaForKey + pos++ * (kl + 4);
    if (l && (strncmp(to(), s0, l) < 0)) break;

    if (pagecnt == CMAXELPAGE)
    {
      if (filtered) pagecnt = filtercursor(pagecnt, page);
      fwrite(page, sizeof(junkl), pagecnt, _f);
      if (pos == -1)
        for (int i = 0; i < pagecnt; i++)
          if (page[i] == rp)
          {
            _pos = ap + i;
            break;
          }
      ap += pagecnt;
      pagecnt = 0;
    }
    page[pagecnt++] = *((long *)(s0 + kl));
  }
  if (pagecnt)
  {
    if (filtered) pagecnt = filtercursor(pagecnt, page);
    fwrite(page, sizeof(junkl), pagecnt, _f);
    if (pos == -1)
      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;
  TRectype& rec = file().curr();
  for (int i = 0; i < pagecnt; i++)
  {
    CRead(&file().filehnd()->f, rec.string(), page[i], _nolock);
    if ((_filterfunction ? _filterfunction(_if) : TRUE ) &&
        (_fexpr ? __evalcondition(rec, _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 ((_filterfunction ? _filterfunction(_if) : TRUE ) &&
      (_fexpr ? __evalcondition(rec, _fexpr) : TRUE))
    return TRUE;
  return FALSE;
}

bool TCursor::changed()

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

  if (_frozen && _lastrec != 0L) return _filename != fh->f.name;

#if XVT_OS==XVT_OS_SCOUNIX
  const TRecnotype eod = file().eod();
#else
  int   junk = 0;
  const TRecnotype eod = cisgeteod(fh, &junk);
#endif

  if (_lastrec != eod ||
      (_lastkrec != fh->i.Base[_nkey -1].PEOD) ||
      (_filename != fh->f.name))
  {
    _lastrec = eod;
    _lastkrec = fh->i.Base[_nkey -1].PEOD;
    _filename = fh->f.name;
    return TRUE;
  }
  else return FALSE;
}

TRecnotype TCursor::update()

{
  file().setkey(_nkey);
  file().read(_isgteq);

  const CURSOR old = get_cursor(TASK_WIN);
  set_cursor(TASK_WIN, CURSOR_WAIT);

  TRecnotype totrec = buildcursor(file().recno());

  set_cursor(TASK_WIN, old);

  return totrec;
}


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

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

  TString kf(_keyfrom), kto(_keyto), kfilter; 

  if (filter)
    kfilter << filter;

  bool  filterchanged = (filter != NULL) && (_filter != kfilter);

  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 = kfilter;
      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;
    }
    file().setkey(_nkey);
    _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* filter, int nkey, TRectype *from, TRectype* to)
: _frozen(FALSE), _filterfunction(NULL)

{
  _if = r;
  _nkey = nkey;
  CHECKD(_nkey > 0 && _nkey <= file().filehnd()->r->NKeys, "Bad key number : ", _nkey);

  _pos = 0;
  _totrec = 0;
  _lastrec = 0;
  _lastkrec = 0;
  _filter << filter;
  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;
  file().setkey(_nkey);
  if (file().tab())
  {
    TTable& f = (TTable&)file();
    _keyfrom = _keyto = f.name();
  }
  if (from != NULL)
  {
    _keyfrom = from->key(_nkey);
    int p;
    while ((p = _keyfrom.find('~')) != -1) _keyfrom[p] = ' ';
  }
  if (to != NULL)
  {
    _keyto = to->key(_nkey);
    int p;
    while ((p = _keyto.find('~')) != -1) _keyto[p] = ' ';
  }
}


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) ||
      (fread(&nrec, sizeof(TRecnotype), 1, _f) != 1))
    fatal_box("Can't read record in file n. %d\n", file().filehnd()->ln);

  fclose(_f);

  curr().setdirty();
  CRead(&file().filehnd()->f, curr().string(), nrec, _nolock);
  repos();
  return nrec;
}

int TCursor::lock(TReclock l)
{
  CLockRec(&file().filehnd()->f, _pos, l);
  return file().filehnd()->f.IOR;
}

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));
}

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

int name2log(const char* name)
{
  int log = 0;

  if (name && *name)
  {
    if (isdigit(*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 _fileid = pos = 0;
  
  int par = s.find('[', pos);
  _name = s.sub(pos, par); _name.strip(" ");
  
  if (par > 0)
  {
    pos = par+1;
    _from = atoi(s.mid(pos));
    if (_from > 0) _from--; else _from = 0;
    par = s.find(',', pos);
    if (par > 0) _to = atoi(s.mid(par+1)); else _to = -1;
  }
  else
  {
    _from = 0;
    _to = -1;
  }
  
  return *this;
}

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

const char* TFieldref::read(const TRectype& rec) const
{
  static TString80 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();
      const int from = (_from > l) ? l : _from;
      const int to = (_to > l || _to < 1) ? l : _to;
      if (to < l) buffer.cut(to);
      if (from > 0) buffer.ltrim(from);
    } 
  }
  return buffer;
}

const char* TFieldref::read(const TRelation* c) const
{
  const char * s;
  
  if (c == NULL)
  {
    TLocalisamfile f(_fileid);
    s = read(f.curr());
  }
  else
    s = read(c->lfile(_id).curr());

  return s;
}

void TFieldref::write(const char* val, TRelation* c) const
{
  TLocalisamfile* f = NULL;
  TRectype* curr = NULL;

  if (c == NULL)
  {
    f = new TLocalisamfile(_fileid);
    curr = &f->curr();
  }
  else
    curr = &c->lfile(_id).curr();

  write(val, *curr);
  
  if (f != NULL) delete f;
}


void TFieldref::write(const char* val, TRectype& rec) const
{
  if (_fileid >= CNF_GENERAL) return;
  if (_from > 0)
  {
    TString80 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;
  
  TLocalisamfile f(_fileid);
  const int len = f.curr().length(_name);
  return  len - _from;
}

// *** EOF relation.cpp


