#include <prefix.h>
#include <diction.h>

#include "svlib01.h"
#include "../ve/velib.h"

#include "svriep.h"
#include "svstat.h"

///////////////////////////////////////////////////////////
// Funzioni di utilita' comune
///////////////////////////////////////////////////////////
#define FREQ_CODES " GSQ12346A"

TFrequenza_statistiche char2frequency(char c)
{                     
  const TFixed_string list(FREQ_CODES);
  TFrequenza_statistiche f = TFrequenza_statistiche(list.find(c));
  return f;
}

char frequency2char(TFrequenza_statistiche f)
{                     
  const char* list = FREQ_CODES;
  return list[f];
}

TString & char2freqname(char c)
{                     
  static TString16 s;
  const TFixed_string list(FREQ_CODES);
  TToken_string std_descr;
	std_descr.add(TR("Giorno"));
	std_descr.add(TR("Settimana"));
	std_descr.add(TR("Quindicina"));
	std_descr.add(TR("Mese"));
  std_descr.add(TR("Bimestre"));
	std_descr.add(TR("Trimestre"));
	std_descr.add(TR("Quadrimestre"));
	std_descr.add(TR("Semestre"));
	std_descr.add(TR("Anno"));
  s=  std_descr.get(list.find(c)-1);
  return s;
}

int last_period(const TDate & d, TFrequenza_statistiche f)
{
  return last_period(d.year(),f);
}

int last_period(int anno, TFrequenza_statistiche f)
{         
  int n = 0;
  switch(f)
  {
  case fs_giornaliera    : n = 365 + (TDate::last_day(2, anno) == 29); break;
  case fs_settimanale    : n = 52; break;
  case fs_quindicinale   : n = 24; break;
  case fs_mensile        : n = 12; break;
  case fs_bimestrale     : n = 6; break;
  case fs_trimestrale    : n = 4; break;
  case fs_quadrimestrale : n = 3; break;
  case fs_semestrale     : n = 2; break;
  case fs_annuale        : n = 1; break;
  default: NFCHECK("Invalid frequency");
  }
  return n;
}

int divide(TFrequenza_statistiche f1, TFrequenza_statistiche f2)
{
  CHECK(f1 >= f2 && f2 != fs_nulla, "Invalid frequency division");

  if (f1 == f2)
    return 1;
  
  switch (f1)  
  {
  case fs_annuale:
    switch(f2)
    {
    case fs_semestrale    : return 2;
    case fs_quadrimestrale: return 3;
    case fs_trimestrale   : return 4;
    case fs_bimestrale    : return 6;
    case fs_mensile       : return 12;
    case fs_quindicinale  : return 24;
    case fs_settimanale   : return 52;
    case fs_giornaliera   : return 365 ;
    default               : break; 
    }
    break;
  case fs_semestrale:
    switch(f2)
    {
    case fs_trimestrale   :return 2;
    case fs_bimestrale    :return 3;
    case fs_mensile       :return 6;
    case fs_quindicinale  :return 12;
    case fs_settimanale   :return 26;
//    case fs_giornaliera   : return (365)/2;
    default               :break;
    }
    break;
  case fs_quadrimestrale:
    switch(f2)
    {
    case fs_bimestrale    :return 2;
    case fs_mensile       :return 4;
    case fs_quindicinale  :return 8;
//    case fs_giornaliera   : return 365/3;
    default               :break;
    }
  case fs_trimestrale:
    switch(f2)
    {
    case fs_mensile       :return 3;
    case fs_quindicinale  :return 6;
    case fs_settimanale   :return 13;
//    case fs_giornaliera   : return 365/4;
    default               :break;
    }
    break;
  case fs_bimestrale:
    switch(f2)
    {
    case fs_mensile       :return 2;
    case fs_quindicinale  :return 4;
//    case fs_giornaliera   : return 365/6;
    default               :break;
    }
    break;
  case fs_mensile:
    if (f2 == fs_quindicinale)
      return 2;
    break;
  case fs_quindicinale  : 
    switch(f2)
    {
    case fs_giornaliera   : return 15;
    default               :break;
    }
    break;
  case fs_settimanale   : 
    switch(f2)
    {
    case fs_giornaliera   : return 7;
    default               :break;
    }
    break;
  default:  
    break;
  }

  return 0;
}

//@cmember ritorna la parte "periodo" di una data (l'anno si ottiene da year())
// tutti i periodi cominciano da 1
int date2period(const TDate& datadoc, TFrequenza_statistiche freq)
{
  int classe;
  if (freq > fs_settimanale)
  {
    classe = datadoc.month();
    switch(freq)
    {
    case fs_quindicinale:
      classe = (classe-1) * 2 + 1;
      if (datadoc.day() > 15) classe++;
      break;
    case fs_bimestrale:
      classe = (classe-1) / 2 + 1;
      break;
    case fs_trimestrale:
      classe = (classe-1) / 3 + 1;
      break;
    case fs_quadrimestrale:
      classe = (classe-1) / 4 + 1;
      break;
    case fs_semestrale:
      classe = (classe-1) / 6 + 1;
      break;
    case fs_annuale:
      classe = 1;
      break;  
    default:     // fs_mensile
      break;
    }    
  }  
  else
  {
    const TDate primo(1, 1, datadoc.year());
    classe = int(datadoc - primo);
    if (freq == fs_settimanale)
    {
      classe /= 7;
      if (classe > 51) classe = 51;
    }  
    classe++;
  }  
  return classe;
}

long date2long(const TDate d, TFrequenza_statistiche f)
{
  return long(d.year())*long(last_period(d.year(),f))+date2period(d,f);
}

long period2long(const int anno, const int periodo, TFrequenza_statistiche f)
{
  if (f==fs_annuale)
    return long(anno)*long(100)+periodo;
  else
    return long(anno)*long(last_period(anno,f))+periodo;
}

// calcola approssimativamente la data corrispondente ad una coppia anno/periodo
const TDate & period2date(const int anno, int periodo, TFrequenza_statistiche f)
{
  static TDate d;
  d.set_year(anno+(periodo-1)/last_period(anno,f));
  d.set_month(1);
  d.set_day(1);
  periodo=periodo % last_period(anno,f);
  switch(f)
  {
    case fs_giornaliera:  d+=periodo-1; break;
    case fs_settimanale:  d+=(periodo-1)*7; break;
    case fs_quindicinale: d+=(periodo-1)*15; break;
    case fs_mensile:      d.set_month(periodo); break;
    case fs_bimestrale:   d.set_month(periodo*2); break;
    case fs_trimestrale:  d.set_month(periodo*3); break;
    case fs_quadrimestrale:d.set_month(periodo*4); break;
    case fs_semestrale:   d.set_month(periodo*6); break;
    case fs_annuale:
    case fs_nulla:
    default:
    break;
  }
  return d;
}

const TDate& floor(TDate& data, TFrequenza_statistiche freq)
{
  switch (freq)
  {           
  case fs_settimanale:
    {     
/*    
      const TDate primo(1, 1, data.year());
      int settimana = int((data - primo) / 7);
      if (settimana > 51) settimana = 51;
      data = primo;
      data += settimana * 7; 
*/
      data -= data.wday()-1;
    }
    break;  
  case fs_quindicinale:
    data.set_day(data.day() <= 15 ? 1 : 16);
    break;
  case fs_mensile:
    data.set_day(1);
    break;        
  case fs_bimestrale:
    data.set_day(1);
    data.set_month(((data.month()-1) / 2) * 2 + 1);
    break;
  case fs_trimestrale:
    data.set_day(1);
    data.set_month(((data.month()-1) / 3) * 3 + 1);
    break;
  case fs_quadrimestrale:
    data.set_day(1);
    data.set_month(((data.month()-1) / 4) * 4 + 1);
    break;
  case fs_semestrale:
    data.set_day(1);
    data.set_month(data.month() <= 6 ? 1 : 7);
    break;
  case fs_annuale:
    data.set_day(1);
    data.set_month(1);
    break;
  default: break;
  }
  return data;
}

const TDate& ceil(TDate& data, TFrequenza_statistiche freq)
{                
  floor(data, freq);
  switch (freq)
  {           
  case fs_settimanale:
    data += 6;
    if (data.month() == 12 && data.day() >= 29)
      data.set_end_month();  
    break;  
  case fs_quindicinale:
    if (data.day() == 1)
      data.set_day(15);
    else
      data.set_end_month();  
    break;
  case fs_mensile:
    data.set_end_month();
    break;        
  case fs_bimestrale:  
    data.addmonth(1);
    data.set_end_month();
    break;
  case fs_trimestrale:
    data.addmonth(2);
    data.set_end_month();
    break;
  case fs_quadrimestrale:
    data.addmonth(3);
    data.set_end_month();
    break;
  case fs_semestrale:
    data.addmonth(5);
    data.set_end_month();
    break;
  case fs_annuale:
    data.set_month(12);
    data.set_day(31);
    break;
  default: break;
  }
  return data;
}

///////////////////////////////////////////////////////////
// TStats_agg
///////////////////////////////////////////////////////////

void TStats_agg::init()
{               
  _ditta = prefix().get_codditta();  

  TConfig ini(CONFIG_DITTA, "sv");

  _frequenza        = char2frequency(ini.get_char("Frequenza"));
  _merce            = ini.get_bool("StatMerce");
  _prestazioni      = ini.get_bool("StatPrestazioni");
  _spesedoc         = ini.get_bool("StatSpesedoc");
  _omaggi           = ini.get_bool("StatOmaggi");
  _omaggio_is_merce = ini.get_bool("OmaggioIsMerce");
  _omaggi_valore    = ini.get_bool("OmaggioValore"); 
  
  _art_nocode     = ini.get_bool("ArtNoCode");
  _art_noanag     = ini.get_bool("ArtNoAnag");
  _art_noanag_grp = ini.get_bool("ArtNoAnagGrp");

  _agente    = ini.get_bool("AgenteGrp");
  _cliente   = ini.get_bool("ClienteGrp");
  _zona      = ini.get_bool("ZonaGrp");
  _giacenza  = ini.get_bool("GiacenzaGrp");
  _magazzino = ini.get_bool("MagazzinoGrp");
  _catvend   = ini.get_bool("CatVendGrp");
  
  _valfield  = ini.get("ValFld");
}

void TStats_agg::test_firm() const
{
  const long ditta = prefix().get_codditta();  
  if (ditta > 0 && ditta != _ditta)
  { 
    CHECK(_data.items() == 0, "Non cambiare ditta durante un'operazione di ricalcolo!");
    ((TStats_agg*)this)->init();
  }
}

// scrive la chiave nel record
// utilizza la convenzione in TStats_agg::find per la tokenstring
void TStats_agg::put_key(TRectype& stat, TToken_string& key) const
{    
  CHECK(stat.num() == LF_SVRIEP, "Ci vuole un record delle statistiche");
  stat.zero();
  key.restart();
  stat.put(SVR_ANNO,    key.get());
  stat.put(SVR_PERIODO, key.get());
  stat.put(SVR_TIPODOC, key.get());
  stat.put(SVR_TIPOART, key.get());
  stat.put(SVR_CODART,  key.get());
  stat.put(SVR_UMQTA,   key.get());
  stat.put(SVR_CODAG,   key.get());
  stat.put(SVR_TIPOCF,  key.get());
  stat.put(SVR_CODCF,   key.get());
  stat.put(SVR_ZONA,    key.get());
  stat.put(SVR_GIAC,    key.get());
  stat.put(SVR_MAG,     key.get());
  stat.put(SVR_CATVEN,  key.get());
}

// ricerca la chiave nell'assocarray
// la convenzione per la tokenstring � utilizzata in put_key
TStats_agg::TStats_data& TStats_agg::find(const TRiga_documento& rdoc)
{
  test_firm();

  const TDocumento& doc = rdoc.doc();
  const TDate datadoc = doc.get_date(DOC_DATADOC);

  TToken_string key(64);         
  
  key.add(datadoc.year()); 
  key.add(date2period(datadoc));
  key.add(doc.get(DOC_TIPODOC));
  
  char tipo = rdoc.tipo().tipo();
  if (tipo == RIGA_OMAGGI && _omaggio_is_merce)
    tipo = RIGA_MERCE;
  key.add(tipo);  
  
  TString80 codart;
  if (tipo != RIGA_PRESTAZIONI && tipo != RIGA_SPESEDOC)
  {
    codart = rdoc.get(RDOC_CODARTMAG);
    if (codart.empty() && !_art_noanag_grp) 
      codart = rdoc.get(RDOC_CODART);
  }
  else
    codart = rdoc.get(RDOC_CODART);
  key.add(codart);
  // l'unit� di misura non viene MAI RAGGRUPPATA sul riepilogo statistiche!
  key.add(rdoc.get(RDOC_UMQTA));
  
  if (_agente)
    key.add(doc.get(DOC_CODAG));
  else
    key.add("");

  if (_cliente)
  {
    key.add(doc.get(DOC_TIPOCF));
    key.add(doc.get(DOC_CODCF));
  } else {
    key.add("");
    key.add("");
  }  
  
  if (_zona)
    key.add(doc.get(DOC_ZONA));
  else
    key.add("");

  if (_giacenza)
    key.add(rdoc.get(RDOC_LIVELLO));
  else
    key.add("");

  if (_magazzino)
    key.add(rdoc.get(RDOC_CODMAG));
  else
    key.add("");
    
  if (_catvend)
    key.add(doc.get(DOC_CATVEN));
  else
    key.add("");

  TStats_data* ptr = (TStats_data*)_data.objptr(key);
  if (ptr == NULL)
  {
    ptr = new TStats_data;
    _data.add(key, ptr);
  }

  return *ptr;
} 

bool TStats_agg::can_add(const TRiga_documento& rdoc) const
{          
  test_firm();

  const TTipo_documento& tip = rdoc.doc().tipo();
  bool ok = tip.statistiche(); 
  if (ok) // Controlla se il tipo documento e' attivo per le statistiche
  {
    const char tipo = rdoc.tipo().tipo();
    switch(tipo)
    {
    case RIGA_MERCE      : ok = _merce; break;
    case RIGA_OMAGGI     : ok = _omaggi; break;
    case RIGA_PRESTAZIONI: ok = _prestazioni; break;
    case RIGA_SPESEDOC   : ok = _spesedoc; break;
    default              : ok = false; break;
    }
    if (ok && (tipo == RIGA_MERCE || tipo == RIGA_OMAGGI) && !rdoc.is_articolo())
    {     
      const TString& codart = rdoc.get(RDOC_CODART);
      if (codart.empty()) 
        ok = _art_nocode;
      else
        ok = _art_noanag;
    }
  }
   
  return ok;
} 

void TStats_agg::reset()
{
  _data.destroy();
}

bool TStats_agg::algebric_sum(const TRiga_documento& rdoc, int sign)
{
  const bool ok = can_add(rdoc);
  if (ok)
  {
    TStats_data& data = find(rdoc);
 
    real val_riga;
    if (_valfield.empty())
    {
      if (_omaggi_valore && rdoc.is_omaggio())
        val_riga = rdoc.imponibile_omaggio(2);  // Considera il valore indipendentemente dall'addebito IVA
      else
        val_riga = rdoc.imponibile();
    }
    else
      val_riga = rdoc.get_real(_valfield);

   const TDocumento& d = rdoc.doc();
   if (d.in_valuta())
    { 
      TCurrency_documento v(val_riga, d);
      v.change_to_firm_val();
      val_riga = v.get_num();
    }

    if (sign < 0)
      data._valore -= val_riga;
    else
      data._valore += val_riga;

    real qta = rdoc.quantita();
    if (qta.is_zero())
      qta = UNO;

    if (sign < 0)
      data._quantita -= qta;
    else
      data._quantita += qta;
  }  
  return ok;
}

bool TStats_agg::sub(const TRiga_documento& rdoc)
{               
  return algebric_sum(rdoc, -1);
}

bool TStats_agg::add(const TRiga_documento& rdoc)
{ 
  return algebric_sum(rdoc, +1);
}

bool TStats_agg::empty()
{
  TLocalisamfile stat(LF_SVRIEP);
//  return stat.empty();  // Per ora non e' affatto affidabile
  return stat.first() != NOERR;
}

bool TStats_agg::update()
{        
  test_firm();

  bool ok = TRUE;

  TToken_string key(64);
  TFast_isamfile stat(LF_SVRIEP);
  stat.set_curr(new TSVriep_record);
  
  TRectype& curr = stat.curr();
  
  _data.restart();
  for (THash_object* h = _data.get_hashobj(); h; h = _data.get_hashobj())
  { 
    
    TStats_data& data = (TStats_data&)h->obj();
    if (data._quantita.is_zero() && data._valore.is_zero())
      continue;
      
    key = h->key();
    
    bool saved = FALSE;
    while (!saved)
    {
      put_key(curr, key);
      int err = stat.read(_isequal, _lock);
      if (err != NOERR)
        put_key(curr, key);
      real quantita(curr.get(SVR_QUANTITA));
      real valore(curr.get(SVR_VALORE));
      quantita += data._quantita;
      valore += data._valore;
      curr.put(SVR_QUANTITA, quantita);
      curr.put(SVR_VALORE, valore);
      
      if (err == NOERR)
        err = stat.rewrite();
      else  
        err = stat.write();
          
      switch(err)
      {
      case NOERR      : saved = TRUE; 
                        break;
      case _isreinsert: break; 
      default         : saved = !yesno_box(FR("Errore %d nell'aggiornamento statistiche:"
                                           "Si desidera ritentare?"), err);
                        break;
      }
    }  
  }
  reset();
  
  return ok;
}

TStats_agg::TStats_agg() : _ditta(-1)
{
}

///////////////////////////////////////////////////////////
// Record del file svriep
///////////////////////////////////////////////////////////

TSVriep_record::  TSVriep_record(): TVariable_rectype(LF_SVRIEP)
{}

TSVriep_record::~TSVriep_record()
{}

const TString & TSVriep_record::get_str(const char* fieldname) const
{
  static TString80 chiavi;
  chiavi=TRectype::get_str(SVR_CHIAVI);

  if (strcmp(fieldname,SVR_CODART)==0)
    return chiavi.mid(0,20);
  else if (strcmp(fieldname,SVR_CODAG)==0)
    return chiavi.mid(20,5);
  else if (strcmp(fieldname,SVR_TIPOCF)==0)
    return chiavi.mid(25,1);
  else if (strcmp(fieldname,SVR_CODCF)==0)
    return chiavi.mid(26,6);
  else if (strcmp(fieldname,SVR_ZONA)==0)
    return chiavi.mid(32,3);
  else if (strcmp(fieldname,SVR_GIAC)==0)
    return chiavi.mid(35,15);
  else if (strcmp(fieldname,SVR_CATVEN)==0)
    return chiavi.mid(50,3);
  else if (strcmp(fieldname,SVR_MAG)==0)
    return chiavi.mid(53,3);
  else
    return TRectype::get_str(fieldname);
}


void TSVriep_record::put_str(const char* fieldname, const char* v)
{
  const  TString80 val(v);
  TString80 chiavi(TRectype::get_str(SVR_CHIAVI));
  
  if (strcmp(fieldname,SVR_CODART)==0)
    chiavi.overwrite(val,0);
  else if (strcmp(fieldname,SVR_CODAG)==0)
    chiavi.overwrite(val,20);
  else if (strcmp(fieldname,SVR_TIPOCF)==0)
    chiavi.overwrite(val,25);
  else if (strcmp(fieldname,SVR_CODCF)==0)
    chiavi.overwrite(val,26);
  else if (strcmp(fieldname,SVR_ZONA)==0)
    chiavi.overwrite(val,32);
  else if (strcmp(fieldname,SVR_GIAC)==0)
    chiavi.overwrite(val,35);
  else if (strcmp(fieldname,SVR_CATVEN)==0)
    chiavi.overwrite(val,50);
  else if (strcmp(fieldname,SVR_MAG)==0)
    chiavi.overwrite(val,53);
  else
  {
    TRectype::put_str(fieldname,val);
    return ;
  }
  TRectype::put_str(SVR_CHIAVI,chiavi);
  return ;
}


///////////////////////////////////////////////////////////
// cerca di restituire una chiave di bassa priorit� (livello
const TString & TStat_cache::getkey2discard()
{
  THash_object * o;
  CHECK(items()>0,"E' stata chiamata la funzione getkey2discard con la cache vuota");
  while ((o=_cache.get_hashobj()) == NULL) ;
  if (((TRectype &)o->obj()).get_int(SVS_LIVELLO)<=1)
  {
    while ((o=_cache.get_hashobj()) == NULL) ;
    if (((TRectype &)o->obj()).get_int(SVS_LIVELLO)==0)
      while ((o=_cache.get_hashobj()) == NULL) ;
  }
  return o->key();
}

TStat_cache::TStat_cache (TLocalisamfile *f, bool lock):
  TRWrecord_cache( f,1,lock)
{
}

// azzera la cache e distrugge il file!
void TStat_cache::zap()
{
  clear();

  int err=NOERR;
  while (err==NOERR)
  {
    err=file().first();
    if (err==NOERR) 
      err=file().remove();
  }
}