#include <applicat.h>
#include <form.h>
#include <automask.h>
#include <progind.h>
#include <recarray.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"

///////////////////////////////////////////////////////////
// Dichiarazioni
///////////////////////////////////////////////////////////

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

///////////////////////////////////////////////////////////
// TFormDylog
///////////////////////////////////////////////////////////

class TRecordDylog;
class TTrasferimentoDylog;

class TFormDylog : 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, TRecordDylog& rec,
                        TTrasferimentoDylog& file, int extra_offset = 0);
  char log2rec(int num, int& rpm) const;

protected:

  virtual TCursor* cursor() { return _sortedcur ? _sortedcur : TForm::cursor(); }

public:
  long trasfer(long codditta, TTrasferimentoDylog& file, const char* tipo);

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

///////////////////////////////////////////////////////////
// TTracciatiDylog
///////////////////////////////////////////////////////////

class TTracciatiDylog : public TObject
{
  TAssoc_array _form;

public:
  TFormDylog& form(const char* quadro);

  void destroy();
  
  TTracciatiDylog();
  virtual ~TTracciatiDylog();
} _trcDylog;


///////////////////////////////////////////////////////////
// TRecordDylog
///////////////////////////////////////////////////////////

const int REC_SIZE = 940; // Lunghezza intero record
const int HDR_SIZE = 35;  // Lunghezza testata record

class TRecordDylog : public TObject
{
  TString _buffer;

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

public: 
  void set(const char* val, char tipo, int pos, int len);

  const TRecordDylog& operator=(const TRecordDylog& rec) 
  { _buffer = rec._buffer; return *this; }
  
  void tipo_record(const char* tipo);

  bool valid() const;

  TRecordDylog();
  TRecordDylog(const char* tipo, const char* cfdich);
  TRecordDylog(const TRecordDylog& rec);
  virtual ~TRecordDylog();
};

///////////////////////////////////////////////////////////
// TTrasferimentoDylog
///////////////////////////////////////////////////////////

class TTrasferimentoDylog : public TObject
{
  TFilename _name;
  ifstream* _in_stream;
  ofstream* _out_stream;
  
  TString16 _defname;
  
public:
  bool open(const char* path = "", char mode = 'r');
  bool close();
  bool write(const TRecordDylog& rec);
  bool read(TRecordDylog& rec);
  bool eof() const { return _in_stream && _in_stream->eof(); }

  virtual bool ok() const;

  const TString& default_name();

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

  long append_quadro(const char* quadro, long codditta, TProgind& pi);

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

  TTrasferimentoDylog(const char* name = "", char mode = 'r');
  virtual ~TTrasferimentoDylog();
};

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

///////////////////////////////////////////////////////////
// TFormDylog
///////////////////////////////////////////////////////////

void TFormDylog::transfer_section(TPrint_section& body, TRecordDylog& rec, 
                                  TTrasferimentoDylog& file, int extra_offset)
{
  TToken_string trc(32, ',');
  TString80 str;

//  body.update(); // Per un errore sui form 1.4 lo faccio io a mano
  body.reset();
  for (word i = 0; i < body.fields(); i++)
  {
    TForm_item& fi = body.field(i);
    if (fi.enabled())
      fi.update();
  }
  
  for (word f = 0; f < body.fields(); f++)
  {
    TForm_item& fi = body.field(f);
    str = fi.get(); str.trim();
    if (fi.shown() && fi.enabled())
    {
      trc = fi.get_special_value("DYLOG");
      if (trc.not_empty())
      {
        const char tipo = trc[0];
        const int pos = trc.get_int(1);
        const int len = trc.get_int();
        rec.set(str, tipo, pos + HDR_SIZE + extra_offset, len);
      }
    }
  }
}

// Obsoleto
bool TFormDylog::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 TFormDylog::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));
  }
}

// Obsoleto
long TFormDylog::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 TFormDylog::first()
{
  TCursor& cur = *cursor();

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

  return ok ? NOERR : _iseof;
}

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

  bool ok;
  if (cur.file().num() == LF_QUAC)  // Obsoleto
  {
    _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 TFormDylog::trasfer(long codditta, TTrasferimentoDylog& file, const char* tipo)
{
  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 TRectype& ditta = cache().get(LF_NDITTE, codditta);
  TString16 codanagr; codanagr << ditta.get_char(NDT_TIPOA) << '|' << ditta.get_long(NDT_CODANAGR);
  const TRectype& pers = cache().get(LF_ANAG, codanagr);
  const TString16 cfdich = pers.get(ANA_COFI);
  
  const char* td = NULL;
  switch (tipo[1])
  {
  case 'A': td = "AU"; break;
  case 'T': td = "T0"; break;
  default : break;
  }
  
  const long items = cur.items();
  if (items > 0)         // Se c'e almeno un record ...
  {
    cur.freeze(TRUE);    // Per efficienza congela il cursore

    TPrint_section& body = section('B', odd_page);
   
    for (int err = first(); err == NOERR; err = next())
    {
      // Inizializza record col primo modulo
      TRecordDylog rec(td, cfdich);
      transfer_section(body, rec, file);
      
      // Il quadro ST contiene un array di 3 sotto-record da azzerare per la Dylog!
      if (stricmp(td, "T0") == 0)
      {  
        cur.curr().zero();
        for (int i = 1; i < 3; i++)
          transfer_section(body, rec, file, i*91);
      }
      
      file << rec;
    }
    
    cur.freeze(FALSE);
  }

  return items;
}

TFormDylog::TFormDylog(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);
}

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

///////////////////////////////////////////////////////////
// TTracciatiDylog
///////////////////////////////////////////////////////////

TFormDylog& TTracciatiDylog::form(const char* quadro)
{
  CHECK(quadro && *quadro != '\0' && quadro[1] != '\0', "Codice quadro non valido");

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

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

  return *frm;
}

void TTracciatiDylog::destroy()
{
  _form.destroy();
}

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

///////////////////////////////////////////////////////////
// TRecordDylog
///////////////////////////////////////////////////////////

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

void TRecordDylog::read_from(istream& ins)
{
  ins.read(_buffer.get_buffer(), _buffer.size());
}

void TRecordDylog::set(const char* val, char tipo, int pos, int len)
{
  TString80 str(val); str.upper();
  int lenstr = str.len();

  if (tipo == 'N') 
  {
    // Controlla date
    if (len == 8 && lenstr == 10 && str[2] == '-' && str[5] == '-')
    {
      int aaaa, mm, gg;
      sscanf(str, "%d-%d-%d", &gg, &mm, &aaaa);
      str.format("%02d%02d%04d", gg, mm, aaaa);
      lenstr = 8;
    } else
    // Controlla numeri reali
    if (len == 11)
    {
      const int point = str.find('.');
      if (point >= 0)
        str.cut(lenstr = point);
      if (real::is_null(str))
      {
        str = "0";
        lenstr = 1;
      }
    } else
    // Controlla booleani
    if (len == 1 && lenstr <= 1 && (str[0] == 'X' || str[0] <= ' '))
    {
      str = str[0] == 'X' ? "1" : "0";
      lenstr = 1;
    }
  }

  if (lenstr > len)
  {
#ifdef DBG
    if (tipo == 'N')
      NFCHECK("Campo troppo lungo: %s (max. %d)", val, len);
#endif
    str.cut(lenstr = len);
  }
  if (lenstr != len)
  {
    if (tipo == 'N')
      str.right_just(len, '0');
    else
      str.left_just(len);
  }
  _buffer.overwrite(str, pos);
}

bool TRecordDylog::valid() const
{
  if (_buffer[0] != 'P')
    return FALSE;

  if (_buffer[1] == ' ' || _buffer[2] == ' ')
    return FALSE;

  return TRUE;
}

void TRecordDylog::tipo_record(const char* tipo)
{ 
  _buffer.spaces(REC_SIZE);
  _buffer[0] = 'P';         // Linea Prassi
  if (tipo && *tipo)        
  {                         
    _buffer[1] = tipo[0];   
    _buffer[2] = tipo[1];   
  }
}

TRecordDylog::TRecordDylog()
{
  tipo_record(NULL);
}

TRecordDylog::TRecordDylog(const char* tipo, const char* cf)
{
  tipo_record(tipo);
  if (cf && *cf)
    set(cf, 'A', 3, 16);   // Codice fiscale dichiarante
}

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

TRecordDylog::~TRecordDylog()
{ }

///////////////////////////////////////////////////////////
// TTrasferimentoDylog
///////////////////////////////////////////////////////////

const TString& TTrasferimentoDylog::default_name()
{
  if (_defname.empty())
  {
    const int anno = anno_dic()+1;
    _defname.format("TRA7%02d", anno % 100);
  }
  return _defname; 
}

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

  if (path && *path)
  {
    _name = path;
    _name.add(default_name());
  }
  if (_name.empty())
    _name = default_name();
  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 TTrasferimentoDylog::ok() const
{
  if (_in_stream) 
    return _in_stream->good() != 0;
  
  if (_out_stream) 
    return _out_stream->good() != 0;

  return FALSE;
}

bool TTrasferimentoDylog::close()
{
  if (_in_stream)
  {
    delete _in_stream;
    _in_stream = NULL;
  }
  if (_out_stream)
  {
    delete _out_stream;
    _out_stream = NULL;
  }
  return TRUE;
}

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

bool TTrasferimentoDylog::read(TRecordDylog& rec)
{
  bool ok = _in_stream != NULL && !_in_stream->eof(); 
  if (ok)
  {
    rec.tipo_record(NULL); // Invalida il record
    (*_in_stream) >> rec;
    ok = rec.valid();
  }
  return ok;
}

long TTrasferimentoDylog::append_quadro(const char* quadro, long codditta,
                                      TProgind& pi)
{
  TString str;
  str << "Trasferimento quadro " << quadro << " ditta " << codditta;
  pi.set_text(str);
  
  TFormDylog& frm = _trcDylog.form(quadro);
  long items = frm.trasfer(codditta, *this, quadro);
  return items;
}

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

  long records = 0;

  TRecordDylog rec;

  long records_per_disk = 0;
  int volumes = 1;
  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;

    unsigned long disk_size = ::xvt_fsys_get_disk_size(path, 'b') != 0;
    records_per_disk = long(disk_size / REC_SIZE - 2);
    volumes = int((records-1)/records_per_disk)+1;
  }

  TProgind pi(records, "Trasferimento records", 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;
      }
    }

    TTrasferimentoDylog outfile(path, 'w');
    while (!outfile.ok())
    {
      if (magnetic)
      {
        if (yesno_box("Errore di scrittura in %s\n"
                      "Si desidera ritentare?", (const char*)path))
          outfile.open(path, 'w');
        else
          break;
      }
      else
      {
        error_box("Errore di scrittura in %s", (const char*)path);
        break;
      }
    }

    // Azzera totali
    long written = 0;

    while (read(rec))
    {
      outfile << rec;
      written++;
      pi.addstatus(1);

      if (magnetic && written >= records_per_disk)
        break;
    }
  }
  return TRUE;
}

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

TTrasferimentoDylog::TTrasferimentoDylog(const char* path, char  mode)
: _in_stream(NULL), _out_stream(NULL)
{
  open(path, mode);
}

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

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

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

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

bool TTransferDylog_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;
}

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

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

void TTransferDylog_app::main_loop()
{
  TTransferDylog_msk m;

  while (m.run() == K_ENTER)
  {
    TFilename tmp; tmp.tempdir();
    TTrasferimentoDylog t(tmp, 'w');
    TRecordDylog rec;

    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));

    TCursor cur_base(&rel_base, "", 1, &da_rec, &a_rec);
    
    TProgind pi(cur_base.items(), "Generazione file di trasferimento", FALSE, TRUE);
    cur_base.freeze();
    for (cur_base = 0; cur_base.ok(); ++cur_base)
    {
      pi.addstatus(1);
      const TRectype& base = cur_base.curr();
      const long codditta = base.get_long(BSE_CODDITTA);
      if (codditta > 0) // Strano, ma puo' succedere
      {
        t.append_quadro("LA", codditta, pi);
        t.append_quadro("ST", 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();
  }

  _trcDylog.destroy();
}

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