#include <applicat.h>
#include <automask.h>
#include <config.h>
#include <execp.h>
#include <filetext.h>
#include <progind.h>
#include <reprint.h>
#include <reputils.h>
#include <tabutil.h>
#include <utility.h>

#include "../ca/pconana.h"
#include "../ca/movana.h"
#include "../ca/rmovana.h"
#include "../ca/calib01.h"
#include "../ca/calib02.h"

#include "crpa1a.h"

#define DESCR_TESTATA TR("Movimento di budget importato")
#define DESCR_RIGA TR("Riga movimento di budget importato")
//---------------------------------
//		TAutomask
//---------------------------------
class TBudget_Import_mask : public TAutomask
{
protected:
  bool on_field_event(TOperable_field& o, TField_event e, long jolly);
	bool select_file_ext(const char* ext, TFilename& filename);
public:
  TBudget_Import_mask();
  virtual ~TBudget_Import_mask(){};
};
  
TBudget_Import_mask::TBudget_Import_mask() :TAutomask ("crpa1a")
{
}  

bool TBudget_Import_mask::select_file_ext(const char* ext, TFilename& filename)
{
	TArray_sheet as(-1, -1, 72, 20, TR("Selezione file"), 
                   "File@48");
	TFilename path = get(F_PATH);
	path.add("*");
	path.ext(ext);
	list_files(path, as.rows_array());
	TFilename name;
	FOR_EACH_ARRAY_ROW(as.rows_array(), i, row)
	{
		name = *row;
		*row = name.name();
	}
	bool ok = as.run() == K_ENTER;
	if (ok)
	{
		filename = as.row(as.selected());
	}
	return ok;
}

bool TBudget_Import_mask::on_field_event(TOperable_field& f, TField_event e, long jolly)
{ 
	switch (f.dlg())
	{
		//giochetto per avere la lista dei files validi nella directory di trasferimento!
		case F_NAMEFILE:
			if (e == fe_button)
			{
				TFilename name;
				if (select_file_ext("csv", name))
					f.set(name);

				if (f.get() == get(F_LASTFILE))
          return error_box(FR("Questo file l'hai gia' importato!\nCaro %s ti consiglio di NON proseguire!"), (const char*)user());
			}
			break;
		case F_KILLOLD:
			if (e == fe_close)
			{
				if (f.get().full())
				{
					return yesno_box(FR("Ehi %s, hai scelto di eliminare tutti i record di tipo Preventivo\nnei files dei movimenti e dei saldi!\nSei sicuro di voler proseguire?"), (const char*)user());
				}
			}
			break;
		default:
			break;
	}
  return true;
}


//--------------------------------
//		TFile_text
//--------------------------------
class TBudget_Import_file: public TFile_text
{ 
protected:
  virtual void validate(TCursor& cur,TRecord_text &rec, TToken_string &val, TString& str);

public:
  TBudget_Import_file(const TString& file_name);
  virtual ~TBudget_Import_file() { }
};

TBudget_Import_file::TBudget_Import_file(const TString& file_name)
: TFile_text(file_name, "crpa1.ini")
{
}

void TBudget_Import_file::validate(TCursor& cur,TRecord_text &rec, TToken_string &s, TString& str)
{
  const TString code(s.get(0));
  TString valore = str;
  if (code == "_UPPERCASE")
  {
    valore.upper(); 
  }
  else NFCHECK("Macro non definita: %s", (const char *)code);
  str = valore;
}


//----------------------------------
//		TSkeleton_application
//----------------------------------
class TBudget_Import : public TSkeleton_application
{
	TBudget_Import_mask*	_msk;
	TBudget_Import_file* _trasfile;
	TConfig*					_configfile;
  TRelation*				_relmovana;
	TString						_lastfile;
	TString4					_codcaus;

	virtual const char * extra_modules() const {return "ba";}

protected:
	void mask2ini();
	void ini2mask();
	bool transfer();
  void elabora_input(const TFilename& file, TLog_report& log);
  void movana_killer(const TAssoc_array& elenco, const bool kill_all);
	void transfer_movimento(const TArray& righe, TLog_report& log);
	const TString& ana2bill(const TString& contone) const;
  const TDate str2date(const TString& stringdata);

	long get_next_key();

public:           
	virtual bool create();
  virtual bool destroy();
  virtual void main_loop();

  TBudget_Import() {}
};



long TBudget_Import::get_next_key()
{
	TLocalisamfile& lfile = _relmovana->lfile();
  long numreg = 1L ;
  
  if (!lfile.empty())
  {
    lfile.zero() ;
    lfile.setkey(1) ;

    lfile.read(_isgteq);
    if (lfile.good())  //se e' tutto ok,si posiziona sull'ultimo record
      lfile.last();

    if (lfile.good())
			numreg += lfile.get_long(MOVANA_NUMREG);
  }                 
  return numreg;
}

const TString& TBudget_Import::ana2bill(const TString& contone) const
{
	TToken_string key;
	key = contone;
	key.add("1");
	const TRectype& rec = cache().get(LF_PANAPDC, key);
	if (rec.empty())
		return EMPTY_STRING;

  TString& tmp = get_tmp_string();
	tmp.format("%03d%03d%06ld", 
		         rec.get_int("GRUPPO"), rec.get_int("CONTO"), rec.get_long("SOTTOCONTO"));
	return tmp;
}

const TDate TBudget_Import::str2date(const TString& stringdata)
{
  const int stringdata_len = stringdata.len();
  TString4 gg = stringdata.left(2);
	TString4 mm = stringdata.mid(3,2);
  TString4 yy;
  if (stringdata_len == 8)   //se la data ha 8 caratteri (es. 12/06/08)...
  {
	  yy = stringdata.right(2);
	  yy.insert("20", 0);
  }
  else  //se invece ne ha 10 (es.12/06/2008)...
    yy = stringdata.right(4);

  const TDate data(atoi(gg),atoi(mm),atoi(yy));
  return data;
}

//metodo di bassa lega che scrive veramente i movimenti analitici sui file movana e rmovana
void TBudget_Import::transfer_movimento(const TArray& righe, TLog_report& log)
{
  const TRecord_text& curr = (const TRecord_text&)righe[0];
  //TESTATA
  //-------
  //formato definitivo del tracciato record dei CSV da importare (vedi anche il file crpa1.ini):
  //codice commessa|fase|conto|importo|inizio cms|fine cms
	TString codcms = curr.get(0);
	codcms.replace('_', '/');
  
  //controllo commesse
	const TRectype& rec_cms = cache().get(LF_COMMESSE, codcms);
	if (rec_cms.empty())
  {
    TString errore;
    errore << "La commessa " << codcms << " non esiste";
    log.log(2, errore);
  }
	
  //Controllo del formato date!
  //Preso atto che il nostro invincibile fuhrer cambia formato date ad ogni importazione, proviamo a..
  //..rendere piu' intelligente il povero programma
  const TDate dataini = str2date(curr.get(4));
	const int annoes = dataini.year();
  const TDate datafine = str2date(curr.get(5));

	//cerca il primo posto libero in movana
	long numreg = get_next_key();

	//crea il movimento di prima nota da aggiungere a movana
	TAnal_mov movana(numreg);
	movana.zero();							//azzera per sicurezza
	//sbatte i dati nei campi
	movana.put(MOVANA_NUMREG, numreg);
	movana.put(MOVANA_ANNOES, annoes);
	movana.put(MOVANA_DATAREG, dataini);
	movana.put(MOVANA_DATACOMP, dataini);
  movana.put(MOVANA_DATAFCOMP, datafine);
	movana.put(MOVANA_DESCR, DESCR_TESTATA);
	movana.put(MOVANA_CODCAUS, _codcaus);
	movana.put(MOVANA_TIPOMOV, 'P');


  //RIGHE
  //-----
  TImporto totdoc;
  for (int i = 0; i < righe.items(); i++)
  {
    const TRecord_text& curr = (const TRecord_text&)righe[i];

    const TString codconto = curr.get(2);
	  const real soldini = curr.get(3);

    //controllo fase e/o sede
    TString fase, sede;
    if (_msk->get(F_CRPA_DIN)[0] == 'C')
    {
      //controllo fasi (ricordiamo che le fasi sono legate alle commesse nel crpa)
      fase = curr.get(1);
      if (fase.full())
      {
        TToken_string chiave = codcms;
        chiave.add(fase);
        const TRectype& rec_fase = cache().get(LF_FASI, chiave);
	      if (rec_fase.empty())
        {
          TString errore;
          errore << "La fase " << fase << " non esiste legata alla commessa " << codcms << "";
          log.log(2, errore);
        }
      } //fase.full...
    }
    else  //if(_msk->get_char(F_CRPA_DIN...
    {
      sede = curr.get(1);
      if (sede.full())
      {
        const TRectype& rec_sede = cache().get(LF_CDC, sede);
    	  if (rec_sede.empty())
        {
          TString errore;
          errore << "La sede " << sede << " non esiste";
          log.log(2, errore);
        }
      }
    }

    //controllo conti
	  TString query;
	  TString tmp_codconto;
	  tmp_codconto << "'" << codconto << "'"; 
	  query << "USE PCONANA KEY 1 \n";
	  query << "SELECT CODCONTO[10,15]=" << tmp_codconto;

	  TISAM_recordset pconana(query);
	  const TRecnotype items = pconana.items();
	  if (items <= 0)
    {
      TString errore;
      errore << "Il sottoconto " << codconto << " non esiste";
      log.log(2, errore);
    }

	  if (items > 1)
    {
      TString errore;
      errore << "Esiste piu' di un sottoconto " << codconto ;
      log.log(1, errore);
    }
	  pconana.move_last();	//si posiziona sul record corretto
	  const TString& contone = pconana.get(PCONANA_CODCONTO).as_string();

    const TString contcon = ana2bill(contone);
	  if (contcon.empty())
    {
      TString errore;
      errore << "Il conto analitico " << contone <<  " non corrisponde ad alcun conto contabile";
      log.log(2, errore);
    }
    //deve stabilire se la sezione e' D o A in base all'indbil del conto analitico
    TAnal_bill anazio(contone);
    const int indbil = anazio.indicatore_bilancio();
    char sezione = 'D';
    if (indbil > 0)
      sezione = (indbil == 1 || indbil == 3) ? 'D' : 'A';
    else
    {
      TString errore;
      errore << "Il conto analitico " << contone <<  " non ha un indicatore di bilancio";
      log.log(2, errore);
    }
	  //incrementa l'importo totlae del documento in base a quanto trovato su questa riga
    TImporto imp_riga(sezione, soldini);
    totdoc += imp_riga;

    //controllo sulle date
	  const TDate curr_dataini = str2date(curr.get(4));
	  const TDate curr_datafine = str2date(curr.get(5));
    if (curr_dataini != dataini)
    {
      TString errore;
      errore << "La commessa " << codcms <<  " ha movimenti con date inizio competenza incongruenti";
      log.log(1, errore);
    }
    if (curr_datafine != datafine)
    {
      TString errore;
      errore << "La commessa " << codcms <<  " ha movimenti con date fine competenza incongruenti";
      log.log(1, errore);
    }

	  TRectype& rec_rmovana = movana.new_row();

	  rec_rmovana.put(RMOVANA_ANNOES, annoes);
	  rec_rmovana.put(RMOVANA_NUMREG, numreg);
	  rec_rmovana.put(RMOVANA_NUMRIG, i+1);
	  rec_rmovana.put(RMOVANA_SEZIONE, sezione);
	  rec_rmovana.put(RMOVANA_DATACOMP, dataini);
	  rec_rmovana.put(RMOVANA_CODCMS, codcms);
    rec_rmovana.put(RMOVANA_CODFASE, fase);
    rec_rmovana.put(RMOVANA_CODCCOSTO, sede);
	  rec_rmovana.put(RMOVANA_CODCONTO, contcon);
	  rec_rmovana.put(RMOVANA_DESCR, DESCR_RIGA);
	  rec_rmovana.put(RMOVANA_IMPORTO, soldini);
  }
	//scrive testata e riga e aggiorna i saldi (write() magggica!)
  movana.put(MOVANA_TOTDOC, totdoc.valore());
	movana.put(MOVANA_SEZIONE, totdoc.sezione());

	TLocalisamfile testate(LF_MOVANA);
	movana.write(testate);

}

//metodo di eliminazione dei movana
void TBudget_Import::movana_killer(const TAssoc_array& elenco, const bool kill_all)
{
  //accoppa i movimenti preventivi che hanno queste commesse
	TRelation relmovana(LF_MOVANA);
	TCursor curmovana(&relmovana, "TIPOMOV=='P'");

	const TRectype& recmovana = relmovana.curr();
	const long nrec = curmovana.items();
	curmovana.freeze();
	TProgind pi(nrec, "Eliminazione movimenti preventivi in corso...");

	TLocalisamfile movana(LF_MOVANA);	//questo serve alla remove()

	for (curmovana = 0; curmovana.pos() < nrec; ++curmovana)
	{
    pi.addstatus(1);
    //si possono uccidere solo i movimenti importati in automatico
    const TString& descr = curmovana.curr().get(MOVANA_DESCR);
    if (descr == DESCR_TESTATA)
    {
		  TAnal_mov anal_kill(curmovana.curr());

      const int original_rows = anal_kill.rows();
      int residual_rows = kill_all ? 0 : original_rows;  //necessario perche', in caso di kill_all=true (genocidio) 

      //se e' un diradamento selettivo in base al codice commessa...
      if (!kill_all)
      {
        TString codcms;
        for (int i = original_rows; i > 0; i--)
        {
          codcms = anal_kill.body().row(i).get(RMOVANA_CODCMS);
          if (elenco.is_key(codcms))
          {
            anal_kill.body().destroy_row(i, true);
            residual_rows--;
          }
        }
      }

      //controlla quante righe sono rimaste: se nessuna->accoppa il movimento, senno' lo riscrive con le..
      //..righe compattate (aggiunto il caso del movimento senza righe)
      if (residual_rows < original_rows || original_rows == 0)
      {
        if (residual_rows == 0)
	        anal_kill.remove(movana);				//la remove() accoppa anche le righe collegate
        else
          anal_kill.rewrite(movana);      //non deve eliminare il movimento ma solo riscriverlo compattato..
      }                                   //..senza le righe eliminate  
    } //if(descr==...
	} //for(curmovana=0...
}

//metodo di medio livello che gestisce l'ordine delle chiamate dei metodi per l'elaborazione dei record
void TBudget_Import::elabora_input(const TFilename& file, TLog_report& log)
{
  //array con le commesse da accoppare
  TAssoc_array movimenti, commesse;

  //per cominciare apre il file di input
  const long dimension = fsize(file);
  if (dimension > 0)
  {
    _trasfile->open(file,'r');

    TRecord_text curr;
    TToken_string key;
    TString codcms;
  
    TProgind pi(dimension, TR("Lettura file di input in corso..."));
    //crea un assoc_array di array (e la madona!!!) in cui la chiave e' il codice commessa|dataini|datafine..
    //..e gli elementi sono array con le righe del file di input (con la stessa key ma fase\conto etc. diversi)
    while (_trasfile->read(curr) == NOERR) 
    {
      pi.setstatus(_trasfile->read_file()->tellg());

      codcms = curr.get(0);
      codcms.replace('_', '/');
      key = codcms;
	    
      //date
      const TString16 str_dataini = curr.get(4);
      key.add(str_dataini);
	    const TString16 str_datafine = curr.get(5);
      key.add(str_datafine);

      TArray* righe = (TArray*)movimenti.objptr(key);
      if (righe == NULL)
      {
        righe = new TArray;
        movimenti.add(key, righe);
        commesse.add(codcms); //commesse e' l'assoc_array di supporto che si usa nella cancellazione
      }
      righe->add(curr);
	  }

    //per finire chiude il file di input che sara' poi riaperto per la vera importazione
    _trasfile->close();
  }

  //accoppa i movana con commesse da importare
  movana_killer(commesse, false);

  //la causale!!!
	_codcaus = _msk->get(F_COD_CAUS);

  //scrittura dei record memorizzati sui files di testata e righe dei movimenti analitici
  const int items = movimenti.items();
  TProgind pi(items, "Generazione movimenti preventivi in corso...");
  FOR_EACH_ASSOC_OBJECT(movimenti, obj, key, itm)
  {
    pi.addstatus(1);
    TArray* righe = (TArray*)itm;
    transfer_movimento(*righe, log);
  }

}

bool TBudget_Import::transfer()
{	
	//azzeramento preventivo di TUTTI i movimenti, righe e saldi di tipo P
	if (_msk->get_bool(F_KILLOLD))
	{
    TAssoc_array assoc_civetta;
    movana_killer(assoc_civetta, true);
	}

	//file da trasferire
	//costruire il nome del file con path
	TFilename file = _msk->get(F_PATH);
	file.add(_msk->get(F_NAMEFILE));
	file.ext("csv");
  _trasfile = new TBudget_Import_file(file);

	int err = NOERR;
  TRecord_text curr;
  //log degli errori di trasferimento con soppressione degli errori ripetuti!
  TLog_report log("Errori");
  log.kill_duplicates();

  //metodo che fa veramente la eliminazione e la scrittura
  elabora_input(file, log);

	delete _trasfile;

	log.preview();
	_lastfile = _msk->get(F_NAMEFILE);

	return true;
}


void TBudget_Import::mask2ini()
{
	//aggiorna l'ultimo file inviato su msk e ini
	_msk->set(F_LASTFILE, _lastfile);
	_configfile->set_paragraph("BUDGET");
	_configfile->set("PATH", _msk->get(F_PATH));
	_configfile->set("LASTFILE", _lastfile);
	_msk->set(F_COD_CAUS, _codcaus);
	_configfile->set("CODCAUS", _msk->get(F_COD_CAUS));
  _configfile->set("CRPADIN", _msk->get(F_CRPA_DIN));
}

void TBudget_Import::ini2mask()
{
	//carica i parametri del file di configurazione
	_configfile->set_paragraph("BUDGET");

	_msk->set(F_PATH, _configfile->get("PATH"));

	_lastfile = _configfile->get("LASTFILE");
	_msk->set(F_LASTFILE, _lastfile);

	_codcaus = _configfile->get("CODCAUS");
	_msk->set(F_COD_CAUS, _codcaus);

  _msk->set(F_CRPA_DIN, _configfile->get("CRPADIN"));
}


bool TBudget_Import::create()
{
  _configfile = new TConfig("crpa1conf.ini");
  _msk = new TBudget_Import_mask();
  open_files(LF_MOVANA, LF_RMOVANA, LF_PCONANA, 0);
	_relmovana = new TRelation(LF_MOVANA);
  return TSkeleton_application::create();
}

bool TBudget_Import::destroy()
{
	delete _msk;
	delete _configfile;
  return TApplication::destroy();
}

void TBudget_Import::main_loop()
{  
  KEY	tasto;
	ini2mask();
  tasto = _msk->run();
  if (tasto == K_ENTER)
  {
		if (transfer())
			message_box(TR("Importazione movimenti completata"));
  }
	mask2ini();
}

int main(int argc, char** argv)
{
  TBudget_Import a;
  a.run(argc, argv, TR("Importazione movimenti di budget per CRPA"));
  return 0;
}