/***************************************************************************
 *                                                                         *
 *   DNF.CPP                                                               *
 *                                                                         *
 *   Copyright (c) 1996-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   DynaFiles, text files with special symbols and tables that can be     *
 *   converted and expanded at runtime.                                    *
 *                                                                         *
 *                                              - R. Stein  8/7/96         *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include <list>
#include <string>
using namespace std;
#include "dnf.h"
#include "tvb.h"

#define FILREV "$Revision: 13 $"


// dnfHandler
#ifdef PPFILE
dnfHandler::dnfHandler(            // handler constructor
dnfMap& _map,                      //   map of DynaFile
ostream& _dnfout,                  //   seq stream where output is to be sent
acthUserID *usr) :
     map(_map),
     dnfout(_dnfout),
     _sayAgain(FALSE),
     state(DNFSTART),
     ridlast(DNFCONTINUE),
     minp(NULL),
     endcount(0),
     _doTextVars(TRUE),
     m_usr( usr )
{
}
#endif

dnfHandler::dnfHandler(            // handler constructor
dnfMap& _map,                      //   map of DynaFile
ostream& _dnfout) :                //   seq stream where output is to be sent
     map(_map),
     dnfout(_dnfout),
     _sayAgain(FALSE),
     state(DNFSTART),
     ridlast(DNFCONTINUE),
     minp(NULL),
     endcount(0),
#ifdef PPFILE
     _doTextVars(TRUE),
     m_usr( NULL )
#else
     _doTextVars(TRUE)
#endif
{
}

dnfHandler::~dnfHandler()          // destructor
{
     closein();
     if (state == DNFPRIMEDO) {
          map.primeQuit();
     }
}

DNFID                              //   returns ID of what to do this step
dnfHandler::process()              // process the dynaFile, one step at a time
{
     DNFID retval=DNFCONTINUE;
     GBOOL TableFinished=FALSE;

     if (_sayAgain) {
          _sayAgain=FALSE;
          return(ridlast);
     }
     switch (state) {
     case DNFSTART:
          switch (map.status) {
          case DNFUNPRIMED:
          case DNFPRIMED:
               map.primeInit();
               state=DNFPRIMEDO;
               break;
          case DNFPRIMING:
               state=DNFPRIMEWAIT;
               break;
          case DNFERROR:
               dnfout << "Error checking map." << endl << map;
               state=DNFENDED;
               break;
          }
          break;
     case DNFPRIMEWAIT:
          switch (map.status) {
          case DNFUNPRIMED:
               map.primeInit();
               state=DNFPRIMEDO;
               break;
          case DNFPRIMING:         // (shouldn't this be timed out?)
               break;
          case DNFPRIMED:
               state=DNFFILESTART;
               break;
          case DNFERROR:
               dnfout << "Error waiting for map." << endl << map;
               state=DNFENDED;
               break;
          }
          break;
     case DNFPRIMEDO:
          if (!map.primeDo()) {
               if (map.status == DNFERROR) {
                    dnfout << "Error priming map.  See below." << endl << map;
                    state=DNFENDED;
               }
               else {
                    state=DNFFILESTART;
               }
          }
          break;
     case DNFFILESTART:
          if (map.errReport(dnfout)) {
               state=DNFENDED;
               break;
          }

#if defined( PPFILE )
          minp = new ppFile( map.filepath, m_usr );
#else
          minp=new ifstream(map.filepath,ios::in|ios::binary);
#endif
          if (minp->fail()) {
               dnfout << "Could not open " << map.filepath << endl;
          }
          else {
               istep=-1;                               // before map begins
               _rowNumber=0;
               _tableDone=FALSE;
               intable=FALSE;
               retval=DNFBEGIN;
               state=DNFFILEPROC;
          }
          break;
     case DNFFILEPROC:
          if (istep >= 0 && map.step[istep].type == DNFTABLE) {
               if (!intable) {
                    intable=TRUE;                 // beginning of table
                    _rowNumber=0;
               }
               if (_tableDone) {
                    intable=FALSE;                // end of table (rowbegin)
                    istep=map.step[istep].ref;    // (skip past it)
                    _tableDone=FALSE;
                    TableFinished=TRUE;
               }
          }
          else if (istep >= 0 && map.step[istep].type == DNFTABLEEND) {
               _rowNumber++;                      // next row in table
               if (_tableDone) {
                    intable=FALSE;                // end of table (mid-row)
                    _tableDone=FALSE;
               }
               else {
                    retval=DNFROWBEGIN;           // (begin a new row)
                    istep=map.step[istep].ref;    // (then come back for more)
                    break;
               }
          }
          if (istep < map.nsteps) {
               istep++;                           // next step in map
               copy(map.step[istep].preswath[0]);
               retval=map.step[istep].id;
               if (TableFinished && retval == DNFROWBEGIN) {
                    _rowNumber=0;
               }
          }
          else {
               state=DNFENDED;                    // end of map
          }
          break;
     case DNFENDED:
          closein();
          retval=DNFEND;
          ASSERTM(++endcount < 1000,spr("Runaway DynaFile %s handler!  "
                                        "Should stop at DNFEND.",
                                        map.filepath));
          break;
     }
     ridlast=retval;
     return(retval);
}

INT
dnfHandler::rowNumber() const      // *inside* a table, indicates row, 0 to N-1
{                                  // *after* a table, indicates total # rows
     return(_rowNumber);
}

VOID
dnfHandler::tableDone()            // application declares a table done
{
     _tableDone=TRUE;
}

VOID
dnfHandler::sayAgain()             // make next process give same return code
{
     _sayAgain=TRUE;
}

VOID
dnfHandler::doTextVars(            // do translate Text Variables (tvb.h)
GBOOL onoff)                       //   TRUE=translate (default) FALSE=turn off
{
     _doTextVars=onoff;
}

VOID
dnfHandler::paste()                // insert text cut by DNFCUT
{
     dnfout << map.step[istep].sym[0];
     copy(map.step[istep].preswath[1]);
     dnfout << map.step[istep].sym[1];
}

DNFHANDLERSTATE                    //   e.g. DNFENDED, see enumeration
dnfHandler::getState() const       // get current state of handler
{
     return(state);
}

DNFSTEP                            //   0 to dnfMap::ntsteps-1
dnfHandler::getStep() const        // get index of step in map's array of steps
{
     return(istep);
}

VOID
dnfHandler::closein()              // close the input file
{
     if (minp != NULL) {
          delete minp;
          minp=NULL;
     }
}

VOID
dnfHandler::copy(                  // copy swath from template to output stream
dnfSwath& swat)                    //   swath (range of bytes)
{
     if (_doTextVars) {
          swat.tvbCopy(*minp,dnfout);
     }
     else {
          swat.copy(*minp,dnfout);
     }
}


// dnfMap

#ifdef PPFILE
dnfMap::dnfMap(                    // map constructor
const CHAR *_filepath,             //   path of DynaFile (template file)
const CHAR *_filedesc,             //   text description of DynaFile
dnfStep *_step,                    //   array of DynaFile steps
acthUserID *usr) :
     step(_step),
     minp(NULL),
     filegmt(0UL),
     iref(DNFNOSTEP),
     status(DNFUNPRIMED),
     m_usr( usr )
{
     init(_filepath, _filedesc);
}
#endif

dnfMap::dnfMap(                    // map constructor
const CHAR *_filepath,             //   path of DynaFile (template file)
const CHAR *_filedesc,             //   text description of DynaFile
dnfStep *_step) :                  //   array of DynaFile steps
     step(_step),
     minp(NULL),
     filegmt(0UL),
     iref(DNFNOSTEP),
#ifdef PPFILE
     status(DNFUNPRIMED),
     m_usr( NULL )
#else
     status(DNFUNPRIMED)
#endif
{
     init(_filepath, _filedesc);
}

void
dnfMap::init(                      // common init function
const CHAR *_filepath,             //   file path
const CHAR *_filedesc)             //   file description
{
     for (nsteps=0 ; step[nsteps].type != DNFMAPEND ; nsteps++) {
          ASSERTM(step[nsteps].type > DNFTYPE_MIN
               && step[nsteps].type < DNFTYPE_MAX,
                  spr("Unterminated DynaFile %s map (needs DNFMAPEND).",
                      _filepath));
     }
     filepath=strcpy(new CHAR[strlen(_filepath)+1],_filepath);
     filedesc=strcpy(new CHAR[strlen(_filedesc)+1],_filedesc);
     emg=new ostrstream();
}

dnfMap::~dnfMap()                  // destructor
{
     if (filepath != NULL) {
          delete[] filepath;
          filepath=NULL;
     }
     if (filedesc != NULL) {
          delete[] filedesc;
          filedesc=NULL;
     }
     errDelete();
     closein();
}

VOID
dnfMap::primeInit()                // prepare for priming the map
{
     errReset();
     if (status == DNFPRIMING) {
          *emg << "Priming begun when already in progress." << endl;
     }
     else if ((nowgmt=getFileGMT(filepath)) == filegmt && status == DNFPRIMED) {
          // status remains DNFPRIMED, primeDo() will return FALSE
     }
     else {
          status=DNFPRIMING;
          scanInit();
     }
}

GBOOL                              //   TRUE=call again FALSE=done
dnfMap::primeDo()                  // continue priming the map
{
     GBOOL retval=TRUE;

     if (status != DNFPRIMING) {
          retval=FALSE;
     }
     else if (!scanDo()) {
          if (presolve()) {
               status=DNFPRIMED;
               filegmt=nowgmt;
          }
          else {
               status=DNFERROR;
          }
          retval=FALSE;
     }
     return(retval);
}

VOID
dnfMap::primeQuit()                // abort priming the map before done
{
     status=DNFUNPRIMED;
     errReset();
}

GBOOL                              //   TRUE=errors detected
dnfMap::isbad() const              // did priming the map produce any errors?
{
     return(emg->pcount() > 0);
}

GBOOL                              //   same return as isbad()
dnfMap::errReport(                 // report priming errors to text stream
ostream& eout)
{
     GBOOL retval;

     retval=isbad();
     if (retval && emg != NULL) {
          eout << emg->rdbuf();
          eout << ", in " << filepath << ", " << filedesc << endl;
          status=DNFUNPRIMED;
          errReset();
     }
     return(retval);
}

const dnfStep&
dnfMap::getStep(                   // get one step from the map's step array
DNFSTEP ist) const                 //   index, must be 0 to nsteps [sic]
{
     if (ist > nsteps) {
          ist=nsteps;
     }
     return(step[ist]);
}

DNFMAPSTATUS                       //   e.g. DNFUNPRIMED, DNFPRIMED, see enum
dnfMap::getStatus() const          // get status of map
{
     return(status);
}

VOID
dnfMap::scanInit()                 // internal work of primeInit()
{
     closein();

#if defined( PPFILE )
     minp = new ppFile( filepath, m_usr );
#else
     minp=new ifstream(filepath,ios::in|ios::binary);
#endif

     if (minp == NULL) {
          *emg << "Not enough memory to open " << filepath << endl;
     }
     else if (minp->fail()) {
          *emg << "Can't open " << filepath << endl;
          closein();
     }
     else {
          symInit();
          lastpos=0UL;
          scanning=FALSE;
     }
}

GBOOL                              //   FALSE=done
dnfMap::scanDo()                   // internal work of primeDo()
{
     CHAR buff[256];

     if (minp == NULL) {
          return(FALSE);           // If scanInit() had problems, scanDo() ends
     }
     if (scanning) {
          INT lentarg=strlen(step[istep].sym[isym]);
          if (lentarg >= sizeof(buff)) {
               *emg << "Symbol too long (limit is " << sizeof(buff)
                    << " characters): \"" << step[istep].sym[isym]
                    << "\"" << endl;
               closein();
               return(FALSE);
          }
          ULONG readat=minp->tellg();
          INT nactual=minp->read(buff,sizeof(buff)).gcount();
          if (nactual < lentarg) {
               *emg << "Symbol missing or out of order: "
                    << step[istep].sym[isym] << endl;
               closein();
               return(FALSE);
          }
          minp->clear();
          INT offs=scanmem(buff,step[istep].sym[isym],nactual,lentarg);
          if (offs == nactual) {
               minp->seekg(-lentarg+1,ios::cur);
          }
          else {
               ULONG newpos=readat+offs;
               step[istep].preswath[isym].pos=lastpos; // rcd intersymbol swath
               step[istep].preswath[isym].len=newpos-lastpos;
               lastpos=newpos+lentarg;
               minp->seekg(lastpos);
               scanning=FALSE;
          }
     }
     else {
          if (symDo()) {
               scanning=TRUE;
          }
          else {
               if (minp != NULL) {
                    minp->seekg(0,ios::end);
                    step[nsteps].preswath[0].pos=lastpos;   // rcd final swath
                    step[nsteps].preswath[0].len=minp->tellg()-lastpos;
               }
               return(FALSE);      // benign end of symbols reached
          }
     }
     return(TRUE);
}

VOID
dnfMap::symInit()                  // prepare to find symbols in DynaFile
{
     istep=-1;
     isym=0;
}

GBOOL                              //   FALSE=done
dnfMap::symDo()                    // find next symbol in DynaFile
{
     while (istep < nsteps) {
          if (istep >= 0 && step[istep].type == DNFCUT) {
               if (isym == 0) {
                    isym=1;
               }
               else {
                    isym=0;
                    istep++;
               }
          }
          else {
               istep++;
          }
          switch (step[istep].type) {
          case DNFTABLE:
               step[istep].id=DNFROWBEGIN;
               iref=istep;
               break;
          case DNFTABLEEND:
               step[istep].id=DNFROWEND;
               if (isym == 0) {
                    if (iref == DNFNOSTEP) {
                         *emg << "Missing DNFTABLE() corresponding to step "
                              << istep+1 << endl;
                         closein();
                         return(FALSE);
                    }
                    step[istep].ref=iref;    // (nested tables not supported)
                    step[iref].ref=istep;
                    iref=DNFNOSTEP;
                    while (istep > 0 && step[istep].type != DNFTABLE) {
                         istep--;
                    }
                    isym=1;
               }
               else {
                    isym=0;
               }
               break;
          case DNFCUT:
          case DNFWATCH:
          case DNFCOLUMN:
               if (step[istep].sym[isym] == NULL) {
                    if (step[istep].type == DNFWATCH && isym != 0) {
                         *emg << "Should use DNFCOLUMN inside tables, step ";
                    }
                    else {
                         *emg << "Missing symbol in step ";
                    }
                    *emg << istep+1 << endl;
                    closein();
                    return(FALSE);
               }
               return(TRUE);
          case DNFMAPEND:          // benign end of symbols reached,
               return(FALSE);      // minp remains open for use by presolve()
          }
     }
     return(FALSE);                // (unexpected exit)
}

GBOOL                              //   TRUE=ok, FALSE=problems
dnfMap::presolve()                 // priming a map, final resolutions
{                                  //   e.g. table prefixes and postfixes
     dnfSwath *pre,*pst,*bth;
     dnfSwath *opre,*opst;
     INT npre,npst,sizbth;
     INT cpre,cpst,cbth;
     DNFSTEP ibeg,iend;

     if (minp == NULL) {
          return(FALSE);           // error occurred, nothing to resolve
     }
     for (ibeg=nsteps-1 ; ibeg >= 0 ; ibeg--) {   // backward, for adj tables
          if (step[ibeg].type == DNFTABLE) {
               iend=step[ibeg].ref;
               if (iend == DNFNOSTEP) {
                    *emg << "Missing DNFTABLEEND() corresponding to step "
                         << ibeg << endl;
                    closein();
                    return(FALSE);
               }
               pre=&step[ibeg].preswath[0];
               opre=&step[ibeg+1].preswath[0];
               bth=&step[ibeg+1].preswath[1];
               pst=&step[iend].preswath[0];
               opst=&step[iend+1].preswath[0];
               sizbth=bth->len;
               for (npst=0 ; npst < sizbth && npst < opst->len ; npst++) {
                    cbth=minp->seekg(bth->pos+npst).get();
                    cpst=minp->seekg(opst->pos+npst).get();
                    if (cbth != cpst || cbth == EOF) {
                         break;         // not very flexible with whitespace
                    }
               }
               for (npre=0 ; npre < sizbth-npst && npre < opre->len ; npre++) {
                    cbth=minp->seekg(bth->pos+sizbth-npre-1).get();
                    cpre=minp->seekg(opre->pos+opre->len-npre-1).get();
                    if (cbth != cpre || cbth == EOF) {
                         break;         // not very flexible with whitespace
                    }
               }
               if (npre+npst < sizbth) {
                    *emg << "Two sample rows of table are not identical "
                         << "(map steps " << ibeg+1 << "-" << iend+1
                         << "): " << npre << " + " << npst
                         << " != " << sizbth <<  endl;
                    // HACK, show details somehow, someday...
                    // dnfSwath msing;
                    // msing.pos=bth->pos+npst;
                    // msing.len=sizbth-npre-npst;
                    // *emg << "\"";
                    // msing.copy(*minp,*emg);
                    // *emg << "\"" << endl;
               }
               pst->pos=opst->pos;
               pst->len=npst;
               opst->pos+=npst;
               opst->len-=npst;
               pre->pos=opre->pos;
               pre->len=opre->len-npre;
               opre->pos+=opre->len-npre;
               opre->len=npre;
          }
     }
     closein();
     return(TRUE);
}

VOID
dnfMap::closein()                  // close input file
{
     if (minp != NULL) {
          delete minp;
          minp=NULL;
     }
}

VOID
dnfMap::errDelete()                // delete accumulated error messages
{
     if (emg != NULL) {
          emg->rdbuf()->freeze(0);
          delete emg;
          emg=NULL;
     }
}

VOID
dnfMap::errReset()                 // delete error messages & prepare for more
{
     errDelete();
     emg=new ostrstream;
}


// dnfStep

dnfStep::dnfStep(                  // step constructor
DNFTYPE _type,                     //   type of step (see enumeration)
DNFID _id,                         //   application IDs, internally defined IDs
const CHAR *sym0,                  //   symbol sought (1st row if DNFCOLUMN)
const CHAR *sym1)                  //   symbol sought for 2nd row (DNFCOLUMN)
{
     init(_type,_id,sym0,sym1);
}

dnfStep::dnfStep() :               // default constructor
     type(DNFTYPE_MIN),
     id(DNFNONE),
     ref(DNFNOSTEP)
{
     sym[0]=NULL;
     sym[1]=NULL;
}

void
dnfStep::init(                     // constructor work
DNFTYPE _type,                     //   type of step (see enumeration)
DNFID _id,                         //   application IDs, internally defined IDs
const CHAR *sym0,                  //   symbol sought (1st row if DNFCOLUMN)
const CHAR *sym1)                  //   symbol sought for 2nd row (DNFCOLUMN)
{
     type=_type;
     id=_id;
     ref=DNFNOSTEP;
     sym[0]=sym0;
     if (sym1 == NULL && _type == DNFCOLUMN) {
          sym[1]=sym0;
     }
     else {
          sym[1]=sym1;
     }
}

dnfStep::~dnfStep()                // destructor
{
}

DNFTYPE                            //   e.g. DNFCOLUMN, DNFMAPEND (see enum)
dnfStep::gettype() const           // get type of step
{
     return(type);
}


// dnfSwath

VOID
dnfSwath::copy(                    // copy swath from one stream to another
#if defined( PPFILE )
ppFile & sfrom,
#else
istream& sfrom,                    //   source stream (random)
#endif
ostream& sto) const                //   destination stream (sequential)
{
     char buffer[256];
     INT nout,ntry,nread;

     sfrom.clear();
     sfrom.seekg(pos);

     //   testing
     //
     sto.flush();

     for (nout=0 ; nout < len && !sto.fail() ; nout+=nread) {
          ntry=min(len-nout,(ULONG)sizeof(buffer));
          if ((nread=sfrom.read(buffer,ntry).gcount()) <= 0 || sfrom.fail()) {
               break;
          }
          sto.write(buffer,nread);
     }

     //   testing
     //
     sto.flush();
}

VOID
dnfSwath::tvbCopy(                 // copy swath stream-stream w/text vars
#if defined( PPFILE )
ppFile &sfrom,
#else
istream& sfrom,                    //   source stream (random)
#endif
ostream& sto) const                //   destination stream (sequential)
{
     sfrom.clear();
     sfrom.seekg(pos);
     tvbTranslation tvt(sfrom,sto);
     tvt.setLimit(len);
     tvt.proceed();
}


// output reporting functions

ostream& EXPORT
operator<<(                        // output map info for debugging
ostream& out,                      //   sequential text stream
dnfMap& dm)
{
     out << "<PRE>" << endl;
     for (dm.primeInit() ; dm.primeDo() ; ) {
     }
     out << "DynaFile " << dm.filepath << ", " << dm.nsteps << " steps"
         << endl;
     for (DNFSTEP i=0 ; i <= dm.nsteps ; i++) {
          out << setw(7) << i+1 << ". " << dm.step[i];
     }
     dm.errReport(out);
     out << endl << "</PRE>" << endl;
     return(out);
}

ostream& EXPORT
operator<<(                        // output step info for debugging
ostream& out,                      //   sequential text stream
const dnfStep& ds)
{
     switch (ds.type) {
     case DNFMAPEND:
          out << "END OF MAP: " << ds.preswath[0];
          break;
     case DNFWATCH:
          out << "NEXT SYMBOL, ID#" << ds.id << ": "
              << ds.sym[0] << " " << ds.preswath[0];
          break;
     case DNFCUT:
          out << "CUTABLE TEXT: "
              << "\"" << ds.sym[0] << "\" " << ds.preswath[0] << ", "
              << "\"" << ds.sym[1] << "\" " << ds.preswath[1];
          break;
     case DNFTABLE:
          out << "TABLE BEGINS";
          if (ds.ref != DNFNOSTEP) {
               out << " (ends at step " << ds.ref+1 << ")";
          }
          out << " " << ds.preswath[0];
          break;
     case DNFCOLUMN:
          out << "COLUMN, ID#" << ds.id << ": "
              << ds.sym[0] << " " << ds.preswath[0] << ", "
              << ds.sym[1] << " " << ds.preswath[1];
          break;
     case DNFTABLEEND:
          out << "TABLE ENDS";
          if (ds.ref != DNFNOSTEP) {
               out << " (begins at step " << ds.ref+1 << ")";
          }
          out << " " << ds.preswath[0];
          break;
     default:
          out << "<unknown>";
          break;
     }
     out << endl;
     return(out);
}

ostream& EXPORT
operator<<(                        // output swath info for debugging
ostream& out,                      //   sequential text stream
const dnfSwath& dsw)
{
     if (dsw.len == 0UL) {
          out << "(0)";
     }
     else if (dsw.len == 1UL) {
          out << dsw.pos << "(" << dsw.len << ")";
          out << hex << " ";
          out << dsw.pos << "(" << dsw.len << ")";
          out << dec;
     }
     else {
          out << dsw.pos << "-"
              << dsw.pos+dsw.len-1 << "(" << dsw.len << ")";
          out << hex << " ";
          out << dsw.pos << "-"
              << dsw.pos+dsw.len-1 << "(" << dsw.len << ")";
          out << dec;
     }
     return(out);
}

