#include <applicat.h>
#include <form.h>
#include <automask.h>
#include <progind.h>
#include <recset.h>
#include <recarray.h>
#include <utility.h>

#include "777.h"
#include "777100a.h"

#include <anagr.h>
#include <anafis.h>
#include <anagiu.h>
#include <comuni.h>
#include <nditte.h>
#include "base.h"
#include "quadroc.h"

///////////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////////

static int anno_dic()
{ 
  static int anno = 0;
  if (anno == 0)
  {
    TConfig ini(CONFIG_STUDIO);
    anno = ini.get_int("AnnoDic");
  }
  return anno; 
}

#define CF_PRODUTTORE "00909290355"

///////////////////////////////////////////////////////////
// TForm770
///////////////////////////////////////////////////////////

class TRecord770;
class TTrasferimento770;

class TForm770 : public TForm
{
  TCursor* _sortedcur;

  int _index;
  TArray _records;
  TPointer_array _positions;

protected:
  bool compatible(const TRectype& r1, const TRectype& r2);
  void add_rec(TRectype& r1, const TRectype& r2);

  long raggruppa_c();
  int first();
  int next();

  void transfer_section(TPrint_section& body, int rigo,
                        TRecord770& rec, TTrasferimento770& file);
  char log2rec(int num, int& rpm) const;

protected:

  virtual TCursor* cursor() { return _sortedcur ? _sortedcur : TForm::cursor(); }
  virtual bool validate(TForm_item &cf, TToken_string &s);

public:
  long trasfer(long codditta, TTrasferimento770& file, char tipo, int rpm);

  TForm770(const char* name);
  virtual ~TForm770();
};

///////////////////////////////////////////////////////////
// TRecord770
///////////////////////////////////////////////////////////

enum { CODE_SIZE = 8, FIELD_SIZE = 16, BLOCK_SIZE = 24, HEADER_SIZE = 89, USEABLE_SIZE = 1800, TOTAL_SIZE = 1900 };

struct TField770 : public TObject
{
  TString _desc;
  int     _pos;   // Base 1
  int     _len;
  char    _type;  // A/N
};

class TTracciato770 : public TObject
{
  char _tipo;
  TArray _fields;

protected:
  void add_field(const char* name, char type, int pos, int len, int filedno = 0);
  void add_filler(int pos, int len, char tipo = 'A')
  { add_field("Filler", tipo, pos, len); }

public:
  const TField770& field(int pos) const;
  void auto_fill(TString& buffer) const;

  TTracciato770(char tipo);
  virtual ~TTracciato770();
};

class TTracciati770 : public TObject
{
  TArray _trc;
  TAssoc_array _form;

public:
  const TTracciato770& tracciato(char tipo);
  TForm770& form(const char* quadro, char& tipo, int& rpm);

  void destroy();
  
  TTracciati770();
  virtual ~TTracciati770();
} _trc770;

class TCache770 : public TObject
{
  TArray _files;
  TAssoc_array _tables;

public:
  const TRectype& get(int num, const char* key);
  const TRectype& get(int num, long key);
  void destroy();

  TCache770() { }
  virtual ~TCache770() { }
} _cache770;

class TRecord770 : public TObject
{
  TString _buffer;

protected: // TObject
  virtual TObject* dup() const { return new TRecord770(*this); }
  virtual void print_on(ostream& outs) const;
  virtual void read_from(istream& ins);

protected: // TObject
  const TTracciato770& tracciato() const 
  { return _trc770.tracciato(tipo_record()); }
  
  const TField770& get_field(int pos) const
  { return tracciato().field(pos); }

  void set(const TField770& fld, const char* val);
  int calculate_blocks(const char* val) const;

public:
  void set(int pos, const char* val);
  void set(int pos, int val);
  void set(int pos, long val);
  void set(int pos, const real& val);
  void set(int pos, const TDate& val);
  void set(int pos, char val);
  void set(int pos, bool val);
  bool add(const char* code, const char* val);

  const char* get(int pos, TString& str) const;
  int get_int(int pos) const;
  char get_char(int pos) const;

  const TRecord770& operator=(const TRecord770& rec) 
  { _buffer = rec._buffer; return *this; }
  
  char tipo_record() const { return _buffer[0]; }
  void tipo_record(char tipo) 
  { _buffer[0] = tipo; tracciato().auto_fill(_buffer); }

  void azzera_campi_non_posizionali();
  bool ha_campi_non_posizionali() const { return strchr("EH", tipo_record()) != NULL; }
  bool ha_campi_non_posizionali_compilati() const
  { return _buffer[90] > ' '; }

  bool valid() const;

  TRecord770();
  TRecord770(char tipo);
  TRecord770(const TRecord770& rec);
  TRecord770(const TRectype& rec);
  virtual ~TRecord770();
};

///////////////////////////////////////////////////////////
// TTrasferimento770
///////////////////////////////////////////////////////////

class TTrasferimento770 : public TObject
{
  TFilename _name;
  ifstream* _in_stream;
  ofstream* _out_stream;

  long _cod_ditta, _codan_dic;
  TString8 _codatt_dic;
  char _tipoa_dic;
  TString16 _codfis_dic;
  TString _ragsoc_dic;
  bool _save_headers;
  
public:
  bool open(const char* path = "", char mode = 'r', int volume = 0);
  bool close();
  bool write(const TRecord770& rec);
  bool read(TRecord770& rec);
  bool eof() const { return _in_stream && _in_stream->eof(); }

  const char* default_name() const;

  TTrasferimento770& operator<<(const TRecord770& rec)
  { write(rec);  return *this; }
  TTrasferimento770& operator>>(TRecord770& rec)
  { read(rec); return *this; }

  const TString& read_codfis_dic(const TRectype& rec);
  const TString& cod_fis_dic() const { return _codfis_dic; }

  int conta_certificazioni() const;
  bool casella_prospetto_st() const;
  bool casella_prospetto_sx() const;
  bool append_record_b();
  long append_quadro(const char* quadro, long codditta, TProgind& pi);

  bool split(const char* dest_path);
  void remove();

  void save_headers(bool s) { _save_headers = s; }
  bool save_headers() const { return _save_headers; }

  TTrasferimento770(const char* name = "", char mode = 'r', int volume = 0);
  virtual ~TTrasferimento770();
};

///////////////////////////////////////////////////////////
// Implementazioni
///////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////
// TForm770
///////////////////////////////////////////////////////////

bool TForm770::validate(TForm_item &cf, TToken_string &s)
{ 
  const TString code(s.get(0)); // prende il primo parametro, il codice del messaggio
  if (code == "_PERIODO_RIF")
  {
    const TRectype& curr = relation()->curr();
    int anno = curr.get_int("QLAP");
    int mese = curr.get_int("QLMP");
    TString8 str;
    str.format("%02d%04d", mese, anno);
    cf.set(str);
    return true;
  }
  return TForm::validate(cf, s);
}

void TForm770::transfer_section(TPrint_section& body, int rigo,
                                TRecord770& rec, TTrasferimento770& file)
{
  TString8 basecode, code, type;
  TString str;

  body.update(); 
  for (word f = 0; f < body.fields(); f++)
  {
    TForm_item& fi = body.field(f);
    str = fi.get(); str.trim();
    if (fi.shown() && str.not_empty())
    {
      basecode = fi.get_special_value("TRASFER");
      if (basecode.not_empty())
      {
        type = fi.get_special_desc("TRASFER");
        if (!type.blank())
        {
          if (type == "BO")
          {
            str = str.blank() ? "" : "1";
            str.right_just(16);
          } else
          if (type == "CU")
          {           
            const int dot = str.find('.');
            if (dot >= 0)
              str.cut(dot);
            if (real::is_null(str))
              str.cut(0);
            else  
              str.right_just(16);
          } else
          if (type == "DT")
          {
            str.strip("-");
            str.right_just(16);
          } else
          if (type == "NU")
            str.right_just(16);
        }
        else
        {
          if (fi.fields() > 0)
          {
            const TFieldref& fr = ((TForm_string&)fi).field(0);
            const int num = fr.file();
            const char* fn = fr.name();
            const TFieldtypes ft = cursor()->curr(num).type(fn);
            switch (ft)
            {
            case _boolfld:
              if (!str.blank())
                str = "1";
              str.right_just(16);
              break;
            case _datefld:
              if (str.len() == 10 && str[2] == '-' && str[5] == '-')
              {
                str.strip("-"); 
                str.right_just(16);
              }
              break;
            case _realfld:
              {   
                const int dot = str.find('.');
                if (dot >= 0)
                  str.cut(dot);
                if (real::is_null(str))
                  str.cut(0);
                else  
                  str.right_just(16);
              }
              break;
              
            default:
              break;
            }
          }

        }
        
        if (str.not_empty())
        {
          const int vero_rigo = atoi(basecode.mid(2, 3)) - 1 + rigo;
          code.format("%c%c%03d%c%c%c", 
                      basecode[0], basecode[1], vero_rigo, 
                      basecode[5], basecode[6], basecode[7]);
          if (!rec.add(code, str))
          {
            file << rec;
            rec.azzera_campi_non_posizionali();
            rec.add(code, str);
          }
        }     
      }
    }
  }
}

bool TForm770::compatible(const TRectype& r1, const TRectype& r2)
{
  if (r1.empty() || r2.empty())
    return true;

  if (r1.get_long(QUC_CODDITTA) != r2.get_long(QUC_CODDITTA))
    return false;
  if (r1.get_char(QUC_TIPOA) != r2.get_char(QUC_TIPOA))
    return false;
  if (r1.get_long(QUC_CODANAGR) != r2.get_long(QUC_CODANAGR))
    return false;
  if (r1.get_char(QUC_CAUSALE) != r2.get_char(QUC_CAUSALE))
    return false;
  if (r1.get_int(QUC_TASSAZIONE) != r2.get_int(QUC_TASSAZIONE))
    return false;

  const real p1 = r1.get_real(QUC_PERC);
  const real p2 = r2.get_real(QUC_PERC);
  bool yes = (p1 == ZERO && p2 != ZERO) || (p1 == p2);
  return yes;
}

void TForm770::add_rec(TRectype& r1, const TRectype& r2)
{
  if (r1.empty())
    r1 = r2;
  else
  {
    for (int f = r1.items()-1; f >= 0; f--)
    {
      const char* name = r1.fieldname(f);
      if (r1.type(name) == _realfld)
      {
        real val = r1.get_real(name);
        val += r2.get_real(name);
        r1.put(name, val);
      }
    }
    r1.put(QUC_PERC, r2.get(QUC_PERC));
  }
}

long TForm770::raggruppa_c()
{
  TCursor& cur = *cursor();
  TRectype& rc = cur.curr();
  _records.destroy();
  for (cur = 0; cur.ok(); ++cur)
  {
    bool append = true;
    const int last = _records.items()-1;
    if (last >= 0)
    {
      TRectype& sum = (TRectype&)_records[last];
      if (compatible(sum, rc))
      {
        add_rec(sum, rc);
        append = false;
      }
    }
    if (append)
    {
      _records.add(rc);
      _positions.add_long(cur.pos()+1);
    }
  }                
 
  return _records.items();
}

int TForm770::first()
{
  TCursor& cur = *cursor();

  cur = 0;
  bool ok = cur.ok();
  
  if (cur.file().num() == LF_QUAC)
  {
    if (ok)
    {
      raggruppa_c();
      _index = 0;
      cur.curr() = (TRectype&)_records[0];
    }
  }

  return ok ? NOERR : _iseof;
}

int TForm770::next()
{
  TCursor& cur = *cursor();

  bool ok;
  if (cur.file().num() == LF_QUAC)
  {
    _index++;
    ok = _index < _records.items();
    if (ok)
    {
      cur = _positions.get_long(_index) - 1;
      cur.curr() = (TRectype&)_records[_index];
    }
  }
  else
  {
    ++cur;
    ok = cur.ok();
  }

  return ok ? NOERR : _iseof;
}

long TForm770::trasfer(long codditta, TTrasferimento770& file, 
                       char rectype, int rpm)
{
  TCursor& cur = *cursor();
  
  // Filtra il cursore sulla ditta corrente
  TRectype filter(cur.curr());
  filter.zero();
  filter.put(BSE_CODDITTA, codditta);
  cur.setregion(filter, filter);
  
  const long items = cur.items();
  if (items > 0)         // Se c'e almeno un record ...
  {
    cur.freeze(true);    // Per efficienza congela il cursore

    // Inizializza record col primo modulo
    TRecord770 rec(rectype);
    int modulo = 1;
    int rigo = 1;
    
    rec.set(2, file.cod_fis_dic());
    rec.set(3, modulo);

    switch (rectype)
    {
    case 'E': rec.set(8, CF_PRODUTTORE); break;
    case 'H': rec.set(9, CF_PRODUTTORE); break;
    default : break;
    }

    TPrint_section& body = section('B', odd_page);

    int err = first();   // Deve sempre tornare NOERR
    for (; err == NOERR; err = next())
    {
      transfer_section(body, rigo, rec, file);
      rigo++;
      if (rigo > rpm)
      {
        // Scrivi record se non vuoto
        if (rec.ha_campi_non_posizionali_compilati())
          file << rec;
        
        // Avanza al prossimo modulo ed azzera la riga
        rigo = 1;
        modulo++;
  
        rec.azzera_campi_non_posizionali();
        rec.set(3, modulo);
      }
    }

    // Ultimo record parzialmente compilato
    if (rec.ha_campi_non_posizionali_compilati())
      file << rec;
    
    cur.freeze(false);
  }

  return items;
}

TForm770::TForm770(const char* name)
: TForm(name), _sortedcur(NULL)
{
  const char* key = NULL;
  
  switch (TForm::cursor()->file().num())
  {
  case LF_QUALA: key = "CODDITTA|216@->RAGSOC|TIPOA|CODANAGR"; break;
  default     : break;
  }
  if (key)
    _sortedcur = new TSorted_cursor(TForm::relation(), key);
}

TForm770::~TForm770()
{
  if (_sortedcur)
    delete _sortedcur;
}

///////////////////////////////////////////////////////////
// TTracciato770
///////////////////////////////////////////////////////////

void TTracciato770::add_field(const char* name, char type, int pos, int len, int fldno)
{ 
  switch (type)
  {
  case 'B':
    CHECKD(len == 1, "Booleano di lunghezza sospetta", len);
    type = 'N';
    break;
  case 'C':
    CHECKD(len == 16, "Codice fiscale di lunghezza sospetta", len);
    type = 'A';
    break;
  case 'D':
    CHECKD(len == 6 || len == 8, "Data di lunghezza sospetta", len);
    type = 'N';
    break;
  case 'P':
    CHECKD(len == 2, "provincia di lunghezza sospetta", len);
    type = 'A';
    break;
  default:
    break;
  }

  TField770* info = new TField770;
  info->_desc = name;
  info->_type = type;
  info->_pos  = pos;
  info->_len  = len;

  if (fldno <= 0)
    fldno = _fields.add(info)+1;
  else
    _fields.add(info, fldno-1);
}

const TField770& TTracciato770::field(int pos) const
{
  TField770* info = (TField770*)_fields.objptr(pos-1);
  CHECKD(info, "Campo non valido ", pos);
  return *info;
}

void TTracciato770::auto_fill(TString& buffer) const
{
  buffer.fill(' ', TOTAL_SIZE);
  for (int f = _fields.last(); f >= 0; f = _fields.pred(f))
  {
    const TField770& info = (const TField770&)_fields[f];
    if (info._desc == "Filler")
    {
      const char fill = info._type == 'N' ? '0' : ' ';
      char* index = buffer.get_buffer();
      index += info._pos - 1;
      memset(index, fill, info._len);
    }
  }
  buffer[0] = _tipo;
  buffer.overwrite("A\r\n", TOTAL_SIZE-3);
}

TTracciato770::TTracciato770(char tipo) : _tipo(tipo)
{
  if (strchr("ABEHZ", tipo) == NULL)
    NFCHECK("Tipo record non valido: %c", tipo);
  
  add_field("Tipo record", 'A', 1, 1);                     // 1
  
  if (tipo == 'A')
  {
    add_filler(2, 14);                                        // 2 
    add_field("Codice fornitura",              'N',  16,  5); // 3
    add_field("Tipo fornitore",                'N',  21,  2); // 4
    add_field("Codice fiscale del fornitore",  'C',  23, 16);
    add_filler(39, 483);
    // Dichiarazione su pi� invii
    add_field("Progressivo dell'invio",        'N', 522,  4); // 7
    add_field("Numero totale degli invii",     'N', 526,  4); 
  } else
  if (tipo == 'B')
  {
    add_field("Codice fiscale dichiarante",           'C',  2, 16); // 2 
    add_field("Progressivo modulo",                   'N', 18,  8);
    add_filler( 26, 3);
    add_filler( 29,25);
    add_field("Spazio a disposizione",                'A', 54, 20); 
    add_field("CF del produttore del software",       'C', 74, 16); // 7
    
    add_field("Flag conferma",                        'N', 90,  1); 
    
    // Tipo di dichiarazione
    add_field("Dichiarazione correttiva nei termini", 'N', 91,  1); // 9
    add_filler( 92, 1);                                             
    add_field("Dichiarazione integrativa",            'N', 94,  1); 
    add_field("Eventi eccezzziunali veramente",       'N',102,  2); // 12

    // Dati del contribuente
    add_field("Cognome",                              'A', 95, 24); // 13
    add_field("Nome",                                 'A',119, 20);
    add_field("Denominazione (Alternativo a 25 e 26)",'A',139, 60);
    add_filler(199,11);
    add_filler(210, 1);
    add_filler(211, 1);

    add_field("Codice Attivit�",                      'N', 212, 5); // 19
    
    add_field("Natura giuridica",                     'N', 486, 2, 33); // 33
    add_field("Data variazione sede legale",          'D', 488, 6);
    add_field("Comune della sede legale",             'A', 494,40);
    add_field("Sigla della provincia sede legale",    'A', 534, 2);
    add_field("Indirizzo della sede legale",          'A', 536,35);
    add_field("CAP del comune della sede legale",     'A', 571, 5);
    add_field("Codice comune",                        'A', 576, 4);
    add_field("Data variazione domicilio fiscale",    'N', 580, 6); // 40 
    add_field("Comune del domicilio fiscale",         'A', 586,40);
    add_field("Provincia del domicilio fiscale",      'A', 626, 2);
    add_field("Indirizzo del domicilio fiscale",      'A', 628,35);
    add_field("CAP del domicilio fiscale",            'N', 663, 5);
    add_field("Codice comune",                        'A', 668, 5);
    add_field("Stato",                                'N', 672, 1);
    add_field("Situazione",                           'N', 673, 1);

    add_field("Firma del dichiarante",                'N', 760, 1, 54); // 54

    add_field("Redazione della dichiarazione",        'N', 796, 1, 75); // 75
    add_field("Numero comunicaz. lavoro dipendente",  'N', 797, 8);
    add_field("Numero comunicaz. lavoro autonomo",    'N', 805, 8);
    add_field("Casella prospetto ST",                 'N', 813, 1);
    add_field("Casella prospetto SX",                 'N', 814, 1);

    add_field("Codice fiscale",                       'C',1030,16, 89); // 89
    add_field("Denominazione (Alternativo a 91 e 92)",'A',1046,60);
    add_field("Cognome",                              'A',1106,24);
    add_field("Nome",                                 'A',1130,20);
    add_field("Comune",                               'A',1150,40);
    add_field("Provincia",                            'A',1190, 2);
    add_field("Codice comune",                        'A',1192, 4);
    add_field("Cap comune",                           'A',1196, 5);
    add_field("Tipologia",                            'A',1201,15);
    add_field("Indirizzo",                            'A',1216,35);
    add_field("Numero Civico",                        'A',1251,10);     // 99

    add_field("Codice fiscale del rappresentante",    'C',1406,16, 106); // 106
    add_field("Codice carica del rappresentante",     'N',1422, 2);
    add_field("Data carica del rappresentante",       'D',1424, 8);      
    add_filler(1432, 11);
    add_field("Denominazione (Alternat. a 111 e 112)",'A',1443,60);      // 110 
    add_field("Cognome",                              'A',1503,24);
    add_field("Nome",                                 'A',1527,20);
    add_field("Sesso",                                'A',1547, 1);
    add_field("Data di nascita",                      'N',1548, 8);
    add_field("Comune di nascita",                    'A',1556,40);      // 115
    add_field("Provincia di nascita",                 'A',1596, 2);
    add_field("Comune residenza",                     'A',1598,40);
    add_field("Provincia residenza",                  'A',1638, 2);
    add_field("Cap residenza",                        'N',1640, 5);
    add_field("Indirizzo residenza",                  'A',1645,35);      // 120

  } else
  if (tipo == 'E')
  {
    add_field("Codice fiscale del dichiarante",       'C',  2, 16); // 2
    add_field("Progressivo modulo",                   'N', 18,  8); 
    add_field("Spazio a disposizione",                'A', 26,  3); 
    add_field("Tipo operazione",                      'A', 29,  1); 
    add_filler(30, 24);
    add_field("Spazio a disposizione",                'A', 54, 20);
    add_field("Codice fiscale della software house",  'A', 74, 16); // 8
  } else
  if (tipo == 'H')
  {
    add_field("Codice fiscale del dichiarante",       'C',  2, 16); // 2
    add_field("Progressivo comunicazione",            'N', 18,  8); 
    add_field("Spazio a disposizione",                'A', 26,  3); 
    add_field("Tipo operazione",                      'A', 29,  1); 
    add_field("Codice fiscale del percipiente",       'A', 30, 16); // 6
    add_filler(46, 8);
    add_field("Spazio a disposizione",                'A', 54, 20);
    add_field("Codice fiscale della software house",  'A', 74, 16); // 9
  } else
  if (tipo == 'Z')
  {
    add_filler(2, 14);                                  // 2 
    add_field("Numero record di tipo 'B'", 'N', 16, 9);
    add_field("Numero record di tipo 'E'", 'N', 25, 9);
    add_field("Numero record di tipo 'F'", 'N', 34, 9);
    add_field("Numero record di tipo 'G'", 'N', 43, 9); // 6
    add_field("Numero record di tipo 'H'", 'N', 52, 9);
    add_filler(61, 1837);
  }
}

TTracciato770::~TTracciato770()
{
}

///////////////////////////////////////////////////////////
// TTracciati770
///////////////////////////////////////////////////////////

const TTracciato770& TTracciati770::tracciato(char tipo)
{
  const int pos = tipo-'A';
  TTracciato770* trc = (TTracciato770*)_trc.objptr(pos);
  if (trc == NULL)
  {
    trc = new TTracciato770(tipo);
    _trc.add(trc, pos);
  }
  return *trc;
}

TForm770& TTracciati770::form(const char* quadro, char& tiporec, int& rpm)
{
  CHECK(quadro && *quadro != '\0' && quadro[1] != '\0', 
        "Codice quadro non valido");

  const char* name;
  switch(quadro[1])
  {
    case 'A': name = "77qla"; tiporec = 'H'; rpm = 1; break;
    case 'T': name = "77qst"; tiporec = 'E'; rpm = 12; break;
    default : name = NULL; break;
  }

  TForm770* frm = (TForm770*)_form.objptr(name);
  if (frm == NULL)
  {
    frm = new TForm770(name);
    _form.add(name, frm);
  }

  return *frm;
}

void TTracciati770::destroy()
{
  _trc.destroy();
  _form.destroy();
}

TTracciati770::TTracciati770()
{
}
 
TTracciati770::~TTracciati770()
{
  destroy();   // Non viene mai chiamato!
}

///////////////////////////////////////////////////////////
// TCache770
///////////////////////////////////////////////////////////

const TRectype& TCache770::get(int num, const char* key)
{
  TRecord_cache* rc = (TRecord_cache*)_files.objptr(num);
  if (rc == NULL)
  {
    rc = new TRecord_cache(num);
    _files.add(rc, num);
  }
  return rc->get(key);
}

const TRectype& TCache770::get(int num, long key)
{
  TString16 str; str << key;
  return get(num, str);
}

void TCache770::destroy()
{
  _files.destroy();
  _tables.destroy();
}

///////////////////////////////////////////////////////////
// TRecord770
///////////////////////////////////////////////////////////

void TRecord770::print_on(ostream& outs) const
{
  outs.write(_buffer, TOTAL_SIZE);
}

void TRecord770::read_from(istream& ins)
{
  _buffer.fill(' ', TOTAL_SIZE);
  ins.read(_buffer.get_buffer(), TOTAL_SIZE);
}

void TRecord770::set(const TField770& fld, const char* val)
{
  TString80 str(val); 
  if (fld._type == 'A')
    str.upper();
  int lenstr = str.len();
  if (lenstr > fld._len)
  {
#ifdef DBG
    NFCHECK("Campo troppo lungo: %s (max. %d)", val, fld._len);
#endif
    str.cut(lenstr = fld._len);
  }
  if (lenstr != fld._len)
  {
    str.trim();
    if (fld._type == 'N')
      str.right_just(fld._len, '0');
    else
  		str.left_just(fld._len);
  }
  _buffer.overwrite(str, fld._pos-1);
}

void TRecord770::set(int pos, const char* val)
{
  const TField770& fld = tracciato().field(pos);
  set(fld, val);
}

void TRecord770::set(int pos, int val)
{
  const TField770& fld = tracciato().field(pos);
  CHECKD(fld._type == 'N', "Invalid numeric field ", pos);
  TString16 str; str.format("%d", val);
  set(fld, str);
}

void TRecord770::set(int pos, long val)
{
  const TField770& fld = tracciato().field(pos);
  CHECKD(fld._type == 'N', "Invalid numeric field ", pos);
  TString16 str; str.format("%ld", val);
  set(fld, str);
}

void TRecord770::set(int pos, const real& val)
{
  const TField770& fld = tracciato().field(pos);
  CHECKD(fld._type == 'N', "Invalid numeric field ", pos);
  const char* str = val.string(fld._len, 0);
  set(fld, str);
}

void TRecord770::set(int pos, const TDate& val)
{
  const TField770& fld = tracciato().field(pos);
  CHECKD(fld._type == 'N' && (fld._len == 6 || fld._len == 8), 
         "Invalid date field ", pos);
  const char* str;
  if (fld._len == 8)
    str = val.string(full, '\0', full, full, gma_date);
  else
    str = val.string(brief, '\0', full, full, gma_date);
  set(fld, str);
}

void TRecord770::set(int pos, char val)
{
  const TField770& fld = get_field(pos);
  CHECKD(fld._type == 'A' && fld._len == 1, "Invalid char field ", pos);
  const char str[2] = { val, '\0' };
  set(fld, str);
}

void TRecord770::set(int pos, bool val)
{
  const TField770& fld = get_field(pos);
  CHECKD(fld._type == 'N' && fld._len == 1, 
         "Invalid boolean field ", pos);
  set(fld, val ? "1" : "0");
}

const char* TRecord770::get(int pos, TString& str) const
{
  const TField770& fld = get_field(pos);
  str = _buffer.mid(fld._pos-1, fld._len);
  return str.trim();
}

int TRecord770::get_int(int pos) const
{
  TString16 str; get(pos, str);
  return atoi(str);
}

char TRecord770::get_char(int pos) const
{
  const TField770& fld = get_field(pos);
  CHECKD(fld._type == 'A', "Invalid char field ", pos);
  return _buffer[fld._pos-1];
}

// Calcola i blocchi necessari per contenere la stringa val
int TRecord770::calculate_blocks(const char* val) const
{                
  // Il primo blocco contiene 16 caratteri, gli altri solo 15 perche' c'e' anche il +
  int blocks = 1;
  const int len = strlen(val);
  if (len > FIELD_SIZE)
    blocks += (len-FIELD_SIZE-1) / (FIELD_SIZE-1) + 1;
  return blocks;
}

// Azzera tutti i campi non posizionali dei record di tipo E
void TRecord770::azzera_campi_non_posizionali()
{
  CHECK(ha_campi_non_posizionali(), "Impossibile azzerare un record senza campi non posizionali");
  char* buf = _buffer.get_buffer() + HEADER_SIZE;
  memset(buf, ' ', USEABLE_SIZE);
}

// Aggiunge un campo non posizionale ai record di tipo E
bool TRecord770::add(const char* code, const char* val)
{
  CHECK(ha_campi_non_posizionali(), "Impossibile aggiungere campi non posizionali");
  CHECKS(code && strlen(code) == CODE_SIZE, "Invalid field code ", code);
  CHECKS(val && *val, "Can't add empty field ", code);
  
  // Cerca il primo posto libero
  int pos;
  for (pos = HEADER_SIZE; pos < HEADER_SIZE+USEABLE_SIZE; pos += BLOCK_SIZE)
  {
    if (_buffer[pos] == ' ')
      break;
  }
  const int free_blocks = (USEABLE_SIZE - pos) / BLOCK_SIZE;
  const int needed_blocks = calculate_blocks(val);
  const bool ok = free_blocks >= needed_blocks;

  if (ok)  // Se ci sono abbastanza blocchi liberi
  {
    TString80 str(val); str.upper();
    const int lenstr = str.len();
    for (int i = 0; i < lenstr; )
    {
      _buffer.overwrite(code, pos);
      pos += CODE_SIZE;
      int maxlen = FIELD_SIZE;
      if (i > 0)
      {
        _buffer.overwrite("+", pos);
        pos++;
        maxlen--;
      }
      const TString& substr = str.mid(i, maxlen);
      _buffer.overwrite(substr, pos);
      pos += maxlen;
      i += maxlen;
    }
  }
  return ok;
}

bool TRecord770::valid() const
{
  char tipo = tipo_record();
  bool ok = (tipo > ' ') && (strchr("ABEHZ", tipo) != NULL);
  return ok;
}

TRecord770::TRecord770() : _buffer(TOTAL_SIZE, ' ')
{
}

TRecord770::TRecord770(char tipo) : _buffer(TOTAL_SIZE, ' ')
{
  tipo_record(tipo);
}

TRecord770::TRecord770(const TRecord770& rec) : _buffer(rec._buffer)
{ }

TRecord770::~TRecord770()
{ }

///////////////////////////////////////////////////////////
// TTrasferimento770
///////////////////////////////////////////////////////////

const char* TTrasferimento770::default_name() const 
{ 
  return format("MOD770%02d", (anno_dic()+1) % 100); 
}

bool TTrasferimento770::open(const char* path, char mode, int volume)
{
  CHECK(mode == 'r' || mode == 'w', "Invalid open mode");
  close();

  if (path && *path)
  {
    _name = path;
    _name.add(default_name());
    if (volume > 0)
      _name << '_' << volume;
  }
  if (_name.empty())
  {
    _name = default_name();
    if (volume > 0)
      _name << '_' << volume;
  }
  if (mode == 'r')
    _in_stream = new ifstream(_name, ios::in | ios::binary);
  else
    _out_stream = new ofstream(_name, ios::out | ios::binary);

  return true;
}

bool TTrasferimento770::close()
{
  if (_in_stream)
  {
    delete _in_stream;
    _in_stream = NULL;
  }
  if (_out_stream)
  {
    delete _out_stream;
    _out_stream = NULL;
  }
  return true;
}

bool TTrasferimento770::write(const TRecord770& rec)
{
  bool ok = _out_stream != NULL;
  if (ok)
    (*_out_stream) << rec;
  return ok;
}

bool TTrasferimento770::read(TRecord770& rec)
{
  bool ok = _in_stream != NULL && !_in_stream->eof(); 
  if (ok)
  {
    (*_in_stream) >> rec;
    ok = rec.valid();
  }
  return ok;
}

const TString& TTrasferimento770::read_codfis_dic(const TRectype& rec)
{
  TString80 key;  // Stringa multiuso

  key = rec.get(BSE_CODDIC);
  if (key.empty())
    key = rec.get(BSE_CODDITTA);

  const TRectype& rec_nditte = _cache770.get(LF_NDITTE, key);
  if (rec_nditte.empty())
  {
    error_box("Non esiste la ditta %s", (const char*)key);
	  return EMPTY_STRING;
  }
  _cod_ditta = atol(key);
  _codatt_dic = rec_nditte.get(NDT_CODATTPREV); //  Codice attivita' prevalente
  _tipoa_dic = rec_nditte.get_char(NDT_TIPOA);
  _codan_dic = rec_nditte.get_long(NDT_CODANAGR);

  key.cut(0);
  key << _tipoa_dic << '|' << rec_nditte.get(NDT_CODANAGR);
  const TRectype& rec_anagr = _cache770.get(LF_ANAG, key);
  if (rec_anagr.empty())
  {
    error_box("Non esiste la persona %s", (const char*)key);
	  return EMPTY_STRING;
  }
  _codfis_dic = rec_anagr.get(ANA_COFI);   //  Codice fiscale del dichiarante
  _ragsoc_dic = rec_anagr.get(ANA_RAGSOC); //  Denominazione del dichiarante
  
  return _codfis_dic;
}

int TTrasferimento770::conta_certificazioni() const
{
  TString query;
  query.format("USE %d\nFROM CODDITTA=%ld\nTO CODDITTA=%ld",
               LF_QUALA, _cod_ditta, _cod_ditta);
  TISAM_recordset recset(query);
  return recset.items();
}

bool TTrasferimento770::casella_prospetto_st() const
{
  // Il quadro ST e' il "vecchio" quadro L
  TString query;
  query.format("USE %d\nFROM CODDITTA=%ld\nTO CODDITTA=%ld",
               LF_QUAL, _cod_ditta, _cod_ditta);
  TISAM_recordset recset(query);
  return recset.items() > 0;
}

bool TTrasferimento770::casella_prospetto_sx() const
{
  return false;
}

bool TTrasferimento770::append_record_b()
{
  bool ok = false;
  if (save_headers())
  {
    // Compila record di testata B
    TRecord770 rec('B');
    rec.set(2, cod_fis_dic());
    rec.set(3, 1);             // Modulo (deve essere sempre 1)
    rec.set(7, CF_PRODUTTORE); // CF del produttore AGA
    if (_tipoa_dic == 'F')
    {
      rec.set(13, _ragsoc_dic.left(24));
      rec.set(14, _ragsoc_dic.mid(30,20));
    }
    else
      rec.set(15, _ragsoc_dic);
    rec.set(19, _codatt_dic);

    TToken_string key;
    key << _tipoa_dic << '|' << _codan_dic;
    const TRectype& rec_anagr = _cache770.get(LF_ANAG, key);

    key = rec_anagr.get(ANA_STATORES);
    key.add(rec_anagr.get(ANA_COMRES), 1);
    const TRectype& rec_comres = _cache770.get(LF_COMUNI, key);
    
    if (_tipoa_dic == 'G')
    {
      const TRectype& rec_anagiu = _cache770.get(LF_ANAGGIU, _codan_dic);
      rec.set(33, rec_anagiu.get(ANG_NATGIU));
      
      rec.set(35, rec_comres.get(COM_DENCOM));
      rec.set(36, rec_comres.get(COM_PROVCOM));
      TString indirizzo;
      indirizzo << rec_anagr.get(ANA_INDRES) << ' ' << rec_anagr.get(ANA_CIVRES);
      indirizzo.strip_double_spaces(); indirizzo.cut(35);
      rec.set(37, indirizzo);
      rec.set(38, rec_anagr.get(ANA_CAPRES));
      rec.set(39, rec_anagr.get(ANA_COMRES));

      rec.set(46, rec_anagiu.get(ANG_STATOSOC));
      rec.set(47, rec_anagiu.get(ANG_SITSOC));
    }

    rec.set(75, 1);
    rec.set(76, 0);
    rec.set(77, conta_certificazioni());
    rec.set(78, casella_prospetto_st());
    rec.set(79, casella_prospetto_sx());

    rec.set(89, cod_fis_dic());
    rec.set(90, _ragsoc_dic);
    rec.set(93, rec_comres.get(COM_DENCOM));
    rec.set(94, rec_comres.get(COM_PROVCOM));
    rec.set(95, rec_anagr.get(ANA_COMRES));
    rec.set(96, rec_anagr.get(ANA_CAPRES));

    TString80 indirizzo = rec_anagr.get(ANA_INDRES);
    indirizzo.strip_double_spaces(); indirizzo.trim();
    TString8 tipologia;
    const int spazio = indirizzo.find(' ');
    if (spazio > 0 && spazio < 16)
    {
      TString16 t = indirizzo.left(spazio);
      t.trim(); t.upper();
      if (t == "L.GO" || t == "LARGO")  tipologia = "LARGO";  else
      if (t == "P.ZA" || t == "PIAZZA") tipologia = "PIAZZA"; else
      if (t == "V."   || t == "VIA")    tipologia = "VIA";    else
      if (t == "V.LE" || t == "VIALE")  tipologia = "VIALE";  else
      if (t == "V.LO" || t == "VICOLO") tipologia = "VICOLO";
    }
    if (tipologia.full())
      indirizzo = indirizzo.mid(spazio+1);
    else
      tipologia = "VIA";
    indirizzo.cut(35);

    rec.set(97, tipologia);
    rec.set(98, indirizzo);
    rec.set(99, rec_anagr.get(ANA_CIVRES));

    const TRectype& rec_nditte = _cache770.get(LF_NDITTE, _cod_ditta);

    key.cut(0) << "F|" << rec_nditte.get(NDT_RAPPR);
    const TRectype& rec_rap = _cache770.get(LF_ANAG, key);
    const TRectype& rec_rap_fis = _cache770.get(LF_ANAGFIS, key.get(1));
    key.cut(0) << "|" << rec_rap_fis.get(ANF_COMNASC);
    const TRectype& rec_com_nas = _cache770.get(LF_COMUNI, key);
    key.cut(0) << "|" << rec_rap.get(ANA_COMRES);
    const TRectype& rec_com_res = _cache770.get(LF_COMUNI, key);
    rec.set(106, rec_rap.get(ANA_COFI));
    rec.set(107, rec_nditte.get(NDT_CARRAPP));
    rec.set(111, rec_rap.get(ANA_RAGSOC).left(24));
    rec.set(112, rec_rap.get(ANA_RAGSOC).mid(30, 20));
    rec.set(113, rec_rap_fis.get(ANF_SESSO));
    rec.set(114, rec_rap_fis.get_date(ANF_DATANASC));
    rec.set(115, rec_com_nas.get(COM_DENCOM));
    rec.set(116, rec_com_nas.get(COM_PROVCOM));
    rec.set(117, rec_com_res.get(COM_DENCOM));
    rec.set(118, rec_com_res.get(COM_PROVCOM));
    rec.set(119, rec_rap.get(ANA_CAPRES));
    indirizzo.cut(0);
    indirizzo << rec_rap.get(ANA_INDRES) << ' ' << rec_rap.get(ANA_CIVRES);
    indirizzo.strip_double_spaces(); indirizzo.cut(35); indirizzo.trim();
    rec.set(120, indirizzo);

    ok = write(rec);
  }
  return ok;
}

long TTrasferimento770::append_quadro(const char* quadro, long codditta, TProgind& pi)
{
  TString str;
  str << "Trasferimento quadro " << quadro << " ditta " << codditta;
  pi.set_text(str);
  
  char tipo;
  int rpm;
  TForm770& frm = _trc770.form(quadro, tipo, rpm);
  long items = frm.trasfer(codditta, *this, tipo, rpm);
  return items;
}

bool TTrasferimento770::split(const char* path)
{
  close();

  const long records = fsize(_name) / TOTAL_SIZE;
  long totale[26]; memset(totale, 0, sizeof(totale));

  TRecord770 rec;

  unsigned long disk_size = 15*1024*1024;   // Max 15 Mb per fornitura
  const bool magnetic = ::xvt_fsys_is_removable_drive(path) !=0;
  if (magnetic)
  {
    if (!yesno_box("Inserire il primo disco del trasferimento nell'unita' %s\n"
                   "Tutti i dischi devono essere vuoti ed avere la stesso formato.\n"
                   "Si desidera iniziare il trasferimento?", path))
      return false;

    disk_size = ::xvt_fsys_get_disk_size(path, 'b');
  }
  const long records_per_disk = long(disk_size / TOTAL_SIZE) - 3;  // Tolgo A,B,Z
  const int volumes = int((records-1)/records_per_disk)+1;

  TProgind pi(records, "Generazione file trasferimento", false, true);
  
  // Read from start
  open("", 'r');
  
  for (int volume = 1; volume <= volumes; volume++)
  {
    if (magnetic && volume > 1)
    {
      if (!yesno_box("Inserire il disco %d di %d:\n"
                     "Si desidera proseguire il trasferimento?",
                     volume, volumes))
      {
        break;
      }
    }

    // Specifica volume solo se necessario
    const int v = (volumes > 1 && !magnetic) ? volume : 0; 
    TTrasferimento770 outfile(path, 'w', v);
  
    if (_save_headers)
    {
      // Compila record di testata A
      rec.tipo_record('A');
  
      rec.set(3, "77S07");
      rec.set(4, 1);    // 01 = Soggetto che invia la propria dichiarazione
      rec.set(5, cod_fis_dic());
      if (volumes > 1)
      {
        rec.set(7, volume);
        rec.set(8, volumes);
      }
      else
      {
        rec.set(7, 0);
        rec.set(8, 0);
      }
  
      outfile << rec;  // Scrive record testata
    }
    
    long written = 0;
  
    if (records > 0)
    {
      while (read(rec))
      {
        pi.addstatus(1);
  
        const char tipo_rec = rec.tipo_record();
        if (tipo_rec <= 'A' || tipo_rec >= 'Z')
          continue;
  
        outfile << rec;
        totale[tipo_rec - 'A']++;
        written++;
  
        if (written >= records_per_disk)
          break;
      }
    }  

    if (_save_headers)
    {
      // Compila record di coda
      rec.tipo_record('Z');
      rec.set(3, totale['B'-'A']);  // Totale B
      rec.set(4, totale['E'-'A']);  // Totale E
      rec.set(5, totale['F'-'A']);  // Totale F
      rec.set(6, totale['G'-'A']);  // Totale G
      rec.set(7, totale['H'-'A']);  // Totale H
  
      // Scrive record di coda
      outfile << rec;
    }
  }  
  return true;
}

// Cancella il file
void TTrasferimento770::remove()
{
  close();
  ::remove(_name);
}

TTrasferimento770::TTrasferimento770(const char* path, char  mode, int volume)
: _in_stream(NULL), _out_stream(NULL), _save_headers(false)
{
  open(path, mode, volume);
}

TTrasferimento770::~TTrasferimento770()
{
  close();
}

///////////////////////////////////////////////////////////
// main
///////////////////////////////////////////////////////////

class TTransfer770_msk : public TAutomask
{
protected:
  virtual bool on_field_event(TOperable_field& of, TField_event fe, long jolly);

public:         
  TTransfer770_msk();
  virtual ~TTransfer770_msk() { }
};

bool TTransfer770_msk::on_field_event(TOperable_field& of, TField_event fe, long jolly)
{
  switch (of.dlg())
  {
  case F_PATH:
    if (fe == fe_modify || fe == fe_close)
    {
      TFilename name = of.get();
      if (!name.exist())
        return error_box("Il percorso non e' valido");
    }
    break;
  default:
    break;
  }
  return true;
}

TTransfer770_msk::TTransfer770_msk() : TAutomask("777100a") 
{ 
  set(F_ANNO, anno_dic());
}

class TTransfer770_app : public TSkeleton_application
{
protected:
  virtual void main_loop();
};

void TTransfer770_app::main_loop()
{
  TTransfer770_msk m;

  while (m.run() == K_ENTER)
  {
    TFilename tmp; tmp.tempdir();
    TTrasferimento770 t(tmp, 'w');
    t.save_headers(m.get_bool(F_HEADERS));

    TRelation rel_base(LF_BASE);
    TRectype da_rec(LF_BASE), a_rec(LF_BASE);
    da_rec.put(BSE_CODDITTA, m.get(F_DADITTA));
    a_rec.put(BSE_CODDITTA, m.get(F_ADITTA));

    TString filter;
    filter << BSE_ANNODIC << '=' << anno_dic();
    TCursor cur_base(&rel_base, filter, 1, &da_rec, &a_rec);
    
    TProgind pi(cur_base.items(), "Generazione file di trasferimento", false, true);
    cur_base.freeze();

    TRecord770 rec;  // Record di lavoro

    for (cur_base = 0; cur_base.ok(); ++cur_base)
    {
      const TRectype& base = cur_base.curr();
      const long codditta = base.get_long(BSE_CODDITTA);
      pi.addstatus(1);
      t.read_codfis_dic(base);

      t.append_record_b();
      t.append_quadro("ST", codditta, pi);
      t.append_quadro("LA", codditta, pi);
    }
    pi.close_modal();

    if (m.get(F_SUPPORTO) == "D")
      tmp = m.get(F_DISK);
    else
      tmp = m.get(F_PATH);

    t.split(tmp);
    t.remove();
  }

  _trc770.destroy();
}

int m777100(int argc, char* argv[])
{
  TTransfer770_app app;
  app.run(argc, argv, "Invio");
  return 0;
}