#include <stdlib.h>
#include <ctype.h>

#include <applicat.h>
#include <expr.h>
#include <isam.h>
#include <mask.h>
#include <utility.h>
#include <validate.h>
#include <defmask.h>

#include <nditte.h>


typedef bool (*VAL_FUNC)(TMask_field&, KEY k);
HIDDEN const TArray* _parms;

HIDDEN int get_val_param_num() { return _parms->items(); }

HIDDEN const char* get_val_param(int i)
{ return i < _parms->items() ? (const char*)((const TString&) (*_parms)[i]):"" ; }

HIDDEN bool _expr_val(TMask_field& f, KEY)
{
  TTypeexp  type = atoi(get_val_param(0)) == 0 ? _numexpr :_strexpr;
  TExpression     e(get_val_param(1), type);

  for (int i = 0 ; i < e.numvar(); i++)
  {
    const char* s = e.varname(i);
    if (s[0] != '#') 
    { 
      TString80 err; err << "Non trovo la variabile " << s << " in " << e;
      return f.error_box((const char*)err);
    }  
    s++;
    const int fldid = atoi(s);
    if (type == _numexpr)
    {
      const real r(fldid == 0 ? f.get() : f.mask().get(fldid));
      e.setvar(i, r);
    }
    else
    {
      const char* v = fldid == 0 ? f.get() : f.mask().get(fldid);
      e.setvar(i, v);
    }
  }
  return bool(e);
}


HIDDEN bool _emptycopy_val(TMask_field& f, KEY)
{            
  if (f.get().empty())
  {
    const short id = atoi(get_val_param(0));
    const TFixed_string val(f.mask().get(id));
    if (val.not_empty()) 
    {
      f.set(val);
      f.on_hit();
    }  
  }
  return TRUE;
}


bool pi_check(const char * st, const char * paiva)
{
  int    tot = 0, y;
  TString16 stato (st); 
  TString16 pi (paiva); 

  if ((stato.not_empty()) && (stato != "IT")) return TRUE;
  if (pi.empty()) return TRUE;
  if (pi.len() != 11)
    return FALSE;

  for (int i = 0; i <= 9; i++)
  {
    if ((i + 1) & 0x0001) tot += (pi[i] - '0');
    else
    {
      y = (pi[i] - '0') * 2;
      if (y >= 10)
      { 
        tot += (y / 10); 
        y = y % 10; 
      } 
      tot += y; 
    } 
  } 
  y = 10 - (tot % 10);
  if (y == 10) y = 0;
  bool ok = (pi[10] == (y + '0'));

  return ok;
}


// Controlla la partita iva se e' non vuota ed italiana
HIDDEN bool _pi_val(TMask_field& f, KEY)
{
  if (f.mask().query_mode()) return TRUE;
  
  TString16 stato(f.mask().get(atoi(get_val_param(0)))), pi(f.get());

  if (stato.not_empty() && stato != "IT") return TRUE;
  if (pi.empty()) return TRUE;
  if (pi.len() != 11)
  {
    f.error_box("Lunghezza partita IVA diversa da 11");
    return FALSE;
  }

  bool ok = pi_check (stato, pi);
  if (!ok) 
  {
    if(f.dirty())
    {
      ok = f.yesno_box("Partita IVA errata, la accetto ugualmente?");
      if (ok) f.set_dirty(FALSE);
    }
    else ok = TRUE;      // Era gia' errata e la ho accettata
  }
  return ok;
}


HIDDEN bool __cf_check (const char * codcf)
{
  const TString16 cf (codcf);
  if (cf.len() != 16) return FALSE;

  const TFixed_string tab("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");

  int tot = 0, y;

  byte val[36][2];

  val[0][0] = 0; val[0][1] = 1;
  val[1][0] = 1; val[1][1] = 0;
  val[2][0] = 2; val[2][1] = 5; 
  val[3][0] = 3; val[3][1] = 7;
  val[4][0] = 4; val[4][1] = 9; 
  val[5][0] = 5; val[5][1] = 13; 
  val[6][0] = 6; val[6][1] = 15; 
  val[7][0] = 7; val[7][1] = 17; 
  val[8][0] = 8; val[8][1] = 19; 
  val[9][0] = 9; val[9][1] = 21;
  val[10][0] = 10; val[10][1] = 2;
  val[11][0] = 11; val[11][1] = 4;
  val[12][0] = 12; val[12][1] = 18;
  val[13][0] = 13; val[13][1] = 20; 
  val[14][0] = 14; val[14][1] = 11; 
  val[15][0] = 15; val[15][1] = 3; 
  val[16][0] = 16; val[16][1] = 6; 
  val[17][0] = 17; val[17][1] = 8; 
  val[18][0] = 18; val[18][1] = 12; 
  val[19][0] = 19; val[19][1] = 14; 
  val[20][0] = 20; val[20][1] = 16; 
  val[21][0] = 21; val[21][1] = 10; 
  val[22][0] = 22; val[22][1] = 22; 
  val[23][0] = 23; val[23][1] = 25; 
  val[24][0] = 24; val[24][1] = 24;
  val[25][0] = 25; val[25][1] = 23;
  val[26][0] = 0; val[26][1] = 1;
  val[27][0] = 1; val[27][1] = 0;
  val[28][0] = 2; val[28][1] = 5;
  val[29][0] = 3; val[29][1] = 7;
  val[30][0] = 4; val[30][1] = 9;
  val[31][0] = 5; val[31][1] = 13;
  val[32][0] = 6; val[32][1] = 15;
  val[33][0] = 7; val[33][1] = 17;
  val[34][0] = 8; val[34][1] = 19;
  val[35][0] = 9; val[35][1] = 21;
  
  for (int i = 0; i <= 14; i++)
  {
    y = tab.find(cf[i]);
    if (y >= 0) tot += val[y][(i + 1) & 0x1];
  }
  const bool ok = (cf[15] == (tot % 26) + 'A');

  return ok;
}


bool cf_check (const char * stato, const char * codcf)  
{
  TString16 cf (codcf);
  if (cf.empty()) 
    return TRUE;
  const bool ok = (cf.len() == 11 && isdigit(cf[0])) ? pi_check(stato, cf) : __cf_check(cf);
  return ok;
}


HIDDEN bool _cf_val(TMask_field& f, KEY key)
{
  if (f.mask().query_mode()) return TRUE;
  
  const TString16 cf(f.get());
  bool ok = TRUE;

  if (cf.empty()) return TRUE;
  if (cf.len() == 11 && isdigit(cf[0]))
  {
    TString16 stato(f.mask().get(atoi(get_val_param(0))));
    if (stato.not_empty() && stato != "IT") return TRUE;

    ok = pi_check (stato, cf);
  }
  else 
    ok = __cf_check(cf);
  if (!ok)
  {
    if(f.dirty())
    {
      ok = f.yesno_box("Codice fiscale errato: lo accetto ugualmente?");
      if (ok) f.set_dirty(FALSE);
    } 
    else ok = TRUE;             // Era gia' errato al caricamento quindi lo accetto
  }
  return ok;
}


HIDDEN bool     _xt_pi_val(TMask_field& f, KEY key)

{
  TMask& m = f.mask();
  if (m.query_mode()) return TRUE;
  
  TString16 value(f.get());                    
  if (value.empty()) return f.error_box("Partita IVA obbligatoria");
  
  const TString16 stato(m.get(atoi(get_val_param(0))));
  if (stato.not_empty() && stato != "IT") return TRUE;

  if (!_pi_val(f, key)) return FALSE;
  
  TLocalisamfile c(LF_COMUNI);

  const int pi = atoi(value.mid(7,3));
  
  for (int i = 1 ; i < 3; i++)
  {
    const int comune = atoi(get_val_param(i));      
    if (comune)
    {
      const TString16 com(m.get(comune));             // Comune residenza fiscale e residenza
      if (com.not_empty())
      {
        c.zero();
        c.put("COM", com);
        if (c.read() == NOERR)
        {
          const int s1 = c.get_int("UFFIVA1");
          const int s2 = c.get_int("UFFIVA2");
          const int s3 = c.get_int("UFFIVA3");
          if (pi != s1 && pi != s2 && pi != s3) 
          { 
            const char* ui = format("%03d", s1);
            if (f.yesno_box("Ufficio IVA della partita IVA non congruente: correggere in %s?", ui))
            {
              value.overwrite(ui, 7);
              f.set(value);
            }  
          }
          break;
        }
      }
    }
  }
  return TRUE;
}


HIDDEN bool _xtz_pi_val(TMask_field& f, KEY key)
{
  if (f.mask().query_mode()) return TRUE;
  TString16 pi(f.get());
  if (pi.empty()) return TRUE;
  return _xt_pi_val(f, key);
}


HIDDEN bool _xt_cf_val(TMask_field& f, KEY key)
{
  if (f.mask().query_mode()) return TRUE;
  
  TString16 cf(f.get()); 
  if (cf.empty()) return f.error_box("Codice fiscale obbligatorio");
  
  if (!_cf_val(f, key)) return FALSE;
  
  if (cf.len() == 11 && isdigit(cf[0]))
  {
    //   if (!_xt_pi_val(f, key)) return FALSE;
    //   TString16 stato(f.mask().get(atoi(get_val_param(0))));
    //   if ((stato.not_empty()) && (stato != "IT")) return TRUE;
    return TRUE;
  }

  bool passed = __cf_check(cf);
  if (!passed) return TRUE;

  TMask& m = f.mask();
  
  TMask_field& fld_sex = m.field(atoi(get_val_param(1)));
  TMask_field& fld_dat = m.field(atoi(get_val_param(2)));
  TMask_field& fld_com = m.field(atoi(get_val_param(3)));
  
  const char sesso = fld_sex.get()[0];
  TString16 data(fld_dat.get());
  TString16 com(fld_com.get());

  TString16 wm("LMNPQRSTUV");
  int p;
  if ((p = wm.find(cf[6])) != -1) cf[6] = '0' + p;
  if ((p = wm.find(cf[7])) != -1) cf[7] = '0' + p;
  if ((p = wm.find(cf[9])) != -1) cf[9] = '0' + p;
  if ((p = wm.find(cf[10])) != -1) cf[10] = '0' + p;
  if ((p = wm.find(cf[12])) != -1) cf[12] = '0' + p;
  if ((p = wm.find(cf[13])) != -1) cf[13] = '0' + p;
  if ((p = wm.find(cf[14])) != -1) cf[14] = '0' + p;
  int gn = atoi(cf.mid(9,2));
  
  if ((sesso == 'F' && gn < 40) || (sesso == 'M' && gn > 40))
  {
    passed = fld_sex.yesno_box("Sesso non congruente al codice fiscale:\n"
                               "correzione automatica?");
    if (passed) fld_sex.set(sesso == 'M' ? "F" : "M");
    else return TRUE;
  }

  if (gn > 40) gn -= 40;        // Aggiusta giorno di nascita delle donne

  // Controllo data di nascita
  wm = "ABCDEHLMPRST";
  if (data.not_empty())
  {
    const TDate d(data);
    int err = 0;
    if ((d.year() % 100) != atoi(cf.mid(6, 2)))
      err = 1;
    if (wm[d.month() - 1] != cf[8])
      err = 2;
    if (d.day() != gn)
      err = 3;
    if (err != 0)
    {
      const char* const what = err==1 ? "Anno" : (err==2 ? "Mese" : "Giorno");
      passed = fld_dat.yesno_box("%s di nascita non congruente al codice fiscale: correzione automatica?", what);
      if (passed) data = "";
      else return TRUE;
    }
  }
  
  if (data.empty())
  {
    const int mn = wm.find(cf[8]) + 1;
    if (mn > 0)
    {
      const TDate d(gn, mn, 1900 + atoi(cf.mid(6, 2)));
      fld_dat.set(d.string());
    }
  }

  // Controllo del comune di nascita
  const char* const codcom = cf.mid(11, 4);
  if (com.not_empty() && com != codcom)
  {
    passed = fld_com.yesno_box("Comune non congruente al codice fiscale: correzione automatica?");
    if (passed) com = "";
    else return TRUE;
  }
  if (com.empty())
  {
    fld_com.set(codcom);
    fld_com.on_key(K_TAB);
  }  
  
  return TRUE;
}


HIDDEN bool     _xtz_cf_val(TMask_field& f, KEY key)

{
  if (f.mask().query_mode()) return TRUE;
  const char* cf = f.get();
  return (*cf == '\0') ? TRUE : _xt_cf_val(f, key);
}


HIDDEN bool _notempty_val(TMask_field& f, KEY)
{
  return f.mask().query_mode() || f.get().not_empty();
}


HIDDEN bool _date_cmp(TMask_field& f, KEY)
{
  TFixed_string s(f.get());
  if (s.empty()) return TRUE;

  TDate d0(s), d1(f.mask().get(atoi(get_val_param(1))));
  TFixed_string op(get_val_param(0));

  if (op == "==") return d0 == d1;
  if (op == "!=") return d0 != d1;
  if (op == ">") return d0 > d1;
  if (op == "<") return d0 < d1;
  if (op == ">=") return d0 >= d1;
  if (op == "<=") return d0 <= d1;

#ifdef DBG  
  f.error_box("Bad date operator '%s' in field %d", (const char*) op, f.dlg()); 
#endif  

  return FALSE;
}


HIDDEN bool     _fixlen_val(TMask_field& f, KEY)
{
  const TFixed_string  s(f.get());

  if (s.empty()) return TRUE;

  const int length = atoi(get_val_param(0));
  const bool ok = s.len() == length;
  if (!ok) f.error_box("Lunghezza errata: deve essere %d", length);
  return ok;
}


HIDDEN bool _mtcheck_val(TMask_field& f, KEY)
{
  const int month = atoi(f.get());
  if (month < 1 || month > 13) return FALSE;

  TLocalisamfile d(LF_NDITTE);
  d.zero();
  d.put(NDT_CODDITTA, main_app().get_firm());
  d.read();
  if (d.bad()) return TRUE;
  if (d.get_char(NDT_FREQVIVA) == 'M') return TRUE;
  return  month == 13 || (month % 3) == 0;
}


HIDDEN bool _reqif_val(TMask_field& f, KEY k)
{
  if (k == K_ENTER)
  {
    if (f.get().not_empty()) return TRUE;
    const int nparms = get_val_param_num();
    for (int i = 0 ; i < nparms; i++)
    {
      const short id = atoi(get_val_param(i));
      if (id > 0 && f.mask().get(id).not_empty()) 
        return FALSE;
    }
  }  
  return TRUE;
}


HIDDEN bool _autoexit_val(TMask_field& f, KEY key)
{
  TMask& m = f.mask();
  
  if (!m.query_mode() || key == K_ENTER) 
    return TRUE;

  const int next = m.next_fld();

  if (next != DLG_NULL && next != f.dlg() && m.field(next).in_key(0))
  {               
    const byte last = m.num_keys();
    
    for (byte k = 1; k <= last; k++)
      if (f.in_key(k) && m.field(next).in_key(k))
        return TRUE;
  }      

  const int nparms = get_val_param_num();
  bool one_not_empty = FALSE;
  for (int i = nparms; i-- > 0;)
  {
    const short id = f.atodlg(get_val_param(i));
    const TMask_field& c = f.mask().field(id);
    const bool empty = c.get().empty();
    if (empty)
    { 
      if (c.check_type() != CHECK_NONE || one_not_empty)
        return TRUE;
    } 
    else 
      one_not_empty = TRUE;
  }
  
  if (one_not_empty)
    f.mask().stop_run(K_AUTO_ENTER);

  return TRUE;
}


HIDDEN bool _numcalc_val(TMask_field& f, KEY k)
{
  if (k != K_TAB) return TRUE;
  TExpression e(get_val_param(0), _numexpr);

  for (int i = 0 ; i < e.numvar(); i++)
  {
    const char* s = e.varname(i);
    if (s[0] != '#') 
    { 
      TString80 err; err << "Cannot load variable " << s << " in " << e;
      return f.error_box((const char*)err);
    }  
    s++;
    const int fldid = atoi(s);
    e.setvar(i, fldid == 0 ? f.get() : f.mask().get(fldid));
  }
  const TFixed_string s((const char*)e);
  f.set(s);
  return TRUE;
}


HIDDEN bool _strcalc_val(TMask_field& f, KEY k)
{
  if (k != K_TAB) return TRUE;
  TExpression     e(get_val_param(0), _strexpr);

  for (int i = 0 ; i < e.numvar(); i++)
  {
    const char* s = e.varname(i);
    if (s[0] != '#') 
    { 
      TString80 err; err << "Cannot load variable " << s << " in " << e;
      return f.error_box((const char*)err);
    }  
    s++;
    const int fldid = atoi(s);
    e.setvar(i, fldid == 0 ? f.get() : f.mask().get(fldid));
  }
  TFixed_string s((const char*) e);
  f.set(s);
  return TRUE;
}


HIDDEN bool _onereq_val(TMask_field& f, KEY k)
{
  if (k != K_ENTER) return      TRUE;
  const TMask& m = f.mask();
  if (m.mode() == MODE_QUERY) return TRUE;
  const int nparms = get_val_param_num();

  for (int i = 0; i < nparms ; i++)
  {
    const char* s = m.get(atoi(get_val_param(i)));
    if (*s) return TRUE;
  }
  return FALSE;
}

HIDDEN bool _chkfld_val(TMask_field& f, KEY k)
{
  if (f.to_check(k))
  {
    const int fldid = atoi(get_val_param(0));

    if (fldid == THIS_FIELD)
    {
      if (!f.required() && f.get().empty())
        return TRUE;
      else
        return f.check();
    }
    
    TMask_field & chkfld = f.mask().field(fldid);

    chkfld.set(f.get());
    return chkfld.check();
  }
  return TRUE;
}

HIDDEN bool _filename_val(TMask_field& f, KEY)
{
  const TFilename fn(f.get());
  return fn.ok();
}

HIDDEN bool _zerofill_val(TMask_field& f, KEY k)
{
  if (f.to_check(k))
  {
    const int columns = atoi(get_val_param(0));
    TString& val = f.get();
    if (val.len() < columns && real::is_natural(val))
    {
      val.right_just(columns, '0');
      f.set(val);
    }
  }
  return TRUE;
}

HIDDEN bool _alpha_val(TMask_field& f, KEY k)
{
  if (f.to_check(k))
  {
    const TString& s = f.get();
    for (int i = 0; s[i]; i++)
      if (!isalpha(s[i])) 
        return error_box("Sono ammessi solo caratteri alfabetici");
  }
  return TRUE;
}

HIDDEN bool _not_empty_chkfld_val(TMask_field& f, KEY k)
{
  bool ok = TRUE;
  if (f.to_check(k) && f.get().not_empty())
  {
    TBrowse * b = ((TEdit_field&)f).browse();
    if (b != NULL) ok = b->check(k == K_TAB ? RUNNING_CHECK : FINAL_CHECK);
  }
  return ok;
}

HIDDEN bool _sconto_val(TMask_field& f, KEY key)
{
  if ( f.to_check( key ) )
  {
    TString work( f.get( ) );
    TString goodexp;
    TString num;
    
    // Elimina gli spazi molesti
    work.strip_spaces( );
    // Per ora nessun errore
    int _errorpos = -1;
    
    int i = 0;
    const int wlen = work.len( );
    // Flag che indica se sono nella parte decimale di un numero
    bool dec = FALSE;
    // Flag che indica se si attende l'inizio di un numero
    bool startnum = TRUE;
    // Flag che indica se siamo all'inizio di un numero   
    while ( i < wlen && _errorpos < 0 )
    { 
      char c = work[ i++ ];     
      switch( c )
      {
      case '+': 
      case '-':
        // Se ero in in numero ...
        if( !startnum )
        {
          real newval( num );
          if( newval >= 0 )
            goodexp << '+';
          goodexp << num;
        }
        // Inizia il nuovo numero
        num = ( c == '-' )?"-":"";  
        startnum = TRUE;
        dec = FALSE;
        break;         
      case '0':  
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        num << c;
        startnum = FALSE;
        break;
      case '.':
      case ',':
        // Se siamo già nella parte decimale ...
        if( dec )
        {
          // Segnala un errore
          _errorpos = i;
          break;
        }
        // Se occorreva un numero ci metto lo 0
        if( startnum )
          num << '0';
        // Interpreto la virgola come punto
        num << '.';
        dec = TRUE;       
        startnum = TRUE;
        break;
      default:
        _errorpos = i;
        break;         
      }
    }
    // Controlla la validità
    bool _valid = ( _errorpos < 0 );
    // Se richiesto segnala l'errore
    if( !_valid )
      warning_box( "Espressione di sconto non valida. Errore sul carattere %d.", _errorpos + 1 );
    if( _valid )
    {
      // Aggiunge l'ultimo numero preso
      real lastval( num );
      if( lastval >= 0 )
        goodexp << '+';
      goodexp << num;
      // Assegna la nuova espressione formattata bene
      f.set( goodexp );
      // Bravo, hai vinto una bambolina
      return TRUE;
    } 
  else
  {
    // Validate fallito
    return FALSE;
  }
  }
  return TRUE;
}

HIDDEN bool _ora_val(TMask_field& f, KEY key)
{
  if ( f.to_check( key ) )
  {
    TFixed_string ora( f.get( ), 6 );
    if ( ora.not_empty( ) || f.required( ) )
    {
      if ( isdigit( ora[ 0 ] ) )
      {
        if ( ora[ 2 ] != ':' )
        {
          if ( ora.len( ) > 4 )
            ora.overwrite( ":", 2 );
          else
            ora.insert( ":", 2 );            
        }
      }
      bool ok;
      ok = ((isdigit(ora[0]))&&(isdigit(ora[1]))&&(isdigit(ora[3]))&&(isdigit(ora[4])));
      ok &= ((atoi(&(ora[0]))<24)&&(atoi(&(ora[3]))<60));
      if ( !ok )
      {
        error_box("Ora errata o formato non valido");
        return FALSE;
      }
      else  
      {
        f.set( ora );
        return TRUE;        
      }
    }
  }
  return TRUE;
}



#define MAX_FUNC 24

HIDDEN VAL_FUNC _global_val_func[MAX_FUNC] = 
{
  _expr_val,
  _emptycopy_val,
  _pi_val,
  _cf_val,
  _notempty_val,
  _date_cmp,
  _xt_pi_val,
  _xt_cf_val,
  _xtz_pi_val,
  _xtz_cf_val,
  _fixlen_val,
  _mtcheck_val,
  _reqif_val,
  _autoexit_val,
  _numcalc_val,
  _strcalc_val,
  _onereq_val,
  _chkfld_val,
  _filename_val,
  _zerofill_val,
  _alpha_val,
  _not_empty_chkfld_val,
  _ora_val,
  _sconto_val,

};

bool validate(int fn, TMask_field& f, KEY k, const TArray& parms)
{                                                                
  _parms = &parms;
  return (fn >= 0 && fn < MAX_FUNC) ? _global_val_func[fn](f, k) : TRUE;
}

