#include <stdlib.h>

#include <tabutil.h>
#include <prefix.h>
#include <utility.h>

#include "cg2101.h"
#include "cg2103.h"

#include <mov.h>
#include <rmov.h>
#include <rmoviva.h>
#include <utility.h>

///////////////////////////////////////////////////////////
// Movimento di prima nota
///////////////////////////////////////////////////////////

TMovimentoPN::TMovimentoPN()
: TRelation(LF_MOV), _cg(LF_RMOV, "NUMRIG"), _iva(LF_RMOVIVA, "NUMRIG")
{
  add(LF_RMOV, "NUMREG=NUMREG");
  add(LF_RMOVIVA, "NUMREG=NUMREG");
}

void TMovimentoPN::destroy_rows(long num)
{                    
  _cg.destroy_rows();
  _cg.renum_key("NUMREG", num);
  _iva.destroy_rows();
  _iva.renum_key("NUMREG", num);
}


TRectype& TMovimentoPN::cg(int i)
{
  return _cg.row(i >= 0 ? i+1 : -1, TRUE);
}

TRectype& TMovimentoPN::iva(int i)
{
  return _iva.row(i >= 0 ? i+1 : -1, TRUE);
}


int TMovimentoPN::read_mov_rows()
{                   
  const TRectype& mov = curr();
  const long numreg = mov.get_long(MOV_NUMREG);
  
  TRectype* cgfilter = new TRectype(LF_RMOV);
  cgfilter->put(RMV_NUMREG, numreg);
  _cg.read(cgfilter);
  
  TRectype* ivafilter = new TRectype(LF_RMOVIVA);
  ivafilter->put(RMI_NUMREG, numreg);
  _iva.read(ivafilter);

/*  
  if (_cg.rows() > 0 && _iva.rows() > 0 && cg(0).get_char(RMV_ROWTYPE) != 'T')
    adjust_row_types();
*/
  return _cg.rows();
}


int TMovimentoPN::read(TIsamop op, TReclock lockop, TDate& atdate)
{
  const int err = TRelation::read(op, lockop, atdate);
  if (err == NOERR) 
  {
    _olddate = file().get("DATAREG");       // Memorizza data operazione per aggiornare i saldi
    read_mov_rows();                        // Riempie i due record array
  }  
  return err;
}

char TMovimentoPN::frequenza_versamenti(int year) const
{
  static int last_year = 0;
  static long last_firm = 0;
  static char last_freq = ' ';
  
  const long firm = prefix().get_codditta();
  
  if (firm != last_firm || year != last_year)
  {
    TString16 key; key.format("%05ld%d", firm, year);
    TTable lia("%LIA");
    lia.put("CODTAB", key);
    if (lia.read() != NOERR)
    { 
      TLocalisamfile nditte(LF_NDITTE);
      nditte.put("CODDITTA", firm);
      nditte.read();
      last_freq = nditte.get_char("FREQVIVA");
    }
    else 
      last_freq = lia.get_char("S7");
    CHECK(last_freq == 'M' || last_freq == 'T', "Frequenza versamenti IVA assurda");
    
    last_firm = firm;  
    last_year = year;
  }
  
  return last_freq;
}  

int TMovimentoPN::date2liq(const TDate& data) const
{
  const int anno = data.year();
  int mese = data.month();
  if (frequenza_versamenti(anno) == 'T')
    mese += 2 - ((mese-1) % 3);
  return mese;  
}  


bool TMovimentoPN::controlla_liquidazione(const TDate& data, TRegistro& registro, bool reset) const
{    
  bool calcolata = FALSE;

  const int anno = data.year();
  const int mese = date2liq(data);
  
  // Chiave di LIM: Anno (1-4), Mese (5-6)
  TString16 key; key.format("%04d%02d", anno, mese);
  TTable lim("LIM");
  lim.setkey(1);
  lim.put("CODTAB", key);
  if (lim.read() == NOERR)
  {
    calcolata = data.month() <= registro.mese_stampa_ultima_liq();  // Controlla se progressivi ricalcolati (registri)
  
    if (reset)
    {
      // Resetta i flag di calcolato sulla liquidazione IVA del mese di registrazione        
      lim.zero("B0");                  // calcolato
      lim.zero("B1");                  // progressivi ricalcolati
      lim.rewrite();
    }
  }  
  
  if (reset)
  {
    const bool att_mista = registro.name().empty() ? FALSE : registro.attivita_mista();
    const int att = att_mista ? 2 : 1; 
    
    // Chiave di PLM: Anno (1-4), Cod. Att. (5-9), Tipo att. (10-10), Mese (11-12)
    TTable plm("PLM"); 
    for (int a = 1; a <= att; a++)
    {
      TString16 chiave;                       
      TString16 attivita(registro.attivita()); attivita.right_just(5, '0');
      chiave << data.year() << attivita << a << format("%02d", data.month());
      plm.put("CODTAB", chiave);
      if (plm.read() == NOERR)
      {
        const bool calcolato = plm.get_bool("B0");
        if (calcolato)
        {
          plm.zero("B0");
          plm.rewrite();
        }  
      }  
    }
  }
  
  return calcolata;
}


int TMovimentoPN::registra(bool re, bool force)
{
  int err = re ? TRelation::rewrite(force) : TRelation::write(force);
  if (err != NOERR) 
    return err;

  const TRectype& m = curr();
  const long numreg = m.get_long("NUMREG");
  
  if (!re)
    _cg.renum_key("NUMREG", numreg);
  err = _cg.write(re);
  if (err != NOERR) 
    return err;

  const int annoiva = m.get_int("ANNOIVA");
  const TString reg(m.get_str("REG"));
  TRegistro registro(reg, annoiva);
  const bool att_mista = reg.empty() ? FALSE : registro.attivita_mista();

  for (int i = 0 ; i < iva_items(); i++)
  {
    TRectype& r = iva(i);
    int tipoatt = 1;
    if (att_mista)
    {
      const char tipo = r.get_char("TIPOC");
      if (tipo <= ' ')               
      {
        TBill c(r.get_int("GRUPPO"), r.get_int("CONTO"), r.get_long("SOTTOCONTO"));
        tipoatt = c.tipo_att();
      }                      
    }
    r.put("TIPOATT", tipoatt);
  }
  
  if (!re)
    _iva.renum_key("NUMREG", numreg);
  err = _iva.write(re);
  if (err != NOERR) 
    return err;


  // Aggiorna data registrazione e protocollo IVA sul registro
  const TDate datareg(m.get("DATAREG"));
  if (reg.not_empty())
  {                             
    const long protiva = m.get_long("PROTIVA");
    const long uprotiva = m.get_long("UPROTIVA");
    const long max = protiva > uprotiva ? protiva : uprotiva;
    registro.update(max, datareg);
  }      
  
  // Aggiorna flags di ricalcolo liquidazione
  
  controlla_liquidazione(datareg, registro, TRUE);
  if (re && datareg != _olddate)
    controlla_liquidazione(_olddate, registro, TRUE);

  return err;
}


int TMovimentoPN::write(bool force, TDate&)
{
  return registra(FALSE, force);
}


int TMovimentoPN::rewrite(bool force, TDate&)
{
  return registra(TRUE, force);
}


int TMovimentoPN::remove(TDate&)
{         
  int err = _cg.remove();
  
  if (err == NOERR)
    err = _iva.remove();

  if (err == NOERR)
    err = TRelation::remove();
  
  if (err == NOERR)
  {                        
    const TRectype& m = curr();
    const TDate datareg(m.get("DATAREG"));
    const int annoiva = m.get_int("ANNOIVA");
    const TString reg(m.get_str("REG"));
    TRegistro registro(reg, annoiva);
    controlla_liquidazione(datareg, registro, TRUE);
  }                           
  
  return err;
}


///////////////////////////////////////////////////////////
// Aggiustamento movimenti rovinati o convertiti male
///////////////////////////////////////////////////////////

class TConti_array : private TAssoc_array
{         
public:  // TObject
  virtual bool ok() const { return items() != 0; }

public:
  bool add(const TBill& conto, const real& importo);
  real importo(const TBill& conto);
  bool remove(const TBill& conto);
  
  bool add_iva(bool det, const real& importo);
  real importo_iva(bool det);
  bool remove_iva(bool det);

  TConti_array() {}
  virtual ~TConti_array() {}
};

bool TConti_array::add(const TBill& conto, const real& importo)
{
  const char* key = conto.string();
  real* imp = (real*)objptr(key);
  if (imp == NULL)
    TAssoc_array::add(key, importo);
  else
    *imp += importo;
  return imp != NULL;    
}

real TConti_array::importo(const TBill& conto)
{
  const char* key = conto.string();
  const real* imp = (real*)objptr(key);
  return imp ? *imp : ZERO;
}

bool TConti_array::remove(const TBill& conto)
{
  const char* key = conto.string();
  return TAssoc_array::remove(key);
}

bool TConti_array::add_iva(bool det, const real& importo)
{
  const char* const key = det ? "D" : "N";
  real* imp = (real*)objptr(key);
  if (imp == NULL)
    TAssoc_array::add(key, importo);
  else
    *imp += importo;
    
  return imp != NULL;  
}

real TConti_array::importo_iva(bool det)
{
  const char* const key = det ? "D" : "N";
  const real* imp = (real*)objptr(key);
  return imp ? *imp : ZERO;
}


bool TConti_array::remove_iva(bool det)
{
  const char* const key = det ? "D" : "N";
  return TAssoc_array::remove(key);
}


HIDDEN bool detraibile(const TRectype& row, const TCausale& cau)
{
  if (cau.iva() == iva_vendite)                     // Vendite sempre detraibili
    return TRUE;

  const int tipo_det = row.get_int(RMI_TIPODET);    // Leggi tipo detraibilita
  if (tipo_det != 0)
    return FALSE;

  const real& prorata = cau.reg().prorata();
  return prorata < 100.0;                           // Se prorata = 100% e' indetraibile
}

// Aggiusta i row types se sono andati persi o non sono stati convertiti
void TMovimentoPN::adjust_rowtypes()
{
  const TRectype& mov = curr();
  const char tipo = mov.get_char(MOV_TIPO);
  const long codice = mov.get_long(MOV_CODCF);
  const int annoiva = mov.get_int(MOV_ANNOIVA);
  const TCausale causale(mov.get(MOV_CODCAUS), annoiva);
    
  TConti_array conti;
  for (int r = 0; r < _iva.rows(); r++)
  {       
    const TRectype& row = iva(r);
    const TBill bill(row); 
    real imponibile(row.get(RMI_IMPONIBILE));
    
    real imposta(row.get(RMI_IMPOSTA));
    const bool det = detraibile(row, causale);
    
    if (imposta.is_zero() && causale.corrispettivi())
    {
      const TCodiceIVA iva(row.get(RMI_CODIVA));
      imposta = iva.scorpora(imponibile);
    }
    conti.add(bill, imponibile);
    conti.add_iva(det, imposta);
  }
  
  bool totale = FALSE;
  bool ritfis = mov.get_real(MOV_RITFIS).is_zero();
  bool ritsoc = mov.get_real(MOV_RITSOC).is_zero();
  bool ivadet = conti.importo_iva(TRUE).is_zero();
  bool ivanon = conti.importo_iva(FALSE).is_zero();
  
  for (r = 0; r < _cg.rows(); r++)
  {       
    TRectype& row = cg(r);
    const char rt = row.get_char(RMV_ROWTYPE);
    switch(rt)
    {               
      case 'F': ritfis = TRUE; break;
      case 'S': ritsoc = TRUE; break;
      case 'T': totale = TRUE; break;
      default : break;
    }
    if (rt > ' ') continue;
    
    if (!totale && row.get_char(RMV_TIPOC) == tipo && row.get_long(RMV_SOTTOCONTO) == codice)
    {
      row.put(RMV_ROWTYPE, 'T');
      totale = TRUE;
      continue;
    }                      
      
    const real importo(row.get(RMV_IMPORTO));
    const TBill bill(row); 
      
    if (!ritfis && importo == mov.get_real(MOV_RITFIS))
    {                                    
      TBill conto_rit; causale.bill(RIGA_RITENUTE_FISCALI, conto_rit);
      if (!conto_rit.ok() || conto_rit == bill)
      {
        row.put(RMV_ROWTYPE, 'F');
        ritfis = TRUE;
        continue;
      }
    }
      
    if (!ritsoc && importo == mov.get_real(MOV_RITSOC))
    {                                    
      TBill conto_rit; causale.bill(RIGA_RITENUTE_SOCIALI, conto_rit);
      if (!conto_rit.ok() || conto_rit == bill)
      {
        row.put(RMV_ROWTYPE, 'S');
        ritsoc = TRUE;
        continue;
      }
    }
    
    if (!conti.ok())
      continue;       // Ho esaurito i conti IVA
      
    if (importo == conti.importo(bill))
    {
      row.put(RMV_ROWTYPE, 'I');
      conti.remove(bill);
      continue;
    }
    
    if (!ivadet && importo == conti.importo_iva(TRUE))
    {
      row.put(RMV_ROWTYPE, 'D');
      conti.remove_iva(TRUE);
      continue;
    }
    
    if (!ivanon && importo == conti.importo_iva(FALSE))
    {
      row.put(RMV_ROWTYPE, 'N');
      conti.remove_iva(FALSE);
      continue;
    }
  }
}
