#include <automask.h>
#include <defmask.h>
#include <recarray.h>
#include <relapp.h>

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

#include "ce4100a.h"

#include "cespi.h"

struct date_commessa : public TObject
{
  TDate _dataini_cms, _datafine_cms;
};

//=============================================================================================
//maschera

class TCesp_anal_mask: public TAutomask 
{
  int _pos_cms, _pos_fase, _pos_cdc, _pos_dtiniuse, _pos_dtfinuse, _pos_util, 
      _pos_dtinicms, _pos_dtfincms, _pos_dtprorcms;

protected:
  real check_perc_tot();
  int calc_date_cespite(TDate& dtini, TDate& dtfine);
  void calc_date_limite(const TString& codcms, TDate& dtini, TDate& dtfine);
  void calc_percutil();

public:
  virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly);

  real TCesp_anal_mask::somma_perc() const;

  TCesp_anal_mask();
};

TCesp_anal_mask::TCesp_anal_mask() : TAutomask ("ce4100a")
{
  //setta le posizioni dei campi dello sheet
  TSheet_field& sf_righe = sfield(F_RIGHE);
  _pos_cms = sf_righe.cid2index(S_CODCMS);
  _pos_fase = sf_righe.cid2index(S_CODFASE);
  _pos_cdc = sf_righe.cid2index(S_CODCDC);
  _pos_dtiniuse = sf_righe.cid2index(S_DTINIUSE);
  _pos_dtfinuse = sf_righe.cid2index(S_DTFINUSE);
  _pos_util = sf_righe.cid2index(S_PERCUTIL);
  _pos_dtinicms = sf_righe.cid2index(S_DTINICMS);
  _pos_dtfincms = sf_righe.cid2index(S_DTFINCMS);
  _pos_dtprorcms = sf_righe.cid2index(S_DTPRORCMS);

  //mette i checktype sui campi dello sheet in base alla configurazione..
  //..analitica della ditta
  TConfig& cfg = ca_config();
  TMask& sf_righe_msk = sf_righe.sheet_mask();
  
  if (cfg.get_bool("CmsRequired"))
  {
    TEdit_field& e_cms = sf_righe_msk.efield(S_CODCMS);
    e_cms.check_type(CHECK_REQUIRED);
  }
  if (cfg.get_bool("CdcRequired"))
  {
    TEdit_field& e_cdc = sf_righe_msk.efield(S_CODCDC);
    e_cdc.check_type(CHECK_REQUIRED);
  }
  if (cfg.get_bool("FscRequired"))
  {
    TEdit_field& e_fase = sf_righe_msk.efield(S_CODFASE);
    e_fase.check_type(CHECK_REQUIRED);
  }
}


real TCesp_anal_mask::check_perc_tot()
{
  TSheet_field& ss = sfield(F_RIGHE);
  real tot_perc;
  FOR_EACH_SHEET_ROW(ss,i,r)
    tot_perc += real(r->get(_pos_util));
  return tot_perc;
}

int TCesp_anal_mask::calc_date_cespite(TDate& dtini, TDate& dtfine)
{
  //si informa sulla vita del cespite
  const TString& idcespite = get(F_IDCESPITE);
  //controlla che il cespite sia vivo nell'esercizio selezionato e quanto dura tale esistenza
  const TRectype& rec_cespi = cache().get(LF_CESPI, idcespite);
  const TDate dtcomp = rec_cespi.get_date(CESPI_DTCOMP);
  const TDate dtalien = rec_cespi.get_date(CESPI_DTALIEN);

  const int anno = get_int(F_ESERCIZIO);
  TEsercizi_contabili esc;
  esc.code2range(anno, dtini, dtfine);

  long durata = dtfine - dtini + 1;

  if (dtalien.ok())
  {
    //cespite alienato prima dell'esercizio
    if (dtalien < dtini)
      return 0;
    //cespite alienato durante l'esercizio
    if (dtalien < dtfine)
      dtfine = dtalien;
  }

  if (dtcomp.ok())
  {
    //cespite acquistato dopo l'esercizio
    if (dtcomp.ok() && dtcomp > dtfine)
      return 0;
    //cespite acquistato durante l'esercizio
    if (dtcomp.ok() && dtcomp > dtini)
      dtini = dtcomp;
  }

  //durata "cespitizzata" dell'esercizio
  durata = dtfine - dtini + 1;
  return durata;
}


void TCesp_anal_mask::calc_date_limite(const TString& codcms, TDate& dtini, TDate& dtfine)
{
  //date limite coincidenti con date vita cespite nell'esercizio (caso standard, senza commessa)
  const TDate dtinicesp = get_date(F_INIZIO_CES);
  const TDate dtfincesp = get_date(F_FINE_CES);
  //se invece la commessa viene aggiunta, le sue date limite influenzano il limite di uso!
  if (codcms.full())
  {
    const TRectype& rec_cms = cache().get(LF_COMMESSE, codcms);
    ca_durata_commessa(rec_cms, dtini, dtfine);
    //se le date di commessa sforano l'esercizio selezionato, vengono ricondotte alle date..
    //..dell'esercizio stesso
    if (dtfine.ok() && (dtfine > dtfincesp))
      dtfine = dtfincesp;

    if ((dtini.ok()) && (dtini < dtinicesp))
      dtini = dtinicesp;
  }
  else
  {
    dtfine = dtfincesp;
    dtini = dtinicesp;
  }
}


//questo � il metodo supermagico ed iperdecisivo per calcolare la %utilizzo in base a tutti i dati..
//..vaganti nella maschera e nello sheet. E' il cuore del programma!
void TCesp_anal_mask::calc_percutil()
{
  //ripartisce in automatico le percentuali di utilizzo analizzando le durate e le compresenze..
  //..delle commesse sullo sheet nell'anno selezionato, oltre che ovviamente la durata del cespite..
  //..nell'anno selezionato

  // 1) stabilisce la durata dell'esercizio per il cespite: infatti pu� essere stato comprato o venduto..
  //..nel corso dell'esercizio stesso
  TDate dtini, dtfine;
  //durata "cespitizzata" dell'esercizio
  const long durata = calc_date_cespite(dtini, dtfine);

  // 2) chiave generale su cui mettere le quote che non appartengono ad alcuna..
  //..commessa! La si trova nella configurazione esercizio cespiti (tanto per incasinarci..
  //..la vita), ovvero tabella CCE
  const TString& anno = get(F_ESERCIZIO);
  const TRectype& rec_cce = cache().get("CCE", anno);

  //trova per l'anno sulla maschera quali sono i valori standard;
  TToken_string key_genspa;
  key_genspa.add(rec_cce.get("S2"), _pos_cdc);
  key_genspa.add(rec_cce.get("S3"), _pos_cms);
  key_genspa.add(rec_cce.get("S4"), _pos_fase);

  //conatore dei giorni cui casca la chiave genspa
  long giorni_genspa = 0L;

  TSheet_field& sf_righe = sfield(F_RIGHE);
  TPointer_array giorni_commessa;
  int riga_genspa = -1;

  // 3) giro su tutti i giorni inclusi tra le date limite in modo da trovare i giorni utili..
  //..di ciascuna commessa e gli eventuali giorni vuoti da scaricare in genspa
  for (TDate data = dtini; data <= dtfine; ++data)
  {
    bool almeno_una = false;
    FOR_EACH_SHEET_ROW(sf_righe, r, row)
    {
      TToken_string key_riga;
      key_riga.add(row->get(_pos_cms));
      key_riga.add(row->get(_pos_fase));
      key_riga.add(row->get(_pos_cdc));

      //la commessa genspa non va calcolata qui!
      if (key_riga != key_genspa)
      {
        const TDate dt_ini_use = row->get(_pos_dtiniuse);
        const TDate dt_fin_use = row->get(_pos_dtfinuse);
        //se la data in esame appartiene allintrvallo date di utilizzo sulla riga
        if (data >= dt_ini_use && data <= dt_fin_use)
        {
          const long giorni = giorni_commessa.get_long(r) + 1;
          giorni_commessa.add_long(giorni, r);
          almeno_una = true;
        }
      }
      else
        riga_genspa = r;
    } //FOR_EACH_SHEET_ROW...
    //se la data in questione non appartiene all'intervallo di utilizzo di nessuna riga..
    //..->la data � scoperta e va a finire nel calderone del genspa
    if (!almeno_una)
      giorni_genspa ++;

  } //for(TDate...

  // 4) attenzione alla commesse generica di riempimento (cms_genspa!) se non esiste va aggiunta
  //se invece la cms_genspa ha gi� la sua riga -> deve solo aggiornare la %util (lo fa dopo..
  //..assieme alle commesse normali)
  if (riga_genspa < 0 && giorni_genspa > 0)
  {
    TToken_string& row_genspa = sf_righe.row(-1);
    row_genspa = key_genspa;
    row_genspa.add(dtini, _pos_dtinicms);
    row_genspa.add(dtfine, _pos_dtfincms);
    //aggiunge la commessa genspa all'array dei giorni_commessa, in modo che venga poi considerata..
    //..nel riproporzionamento successivo con il distrib
    riga_genspa = sf_righe.items() - 1;
  }
  //se la riga genspa esiste (sia che l'abbia appena creata che esistesse da tempo immemorabile)...
  //..calcola la % di giorni genspa nell'esercizio cespitizzato (� la %util di genspa)
  real perc_genspa = ZERO;
  if (riga_genspa >= 0)
  {
    giorni_commessa.add_long(giorni_genspa, riga_genspa);

    perc_genspa = giorni_genspa * CENTO / durata;
    perc_genspa.round(2);
    sf_righe.row(riga_genspa).add(perc_genspa.string(), _pos_util);
  }

  // 5) procede a caricare un distrib con i valori di giorni_commessa
  TGeneric_distrib tamoil(CENTO - perc_genspa, 2);
  for (int i = 0 ; i < sf_righe.items(); i++)
  {
    if (i != riga_genspa)
      tamoil.add(giorni_commessa.get_long(i));
  }

  // 6) adesso scarica il distrib, mettendo la % di ogni commessa nel campo di %di utilizzo
  FOR_EACH_SHEET_ROW(sf_righe, gg, linea)
  {
    if (gg != riga_genspa)
      linea->add(tamoil.get().string(), _pos_util);
  }

  //..e alla fine vediamo che succede!
  sf_righe.force_update();
}



bool TCesp_anal_mask::on_field_event(TOperable_field& o, TField_event e, long jolly)
{
  switch (o.dlg())
  {

  //maschera principale
  //-------------------
  //case F_IDCESPITE:
  case F_IDCESPITE_N:
  case F_ESERCIZIO:
    if (e == fe_modify || e == fe_close)
    {
      //nel caso vengano cambiati il cespite e/o l'esercizio, deve controllare la vita del cespite..
      //..nell'esercizio
      TDate dtinies = get_date(F_INIZIO_ES);
      TDate dtfines = get_date(F_FINE_ES);
      if (calc_date_cespite(dtinies, dtfines) <= 0)
        return error_box("Cespite NON appartenente all'esercizio selezionato!");
      set(F_INIZIO_CES, dtinies);
      set(F_FINE_CES, dtfines);
    }
    break;

  case F_RIGHE:
    //all'uscita della riga...
    if (e == se_notify_modify)
    {
      //controllo di duplicazione chiave della riga (cms/fase/cdc)
      TAssoc_array keys_list;
      TSheet_field& ss = sfield(F_RIGHE);
      FOR_EACH_SHEET_ROW(ss,i,r)
      {
        const TString& cms = r->get(_pos_cms);
        const TString& cdc = r->get(_pos_cdc);
        const TString& fase = r->get(_pos_fase);
        
        TToken_string row_key;
        row_key.add(cms, _pos_cms);
        row_key.add(fase, _pos_fase);
        row_key.add(cdc, _pos_cdc);
        if (keys_list.is_key(row_key))
          return error_box("Codice riga %s duplicato", (const char *)row_key);

        keys_list.add(row_key);
      }
    }
    break;

    //maschera di riga sheet
    //----------------------
    //controllo sulle date limite di utilizzo: se c'� la commessa le date lim uso devono ricadere.. 
    //..all'interno della vita della commessa, se invece la commessa non c'� le date lim uso devono..
    //..ricadere all'interno della vita utile del cespite nell'esercizio
  case S_CODCDC:
    if (e == fe_modify && !o.empty())
    {
      TMask& mask_riga = o.mask();
      const int anno = get_int(F_ESERCIZIO);
      TDate dtini, dtfine;
      TEsercizi_contabili esc;
      esc.code2range(anno, dtini, dtfine);
      calc_date_cespite(dtini, dtfine);
      TDate dtiniuse = mask_riga.get_date(S_DTINIUSE);
      if (dtiniuse.empty())
        mask_riga.set(S_DTINIUSE, dtini);
      TDate dtfinuse = mask_riga.get_date(S_DTFINUSE);
      if (dtfinuse.empty())
        mask_riga.set(S_DTFINUSE, dtfine);
    }
    break;

  case S_CODCMS:
    if (e == fe_modify && !o.empty())
    {
      TMask& mask_riga = o.mask();
      const TString& codcms = o.get();
      TDate dtini, dtfine;
      calc_date_limite(codcms, dtini, dtfine);
      TDate dtiniuse = mask_riga.get_date(S_DTINIUSE);
      if (dtiniuse.empty())
        mask_riga.set(S_DTINIUSE, dtini);
      TDate dtfinuse = mask_riga.get_date(S_DTFINUSE);
      if (dtfinuse.empty())
        mask_riga.set(S_DTFINUSE, dtfine);
    }
    break;

  case S_DTINIUSE:
    if (e == fe_modify || e == fe_close)
    {
      TDate dtini, dtfine;
      const TString& codcms = o.mask().get(S_CODCMS);
      calc_date_limite(codcms, dtini, dtfine);
      const TDate dtiniuse = o.get();
      if (dtiniuse.ok() && dtiniuse < dtini)
        return error_box("La data inizio uso non pu� essere antecedente alla data inizio commessa!");
    }
    break;

  case S_DTFINUSE:
    if (e == fe_modify || e == fe_close)
    {
      TDate dtini, dtfine;
      const TString& codcms = o.mask().get(S_CODCMS);
      calc_date_limite(codcms, dtini, dtfine);
      const TDate dtfinuse = o.get();
      if (dtfinuse.ok() && dtfinuse > dtfine)
        return error_box("La data fine uso non pu� essere successiva alla data fine commessa!");
    }
    break;

    //bottoni
    //-------
  case DLG_CALCPERC:
    if (e == fe_button)
    {
      //in caso di richiesta esplicita di ricalcolo, viene ordinato lo sheet..
      //..in base al primo campo (in genere cms) e poi effettuato il calcolo
      TSheet_field& sf_righe = sfield(F_RIGHE);
      sf_righe.sort();
      calc_percutil();
    }
    break;
  case DLG_SAVEREC:
    if (e == fe_button)
    {
      const real perc_tot = check_perc_tot();
      if (perc_tot != CENTO)
      {
        const bool proseguo = yesno_box("Percentuale complessiva diversa da 100%.\nRegistrare ugualmente?");
        if (!proseguo)
          return false;
      }
    }
    break;
  default:
    break;
  }
  return true;
}

//===============================================================================================
//Applicazione
class TCesp_anal: public TRelation_application 
{
  TCesp_anal_mask *_msk; // maschera principale
  TRelation *_rel; // relazione principale

//metodi virtuali obbligatori per gli oggetti TRelation_application
protected:
  virtual bool user_create();
  virtual bool user_destroy();
  virtual TMask* get_mask(int mode) { return _msk; }
  virtual bool changing_mask(int mode) { return false;}
  virtual void init_query_mode(TMask& m);
  virtual void init_query_insert_mode(TMask& m);
  virtual void init_insert_mode(TMask& m);
  virtual void init_modify_mode(TMask& m);
  virtual bool remove();

  virtual TRelation* get_relation() const { return _rel; }

public:
  virtual ~TCesp_anal() {}
};

//metodi per la maschera
//abilita i campi per inserimento nuovo cespite,attivando i campi del gruppo 2 (che fanno riferimento al file LF_CESPI)
void TCesp_anal::init_query_insert_mode(TMask& m)
{
  m.enable(-2);
  m.show(-2);
  m.hide(-1);
}
//abilita i campi per ricercare un cespite gia' inserito,attivando i campi del gruppo 1(LF_SALCECMS)
void TCesp_anal::init_query_mode(TMask& m)
{
  m.show(-1);
  m.hide(-2);
  m.set(F_DESC,"");
}

void TCesp_anal::init_insert_mode(TMask& m)
{
  m.hide(-1);
  m.disable(-2);
}

void TCesp_anal::init_modify_mode(TMask& m)
{
  m.hide(-2);
}

bool TCesp_anal::remove()
{
  _rel->read(_isequal, _unlock);
  TSheet_field& sf = _msk->sfield(F_RIGHE);
  int err = sf.record()->remove();
  return err == NOERR;
}

bool TCesp_anal::user_create()
{
  _rel = new TRelation(LF_SALCECMS);
  _msk = new TCesp_anal_mask;
  return true;
}

bool TCesp_anal::user_destroy() 
{
  delete _rel;
  delete _msk;
  return true;
}

int ce4100(int argc, char* argv[])
{
  TCesp_anal a;
  a.run(argc,argv,TR("Ripartizione analitica cespiti"));
  return 0;
}