// gestione livelli di giacenza e articoli
// oggetto movimento di magazzino
// funzione di ricostruzione saldi

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

#include "mglib.h"

// *******************************
// *******************************
#define MAXSIMBOLS 256
#define MAXSTATES 25
class TStateset {
  unsigned char _container[MAXSTATES];
  int _current;
public:
  TStateset & empty();
  TStateset & enclose(int e);
  TStateset & singleton(int e);
  TStateset & cap(TStateset & s);
  int get_first() ;
  int get_next() ;
  bool is_empty() const;
  bool is_member(int e) const;
  TStateset();  
  ~TStateset() {};  
};

struct TAutoma_state  {
  short _transaction[MAXSIMBOLS];
  TString16 * _label;
  bool _final;
};

// *******************************
// *******************************
// automa per il riconoscimento di metacaratteri
#define EPSILON 0
#define FIRST_STATE 1
#define FIRST_NEMPTY_SYMBOL 1

// *******************************
// *******************************
// automa per il riconoscimento di metacaratteri
class TR_automa {
  TAutoma_state st[MAXSTATES];
  short _maxstate;
protected:  
  TStateset union_of_closures(TStateset &s);

  bool is_final(int statenum) const;
  bool is_final(TStateset states) const;

  TR_automa & set_label(int statenum, const char *label);
  bool is_state(int statenum);
  const char *label(int statenum);
  int label2state(const char * label);

  void del_trans(int statenum,unsigned char symbol);
  int trans_to(int statenum,unsigned char symbol);
  bool _isdeterministic;
  
public:
  TR_automa & reset_state(int statenum=-1);

  void add_tran(int statenum,unsigned char symbol,int next);// aggiunge una transizione
  int add_state(const char * label);// aggiunge uno stato
  TR_automa & set_final(int statenum,bool v=TRUE); // pone lo stato come finale

  bool is_deterministic() {return _isdeterministic;}; // 
  bool recognized(const char * s);          // tenta di riconoscere la stringa passata

  TR_automa(TR_automa *a=NULL,bool makedet=FALSE); // duplica un automa (e lo rende deterministico)
  ~TR_automa();
  static void set2label(const TStateset ss,TString16 & label);
  static void label2set(const TString16 & label,TStateset & ss);

};


// *******************************
TStateset::TStateset()
{ 
  empty();
}

TStateset & TStateset::empty()
{ _current=0;
  for (int i= 0; i< MAXSTATES; _container[i++]=0);
  return *this;
}

TStateset & TStateset::enclose(int el)
{ if (el< MAXSTATES && el>=0)
    _container[el]=1;
  return *this;
}

TStateset & TStateset::singleton(int el)
{ empty();
  _container[el]=1;
  return *this;
}

TStateset & TStateset::cap(TStateset & s)
{
  for (int i= 0; i< MAXSTATES; i++)
    _container[i]|=s._container[i];
  return *this;
}
  
bool TStateset::is_empty() const
{ 
  for (int i= 0; i< MAXSTATES; i++) {
    if (_container[i]) return FALSE;
  }
  return TRUE;
}

bool TStateset::is_member(int e)    const
{ 
  return ((e< MAXSTATES && e>=0)?  _container[e] : FALSE );
}

int TStateset::get_first()
{
  _current=-1;
  return(get_next());
}

int TStateset::get_next()
{
  while (_current+1 < MAXSTATES)  {
    if (_container[++_current])
      return _current;
  } 
  return (-1);  
}

// labels and states
bool TR_automa::is_state(int statenum)
{
  return(statenum<=_maxstate && statenum>0);
}

// restituisce il numero dello stato con quella etichetta
int TR_automa::label2state(const char *label)
{
  for (int s=0; s<_maxstate;s++) {
    if (*(st[s]._label)==label)
      return(s+1);
  }
  return(0);  
}


// resetta uno stato dell'automa (senza eliminarlo)
TR_automa & TR_automa::reset_state(int statenum)
{
  int _from,_to;
  if (statenum>0 && statenum<=_maxstate) {
    _from=statenum;
    _to=statenum;
  } else {
    _maxstate=0;
    _isdeterministic=TRUE;
    _from=1;
    _to=MAXSTATES;
  } 
  {
    for (int i=_from; i<=_to;i++) {
      set_label(i,"");
      set_final(i,FALSE);
      for (int j=0; j<MAXSIMBOLS;j++) {
        del_trans(i,j); 
      }
    }     
  }
  return *this;
}

// aggiunge una label
int TR_automa::add_state(const char * label)
{
  _maxstate++;
  reset_state(_maxstate);
  set_label(_maxstate,label);
  return _maxstate;
}

TR_automa & TR_automa::set_label(int statenum, const char * name)
{
  (*(st[statenum-1]._label))=name;
  return *this;
}

const char * TR_automa::label(int statenum)
{
  return (*(st[statenum-1]._label));
}

bool TR_automa::is_final(int statenum) const
{ 
  return (st[statenum-1]._final);
}

bool TR_automa::is_final(TStateset ss ) const
{ 
  bool retv=FALSE;
  int statenum=ss.get_first();
  do {
    retv=retv || is_final(statenum);
  } while ((statenum=ss.get_next())>0);
    
  return retv;
}

TR_automa & TR_automa::set_final(int statenum,bool v)
{
  st[statenum-1]._final=v;
  return *this;
}

//*******************
// bows
void TR_automa::add_tran(int statenum, unsigned char symbol,int next)
{
  st[statenum-1]._transaction[symbol]=next;   
  if (symbol==EPSILON)
    _isdeterministic=FALSE;
}

void TR_automa::del_trans(int statenum, unsigned char symbol)
{
  st[statenum-1]._transaction[symbol]=0;
}

int TR_automa::trans_to(int statenum, unsigned char symbol)
{
  return (st[statenum-1]._transaction[symbol]);   
}

// costruttore "di copi": crea un automa non deterministico 
// (transazioni Epsilon ma nessuna transazione multipla)
// o ne crea l'equivalente automa deterministico
TR_automa::TR_automa(TR_automa * aa,bool makedet) 
{
  TStateset arrival;
  TStateset newstateset;
  TString16  tmplabel;
  int curr_new_state;

  for (int i=0; i<MAXSTATES;i++) {
    st[i]._label = new TString16;
  }     
  if (aa==NULL) {
    reset_state();
    return;
  }
  if (makedet) {
    reset_state();
    // crea il primo stato nell'automa deterministico
    set2label(aa->union_of_closures(newstateset.singleton(FIRST_STATE)),tmplabel);
    add_state(tmplabel);
    curr_new_state=FIRST_STATE;
    while (is_state(curr_new_state)) {
      // determina l'insieme degli stati dell'automa non deterministico
      // che corrispondono a questo stato dell'automa deterministico
      label2set(label(curr_new_state),newstateset);
      // lo stato � finale se include stati finali dell'automa non det.
      set_final(curr_new_state,aa->is_final(newstateset));
      // determina tutte le transazioni 
      for (short symbol=FIRST_NEMPTY_SYMBOL; symbol<MAXSIMBOLS; symbol++) {
        // determina lo stato di arrivo nell'automa det.:
        // esso � pari all'insieme degli stati raggiunti col questo simbolo 
        // dal sottoinsieme degli stati dell'automa non det. che etichetta il nuovo stato nell'automa det.
        arrival.empty();
        int new_next,old_next;
        int old_state=newstateset.get_first() ;
        do {
          if (old_next=aa->trans_to(old_state,(unsigned char)symbol))
            arrival.enclose(old_next);
        } while ((old_state=newstateset.get_next())>0);
        if (!arrival.is_empty()) {
            // crea il nuovo arco nell'automa deterministico
          set2label(aa->union_of_closures(arrival),tmplabel);
          if (!(new_next=label2state(tmplabel))) {
          new_next=add_state(tmplabel);
          } 
          add_tran(curr_new_state,(unsigned char)symbol,new_next);
        }
      } // loop symbol
      curr_new_state++;
    }
    _isdeterministic=TRUE;
  } // fine  conversione
  else 
  {
    *this=*aa;
    for (int i=0; i<MAXSTATES;i++) {
      st[i]._label= aa->st[i]._label;
    }     
  }
}


TR_automa::~TR_automa()
{ 
  for (int i=0; i<MAXSTATES;i++) {
    delete (st[i]._label);
  }     
}


void TR_automa::set2label(const TStateset ss,TString16 & label)
{
  char coded[MAXSTATES];
  char c=0;
  for (int i= 0; i< MAXSTATES; i++) {
    if (ss.is_member(i)) 
      coded[c++]=(char)(i+'0');
  }
  coded[c]='\0';
  label=coded;    
}

void TR_automa::label2set(const TString16 & label,TStateset & ss)
{
  ss.empty();
  for (int i= 0; i<= label.len();i++)
    ss.enclose(int(label[i]-'0'));
}


// costruisce l'unione delle epslilon closures
TStateset TR_automa::union_of_closures(TStateset &start_set)
{
  TStateset u_of_clo,clo;
  int _state,_next;
  bool toadd;
  
  
  u_of_clo.empty();
  _state=start_set.get_first() ;
  do {
    // la chiusura � composta dallo stato e tutte le sue transazioni epsilon
    clo.singleton(_state);
    do {
      int _state2=clo.get_first();
      do {
        toadd=((_next=trans_to(_state2,EPSILON)) && (!clo.is_member(_next)));
        if (toadd)
          clo.enclose(_next);
      } while ((_state2=clo.get_next())>0);
    } while (toadd) ;
    u_of_clo.cap(clo);
  } while ((_state=start_set.get_next())>0);
  return u_of_clo;
}

// tenta di riconoscere la stringa passata
bool TR_automa::recognized(const char * t_str) 
{
  if (_isdeterministic) {
    // ricoNosce la stringa di token
    int curr_state=FIRST_STATE;
    for (int i=0; t_str[i]; i++) {
      if (!(curr_state=trans_to(curr_state,t_str[i])))
        // fine per mancanza di trasizioni
        return FALSE; 
    }
    // fine per mancanza di caratteri di input della stringa
    return (is_final(curr_state));
  } else {
    //    
    return FALSE;
  }
}

// *******************************
// gestione di stringhe di metacaratteri
// *******************************
#define C_ESCAPEMETA '\\'
#define S_BLANK 1
#define S_DIGIT 2
#define S_LETTER 3
#define S_ANY 4

// *******************************
// *******************************
// riconoscimento di metacaratteri
class TR_automa;

  
bool TMetachar::recognized(const char * s)
{
  return(_au->recognized(s));
}
        
void TMetachar::add_tran(int s,unsigned char metasymbol, int nextstate)
{
    unsigned char c;
    switch (metasymbol) {
      case EPSILON:// blank
        _au->add_tran(s,EPSILON,nextstate);
      break;
      case S_BLANK:// blank
        _au->add_tran(s,' ',nextstate);
      break;
      case S_DIGIT:// cifra 
        for (c='0';c<='9';c++)
          _au->add_tran(s,c,nextstate);
      break;
      case S_LETTER: // lettera 
        for (c='a';c<='z';c++)
          _au->add_tran(s,c,nextstate);
        for (c='A';c<='Z';c++)
          _au->add_tran(s,c,nextstate);
      break;
      case S_ANY: // qualsiasi carattere 
        for (c=MAXSIMBOLS-1;c>=FIRST_NEMPTY_SYMBOL;c--)
          _au->add_tran(s,c,nextstate);
      break;
      default:
          _au->add_tran(s,metasymbol,nextstate);
      break;
    }
}


// restituisce la stringa di metacaratteri del linguaggio riconosciuto
const char * TMetachar::language() const
{
  return(_language);
}

void TMetachar::set_language(const char * language)
{
  int s;
  bool escaped_char=FALSE;
  TString16 label("-"),nextlabel("-");
  
  // crea gli insiemi di metacaratteri standard
  strcpy(_metach_mand,"0LA&");
  strcpy(_metach_opz,"#9?ac");

  
  _language=language;
  // crea l'automa 
  _au->reset_state();
  for (int i=0; language[i]; i++) {
    label[0]='a'+i;
    nextlabel[0]='a'+i+1;
    if (language[i]!=C_ESCAPEMETA) {
      if (!escaped_char)
      {
      // meta-caratteri e literal fuori set
        s=_au->add_state(label);
        switch (language[i]) {
          case '#':// cifra o blank opzionale
            add_tran(s,S_BLANK,s+1);
            add_tran(s,'-',s+1);
            add_tran(s,'+',s+1);
          case '9':// cifra opzionale
            add_tran(s,EPSILON,s+1);
          case '0':// cifra obbligatoria
            add_tran(s,S_DIGIT,s+1);
          break;
          case '?': // lettera opzionale
            add_tran(s,EPSILON,s+1);
          case 'L': // lettera obbligatoria
            add_tran(s,S_LETTER,s+1);
          break;
          case 'a': // lettera o numero opzionale
            add_tran(s,EPSILON,s+1);
          case 'A': // lettera o numero obbligatorio
            add_tran(s,S_LETTER,s+1);
            add_tran(s,S_DIGIT,s+1);
          break;
          case 'c': // qualsiasi carattere opzionale
            add_tran(s,EPSILON,s+1);
          case '&': // qualsiasi carattere obbligatorio
            add_tran(s,S_ANY,s+1);
          break;
          default:
            _au->add_tran(s,language[i],s+1);
          break;
        }
      } else {
      // escaped char
          s=_au->add_state(label);
        _au->add_tran(s,language[i],s+1);
      }
    }
    escaped_char=(language[i]==C_ESCAPEMETA);
  } // end of loop
  // aggiunge lo stato finale
  s=_au->add_state(nextlabel);
  _au->set_final(s);

}

// ricerca caratteri del set opzionale
bool TMetachar::has_opzchars(const char * pattern)
{
  int i=0; 
  bool next_literal=FALSE;
  
  while (pattern[i]) {           
    if (!next_literal && strchr(_metach_opz,pattern[i]))
      return(TRUE);
    next_literal=(!next_literal && pattern[i]==C_ESCAPEMETA);
    i++;
  }
  return(FALSE);
}

// ricerca caratteri del set opzionale
bool TMetachar::has_mandchars(const char * pattern)
{
  int i=0; 
  bool next_literal=FALSE;
  
  while (pattern[i]) {
    if (next_literal || strchr(_metach_mand,pattern[i]))
      return(TRUE);
    next_literal=(!next_literal && pattern[i]==C_ESCAPEMETA);
    i++;
  }
  return(FALSE);
}

// stabilisce la lunghezza della stringa di metacaratteri
int TMetachar::maxstrlen(const char * pattern) 
{
  int i=0,l=0; 
  bool next_literal=FALSE;
  
  while (pattern[i]) {
    if (!next_literal)
      l++;
    next_literal=(!next_literal && pattern[i]==C_ESCAPEMETA);
    i++;
  }
  return(l);
}

// costruttore e distruttore
TMetachar::TMetachar ()
{
  _au = new TR_automa;       
  set_language("");
}

TMetachar::TMetachar (const char * metastr)
{
  // crea l'automa e lo trasforma in deterministico
//  TR_automa auxau;
//  set_language(&auxau,metastr);
  _au=new TR_automa;
  set_language(metastr);
  if (!_au->is_deterministic()) {
    TR_automa * auxau = new TR_automa(_au,TRUE);       
    delete _au;
    _au=auxau;
  } 
}

TMetachar::~TMetachar ()
{
  delete _au;
}

// **************************
// classe codice a livelli 


void TCodice_livelli::update_firm()
{
  _last_firm = prefix().get_codditta();
}

void TCodice_livelli::test_firm()
{
  long firm = prefix().get_codditta();
  if (firm > 0 && firm != _last_firm)
  {                   
    init();
    update_firm();  // Lo dovrebbe gia' fare la init
  }
}

void TCodice_livelli::load(bool enabled, const char *tabname,const char *tabgrp) 
{
  TTable _tabformato(tabname);  
  int e;
  
  _lev_enabled = enabled;
  _last_level=0;
  for (int i=0; i <  MANY_MAG_LEV; i++)
  {
    _name[i]="";
    _code_length[i]=0;
    _picture[i]="";
  }
  if (_lev_enabled)
  {
    e = _tabformato.first(); 
    for (int i=0; e == NOERR && i < MANY_MAG_LEV; i++)
    {
      _name[i]=_tabformato.get("S0");
      _picture[i]=_tabformato.get("S1");
      _code_length[i]=TMetachar::maxstrlen(_picture[i]);
      _last_level=i+1;
      e = _tabformato.next();
    }
  }
  
  if (_gruppi == NULL)
    _gruppi = new TDecoder(tabgrp);
}

const char *TCodice_livelli::code_format(int levnum) const 
{
  CHECK(enabled(), "iu chent get de code format if levels ar disebold");
  if (levnum<0) {
    levnum=max(last_level(),1);
  } 
  ((TCodice_livelli *)this)->add_metachar(levnum);
  return ((TMetachar*)_metachars.objptr(levnum-1))->language();
}


bool TCodice_livelli::fit_to_format(const char *codepart,int levnum) const 
{
  CHECK(enabled(), "iu chent cec ueter de format fits if levels ar disebold");
  if (levnum<0) {
    levnum=max(last_level(),1);
  } 
  ((TCodice_livelli *)this)->add_metachar(levnum);
  return ((TMetachar*)_metachars.objptr(levnum-1))->recognized(codepart);
}


void TCodice_livelli::add_metachar(int levnum)
{
  if (_metachars.objptr(levnum-1)==NULL)
    _metachars.add(new TMetachar(picture(levnum)),levnum-1);
}

const bool TCodice_livelli::enabled() const
{
  return _lev_enabled && enabled(1);
}

const bool TCodice_livelli::enabled(int levnum) const
{
  return (_lev_enabled && levnum<=MANY_MAG_LEV &&  levnum>0 && levnum<=_last_level);
}


const int TCodice_livelli::code_start(int levnum) const
{
  return packed_length(levnum-1)+1;
}


const int TCodice_livelli::code_length(int levnum)   const
{           
/*
  if (_lev_enabled && levnum <= MANY_MAG_LEV && levnum > 0)
    return(_code_length[levnum-1]);
  else
    return(0);
*/
  return enabled() ? _code_length[levnum-1] : 0;
}


const int TCodice_livelli::packed_length(int levnum) const
{
  int start=0;
  for (int i=0; _lev_enabled && i<levnum && levnum<MANY_MAG_LEV; i++) 
    start+= _code_length[i];
  return start;
}


const TString & TCodice_livelli::name(int levnum) const
{
  if (levnum<=MANY_MAG_LEV &&  levnum>0) 
    return(_name[levnum-1]);
  else  
    return(_name[0]);     // empty
}

const TString & TCodice_livelli::picture(int levnum)   const
{
  if (levnum<=MANY_MAG_LEV &&  levnum>0) 
    return(_picture[levnum-1]);
  else  
    return(_picture[0]);     // empty
}

void TCodice_livelli::pack_grpcode(TString & pc, const TString &codlev, const int levnum)
{
  pc.overwrite(codlev.left(_code_length[levnum-1]),packed_length(levnum-1));        
}

TString TCodice_livelli::unpack_grpcode(const TString& pc, const int levnum) const
{
  int start=0;
  for (int i=1; _lev_enabled && i<levnum; i++) 
    start+= _code_length[i-1];
  if (start>=pc.len())
    return("");
  else   {
    if (levnum < 1 || levnum > _last_level )
      return("");
    return(pc.mid(start,levnum == _last_level ? -1 : _code_length[levnum-1]));
  }
}

TString TCodice_livelli::build_tabcode(const TString & pc, const int levnum) const
{
  TString valore;
  valore << levnum;
  valore << unpack_grpcode(pc,levnum);
  if (_lev_enabled && valore.len()>1) 
    return valore;
  else  
    return "";
}


const TString & TCodice_livelli::group_descr(const char * packed_code, int levnum) const
{
  return _gruppi->decode(build_tabcode(packed_code,levnum));
}

void TCodice_livelli::set_sheetcolumn(TSheet_field &fld_righe,int field, int lev) const
{
  if (enabled(lev)) 
  {
    fld_righe.sheet_mask().field(field).show();
    fld_righe.set_column_header(fld_righe.cid2index(field),name(lev));
    fld_righe.sheet_mask().field(field).set_prompt(name(lev));
  } 
  else 
  {
    fld_righe.sheet_mask().field(field).hide();
    fld_righe.delete_column(fld_righe.cid2index(field));
  }
}

TCodice_livelli::TCodice_livelli() 
               : _last_firm(-1), _gruppi(NULL) {}  

TCodice_livelli::~TCodice_livelli() 
{
  if (_gruppi) 
    delete _gruppi;
}               



void TCodart_livelli::init()
{
  TConfig mgconfig(CONFIG_DITTA, "mg");
  load(mgconfig.get_bool("GESLIVART"), "FCA","GCA");
  // imposta il riconoscimento dei  caratteri del formato dell'ultima parte dell'articolo
  TString80 format;
  TTable tabfca("FCA");
  if (tabfca.last() == NOERR)
    format = tabfca.get("S1");
  
  _metachars.destroy();
  _metachars.add(new TMetachar(format),max(last_level()-1,0));
  
  update_firm();
}

TCodart_livelli ::TCodart_livelli() 
{   
  init();
}


TCodart_livelli::~TCodart_livelli() 
{
}

void TCodgiac_livelli::init()
{
  TConfig mgconfig(CONFIG_DITTA, "mg");
  load(mgconfig.get_bool("GESLIVGIAC"),"FCG","GCG");
  update_firm();
}

TCodgiac_livelli::TCodgiac_livelli()
{
  init();
}


TMagazzini::TMagazzini() : 
  TRecord_cache("MAG") , _stdmag(""), _mgconfig(CONFIG_DITTA, "mg")
{
  TTable mag("MAG");
  mag.first();
  while (!mag.eof() && !mag.get_bool("B1"))
    mag.next();
  if (!mag.eof()) 
  { 
    const TString& codtab = mag.get("CODTAB");
    _stdmag = codtab.left(3);
    _stddep = codtab.mid(3);
  }
}