/*      $Id: relation.h,v 1.31 1995-11-27 08:39:10 guy Exp $      */
// join.h
// fv 12/8/93
// join class for isam files

#ifndef __RELATION_H
#define __RELATION_H

#ifndef __ISAM_H
#include <isam.h>
#endif

#ifndef __SORT_H
class TSort;
#endif

class TRelation : public TObject
{
  friend class TRelationdef;
  friend class TRelation_description;
  friend class TCursor;

  TToken_string _status;  // stato della relazione
  TArray _files;          // file descriptors
  TArray _reldefs;        // TRelationdef array
  int _errors;

protected:
  int log2ind(int logicnum) const;
  int alias2ind(int alias) const;
  int name2ind(const char* name) const;

  TRelationdef& reldef(int i) const { return (TRelationdef&)_reldefs[i]; }
  TLocalisamfile& file(int i = 0) const { return (TLocalisamfile&)_files[i]; }

  // position_rels fa tutto il lavoro: se non trova un record
  // adatto su un file, svuota il record corrente e non ritorna errore.
  // write etc. poi procedono normalmente
  int position_rels(TIsamop op = _isequal, TReclock lockop = _nolock, TDate& atdate = (TDate&)botime, int first = 0);

public:  // TObject
  virtual bool ok() const { return good(); }
  virtual void print_on(ostream& out) const;      
  
public:
  int update() { return position_rels(_isequal, _nolock);}
  void zero();
  virtual int next(TReclock lockop = _nolock) { return file().next(lockop) == NOERR ? position_rels(_isequal, lockop) : file().status(); }
  virtual int prev(TReclock lockop = _nolock) { return file().prev(lockop) == NOERR ? position_rels(_isequal, lockop) : file().status(); }
  virtual int next(TDate& atdate) { return file().next(atdate) == NOERR ? position_rels(_isequal, _nolock, atdate) : file().status(); }
  virtual int prev(TDate& atdate) { return file().prev(atdate) == NOERR ? position_rels(_isequal, _nolock, atdate) : file().status(); }
  virtual int first(TReclock lockop = _nolock) { return file().first(lockop) == NOERR ? position_rels(_isequal, lockop) : file().status(); }
  virtual int last(TReclock lockop = _nolock) { return file().last(lockop) == NOERR ? position_rels(_isequal, lockop) : file().status(); }
  virtual int skip(TRecnotype nrec, TReclock lockop = _nolock) { return file().skip(nrec, lockop) == NOERR ? position_rels(_isequal, lockop) : file().status(); }
  virtual int read(TIsamop op = _isgteq, TReclock lockop = _nolock, TDate& atdate = (TDate&)botime) { return file().read(op, lockop, atdate) == NOERR ? position_rels(_isequal, lockop, atdate) : file().status();}
  
  TLocalisamfile& lfile(int logicnum = 0) const;
  TLocalisamfile& lfile(const char* name) const;
  TLocalisamfile& operator[] (int logicnum) const { return lfile(logicnum); }

  void write_enable(int logicnum = 0, const bool on = TRUE) ;
  void write_enable(const char* name, const bool on = TRUE) ;
  void write_disable(int logicnum = 0) { write_enable(logicnum, FALSE); }
  void write_disable(const char* name) { write_enable(name, FALSE); }

  TRectype& curr(int logicnum = 0) const { return lfile(logicnum).curr(); }
  // next_match for 0ne-to-many relations; positions logicnum (!= main)
  // on next matching record; returns TRUE or FALSE if no more matches; in
  // any case relation is kept consistent except when inconsistent in
  // first place
  bool next_match(int logicnum,  const char* fieldlist = NULL, int nkey = 0);

  bool add(TLocalisamfile* f, const char* relexprs, int key,
           int linkto, int alias, bool allow_lock);
  bool add(int logicnum, const char* relexprs, int key = 1,
           int linkto = 0, int alias = 0, bool allow_lock = FALSE);
  bool add(const char* tabname, const char* relexprs, int key = 1,
           int linkto = 0, int alias = 0, bool allow_lock = FALSE);
  void replace(TLocalisamfile* f, int index = 0);

  // write methods
  virtual int write  (bool force = TRUE, TDate& atdate = (TDate&)botime);
  virtual int rewrite(bool force = TRUE, TDate& atdate = (TDate&)botime);
  virtual int remove (TDate& atdate = (TDate&)botime);

  // status methods
  // return the status of the relation when called
  // with no args, or the status of the file when called with
  // a logical number

  bool eof( int logicnum = 0)   const { return lfile(logicnum).eof(); }
  bool bof( int logicnum = 0)   const { return lfile(logicnum).bof(); }
  bool status(int logicnum = 0) const { return lfile(logicnum).status(); }
  bool good( int logicnum = 0)  const { return lfile(logicnum).good(); }
  bool bad( int logicnum = 0)   const { return lfile(logicnum).bad(); }
  bool empty( int logicnum = 0) const { return lfile(logicnum).empty(); }

  // isconsistent() returns TRUE if every file in the relation is
  // OK, current record is non-empty, and relation is consistent.
  // If it's not and reset is TRUE, it tries to reset the relation
  // to a consistent state (based on main record) -- no further check
  // is done.
  // Also called internally by update and remove.
  bool isconsistent(bool reset = FALSE);

  // TRUE se c'e' un record ed e' il primo match (non si e' mai fatta
  // position_rels)
  bool is_first_match(int logicnum);

  // items() ritorna il numero di files collegati
  int items()        { return _reldefs.items(); }   

  void   save_status    () ;
  void   restore_status () ;
  
  // positioning operators. return status
  TRecnotype operator +=(const TRecnotype npos) { return skip(npos);  }
  TRecnotype operator -=(const TRecnotype npos) { return skip(-npos); }
  TRecnotype operator ++() { return next(); }
  TRecnotype operator --() { return prev(); }
  
  TRelation(int logicnum);
  TRelation(const char* tabname);
  TRelation(TLocalisamfile* f);

  virtual ~TRelation();
};


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

class TRecord_array : private TArray
{ 
  int _file;                                          // Numero logico del file principale
  int _offset;                                        // Offset iniziale del record array
  TString16 _num;                                     // Nome del campo col numero di riga

protected:
  int rec2row(const TRectype& r) const;               // Estrae il numero riga di un record
  const TString& num_field() const { return _num; }
  int remove_from(int i) const;
  bool good(const TRectype& rec) const;

public:         
  const TRectype& key() const;
  int rows() const { return items()-1; }              // Numero di righe presenti 
  
  int succ_row(int r) const { return succ(r - _offset) + _offset; }
  int pred_row(int r) const { return pred(r - _offset) + _offset; };
  int last_row() const { return last() + _offset; }   // Ultima riga 
  int first_row() const { return succ_row(0); }       // Prima riga 
  
  const TRectype& row(int r) const                    // Riga r costante
  { CHECKD(r > _offset, "Bad record number ", r); return (const TRectype&)operator[](r - _offset); }
  
  bool exist(int r) const;                            // Controlla se esiste la riga r
  TRectype& row(int r, bool create);                  // Riga r con possibilita' di crearla
  
  virtual int add_row(TRectype* r);                   // Aggiungi/cambia una riga
  int add_row(const TRectype& r) { return add_row((TRectype*)r.dup()); }
  virtual bool destroy_row(int n, bool pack = FALSE); // Cancella una riga
  virtual bool destroy_row(const TRectype& r, bool pack = FALSE) { return destroy_row(rec2row(r), pack); }
  void destroy_rows();                                // Cancella tutte le righe
  
  int logic_num() const { return _file; } 
  void set_key(TRectype* r);                          // Cambia l'intera chiave (solo se vuoto!)
  bool renum_key(const char* field, const TString& num);
  bool renum_key(const char* field, long num);        // Rinumera campo chiave in seguito a reinsert

  virtual int read(const TRectype& r);                // Leggi tutto l'array da file
  virtual int read(TRectype* r);                      // Leggi tutto l'array da file
  virtual int write(bool re = FALSE) const;           // Aggiorna il file
  virtual int rewrite() const { return write(TRUE); }
  virtual int remove() const;                         // Cancella tutti i record dal file
  
  TRecord_array(const TRectype& r, const char* numfield, int first = 1);
  TRecord_array(int logicnum, const char* numfield, int first = 1);
  TRecord_array(const TRecord_array& a);
};


// Classe TCursor : public TObject

class TExpression;

typedef bool (*FILTERFUNCTION)(const TRelation* r);

class TCursor : public TContainer
{
  TRelation*            _if;
  int                   _nkey;
  TRecnotype            _pos;                  // Posizione corrente
  TRecnotype            _totrec;
  TRecnotype            _lastrec;
  TRecnotype            _lastkrec; 
  TFilename             _filename;
  TString               _filter;               // Filtro
  TString               _keyfrom;              // chiave iniziale
  TString               _keyto;                // chiave finale
  TExpression*          _fexpr;                // Espressione relativo filtro
  bool                  _frozen;
  bool                  _filter_update;        // Flag che permette l'update della relazione per l'espressione-filtro
  bool                  _filterfunction_update;// Flag che permette l'update della relazione per la funzione-filtro
  FILTERFUNCTION        _filterfunction;
  TFilename             _indexname;

  TRecnotype update();


protected:
  FILE* open_index(bool create = FALSE);
  virtual bool changed();
  virtual TRecnotype buildcursor(TRecnotype rp);
  virtual int filtercursor(int pagecnt, TRecnotype* page);
  TRecnotype readrec();
  void filter(const char* filter, const TRectype* from = NULL,
              const TRectype* to = NULL);
  bool update_relation() {return (_filter_update || _filterfunction_update);}

public:
  // @FPUB
  TRecnotype operator =(const TRecnotype nr);          // Va alla posizione nr
  TRecnotype operator +=(const TRecnotype nr);        
  TRecnotype operator -=(const TRecnotype npos) { return operator +=(-npos); }
  TObject* first_item( ){ operator =( 0 ); return &curr( ); }
  TObject* succ_item( ){ operator +=( 1 ); return &curr( ); }
  TObject* pred_item( ){ operator -=( 1 );  return &curr( );}
  TObject* last_item( ){ operator =( items( ) -1 );  return &curr( ); }
  long objects( ) { return items( ); }
  
  TRecnotype& pos() { return _pos; }
  TRecnotype items();
  TRecnotype size() const { return file().eod(); }

  const TString& from() const { return _keyfrom; }
  const TString& to() const { return _keyto; }

  TRectype& curr(int log = 0) const { return _if->curr(log); }
  TRectype& curr(const char * tab) const { return _if->lfile(tab).curr(); }
  TRecnotype read(TIsamop op = _isgteq, TReclock lockop = _nolock, TDate& atdate = (TDate&)botime);
  int lock(TReclock = _lock);
  int unlock() { return lock(_unlock); }

  virtual bool ok() const;

  const char* filter() const { return _filter; }
  void freeze(bool on = TRUE) { _frozen = on; }
  bool frozen() const { return _frozen; }
  void setfilter(const char* filter_expr, bool update=FALSE) { filter(filter_expr); _filter_update = update; }
  void setregion(const TRectype& from, const TRectype& to)
  { filter(NULL,&from, &to); }

  TRelation* relation() const { return _if; }
  TLocalisamfile& file(int lnum = 0)    const { return _if->lfile(lnum); }
  TLocalisamfile& file(const char* name) const { return _if->lfile(name); }
  int repos() { return _if->position_rels(); }

  TExpression* expression() const { return _fexpr; }
  FILTERFUNCTION filterfunction() const { return _filterfunction; }

  void setkey() { file().setkey(_nkey); }
  void setkey(int nkey);
  int key() const { return _nkey; }

  bool next_match(int lognum, const char* fl = NULL, int nk = 0);
  bool is_first_match(int ln);
  
  void set_filterfunction(FILTERFUNCTION ff, bool update=FALSE) { _filterfunction = ff; _lastrec = 0L; _filterfunction_update = update;}
  bool has_filter() const { return _filter.not_empty() || _filterfunction; }

  void save_status    () { _if->save_status(); }
  void restore_status () { _if->restore_status(); }

  TCursor(TRelation* f, const char* filter = "", int key = 1, const TRectype* from = NULL, const TRectype* to = NULL);
  virtual ~TCursor();
};



// Classe TSorted_cursor. Costruisce e gestisce un cursore ordinato per chiavi diverse da quelle specificate nel file.
// Il formato dell'espressione deve essere: [LF->]FIELDNAME[[from,to]][+|-]... Inoltre tutta l'espressione puo' essere racchiusa
// dall'operatore UPPER(), per controlli non case-sensitive. 
//      FIELDNAME e' il nome del campo per quale si vuole effettuare il sort, LF indica il numero logico del file(appartenente alla relazione!).
//      from e to sono parametri opzionali usati per specificare un sottocampo. Per default prende l'intera lunghezza.
//      + e - sono parametri opzionali usati per specificare il tipo di ordinamento: + significa crescente, - significa 
//      decrescente. Per default l'ordinamento e' +(crescente). E' cosi' possibile creare chiavi con campi non appartententi al
//      file principale della relazione.
// Es. "CODCF-|TIPOCF|SOTTOCONTO+|UPPER(20->RAGSOC[1,40]-)"

class TSorted_cursor : public TCursor
{                                                    
  TToken_string    _order_expr;
  TSort            *_sort; 
  bool             _is_changed_expr;        
  bool             _is_valid_expr;
  
protected:     
  bool       check_expr(TString& s);                                            // Controlla il formato della singola espressione di un campo di sort
  bool       is_upper(TString& s);                                                      // Controlla se la singola espressione contiene l'operatore UPPER(), ritornandone l'argomento
  virtual TRecnotype buildcursor(TRecnotype rp);                                        // Vedi i TCursor
  virtual int        filtercursor(int pagecnt, TRecnotype* page);       // Vedi i TCursor
  virtual bool       changed();                                                                         // Vedi i TCursor
  
public: 
  TRecnotype operator =(const TRecnotype nr) {return TCursor::operator =(nr); }    
  void change_order(const char * order_expr);                                           // Permette di cambiare l'ordinamento del cursore.
  TSorted_cursor(TRelation *f,const char * order_expr, const char * filter = "", int key = 1, const TRectype* from = NULL, const TRectype* to = NULL);
  virtual ~TSorted_cursor();
};


// Classe TFieldref : public TObject

class TFieldref : public TObject
{
  short _fileid;      // Numero del file
  TString _id;        // Nome tabella o stringa col numero del file
  TString _name;      // Nome del campo
  int _from, _to;     // Substring

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

public:
  TFieldref();
  TFieldref(const TString&, short defid);

  TFieldref& operator =(const TString& s);   // Operatore di assegnazione

  virtual bool ok() const { return _name.not_empty(); }    // Vero se il numero del file e' valido

  int file() const { return _fileid; }       // ritorna il file
  void set_file(int f);
  
  const TString& name() const { return _name; } // ritorna il nome del campo
  void set_name(const char* n) { _name = n; }
  void set_from(int f) { if (f > 0) f--; else f = 0; _from = f; }
  void set_to(int t) { _to = t; }

  int from() const { return _from; }
  int to() const { return _to; }
  int len(TRectype &rec) const;
  const char* read(const TRelation*) const;
  const char* read(const TRectype&) const;
  void write(const char* val, TRelation*) const;
  void write(const char* val, TRectype& rec) const;
};                          

// Converte una stringa in numero logico o numero tabella
int name2log(const char* name);

class TRelation_description : public TObject
{       
  // gestisce l'uso interattivo di una relazione (scelta campi, descrizione)
  // domani o doman l'altro gestira' l'editing interattivo e grafico                      
  
  TRelation*      _rel;                     // relation described     
  TString_array   _files;  
  TArray          _fields;  
  int             _cur_file;
  int             _cur_field;
  bool            _menu;
  TToken_string   _cfile, _cfield;
  
protected:   
  void init_files_array();
  void read_rel();                   
  
public: // TObject
  virtual bool ok() const { return _files.items() > 0; }

public:
  
  // "choose" interface: after choosing a field (must return TRUE)
  // methods allow to know all data concerning the field 
  // parameters set the one initially selected
  bool choose_file (int id = 0);                         
  bool set_cur_file(int id = 0);

  // file must have been chosen: first file in list if called 
  // before choose_file
  bool choose_field(const char* fld = "");                         
  
  // if wanted, build a menu tree and set current field upon
  // selecting it (should be popup)             
  bool build_menu(const char* title = "Database");               
  bool remove_menu();        
  
  // if choose_file == TRUE or menu has been used these return valid data
  int         file_num();
  const char* file_desc();
  void        file_desc(const char* desc);
  bool        set_field_description(const char* field, const char* des);
  const char* get_field_description(const char* field);

  // if choose_field == TRUE or menu has been used these return valid data
  const char* field_desc();
  void        field_desc(const char* desc);
  const char* field_name();
  int         field_len();
  TFieldtypes field_type();
  
  const TString_array& get_all_desc() const { return _files; }
  void change_relation(TRelation& rel, TString_array& arr);
  
  virtual void print_on(ostream& out) const;
  
  TRelation_description(TRelation& r);
  virtual ~TRelation_description(); 
};


#endif
// ** EOF relation.h