#include <prefix.h>
#include <recarray.h>
#include <tabutil.h>

///////////////////////////////////////////////////////////
// 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) 
{}

TRecord_array::~TRecord_array()
{
}

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;
  TObject* r = objptr(i);
  return r != NULL;
}  

// @doc EXTERNAL

// @mfunc Ritorna la riga <p r>-esima; se tale riga non esiste e se <p create> assume il valore TRUE la riga viene creata
//
// @rdesc Ritorna un TRectype alla riga cercata o eventualemete il reference della nuova creata,
//    NULL nel caso non esista e non venga creata.
TRectype& TRecord_array::row(
          int n,  // @parm Numero della riga da creare
          bool create)  // @parm Indica se creare una nuova riga se non esiste:
      //
      // @flag TRUE | Se la riga <p n> non esiste viene creata
      // @flag FALSE | Se la riga <p n> non esiste non viene creata

// @comm Nel caso <p create> sia TRUE e venga richiesta una riga non esistente si crea un nuovo
//   record copiando la chiave.
{                    
  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;
}

// @doc EXTERNAL

// @mfunc Rinumera il campo chiave in seguito a reinsert
//
// @rdesc Ritorna se e' stato rinumerato il campo chiave
bool TRecord_array::renum_key(
     const char* field,   // @parm Campo della chiave a cui assegnare un nuovo valore
     const TString& num)  // @parm Nuovo valore da assegnare al campo
        // @parm long | num | Nuovo valore da assegnare al campo

// @syntax bool renum_key(const char* field, const TString& num);
// @syntax bool renum_key(const char* field, long num);

{      
  CHECKS(_num != field, "You can't renumber field ", field);
  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::insert_row(TRectype* r)
{      
  const int nr = rec2row(*r);
  CHECK(nr > 0, "You cannot insert a new key");
  const bool shift = exist(nr);
  insert(r, nr);
  if (shift)                     
  {                             
    for (int f = last(); f > nr; f = pred(f))         
    {    
      char n[16]; 
      TRectype & rec = row(f, FALSE);
      
      sprintf(n, "%ld", (long)_offset + f);
      rec.renum_key(_num, n);
    }
  }
  return nr;
}

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


// @doc EXTERNAL

// @mfunc Elimina le righe vuote
void TRecord_array::pack()
{                       
  TArray::pack();
  
  for (int i = size()-1; i > 0; i--)
  {
    TRectype* r = (TRectype*)objptr(i);
    if (r != NULL)
    {
      TString16 n; n << i+_offset;
      r->renum_key(_num, n);
    }  
  }  
} 


// @doc EXTERNAL

// @mfunc Cancella una riga
//
// @rdesc Ritorna se e' riuscito ad eliminare la riga
bool TRecord_array::destroy_row(
     int r,   // @parm Numero della riga da eliminare
     bool pack) // @parm Indica se compattare gli elementi dell'array (default FALSE)
    // @parm const TRectype& | r | Record da eliminare

// @syntax bool destroy_row(int n, bool pack);
// @syntax bool destroy_row(const TRectype& 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);
} 

// @doc EXTERNAL

// @mfunc Confronta i campi della chiave 1 scartando l'ultimo
//
// @rdesc Ritorna il risulato del confronto:
//
// @flag TRUE | Se i campi sono uguali
// @flag FALSE | Se i campi sono diversi
bool TRecord_array::good(
     const TRectype& rec) const  // @parm Record di cui confrontare i campi
{                                
  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;                                       
      
      // Se pos < last_on_file sicuramente la read fallira' per cui la riga
      // non puo' esistere su file: non esistendo nemmeno in memoria non devo
      // fare assolutamente nulla!
      if (pos >= last_on_file)                // Puo' esistere su 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))       // Cerca una riga >= pos sul file
        {               
          last_on_file = atoi(rec->get(_num));
          if (last_on_file == pos)            // La riga c'era ma ora non piu'
          {
            err = (rec->remove(f));           // Cancello il record indesiderato
            if (err != NOERR) 
            {         
              delete rec;                     // Orrore! Un errore di cancellazione!
              break;
            }  
          }
        } 
        else
          last_on_file = EOR;                 // Sul file non ci sono piu' righe da cancellare
        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);
}

///////////////////////////////////////////////////////////
// TFile_cache
///////////////////////////////////////////////////////////

TFile_cache::TFile_cache(int num, int key)
             : _file(NULL), _key(key)
{    
  _code.format("%d", num);
}

TFile_cache::TFile_cache(const char* tab, int key)
             : _file(NULL), _key(key)
{
  _code = tab;
}

TFile_cache::~TFile_cache()
{ 
  if (_file != NULL)
    delete _file;
}

void TFile_cache::init_file()
{
  CHECK(_file == NULL, "File already initialized");

  int logicnum = atoi(_code);
  if (logicnum == 0)
  {
    _file = new TTable(_code);
    logicnum = _file->num();
  }  
  else                                   
    _file = new TLocalisamfile(logicnum);
    
  TDir dir; 
  dir.get(logicnum);
    
  // Se e' un file comune metti a -1, altrimenti alla ditta corrente
  _last_firm = dir.is_com() ? -1 : prefix().get_codditta();
}

void TFile_cache::test_firm()
{             
  if (_file == NULL)
    init_file();
  
  if (_last_firm >= 0)  // Se e' un file di ditta ...
  {                     // ... controlla che non sia cambiata
    const long cur_firm = prefix().get_codditta();
    if (cur_firm != _last_firm)
    {                                             
      _last_firm = cur_firm;
      destroy();
    }  
  }  
}

const TObject& TFile_cache::query(const char* code)
{               
  test_firm();

  _code = code;
  TObject* obj = objptr(_code);
  if (obj == NULL)
  {     
    int err = ~NOERR;
    TRectype& curr = _file->curr();
    if (_code.not_empty())
    {
      const RecDes* recd = curr.rec_des();    // Descrizione del record della testata
      const KeyDes& kd = recd->Ky[_key-1];    // Elenco dei campi della chiave
      for (int i = 0; i < kd.NkFields; i++)   // Riempie la chiave selezionata
      {                        
        const int nf = kd.FieldSeq[i] % MaxFields;
        const RecFieldDes& rf = recd->Fd[nf];  
        const char* val = _code.get();
        if (val)
          curr.put(rf.Name, val);
        else
          NFCHECK("Campo chiave mancante per il decoder: %s", rf.Name);
      }
      _file->setkey(_key);
      err = _file->read();
    }
    if (err != NOERR)
      curr.zero();
    obj = rec2obj(curr);
    add(_code, obj);
  }
  
  return *obj;
}

int TFile_cache::fill()
{               
  test_firm();
  _file->setkey(_key);

  TRectype& curr = _file->curr();
  const RecDes* recd = curr.rec_des();    // Descrizione del record della testata
  const KeyDes& kd = recd->Ky[_key-1];    // Elenco dei campi della chiave

  for (int err = _file->first(); err == NOERR; err = _file->next())
  {        
    _code.cut(0);
    for (int i = 0; i < kd.NkFields; i++)   // Riempie la chiave selezionata
    {                        
      const int nf = kd.FieldSeq[i] % MaxFields;
      const RecFieldDes& rf = recd->Fd[nf];  
      const char* val = curr.get(rf.Name);
      _code.add(val);
    }
    TObject* obj = rec2obj(curr);
    add(_code, obj);
  }
  
  return items();
}
  
///////////////////////////////////////////////////////////
// TDecoder
///////////////////////////////////////////////////////////

TDecoder::TDecoder(int num, const char* outf, int key)
        : TFile_cache(num, key), _outf(outf)
{ }        
 
TDecoder::TDecoder(const char* tab, const char* outf, int key)
        : TFile_cache(tab, key), _outf(outf)
{ }        

TObject* TDecoder::rec2obj(const TRectype& curr) const
{
  return new TString(curr.get(_outf));
}

const TString& TDecoder::decode(const char* code)
{ 
  const TString& obj = (const TString&)query(code);
  return obj;
}

const TString& TDecoder::decode(long code)
{
  TString16 c; 
  if (code > 0)
    c << code;
  return decode(c);
}  

///////////////////////////////////////////////////////////
// TRecord_cache
///////////////////////////////////////////////////////////

TRecord_cache::TRecord_cache(int num, int key)
             : TFile_cache(num, key)
{ }        
 
TRecord_cache::TRecord_cache(const char* tab, int key)
             : TFile_cache(tab, key)
{ }        

TObject* TRecord_cache::rec2obj(const TRectype& curr) const
{
  return new TRectype(curr);
}

const TString& TRecord_cache::get(const char* key, const char* field)
{
  const TRectype& rec = (const TRectype&)query(key);
  return rec.get(field);
}  

const TRectype& TRecord_cache::get(const char* key)
{
  const TRectype& rec = (const TRectype&)query(key);
  return rec;
}  

