#include <applicat.h>
#include <automask.h>
#include <colors.h>
#include <defmask.h>
#include <progind.h>
#include <recarray.h>
#include <relation.h>
#include <textset.h>

#include "../cg/cglib01.h"
#include "../ca/calib01.h"
#include "../ca/calib02.h"

#include "pd6342.h"
#include "pd6342300a.h"

#include "../ca/movana.h"
#include "../ca/rmovana.h"
#include "../ca/rip.h"
#include "../ca/rrip.h"

//--------------------------------------------------------------------
//  MASCHERA
//--------------------------------------------------------------------
class TRib_movanal_msk : public TAnal_report_mask
{
protected:
  virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly);

public:
  TRib_movanal_msk();
};


bool TRib_movanal_msk::on_field_event(TOperable_field& o, TField_event e, long jolly)
{
  switch (o.dlg())
  {
	case F_DATAINI:
	case F_DATAFIN:
		if (e == fe_close)
		{
			const int anno = get_int(F_ANNO);

			TEsercizi_contabili esc;	//..le date devono essere incluse nell'esercizio selezionato!
			const TDate data(o.get());
			if (!data.empty() && esc.date2esc(data) != anno)
				return error_box(TR("La data deve appartenere all'anno selezionato"));
		}
		break;

  default: 
    break;
  }
  return TAnal_report_mask::on_field_event(o, e, jolly);
}

TRib_movanal_msk::TRib_movanal_msk() : TAnal_report_mask("pd6342300a")
{
}

class TCSV_recset : public TCSV_recordset
{
public:
//	bool save_as_html(const char* path);
  TCSV_recset(TAssoc_array & calc);
};

TCSV_recset::TCSV_recset(TAssoc_array & calc) : TCSV_recordset("CSV(;)\n")
{
	set_separator(';');
	TAssoc_array kcol;
	TString_array rowkeys;
	TString_array colkeys;

	FOR_EACH_ASSOC_OBJECT(calc, obj, key, item)
	{
		rowkeys.add(key);
		TAssoc_array * row = (TAssoc_array *) item;

		FOR_EACH_ASSOC_OBJECT((*row), rowobj, colkey, rowitem)
			kcol.add(colkey);
	}
	rowkeys.sort();
	kcol.get_keys(colkeys);
	colkeys.sort();
	destroy_column();

	const int ncols = colkeys.items();
	const int nrows = rowkeys.items();
	TString colname(20);

	create_column("Key");
	new_rec("");
	for (int i= 1; i <= ncols; i++)
	{
		colname = colkeys.row(i - 1);
		colname.replace(',', ' ');
		colname.strip_double_spaces();
		create_column(colname, _realfld);
		set(i, colname);
	}
	for (int i= 0; i < nrows; i++)
	{
		new_rec("");
		TString rowkey = rowkeys.row(i);
		TAssoc_array * row = (TAssoc_array *) calc.objptr(rowkey);
		
		rowkey.replace(',', ' ');
		rowkey.strip_double_spaces();
		set(0, rowkey);
		if (row != NULL)
		{
			for (int j= 1; j <= ncols; j++)
			{
				const real * val = (const real *) row->objptr(colkeys.row(j - 1));
				if (val != NULL)
					set(j, *val);
			}
		}
	}
}

/*

bool TCSV_recset::save_as_html(const char* path)
{
  TProgind pi(items(), TR("Esportazione in corso..."), true, true);
  ofstream out(path);
  out << "<html>" << endl;
  save_html_head(out, main_app().title());
  out << "<body>" << endl;

  TString qry; parsed_text(qry);
  if (qry.full())
  {
    for (int i = qry.find('\n'); i > 0; i = qry.find('\n', i+1))
      qry.insert("<br/>", i+1);
    out << "<p><b>" << qry << "</b></p>" << endl;
  }

  out << "<table border=\"1\">";
  out << " <caption>" << main_app().title() << "</caption>" << endl;

  const unsigned int cols = columns();
  if (cols > 0)
  {
    out << " <thead>" << endl;
    for (unsigned int c = 0; c < cols; c++)
    {
      const TRecordset_column_info& ci = column_info(c);
      out << "  <col ";
      switch (ci._type)
      {
      case _intfld :
      case _longfld:
      case _realfld: out << "align=\"right\""; break;
      case _boolfld: out << "align=\"center\""; break;
      default      : out << "style=\"mso-number-format:\\@\""; break;
      }
      out << " />" << endl;
    }

    TXmlItem tr; tr.SetTag("tr"); 
    tr.SetColorAttr("bgcolor", BTN_BACK_COLOR);
    tr.Write(out, 2);
    out << endl;

    for (unsigned int c = 0; c < cols; c++)
    {
      const TRecordset_column_info& ci = column_info(c);
      TToken_string header(ci._name, '\n');
      TString str;
      FOR_EACH_TOKEN(header, tok)
      {
        if (str.not_empty())
          str << "<br/>";
        str << tok;
      }
      out << "   <th>" << str << "</th>" << endl;
    }
    out << "  </tr>" << endl;
    out << " </thead>" << endl;
  }

  out << " <tbody>" << endl;
  TString val;
  for (bool ok = move_first(); ok; ok = move_next())
  {
    if (!pi.addstatus(1))
      break;

    out << "  <tr>" << endl;
    for (unsigned int c = 0; c < cols; c++)
    {
      const TRecordset_column_info& ci = column_info(c);
      out << "   <td>";
      switch (ci._type)
      {
      case _intfld:
      case _longfld:
        {
          const long r = get(c).as_int();
          val.cut(0);
          if (r != 0)
            val << r;
        }
        break;
      case _realfld:
        {
          const real r = get(c).as_real();
          if (r.is_zero())
            val.cut(0);
          else
            val = r.stringe();
        }
        break;
      default: 
        get(c).as_string(val); 
        break;
      }
      if (val.full())
      {
        val.rtrim();
        out << val;
      }
      out << "</td>" << endl;
    }
    out << "  </tr>" << endl;
  }
  out << " </tbody>" << endl;
  out << "</table>" << endl;
  out << "</body>" << endl;
  out << "</html>" << endl;

  return !pi.iscancelled();
}
*/
//--------------------------------------------------------------------
//  APPLICAZIONE
//--------------------------------------------------------------------
class TRib_movanal_app : public TSkeleton_application
{
  TCache_ripartizioni _cache_rip;
  bool _definitivo;
	TAssoc_array _calc;

protected:
  virtual const char * extra_modules() const {return "ca";}
  virtual void main_loop();

  bool elabora_righe(TAnal_mov& anal_mov, TRecord_array& input_rows, TRecord_array& output_rows);
  bool ripartizione(const TAnal_ripartizioni_batch& rrip, const TRectype& rec, const TToken_string& path_item, TRecord_array& output_rows, TArray & output_paths);
  bool pareggio(TAnal_mov& anal_mov, const TAnal_ripartizioni_batch& rrip, const TRectype& rec, const TToken_string& path, TRecord_array& output_rows, TArray & output_paths);

public:
  bool elabora_movimento(TAnal_mov& anal_mov, const bool esplodi);
	TRib_movanal_app(){}
};


bool TRib_movanal_app::pareggio(TAnal_mov& anal_mov, const TAnal_ripartizioni_batch& rrip, const TRectype& rec, const TToken_string& path, 
                                TRecord_array& output_rows, TArray & output_paths)
{
  bool ho_pareggiato = false;

  TImporto totdoc(anal_mov.get_char(MOVANA_SEZIONE), anal_mov.get_real(MOVANA_TOTDOC));

  const TImporto imp_riga(rec.get_char(RMOVANA_SEZIONE), rec.get_real(RMOVANA_IMPORTO));

  totdoc -= imp_riga;
  totdoc.normalize();
  anal_mov.put(MOVANA_TOTDOC, totdoc.valore());
  anal_mov.put(MOVANA_SEZIONE, totdoc.sezione());

  //aggiunge la riga originale alle righe di output (e' un pareggio)
  const int original_nriga = output_rows.rows() + 1;
  TRectype* newrec = new TRectype(rec);
  newrec->put(RMOVANA_NUMRIG, original_nriga);
  output_rows.add_row(newrec);

  //swappa la sezione e la manda al ripartitore in modo da generare righe con la sezione rovesciata rispetto a quella..
  //..originale ed ottenere cosi' il pareggio
  TRectype swaprec(rec);
  swaprec.put(RMOVANA_SEZIONE, imp_riga.sezione() == 'D' ? 'A' : 'D');
  ho_pareggiato = ripartizione(rrip, swaprec, path, output_rows, output_paths);

  //elimina il codcontoori da tutte le righe aggiunte per il pareggio
  for (int i = output_rows.rows(); i > original_nriga; i--)
    output_rows.row(i, false).zero(RMOVANA_CODCONTORI);

  return ho_pareggiato;
}

bool TRib_movanal_app::ripartizione(const TAnal_ripartizioni_batch& rrip, const TRectype& rec, const TToken_string& path_item, TRecord_array& output_rows, TArray & output_paths)
{
  bool ho_ripartito = false;
  // Importo totale da distribuire arrotondato ai decimali della valuta di conto
	const real importo = rec.get_real(RMOVANA_IMPORTO);
	const char sez = rec.get_char(RMOVANA_SEZIONE);
  TGeneric_distrib distrib(importo, TCurrency::get_firm_dec());
	TToken_string rowkey("", ',');

	rowkey.add(rec.get(RMOVANA_CODCCOSTO));
	rowkey.add(rec.get(RMOVANA_CODCMS));
	rowkey.add(rec.get(RMOVANA_CODFASE));
	TToken_string browkey = rowkey;

	TAssoc_array * row = (TAssoc_array *)_calc.objptr(rowkey);

	if (row == NULL)
		_calc.add(rowkey, row = new TAssoc_array);

	real * value = (real *) row->objptr(browkey);

	if (value == NULL)
		row->add(rowkey, value = new real);
	
	*value = *value + (sez == 'D' ? importo : -importo);

	browkey.add("+");
	row = (TAssoc_array *)_calc.objptr(browkey);

	if (row == NULL)
		_calc.add(browkey, row = new TAssoc_array);

	value = (real *) row->objptr(rowkey);

	if (value == NULL)
		row->add(rowkey, value = new real);
	
	*value = *value + (sez == 'D' ? -importo : importo);
  // Calcolo tutte le percentuali da ripartire
  int i;
  const int righe_ripartizione = rrip.rows();
  for (i = 1; i <= rrip.rows(); i++)
  {
    const real importanza_riga = rrip[i].get_real(RRIP_RIPARTO);
    distrib.add(importanza_riga);
  }

  for (i = 1; i <= righe_ripartizione; i++)
  {
    const real imp = distrib.get(); // Legge la quota da distribuire

	  if (imp != ZERO)
	  {
		  TRectype* newrec = new TRectype(rec);
		  newrec->put(RMOVANA_NUMRIG, output_rows.rows() + 1);
		  newrec->put(RMOVANA_IMPORTO, imp);  //e la mette nella nuova riga
		  //poi copia i valori dei campi cdc,cms,fsc,in quelli di tipo ori (nello stesso record)
			if (rec.get(RMOVANA_CODCCORI).blank() && rec.get(RMOVANA_CODCMSORI).blank() &&
					rec.get(RMOVANA_CODFASEORI).blank()) //  RMOVANA_CODCONTORI � vuoto nel caso di pareggio
			{
				ca_copia_campo(rec, RMOVANA_CODCCOSTO, *newrec, RMOVANA_CODCCORI);
				ca_copia_campo(rec, RMOVANA_CODCMS,    *newrec, RMOVANA_CODCMSORI);
				ca_copia_campo(rec, RMOVANA_CODFASE,   *newrec, RMOVANA_CODFASEORI);
				ca_copia_campo(rec, RMOVANA_CODCONTO,  *newrec, RMOVANA_CODCONTORI);
			}
			else
			{
				ca_copia_campo(rec, RMOVANA_CODCCORI, *newrec, RMOVANA_CODCCORI);
				ca_copia_campo(rec, RMOVANA_CODCCORI,    *newrec, RMOVANA_CODCMSORI);
				ca_copia_campo(rec, RMOVANA_CODCCORI,   *newrec, RMOVANA_CODFASEORI);
				ca_copia_campo(rec, RMOVANA_CODCCORI,  *newrec, RMOVANA_CODCONTORI);
			}
		  //e mette nei campi std i valori che trova nelle righe ripartizione

			ca_copia_campo(rrip[i], RRIP_CODCOSTO, *newrec, RMOVANA_CODCCOSTO);
		  ca_copia_campo(rrip[i], RRIP_CODCMS,   *newrec, RMOVANA_CODCMS);
		  ca_copia_campo(rrip[i], RRIP_CODFASE,  *newrec, RMOVANA_CODFASE);
		  ca_copia_campo(rrip[i], RRIP_CODCONTO, *newrec, RMOVANA_CODCONTO);
			TToken_string ripkey("", ',');

			ripkey.add(newrec->get(RMOVANA_CODCCOSTO));
			ripkey.add(newrec->get(RMOVANA_CODCMS));
			ripkey.add(newrec->get(RMOVANA_CODFASE));
//			ripkey.add(newrec->get(RMOVANA_CODCONTO));

		  int r = output_rows.add_row(newrec);
			TToken_string * rowpath = (TToken_string *) output_paths.objptr(r);

			if (rowpath == NULL)
				output_paths.add(rowpath = new TToken_string, r);
			rowpath->add(path_item);

			real * value = (real *) row->objptr(ripkey);

			if (value == NULL)
				row->add(ripkey, value = new real);
			*value = *value + (sez == 'D' ? imp : -imp);

      ho_ripartito = true;
	  } //if(imp!=ZERO)...
  } //for(i=1;i<=righe_ripartizione...

  return ho_ripartito;
}


bool TRib_movanal_app::elabora_righe(TAnal_mov& anal_mov, TRecord_array& input_rows, TRecord_array& output_rows)
{
  bool ho_cambiato_qualchecosa = false;

  const int annoes = anal_mov.get_int(MOVANA_ANNOES);
  const char tipomov = anal_mov.get_char(MOVANA_TIPOMOV);
	TArray input_paths;
	TArray output_paths;
	int loop = 0;

	while (loop++ < 50)
	{
		bool modified = false;
		const int nrows = input_rows.rows();

		for (int r = 1; r <= nrows; r++)
		{
			const TRectype& rec = input_rows.row(r);
			TAnal_bill zio(rec);
			const int rmovana_indbil = zio.indicatore_bilancio();

			//ripartizione batch: passa il conto perche' per prima cosa provera' una ripartizione di tipo 'P' con chiave 3; se non..
			//..ci riuscira', provera' da solo (metodi della TCache_ripartizioni) le ripartizioni di tipo 'B' con chiave 4.
 			const TAnal_ripartizioni_batch& rrip = _cache_rip.righe(zio, annoes, rmovana_indbil, tipomov);
			if (input_paths.objptr(r) == NULL)
				input_paths.add(new TToken_string, r);
			TToken_string & input_path = (TToken_string &) input_paths[r];
			const char tiporip = rrip.tiporip();

			//ci sono righe di ripartizione
			const int righe_ripartizione = rrip.rows();
			bool ripartisci = righe_ripartizione > 0;

			//se ci sono righe di ripartizione/pareggio si va a ripartire!
				TToken_string path_item("", ',');

				path_item.add(rec.get(RMOVANA_CODCCOSTO));
				path_item.add(rec.get(RMOVANA_CODCMS));
				path_item.add(rec.get(RMOVANA_CODFASE));
				path_item.add(rec.get(RMOVANA_CODCONTO));

			if (rec.get(RMOVANA_CODCMS) == "0001")
				int i = 1;

			if (ripartisci)
				ripartisci = input_path.find(path_item) < 0;
			input_path.add(path_item);
			if (ripartisci)  
			{
				switch (tiporip)
				{
				//procedura di ripartizione batch 'B' originale; se tiporip=='P' invece ci vuole il pareggio del movana
				case 'B':
					modified |= ripartizione(rrip, rec, path_item, output_rows, output_paths);
					break;
				case 'P':
					modified |= pareggio(anal_mov, rrip, rec, input_path, output_rows, output_paths);
					break;
				default:
					break;
				}
			}
			else  //if(ripartisci...   nessuna riga di ripartizione->aggiungo la riga input all'output
			{
				TRectype* newrec = new TRectype(rec);
				newrec->put(RMOVANA_NUMRIG, output_rows.rows() + 1);
				output_rows.add_row(newrec);
			}
		} //for(int r=1; r<=nrows...
		if (modified)
		{
			ho_cambiato_qualchecosa = true;
			input_rows = output_rows;
			output_rows.destroy_rows();
		}
		else
			break;
  }
  return ho_cambiato_qualchecosa;
}


bool TRib_movanal_app::elabora_movimento(TAnal_mov& anal_mov, const bool esplodi)
{
  TRecord_array& input_rows = anal_mov.body();  //record_array con le righe del mov_anal (INPUT)
  bool do_rewrite = false;

//Per prima cosa prende le righe del movimento su RMOVANA e le ricompatta..
  TRecord_array compact_rows = input_rows; //record array con le righe compattate da creare con la..
  compact_rows.destroy_rows();             //..implode_rows(); intanto le azzera per sicurezza

  if (esplodi)
  {
    //Imploditore
    ca_implode_rows(input_rows, compact_rows);
    //..poi lo riesplode in tutte le righe che possono nascere secondo le regole delle ripartizioni!
    TRecord_array output_rows = input_rows; //crea il record_array di output come copia dell'INPUT..
    output_rows.destroy_rows();             //..e poi lo pulisce

    //Esploditore
    if (elabora_righe(anal_mov, compact_rows, output_rows))
    {
      input_rows = output_rows; //rimette i record elaborati negli originali
      do_rewrite = true;
    }
    
    if (_definitivo)  //se l'elaborazione e' definitiva...
    {
      anal_mov.put(MOVANA_BLOCCATO, 'X'); //..mette bloccato = X nella testata del movimento
      do_rewrite = true;
    }
  }
  else
  {
    //Imploditore
    do_rewrite = ca_implode_rows(input_rows, compact_rows);
		for (int r = 1; r <= compact_rows.rows(); r++)
		{
			TRectype& rec = compact_rows[r];

			rec.zero(RMOVANA_CODCCORI);
			rec.zero(RMOVANA_CODCMSORI);
			rec.zero(RMOVANA_CODFASEORI);
			rec.zero(RMOVANA_CODCONTORI);
		}
    if (do_rewrite)
    {
      input_rows = compact_rows; // rimette i record compattati negli originali
      anal_mov.update_totdoc();   //aggiorna il totale movana (necessarip per ripartizioni a pareggio, di sicurezza per le altre)
    }
  }

  return do_rewrite; //se ha elaborato delle righe e/o e' una elaborazione definitiva, riscrive la..
}

static bool ripartisci_callback(const TRelation& rel, void* pJolly)
{
  TRib_movanal_app& app = *(TRib_movanal_app*)pJolly;
  const long numreg = rel.curr().get_long(MOVANA_NUMREG);
  TAnal_mov anal_mov(numreg);
  //se va tutto bene riscrive l'intero movimento analitico con conseguente ricalcolo saldi
  app.elabora_movimento(anal_mov, false);
  if (app.elabora_movimento(anal_mov, true))
	  anal_mov.rewrite(rel.lfile());

  return true;
}

static bool compatta_callback(const TRelation& rel, void* pJolly)
{
  TRib_movanal_app& app = *(TRib_movanal_app*)pJolly;
  const long numreg = rel.curr().get_long(MOVANA_NUMREG);
  TAnal_mov anal_mov(numreg);
  //se va tutto bene riscrive l'intero movimento analitico con conseguente ricalcolo saldi
  if (app.elabora_movimento(anal_mov, false))
    anal_mov.rewrite(rel.lfile());

  return true;
}

void TRib_movanal_app::main_loop()
{
  TRib_movanal_msk mask;
  while (mask.run() == K_ENTER)
  { 
    //deve scandire il file MOVANA con chiave 2 (per data e numero di registrazione)
    TRelation rel_movana(LF_MOVANA);
    TRectype darec(LF_MOVANA), arec(LF_MOVANA);
    const TDate & dal = mask.get_date(F_DATAINI);
    darec.put(MOVANA_DATACOMP, dal);
		const TDate & al = mask.get_date(F_DATAFIN);
    arec.put(MOVANA_DATACOMP, al);
		_cache_rip.set_esercizio(mask.get_int(F_ANNO));

		const TString & tipo = mask.get(F_CLASSEMOV);
		TString filter;

		if (tipo.blank())
			filter = "BLOCCATO!=\"X\"";
		else
			if (tipo == "N")
				filter = "(BLOCCATO!=\"X\")&&(TIPOMOV==\"\")";
			else
				filter = "(BLOCCATO!=\"X\")&&(TIPOMOV!=\"\")";
    TCursor cur_movana(&rel_movana, filter, 2, &darec, &arec);
    const long items = cur_movana.items();
    if (items > 0)
    {
      bool run = yesno_box(FR("Si desidera elaborare %ld movimenti?"), items);
      //e' una compattazione?
      const bool compattazione = mask.get_bool(F_COMPATTA);
      //se e' un ripartizione potrebbe essere definitiva!
      if (!compattazione)
      {
        // avvisa l'utente scapestrato che se fa una ripartizione definitiva
        // blocchera' i movimenti che processa e non potra' piu' tornare indietro
        _definitivo = mask.get_bool(F_DEFINITIVO);
        if (run && _definitivo)
          run = yesno_box(TR("E' stata selezionata l'elaborazione definitiva\nSi desidera proseguire?"));
      }

      //Presa la decisione si parte! Tenetevi forte...
      if (run)
      {
        if (compattazione)
          cur_movana.scan(compatta_callback, this, TR("Compattamento movimenti..."));
        else
          cur_movana.scan(ripartisci_callback, this, TR("Ripartizione movimenti..."));
				TFilename fname(mask.get(F_PATH));
		
				fname.add(mask.get(F_NAME));
				if (fname.full())
				{
					TCSV_recset recset(_calc);

					recset.save_as(fname, fmt_unknown);
				}
      } //if(run)...
    }
    else
      message_box(TR("Non ci sono movimenti da elaborare nel periodo selezionato"));
  } 
}

int pd6342300(int argc, char* argv[])
{
  TRib_movanal_app app;
  app.run(argc, argv, TR("Ripartizione movimenti di analitica (Habilita)"));
  return 0;
}