#include <applicat.h>
#include <expr.h>
#include <files.h>
#include <lffiles.h>
#include <utility.h>
#include <config.h>

#include <extcdecl.h>

#define BLOCKLEN 512
#define INVFLD    255

TFile::TFile (int lenrec, int base)
{
  _file = new SecDef;
  _file->IOR = NOERR;
  _len = lenrec;
  _base = base;
  _file->LockMode = _manulock;
  _file->lpos = -1;
  _file->name[0] = '\0';
}

TFile::~TFile ()
{
  delete _file;
}

// @doc EXTERNAL

// @mfunc Apre il file specificato
void TFile::open (
  const char *name,   // @parm Nome del file da aprire
  TFilelock lockmode) // @parm Tipo di lock da attivare durante l'apertura del file
  //       (default _manulock)
{
  COpen (_file, (char *) name, _len, _base, lockmode);
}

bool TFile::verify (const char *name)

{
  CVerify (_file, (char *) name);
  return _file->IOR == NOERR;
}

// @doc EXTERNAL

// @mfunc Crea il file specificato
void TFile::create (
  const char *name,   // @parm Nome del file da creare
  TRecnotype nrecord) // @parm Numero di recird che deve avere il file al momento
  //              della creazione (default 0)
  
  // @comm Il file creato avra' dimensioni sufficienti a contenere <p nrecord> record

{
  CCreate (_file, (char *) name, _len, _base, (nrecord * _len) / BLOCKLEN + 1);
}

void TFile::chsize (const char *name, TRecnotype nrecord)

{
  CChsize (_file, (char *) name, _len, _base, (nrecord * _len) / BLOCKLEN + 1);
}

void TFile::close ()

{
  CClose (_file);
}

void TFile::unlink (const char *name)

{
  CDelete (_file, (char *) name);
}

// @doc EXTERNAL

// @mfunc Legge un record dal file
void TFile::read (
  char *record,      // @parm Record letto
  TRecnotype recnum, // @parm Numero del record da leggere
  TReclock lock)     // @parm Tipo di blocco da attivare durante la letture (default _nolock)

{
  _file->LenRec = _len;
  _file->BaseFil = _base;
  CRead (_file, record, recnum, lock);
}

// @doc EXTERNAL

// @mfunc Scrive un record sul file
void TFile::write (
  char *record,      // @parm Record da scrivere
  TRecnotype recnum, // @parm Numero del record da scrivere
  TReclock lock)     // @parm Tipo di blocco da attivare durante la letture (default _nolock)

{
  _file->LenRec = _len;
  _file->BaseFil = _base;
  CWrite (_file, record, recnum, lock);
}

int TFile::status () const
{
  return _file->IOR;
}

TDir::TDir () : _num(-1), _com(false)
{
  zero ();
}

TDir::TDir(int logicnum) : _num(logicnum), _com(false)
{
  zero();
  get(logicnum, _nolock, _nordir, _sysdirop);
}


TDir::TDir(const TDir& d) 
{             
  _dir = d._dir;
  _num = d._num;
  _com = d._com;  
}

TDir::~TDir ()
{ }

const char *TDir::name () const
{ 
  return _dir.SysName;
}

const char* TDir::filename() const
{ 
  TString& tmp = get_tmp_string();
  tmp = CAddPref(_dir.SysName); 
  for (int i=tmp.len()-1; i>=0; i--)
  {
    const char c = tmp[i];
    if (is_slash(c))
    {
      tmp << ".dbf";
      break;
    }
    else
      if (c == '.') 
        break;
  }
  return tmp;
}

void TDir::set_name(const char* name)
{                       
  CHECK(name != NULL, "NULL Filename");     
  CHECK(strlen(name) < 42, "Name too long");

  for (int i=strlen(name); i>0; i--)
  {
    char c = name[i-1];
    if (c == '\\' || c == '/')
      break;
    else
      if (c == '.') 
        c = '\0';
        break;
  }

  strncpy(_dir.SysName, name, 42);
  _dir.SysName[41] = '\0';
}

const char *TDir::des () const
{           
  return _dir.Des;
}

const char* TDir::tab_des(const char* tab)
{                                         
  const char* t = tab; if (t[0] == '$' || t[0] == '%') t++;

  TString& tmp = get_tmp_string();          
  tmp << DESCDIR << "/d" << t << ".des";  

  TConfig cnf(tmp, DESCTAB);
  tmp = cnf.get("Description", NULL, -1, tab);
  
  return tmp;
}


const char *TDir::expr () const
{
  return _dir.FCalc;
}

TRecnotype TDir::eod () const
{
  return _dir.EOD;
}

TRecnotype TDir::eox () const
{
  return _dir.EOX;
}

long TDir::flags () const
{
  return _dir.Flags;
}

word TDir::len () const
{
  return _dir.LenR;
}

int TDir::status (TDirtype dirop) const
{
  return fdir[dirop].IOR;
}

void TDir::set_len (const UINT16 len)
{
  _dir.LenR = len;
}

void TDir::set_flags(const RecNoType f)
{
  _dir.Flags = f;
}

void TDir::set_eox(const RecNoType eox)
{
  _dir.EOX = eox;
  if (eox < eod())
    set_eod(eox);
}

void TDir::reset_eox()
{ set_eox(0); }

void TDir::set_eod(const RecNoType eod)
{
  _dir.EOD = eod;
  if (eod > eox())
    set_eox(eod);
}

void TDir::set (const char *name, const RecNoType eod, const RecNoType flag, const char *des, const char *calc)
{
  strncpy (_dir.SysName, name, sizeof (_dir.SysName));
  set_eod(eod);
  set_flags(flag);
  strncpy(_dir.Des, des, sizeof (_dir.Des));
  strncpy(_dir.FCalc, calc, sizeof (_dir.FCalc));
}

// @doc EXTERNAL

// @mfunc Aggiorna l'oggetto TDir con i parametri del file  
void TDir::get (
  int nfile,         // @parm Numero del file con cui aggiornare l'oggetto
  TReclock lock,     // @parm Tipo di blocco (default _nolock)
  TDirtype dirtype,  // @parm Direttorio nella quale e' contenuto il file (default _nordir)
  TDirop op)         // @parm Tipo di operazioni effettuabili sul file (default _nordirop)

{
  const int whichdir = (dirtype == _nordir ? NORDIR : COMDIR);

  if (op == _nordirop)
    COpenFile (nfile, &_dir, int(lock), whichdir);
  else
    CGetFile (nfile, &_dir, int(lock), whichdir);
  _num = nfile;
  _com = _dir.SysName[0] != '$';
}

// @doc EXTERNAL

// @mfunc Aggiorna il file indicato con i parametri passati
void TDir::put (
  int nfile,         // @parm Numero del file da aggiornare
  TDirtype dirtype,  // @parm Direttorio nella quale e' contenuto il file (default _nordir)
  TDirop op)         // @parm Tipo di operazioni effettuabili sul file (default _nordirop)
{
  CHECKD(nfile > 0, "Bad file number ", nfile);

  const int whichdir = (dirtype == _nordir ? NORDIR : COMDIR);

  if (op == _nordirop)
    CCloseFile (nfile, &_dir, whichdir);
  else
    CPutFile (nfile, &_dir, whichdir);
}

void TDir::zero ()
{
  zerofdes(&_dir);
}

void TDir::print_on (ostream & out) const
{
  out << _num << '\n';
  out << (int)_com << '\n';
  out << _dir.SysName << '|';
  out << _dir.EOD << '|';
  out << _dir.EOX << '|';
  out << _dir.LenR << '|';
  out << _dir.Flags << '|';
  out << _dir.Des << '|';
  out << _dir.FCalc << '|';
  out << _dir.GenPrompt << '|' << '\n';
}

void TDir::read_from (istream & in)
{
  TToken_string line(256, '|');
  char* buf = line.get_buffer();
  const int bufsize = line.size();

  in.getline (buf, bufsize, '\n');
  _num = atoi(buf);
  in.getline (buf, bufsize, '\n');
  _com = atoi(buf) != 0;
  in.getline (buf, bufsize, '\n');
  
  line.restart();
  strncpy(_dir.SysName,   line.get(), sizeof(_dir.SysName));
  _dir.EOD   = line.get_long();
  _dir.EOX   = line.get_long();
  _dir.LenR  = line.get_int();
  _dir.Flags = line.get_long();
  strncpy(_dir.Des,       line.get(), sizeof(_dir.Des));
  strncpy(_dir.FCalc,     line.get(), sizeof(_dir.FCalc));
  strncpy(_dir.GenPrompt, line.get(), sizeof(_dir.GenPrompt));
}

// @doc EXTERNAL

// @mfunc Ritorna il numero di file presenti nella directory <p dirtype>
//
// @rdesc Ritorna il numero di file presenti
int TDir::items (
    TDirtype dirtype) const  // @parm Directory della quale contare i file

{
  const int whichdir = (dirtype == _nordir ? NORDIR : COMDIR);
  FileDes f;
  CGetFile (LF_DIR, &f, _nolock, whichdir);
  return (int) f.EOD;
}

TTrec::TTrec (int logicnum, TDirtype dirtype) : _des(NULL), _num(-1)
{
  zero();
  get(logicnum, dirtype);
}


TTrec::TTrec () : _des(NULL), _num(-1)
{
  zero ();
}

TTrec::TTrec(const TTrec & r) : _des(NULL)
{             
  _rec = r._rec;
  _num = r._num;
}

TTrec::~TTrec ()
{ }

int TTrec::compare(const TSortable & a) const
{
  const TTrec & r = (const TTrec &) a;
  int res = 0;
  
  if (_rec.NFields != r._rec.NFields || _rec.NKeys != r._rec.NKeys)  // Confronta prima il numero dei campi o il numero delle chiavi
    res = 1;
  else
  {
    const int nkeys = _rec.NKeys;
    for (int i=0; i<nkeys && res==0; i++) // scorre la lista delle chiavi (sono meno del numero di campi)
    {
      const KeyDes& k1 = _rec.Ky[i];
      const KeyDes& k2 = r._rec.Ky[i];
      if (k1.DupKeys != k2.DupKeys || k1.NkFields != k2.NkFields)
        res = 1;
      else
        for (int j=0; j<k1.NkFields && res==0; j++)
          if (k1.FieldSeq[j] != k2.FieldSeq[j] || k1.FromCh[j] != k2.FromCh[j]
              || (k1.FromCh[j] != INVFLD && k1.ToCh[j] != k2.ToCh[j]))
              res = 1;
    }
    
    if (res == 0) // se sono ancora uguali confronta tutti i campi
    {
      const int nflds = _rec.NFields;
      for (int n=0; n<nflds && res==0; n++)
      {
        const RecFieldDes& r1 = _rec.Fd[n];
        const RecFieldDes& r2 = r._rec.Fd[n];
        if (r1.TypeF != r2.TypeF || r1.Len != r2.Len || r1.Dec != r2.Dec)
          res = 1;
        else
          if (strcmp(r1.Name,r2.Name) != 0) 
            res = 1;
      }
    }
  }
  return res; 
}

void TTrec::rehash()
{
  int  pos, tmppos, nf, i;
  
  for (i = 0; i < MaxFields; i++) _rec.SortFd[i] = INVFLD; 
  if (_rec.NFields)
  {
    if (_rec.NFields> MaxFields)
      fatal_box("Troppi campi in questo tracciato") ;
    for (i = 0; i < _rec.NFields; i++) 
    { 
      nf = i; 
      pos = hashfun(_rec.Fd[nf].Name);
      while (TRUE)
      { 
        if (_rec.SortFd[pos] == INVFLD) 
        { 
          _rec.SortFd[pos] = (byte) nf; 
          break; 
        } 
        else 
        { 
          if (strcmp(_rec.Fd[_rec.SortFd[pos]].Name, _rec.Fd[nf].Name) <= 0) 
          { 
            pos++;
            if (pos >= MaxFields)
              pos = 0; 
          } 
          else 
          { 
            tmppos = _rec.SortFd[pos]; 
            _rec.SortFd[pos] = (byte) nf; 
            nf = tmppos; 
          } 
        } 
      } 
    }
    _rec.Fd[0].RecOff = 1; 
    for (i = 1; i < _rec.NFields; i++) 
      _rec.Fd[i].RecOff = _rec.Fd[i - 1].RecOff + _rec.Fd[i - 1].Len; 
  }
}

TTrec & TTrec::operator = (const TTrec & b)
{
  _num = b._num;
  _rec = b._rec;
  return *this;
}

// @doc EXTERNAL

// @mfunc Assegna all'oggetto TTrec il tracciato record del file indicato
void TTrec::get (
  int nfile,        // @parm Numero del file da cui estrarre il tracciato record
  TDirtype dirtype) // @parm Direttorio nella quale e' contenuto il file (default _nordir)

{
  const int whichdir = (dirtype == _nordir ? NORDIR : COMDIR);
  CGetRec (nfile, &_rec, whichdir);
  _num = nfile;
}

// @doc EXTERNAL

// @mfunc Setta il file indicato con il tracciato record dell'oggetto                                
void TTrec::put (
  int nfile,        // @parm Numero del file di cui settare il tracciato record
  TDirtype dirtype) // @parm Direttorio nella quale e' contenuto il file (default _nordir)

{
  int _whichdir = (dirtype == _nordir ? NORDIR : COMDIR);

  if (nfile <= 0)
    fatal_box ("Bad file number %d", nfile);
  CPutRec (nfile, &_rec, _whichdir);
}

void TTrec::zero ()

{
  int i;
  
  _rec.NFields = 0; 
  for (i = 0; i < MaxFields; i++) 
  { 
    strcpy(_rec.Fd[i].Name, ""); 
    _rec.Fd[i].TypeF = _nullfld;
    _rec.Fd[i].Len = 0; 
    _rec.Fd[i].Dec = 0; 
    _rec.Fd[i].RecOff = 0;
    _rec.SortFd[i] = INVFLD; 
  } 
  _rec.NKeys = 0; 
  for (i = 1; i < MaxKeys; i++) 
  { 
    _rec.Ky[i].DupKeys = FALSE; 
    _rec.Ky[i].NkFields = 0; 
    for (int j = 0; j < MKFields; j++)
    {
      _rec.Ky[i].FieldSeq[j] = INVFLD; 
      _rec.Ky[i].FromCh[j] = INVFLD; 
      _rec.Ky[i].ToCh[j] = INVFLD; 
    }
  } 
}

int TTrec::status (TDirtype dirop) const
{
  return rdir[dirop].IOR;
}

int TTrec::field(const char *name) const
{
  return findfld(&_rec, name);
}

const char* TTrec::fielddef(int fld) const
{
  const RecFieldDes& fd = _rec.Fd[fld];
  TString& tmp = get_tmp_string(50);
  tmp.format("%s|%d|%d|%d", fd.Name, (int)fd.TypeF, (int)fd.Len, (int)fd.Dec);
  return tmp;
}

const char *TTrec ::keydef (int key) const
{
  TString& s = get_tmp_string(128);

  s = "";
  for (int j = 0; j < _rec.Ky[key].NkFields; j++)
  {
    const bool upper = _rec.Ky[key].FieldSeq[j] > MaxFields;
    const int nfld = _rec.Ky[key].FieldSeq[j] - (upper ? MaxFields : 0);

    if (j)
      s << "+";
    if (upper)
      s << "UPPER(";
    s << _rec.Fd[nfld].Name;
    if (_rec.Ky[key].FromCh[j] < INVFLD)
      s << format ("[%d,%d]", _rec.Ky[key].FromCh[j] + 1,
                   _rec.Ky[key].ToCh[j] + 1);
    if (upper)
      s << ")";
  }
  s << (_rec.Ky[key].DupKeys ? "|X" : "| ");
  return s;
}

int TTrec::len() const
{ 
  const int n = fields();
  return (n > 0 && n <= MaxFields) ? _rec.Fd[n - 1].RecOff + _rec.Fd[n - 1].Len : 0; 
}

#ifndef FOXPRO

bool TDir::is_active () const
{
  const int module = abs((int)flags());
  return main_app().has_module(module, CHK_DONGLE);
}

void TTrec::update_fielddef (int nfld, TToken_string& s)
{
  RecFieldDes& rfd = _rec.Fd[nfld];
  strcpy (rfd.Name, s.get(0));
  rfd.TypeF = s.get_int ();
  rfd.Len = s.get_int ();
  rfd.Dec = s.get_int ();
}

void TTrec::update_keydef (int key, TToken_string& s)
{
  TExpression expr ("", _strexpr);
  TString ke (s.get(0));

  if (expr.set(ke, _strexpr))
  {                          
    const char* dup = s.get();
    _rec.Ky[key].DupKeys = (dup && *dup != ' ');
    TCodearray & c = expr.code ();
    c.begin ();
    int n = 0;
    while (!c.end ())
    {
      TCode & inst = c.step ();
      TCodesym sym = inst.getsym ();

      if (sym == _variable)
      {
        const char *s = inst.string ();
        int i;

        for (i = 0; i < _rec.NFields; i++)
          if (strcmp (_rec.Fd[i].Name, s) == 0)
            break;

        _rec.Ky[key].FieldSeq[n] = i;
        _rec.Ky[key].FromCh[n] = INVFLD;
        _rec.Ky[key].ToCh[n]   = INVFLD;
        inst = c.step ();
        sym = inst.getsym ();
        if (sym == _upper)
          _rec.Ky[key].FieldSeq[n] += MaxFields;
        else if (sym == _number)
        {
          _rec.Ky[key].FromCh[n] = (int)inst.number().integer() - 1;
          inst = c.step ();
          _rec.Ky[key].ToCh[n] = _rec.Ky[key].FromCh[n] + (int)inst.number ().integer () - 1;
          inst = c.step ();
        }
        else
          c.backtrace ();
        n++;
      }
    }
    _rec.Ky[key].NkFields = n;
  }
}

void TTrec::print_on(ostream & out) const
{
  const int n = num();
  out << n;
  if (_des && n>=LF_TABGEN && n<=LF_TAB && _tab.not_empty()) // Solo se e' una tabella...
    out << "|" << _tab;
  out << '\n';
  const int nfields = fields ();
  TToken_string s (80);
  int i;

  out << nfields << '\n';
  for (i = 0; i < nfields; i++)
  {
    s = fielddef (i);
    out << s ;
    if (_des)
      out << "|" << _des->get(_rec.Fd[i].Name);
    out << '\n';
  }
  const int nkeys = keys ();

  out << nkeys << '\n';
  for (i = 0; i < nkeys; i++)
  {
    s = keydef (i);
    out << s << '\n';
  }
}

void TTrec::read_from(istream & in)
{
  const int n = num();

  TToken_string t(256);
  in.getline (t.get_buffer(), t.size(), '\n');
  if (_des && n >= LF_TABGEN && n <= LF_TAB)
  {
    if (t.find('|') >= 0) 
    {
      const TString4 tabname(t.right(3));
      t.rtrim(4); // Toglie il nome della tabella
      if (_tab != tabname && !yesno_box("Descrizione relativa alla tabella %s nel file %d.\n Continuare?",(const char*) tabname), n)
        return;
    }
  }
  const int ln = t.get_int(0);
  if (n > 0 && ln != n && !yesno_box ("Descrizione relativa al file n.ro %d.\n Continuare?", ln))
    return;

  in.getline (t.get_buffer(), t.size(), '\n');
  const int nfields = t.get_int(0);
  set_fields (nfields);
  
  for (int i = 0; i < nfields; i++)
  {
    in.getline (t.get_buffer(), t.size(), '\n');
    update_fielddef (i, t);
    const int itms = t.items();
    if (_des && itms >= 5) // La descrizione viene caricata solo se esiste
      _des->set(_rec.Fd[i].Name, t.get(4));
  }

  in.getline (t.get_buffer(), t.size(), '\n');
  const int nkeys = t.get_int(0);
  set_keys (nkeys);
  for (int i = 0; i < nkeys; i++)
  {
    in.getline (t.get_buffer(), t.size(), '\n');
    update_keydef (i, t);
  }
  rehash ();
  _des = NULL;
  _tab = "";
}

#endif // FOXPRO