#include <currency.h>
#include <fraction.h>
#include <mask.h>
#include <msksheet.h>
#include <printapp.h>
#include <progind.h>
#include <tabutil.h>

#include "sv1.h"
#include "sv1200a.h"
#include "sv1200b.h"
#include "sv1200c.h"
#include "svlib01.h"
#include "svriep.h"
#include "svstat.h"

#include "../mg/anamag.h"
#include "../mg/mglib.h"
#include "../mg/umart.h"
#include "../pr/agenti.h"
#include "../ve/clifor.h"

#define  STR_TOTGEN   "T"
#define  STR_TIPODOC  "D"
#define  STR_UMISURA  "U"
#define  STR_ARTICOLO "A"
#define  STR_LIVGIAC  "L"
#define  STR_CATVEN   "V"
#define  STR_CLIFOR   "C"
#define  STR_AGENTE   "G"
#define  STR_MAGAZZ   "M"
#define  STR_ZONA     "Z"
#define  STR_ARTFIELD "B"

#define  CHR_TOTGEN   'T'
#define  CHR_TIPODOC  'D'
#define  CHR_UMISURA  'U'
#define  CHR_ARTICOLO 'A'
#define  CHR_LIVGIAC  'L'
#define  CHR_CATVEN   'V'
#define  CHR_CLIFOR   'C'
#define  CHR_CLI      'C'
#define  CHR_FOR      'F'
#define  CHR_AGENTE   'G'
#define  CHR_MAGAZZ   'M'
#define  CHR_ZONA     'Z'
#define  CHR_ARTFIELD 'B'

#define COMP_AS_VALUE 'V'
#define COMP_AS_DIFF  'D'
#define COMP_AS_PERC  'P'

#define COLONNA_VALORE   "V"
#define COLONNA_QTA      "Q"
#define COLONNA_ENTRAMBI "E"

#define ALG_VALORI      'V'
#define ALG_PROGRESSIVI 'P'
#define ALG_MOBILI      'M'

#define LARG_COLCODICE  49
#define POS_PRIMACOL  51
#define PICTURE_VALORI "###.###.###.##@"
#define LARG_COLVAL 16 
#define PICTURE_QUANTITA "###.###.##@,@##"
#define LARG_COLQTA 16 
#define PICTURE_PERCENT  "###@,@"
#define LARG_COLPERC 8

#define FORMWIDTH 150
#define DEFAULT_OUTFILE "outstat0"
///////////////////////////////////////////////////////////
// TPeriodo
///////////////////////////////////////////////////////////

class TPeriodo : public TSortable
{
  static TFrequenza_statistiche _frequenza;
  
  int _anno;
  int _periodo;
  
protected:
  virtual int compare(const TSortable& s) const;
  
  void set(const TPeriodo& ap) { set(ap._anno, ap._periodo); }

public:
  static void set_frequency(TFrequenza_statistiche f)
  { CHECK(f != fs_nulla, "Frequenza nulla"); _frequenza = f; }
  static TFrequenza_statistiche frequency() { return _frequenza; }
  
  int operator -(const TPeriodo& ap) const;
  int difference(const TPeriodo& p, TFrequenza_statistiche freq) const;
  const TPeriodo& operator +=(int n);
  const TPeriodo& sum(int n, TFrequenza_statistiche freq);
  
  TDate first_day() const;
  TDate last_day() const;
  
  void set_year(int y);
  void set_period(int p);
  void set(int y, int p) { set_year(y); set_period(p); }
  void set(const TDate& d);
  
  const TPeriodo& operator=(const TPeriodo& ap) { set(ap); return ap; }
  const TPeriodo& operator=(const TDate& d) { set(d); return *this; }
                      
  TPeriodo() : _anno(1900), _periodo(1) { }
  TPeriodo(int anno, int periodo) { set(anno, periodo); }
  TPeriodo(const TDate& d) { set(d); }
  TPeriodo(const TPeriodo& ap) { set(ap); }
  virtual ~TPeriodo() { }
};

TFrequenza_statistiche TPeriodo::_frequenza = fs_nulla;

int TPeriodo::compare(const TSortable& s) const
{
  const TPeriodo& ap = (const TPeriodo&)s;
  int diff = _anno - ap._anno;
  if (diff == 0)
    diff = _periodo - ap._periodo;
  return diff;  
}

int TPeriodo::operator -(const TPeriodo& ap) const
{                                   
  int result = 0;
  int annomin, periodomin, annomax, periodomax;
  
  const bool swapped = compare(ap) < 0;
  if (swapped)    
  {
    annomin    = _anno;
    periodomin = _periodo;
    annomax    = ap._anno;
    periodomax = ap._periodo;
  }
  else
  {
    annomin    = ap._anno;
    periodomin = ap._periodo;
    annomax    = _anno;
    periodomax = _periodo;
  } 
  if (annomin < annomax)
  {
    result += last_period(annomin, _frequenza) - periodomin + 1;
    for (annomin++; annomin < annomax; annomin++)
      result += last_period(annomin, _frequenza);
    periodomin = 1;  
  }
  result += periodomax - periodomin;

  return swapped ? -result : result;
}

int TPeriodo::difference(const TPeriodo& p, TFrequenza_statistiche freq) const
{ 
  CHECK(freq > _frequenza, "Incomputable difference");
  const TDate d1 = first_day();
  const TDate d2 = p.first_day();
  
  const TFrequenza_statistiche old_freq = _frequenza;
  _frequenza = freq;
  
  const TPeriodo p1(d1);
  const TPeriodo p2(d2);
  const int diff = p1 - p2;
  
  _frequenza = old_freq;
  
  return diff;
} 

const TPeriodo& TPeriodo::operator +=(int n)
{             
  // Sembra orrendo ma per ora e' modo piu' sicuro!
  
  if (n >= 0)
  {   
    int lp = last_period(_anno, _frequenza);
    for (int i = n; i > 0; i--)
    {
      _periodo++;
      if (_periodo > lp)
      {
        _anno++;
        _periodo = 1;
        lp = last_period(_anno, _frequenza);
      }
    }
  }
  else
  {
    for (int i = n; i < 0; i++)
    {
      _periodo--;
      if (_periodo <= 0)
      {
        _anno--;
        _periodo = last_period(_anno, _frequenza);
      }
    }
  }  
  return *this;
}

const TPeriodo& TPeriodo::sum(int n, TFrequenza_statistiche freq)
{
  CHECK(freq > _frequenza, "Incomputable sum");
  TDate d = first_day();
  
  const TFrequenza_statistiche old_freq = _frequenza;
  _frequenza = freq;
  
  TPeriodo p1(d);
  p1 += n;
  d = p1.first_day();
  
  _frequenza = old_freq;
  
  set(d);
  return *this;
}

TDate TPeriodo::first_day() const
{
  TDate d(1, 1, _anno);
  switch(_frequenza)
  {
  case fs_annuale       : break;
  case fs_semestrale    : if (_periodo > 1) d.set_month(7); break;
  case fs_quadrimestrale: d.set_month((_periodo - 1) * 4 + 1); break;
  case fs_trimestrale   : d.set_month((_periodo - 1) * 3 + 1); break;
  case fs_bimestrale    : d.set_month((_periodo - 1) * 2 + 1); break;
  case fs_mensile       : d.set_month(_periodo); break;
  case fs_quindicinale  : d.set_month((_periodo - 1) / 2 + 1); 
                          if ((_periodo & 0x1) == 0) d.set_day(16);
                          break;
  case fs_settimanale   : d += 7 * (_periodo - 1); break;
  case fs_giornaliera   : d += _periodo - 1;                       
  }
  return d;
}

TDate TPeriodo::last_day() const
{
  TDate d = first_day();
  ceil(d, _frequenza);
  return d;
}

void TPeriodo::set_year(int y) 
{ 
  CHECKD(y >= 1900 && y < 3000, "Bad year ", y); 
  _anno = y; 
}

void TPeriodo::set_period(int p) 
{ 
  CHECKD(p > 0 && p <= last_period(_anno, _frequenza), "Bad period ", p); 
  _periodo = p; 
} 

void TPeriodo::set(const TDate& d)
{
  set(d.year(), date2period(d, _frequenza));
}

///////////////////////////////////////////////////////////
// TFrequenza_colonne
///////////////////////////////////////////////////////////

class TFrequenza_colonne : public TObject
{
  TFrequenza_statistiche _frequenza;
  int _multiplo;
  TPeriodo _periodo_inizio;
  TDate _data_inizio;  
  
public:
  void init(TFrequenza_statistiche f, int m, const TDate& d);
  
  int period2column(const TPeriodo& p) const;
  int date2column(const TDate& d) const;
  TDate column_first_day(int n) const;
  TDate column_last_day(int n) const;
  
  const TPeriodo& inizio() const { return _periodo_inizio; }
};

void TFrequenza_colonne::init(TFrequenza_statistiche f, int m, const TDate& d) 
{ 
//  _frequenza = (f == fs_nulla || m <= 0) ? TPeriodo::frequency() : f;
  _frequenza = (f == fs_nulla || m >0) ? TPeriodo::frequency() : f;
  _multiplo = (m <= 0) ? divide(_frequenza, TPeriodo::frequency()) : m; 
  _periodo_inizio = d;
  _data_inizio = _periodo_inizio.first_day();
}

int TFrequenza_colonne::period2column(const TPeriodo& p) const
{
  int result;
  
  if (_multiplo > 0)
  {
    int diff = p - _periodo_inizio;
    result = diff / _multiplo;
    // Corregge arrotondamento per periodi inferiori a _periodo_inizio
    // ad esempio: -1 / 12  = 0   e non -1 (risultato corretto)
    if (diff < 0 && ((diff % _multiplo) != 0))
      result--;
  }
  else
    result = p.difference(_periodo_inizio, _frequenza);
  
  return result;
}

int TFrequenza_colonne::date2column(const TDate& d) const
{
  const TPeriodo p(d);
  return period2column(p);
}

TDate TFrequenza_colonne::column_first_day(int n) const
{                      
  TPeriodo p(_periodo_inizio);
  if (_multiplo > 0)
    p += n * _multiplo;
  else
    p.sum(n, _frequenza);

  TDate d = p.first_day();
  return d;
}

TDate TFrequenza_colonne::column_last_day(int n) const
{                      
  TDate d = column_first_day(n+1);
  --d;
  return d;
}

///////////////////////////////////////////////////////////
// Stampa statistiche
///////////////////////////////////////////////////////////

class TStampa_stat : public TPrint_application
{
  enum { MAX_ROWS = 8 }; // numero massimo di raggruppamenti

  enum { LINEA_DATI='D', LINEA_RAFFRONTI='R', LINEA_TARGET='T'}; // codici per le righe
  
  TMask * _msk, *_print_msk,*_export_msk;
  TStats_agg _stats;
  TFrequenza_colonne _freq;
  TCodart_livelli* _liv_art;
  TCodgiac_livelli* _liv_giac;
  TRecord_cache *_tipodoc,*_catven, *_umart;

  TToken_string _key;
  TString _last_key;
  TRectype  * _last_data;

  TString_array _des_fld;
  TString_array _file_fld;
  TString_array _key_fld;
  TString_array _field_fld;

  bool _ragg_per_um;

  // ******************
  // costruzione file di Output
  TStat_cache * _svcache;         // cache R/W per le statistiche
  
  int         _last_lev_grp,    // livello dell'ultimo raggruppamento incontrato
              _liv_riga;        // livello della riga
  TArray      _group_recs;      // array dei record dei raggruppamenti
  TArray      _group_recs_raff;      // array dei record dei raggruppamenti (raffronti)
  int         _indent[MAX_ROWS];// array delle posizione di indentazione
  real        _totval_riga[SVS_NUMCOLONNE]; //array dei totali degli "anni" della riga
  real        _totqta_riga[SVS_NUMCOLONNE]; //array dei totali degli "anni" della riga
  
  int       _colpage,    // numero di pagina ripetuta per contenere in larghezza tutte le colonne
            _last_page;   // ultima pagina della "prima copia"
  int _larg[7];
  int _largcol,        // larghezza di una colonna
      _numcol_dati,         // numero di colonne contenenti dati
      _numcol,         // numero totale di colonne (dati+totali)
      _pagewidth,      // larghezza di una pagina
      _col_anno ;      // numero di colonne che formano un "Anno" (=periodo di rif.)
  bool _st_totr;       // flag stampa totali di riga
  bool _st_totc;       // flag stampa totali di colonna
  bool _st_tota;       // flag stampa totali di "anno"
  bool _st_val;        // Stampa valori   
  bool _st_qta;        // Stampa quantita'
	bool _st_um;				 // Stampa UM
  bool _st_uni;        // Stampa valori/quantita'
  TString16 _valid_types;// Stringa contenente i tipi di riga da stampare
  // ******************
  // stampa file di Output
  TString _wrk_row, _save_code; // stringhe di lavoro per le righe di stampa
  TRelation * _rel;     // relazione per la stampa
private:

  
  void put_column(TRectype & statrec,int col, const TSVriep_record& curr);
	const real fc_um(const TRectype& statrec,const TSVriep_record& rieprec);
	void invalid_columns(TRectype & statrec);
  void standardize_um(TSVriep_record& rieprec, bool force=FALSE);

  // generazione del file di output
  void set_ragg_per_um();
  bool filtro_chiavi(TSVriep_record &curr);
  void update_file(const char * key, const char * code,const char tipo, const int level, const int col, TSVriep_record& curr, const TString & fld, int row);
  void genera_file(const char *outfn);

  // handler per i campi di maschera
  static bool chiave_notify(TSheet_field& f, int r, KEY k);
  static bool chiave_handler(TMask_field& f, KEY k);
  static bool campo_handler(TMask_field& f, KEY k);
  static bool artfld_handler(TMask_field& f, KEY k);

  static bool filename_handler(TMask_field& f, KEY k);
  static bool codice_handler(TMask_field& f, KEY k);
  static bool multiplo_handler(TMask_field& f, KEY k);
  static bool numero_handler(TMask_field& f, KEY k);
  static bool periodo_handler(TMask_field& f, KEY k);
  static bool dataini_handler(TMask_field& f, KEY k);
  static bool datafin_handler(TMask_field& f, KEY k);
  static bool raffronto_handler(TMask_field& f, KEY k);
  static bool dataraf_handler(TMask_field& f, KEY k);
protected:
  virtual bool user_create();
  virtual bool user_destroy();
  virtual bool set_print(int i);
  virtual void set_page(int file, int count);

  virtual bool preprocess_print(int file, int counter) ;
  virtual print_action postprocess_print(int file, int counter);
  virtual bool preprocess_page(int file, int counter);
  virtual print_action postprocess_page(int file, int counter);
  virtual void preprocess_header();
  virtual void preprocess_footer();

  virtual void on_config_change();

  void set_descr(int& row);
  int handle_levchange(int row,const int level);
  int set_rows_colonne(int row, const TRectype &strec);
  void set_row_atpos(char section,int row,const char * f,int pos);
  void set_filled_row(char section,int row,char c,int pos, int len);
  void reset_grplevels();
  
  bool test_field(const TString& cod, TMask_field& f) const;

  void set_printmask();

  void fill_field_list(TMask& m);
  void set_frequency();
  bool set_column_frequency();
  bool recalc_period();

  TString& build_key(const TRectype& rec);


  const TString& nome_colonna1(int c, TString& nome);
  const TString& nome_colonna2(int c, TString& nome);
  const TString& nome_totale(int t, TString& nome);
  const TString& partkey_name(const char *lev_code, int row, TString& park) const;
  const TString& get_part(TString & lev_code, TSVriep_record & rieprec, const TString & fld); // const;
  const TString& get_descrpart(const char *lev_code, TSVriep_record &rieprec, const TString & fld, int row);
  void adjust_record(TRectype &strec, int from) const;
  int numlevels() const { return selmask().sfield(F_CHIAVE).items(); }

public:
  TMask& selmask() const { return *_msk; }
  TMask& printmask() const { return *_print_msk; }
  TMask& expmask() const { return *_export_msk; }
  virtual bool menu(MENU_TAG);
  TStampa_stat() {}
  virtual ~TStampa_stat() { }
};

inline TStampa_stat& app() { return (TStampa_stat&)main_app(); }

print_action TStampa_stat::postprocess_print(int file, int count)
{
  reset_footer();
  reset_header();
  _last_page=get_page_number();
  _colpage++;
  //printer().formfeed();
  return TPrint_application::postprocess_print(file,count);
}
bool TStampa_stat::preprocess_page(int file, int count)
{
  return TPrint_application::preprocess_page(file,count);
}

print_action TStampa_stat::postprocess_page(int file, int count)
{
  reset_print();
  return TPrint_application::postprocess_page(file,count);
}


bool TStampa_stat::set_print(int i)
{ 
  if (printmask().run() == K_QUIT)
    return FALSE;
  _st_totr=printmask().get_bool(F_STTOTALIRIGA);
  _st_totc=printmask().get_bool(F_STTOTALICOL);
  _st_tota=printmask().get_bool(F_STTOTALIANNO);

  _largcol = 0;            
  for (int l = 0; l < 7; l++)
    _larg[l] = 0;
  if (_st_val) 
  { 
    _largcol += LARG_COLVAL;
    _larg[0] = LARG_COLVAL;
    if (printmask().get_bool(F_STRAFFRONTO))
    {
      switch (printmask().get(F_TIPORAFFRONTO)[0])
      {
        case COMP_AS_DIFF:
          _largcol += LARG_COLVAL;
          _larg[1] = LARG_COLVAL;
          break;
        case COMP_AS_PERC:
          _largcol += LARG_COLPERC;
          _larg[1] = LARG_COLPERC;
          break;                  
        case COMP_AS_VALUE:
        default:
          break;
      }
    }
    if (_st_totr)
    {
      _largcol += LARG_COLPERC;
      _larg[2] = LARG_COLPERC;
    }
  }  
  if (_st_qta)  
  {
    _largcol += LARG_COLQTA;
    _larg[3] = LARG_COLVAL;
    if (printmask().get_bool(F_STRAFFRONTO))
    {
      switch (printmask().get(F_TIPORAFFRONTO)[0])
      {
        case COMP_AS_DIFF:
          _largcol += LARG_COLVAL;
          _larg[4] = LARG_COLVAL;
          break;
        case COMP_AS_PERC:
          _largcol += LARG_COLPERC;
          _larg[4] = LARG_COLPERC;
          break;                  
        case COMP_AS_VALUE:
        default:
          break;
      }
    }
    if (_st_totr)
    {
      _largcol += LARG_COLPERC;
      _larg[5] = LARG_COLPERC;
    }
  }  
  if (_st_uni) 
  {
    _largcol += LARG_COLVAL;
    _larg[6] = LARG_COLVAL;
  }
    
  if (_largcol <= 0)  
    _largcol = 1;
  
  _numcol_dati = selmask().get_int(F_PERIODO);
  _col_anno =min(_numcol_dati,printmask().get_int(F_COLANNO));  //numero di colonne che formano un "Anno"
  if (_col_anno <= 0) _col_anno = 12; // ???

  _numcol = _numcol_dati+ (_st_tota ? int((_numcol_dati -1)/_col_anno+1) : 0);
//  _pagewidth=POS_PRIMACOL+int((printer().formwidth()-POS_PRIMACOL)/_largcol)*_largcol;
  _pagewidth=POS_PRIMACOL+int((printer().calc_num_cols()-POS_PRIMACOL)/_largcol)*_largcol;

  // reset delle variabili per la gestione di gruppi ai vari livelli
  _colpage=1;
  _last_page=0;  
  reset_grplevels();
  // setta header e footer
  int row=0;
  // resetta _save_code per stampare linee raffronti nel caso non abbiano la corrispondente linea dati
  _save_code = "";
  
  reset_header();
  reset_footer();
  printer().footerlen(3);

  return TRUE;
}


bool TStampa_stat::preprocess_print(int file, int counter)  
{
//  _pagewidth=POS_PRIMACOL+int((printer().formwidth()-POS_PRIMACOL)/_largcol)*_largcol;
  return TRUE;
}

void TStampa_stat::preprocess_footer()
{
  set_filled_row('F',1,'_',0,_pagewidth);
  _wrk_row.format(FR("Pagina @#"),get_page_number()-_last_page*(_colpage-1));
  set_row_atpos('F',2,_wrk_row,(_colpage-1)*_pagewidth+(_pagewidth-9)/2);
}

void TStampa_stat::preprocess_header()
{
  int bkg=0,col=0,row=0,lastrow;

  // *****************
  // header
  set_header(++row,"");
  set_header(++row,"");
  if (selmask().get(F_DESCR).empty())
    set_row_atpos('H',++row,FR("@bStampa statistiche "),0);
  else
  {
    _wrk_row.format("@b%s",(const char *)selmask().get(F_DESCR));
    set_row_atpos('H',++row,(const char *)_wrk_row,0);
    _wrk_row.format(FR("dal %s al %s "),(const char *)selmask().get(F_DATAINI),(const char *)selmask().get(F_DATAFIN));
    set_row_atpos('H',row,(const char *)_wrk_row,51);
    if (printmask().get_bool(F_STRAFFRONTO ))
    {
      _wrk_row.format(FR("confrontata con i corrispondenti periodi a partire dal %s"),(const char *)selmask().get(F_DATARAF));
      set_row_atpos('H',++row,(const char *)_wrk_row,0);
    }
  }
  set_header(++row,"");
  set_row_atpos('H',++row,FR("  Codice     @23gDescrizione"),0);
  lastrow=row;
  for (int c=0; c < _numcol; c++) // colonne 
  {
    if (_st_tota && c && (((c+1)%(_col_anno+1))==0 || c==_numcol-1) )
    {
      nome_totale(1+int((c-1)/_col_anno), _wrk_row);
      set_row_atpos('H',row,_wrk_row,POS_PRIMACOL+c*_largcol+_largcol/2 );
    } 
    else 
    {
      TString col1, col2;
      int pos = POS_PRIMACOL+c*_largcol;

      nome_colonna1(col, col1);
      nome_colonna2(col, col2);
      col1 << ' ' << col2;
      const int l = col1.len();
      TParagraph_string s(col1, _largcol);
      
      col1 = s.get();
      col1.center_just(_largcol);
      set_row_atpos('H', row, col1, pos);
      col2 = s.get();
      if (col2.empty())
        lastrow = row + 1;
      else
      { 
        col2.center_just(_largcol);
        set_row_atpos('H', row + 1, col2, pos);
        lastrow = row + 2;
      }
      
      if (_st_val)
      {
        pos += _larg[0];
        set_row_atpos('H',lastrow,TR("Valore"), pos - 7);     
        if (printmask().get_bool(F_STRAFFRONTO))
        {
          pos += _larg[1];
          switch (printmask().get(F_TIPORAFFRONTO)[0])
          {
            case COMP_AS_DIFF:
              set_row_atpos('H',lastrow,TR("Differenza"), pos - 11);     
              break;
            case COMP_AS_PERC:
              set_row_atpos('H',lastrow,TR("%%Diff"), pos - 7);     
              break;                  
            case COMP_AS_VALUE:
            default:
              break;
          }
        }
        if (_st_totr) 
        {
          set_row_atpos('H', lastrow, TR("Perc."), pos);
          pos += _larg[2];
        }
      }  
      if (_st_qta)
      {
        pos += _larg[3];
        set_row_atpos('H',lastrow,TR("Quantita'"),pos - 12);
        if (printmask().get_bool(F_STRAFFRONTO))
        {
          pos += _larg[4];
          switch (printmask().get(F_TIPORAFFRONTO)[0])
          {
            case COMP_AS_DIFF:
              set_row_atpos('H',lastrow,TR("Differenza"), pos - 13);     
              break;
            case COMP_AS_PERC:
              set_row_atpos('H',lastrow,TR("%%Diff"), pos - 7);     
              break;                  
            case COMP_AS_VALUE:
            default:
              break;
          }
        }
        if (_st_totr) 
        {
          set_row_atpos('H', lastrow, TR("Perc."), pos);
          pos += _larg[5];
        }
      }  
      if (_st_uni)
        set_row_atpos('H',lastrow,TR("Valore medio"), pos + _larg[6] - 12);     
      col++;
    }
  }
  row=lastrow;
  set_header(++row,"");
  // *****************
  // background
  const int lasthline=(printmask().get_bool(F_STRAFFRONTO )? 5 :4);
  _wrk_row.format("PnW1l(1,2,%d,2)l(1,%d,%d,%d)l(1,%d,%d,%d)",_pagewidth,lasthline,_pagewidth,lasthline,row,_pagewidth,row);
  row++;
  set_background(_wrk_row);
}

const TString& TStampa_stat::nome_colonna1(int c, TString& s_park)
{
  const char code_freq=selmask().get(F_MULTIPLO)[0];
  const TFrequenza_statistiche freq = char2frequency(code_freq);
  const TFrequenza_statistiche maskfreq = char2frequency(selmask().get(F_FREQUENZA)[0]);
  const int numero=selmask().get_int(F_NUMERO); // numero di periodi in una col
  const TDate din=selmask().get(F_DATAINI);  // data iniziale
  const int start=date2period(din,freq);    // periodo della prima colonna
  const int lastp=last_period(din,freq);    // numero di periodi in un anno

  if (freq  == maskfreq && numero > 1 && maskfreq != fs_giornaliera)
  {
    // numerico; pu� essere un qualsiasi multiplo della freq
    s_park.format("%s %d�%d",(const char *)char2freqname(code_freq),1+(start+c*numero-1)%lastp , 1+(start+(c+1)*numero-2)%lastp);
  } 
  else 
  {
    if (freq  == maskfreq && maskfreq == fs_giornaliera  &&  numero>1)
    {
      // Le date vanno da qualsiasi giorno a qualsiasi giorno
      TDate dfrom(app()._freq.column_first_day(c));
      s_park.format("Dal %s",(const char *)dfrom.string());
    }
    else
      switch (freq)
      {
        case fs_giornaliera: 
          {
            TDate d = din; d += c*numero;
            s_park.format("%s",(const char*)itow(d.wday()));
          }
          break;
        case fs_settimanale:
        case fs_quindicinale: 
        case fs_bimestrale:
        case fs_trimestrale:
        case fs_quadrimestrale:
        case fs_semestrale:
          s_park.format("%d^%s",1+(start+c-1)%lastp,(const char *)char2freqname(code_freq));
          break;
        case fs_annuale:     
          s_park.format(FR("Anno %d"),din.year()+start+c-1);
          break;
        case fs_mensile:
          s_park.format("%s",itom(start+c) );
          break;
      }
  }
//  s_park.center_just(_largcol);
  return s_park;
}

const TString& TStampa_stat::nome_colonna2(int c, TString& s_park)
{
  const TDate din=selmask().get(F_DATAINI);  // data iniziale
  const char code_freq=selmask().get(F_MULTIPLO)[0];
  const int numero=selmask().get_int(F_NUMERO); // numero di periodi in una col
  const  TFrequenza_statistiche freq = char2frequency(code_freq);
  const  TFrequenza_statistiche maskfreq = char2frequency(selmask().get(F_FREQUENZA)[0]);
  const int lastp=last_period(din,freq);    // numero di periodi in un anno
  const int start=date2period(din,freq);    // periodo della prima colonna
  s_park.cut(0);

  if (freq == maskfreq && numero > 1 && maskfreq==fs_giornaliera) 
  {
    TDate dto(app()._freq.column_last_day(c));
    s_park.format(FR("Al  %s"),(const char *)dto.string());
  }
  else
  {
      switch (freq)
      {
        case fs_giornaliera: 
          {
            TDate d = din; d += c*numero;
            s_park.format("%s",(const char *)d.string());
          }
          break;
        case fs_annuale:     
          break;
        case fs_mensile:
          s_park.format("%d",din.year()+int((start+c-1)/lastp));
          break;
        case fs_settimanale:
        case fs_quindicinale: 
        case fs_bimestrale:
        case fs_trimestrale:
        case fs_quadrimestrale:
        case fs_semestrale:
          s_park.format(FR(" anno %d"),din.year()+int((start+c-1)/lastp));
          break;
      }
  }
  return s_park;
}

const TString& TStampa_stat::nome_totale(int c, TString& s_park)
{
  const char code_freq=selmask().get(F_MULTIPLO)[0];
  s_park.cut(0);
  {
    switch (code_freq)
    {
      case fs_giornaliera: // giorno
      case fs_settimanale:
      case fs_quindicinale: 
      case fs_mensile:
      case fs_bimestrale:
      case fs_trimestrale:
      case fs_quadrimestrale:
      case fs_semestrale:
      case fs_annuale:
      default:
        s_park.format(FR("Totale %d"),c);
    }
  }
  return s_park;
}


void TStampa_stat::set_descr(int& row)
{
  const int MAX_DESC_LEN = 27;
  TRectype& strec = current_cursor()->curr();
  TParagraph_string descr("",MAX_DESC_LEN); // Spezzatura manuale, per evitare righe accavallate
  TString r;
  const int lastlev = numlevels();
  const int level = strec.get_int(SVS_LIVELLO);
  int lencode = 0;

  if (_group_recs.objptr(level+1))
  {
    const TRectype& rec_grp= (const TRectype&)_group_recs[level+1];
    lencode = rec_grp.get(SVS_CODICE).len();
  }
  adjust_record(strec,lencode);
  partkey_name(strec.get(SVS_LEVCODE), lastlev - level - 1, r);
  r.upper(0,0);            
  if (r.blank())
    r.cut(0);
  else
    r << " ";
            
  r << strec.get(SVS_CODICE).mid(lencode);
  descr = strec.get(SVS_DESCR);
  if (descr.not_empty())
    r << "@23g" << descr.get(0);
  set_row_atpos('R',++row,(const char *)r,_indent[level]);
            
  // Seconda riga eventuale...
  const int items = descr.items();
  if (items > 1) 
  {
    r = "";
    r << descr.get(1);
    // Sfrutta al massimo la seconda riga, mettendo anche il terzo elemento
    // e cmq trimmando al 25o carattere
    if (items > 2)
    {
      r << " " << descr.get(2);
      if (r.len() > MAX_DESC_LEN)
        r.cut(MAX_DESC_LEN);
    }
    r.insert("@23g");
    set_row_atpos('R',++row,(const char *)r,_indent[level]);
  }
}

void TStampa_stat::set_page(int file, int count)
{
  // prepara la pagina di stampa 
  TRectype& strec = current_cursor()->curr();
  const int level = strec.get_int(SVS_LIVELLO);
  const int lastlev = numlevels();

  int row=0;
  
  // *****************
  // gestione totali di raggruppamento
  row = handle_levchange(row, level);
  const char tipo_riga = *strec.get(SVS_TIPO);
  if (level > 0)
  {
    // salva i record dei livelli di raggruppamento
    if (level == lastlev)
      strec.zero(SVS_CODICE);
    switch (tipo_riga)
    {
      case LINEA_DATI:
        _group_recs.add(strec,level);
        break;
      case LINEA_RAFFRONTI:
        _group_recs_raff.add(strec,level);
        break;
    }
  }
  // *****************
  // gestione delle "righe"
  if (level != lastlev && 
      printmask().get_bool(F_FLAGSTOTALI+lastlev-level))
  {
    switch (tipo_riga)
    {
      case LINEA_DATI:
        if (level >= _liv_riga)
        {
          _save_code = strec.get(SVS_CODICE);
          set_descr(row);
        }
        if (level == _liv_riga)
          row = set_rows_colonne(row, strec);
        break;
      case LINEA_RAFFRONTI:
        if (printmask().get_bool(F_STRAFFRONTO))
        {
          if (_save_code != strec.get(SVS_CODICE))
          {
            TRectype stempty(LF_SVSTAT);
            stempty.put(SVS_CODICE,strec.get(SVS_CODICE));
            stempty.put(SVS_LEVCODE,strec.get(SVS_LEVCODE));
            stempty.put(SVS_LIVELLO,strec.get(SVS_LIVELLO));
            stempty.put(SVS_TIPO,strec.get(SVS_TIPO));
            set_descr(row);
            row = set_rows_colonne(row, stempty);
          }
          if (level == _liv_riga)
            row = set_rows_colonne(++row, strec);
        }
        break;
    }
  }
  // *****************
  // totale generale e break di livello
  if (current_cursor()->pos() == current_cursor()->items()-1)
  {
    handle_levchange(row, numlevels());
  }
}

// fa le set row per la "riga" contenente i valori delle colonne di statistica
int TStampa_stat::set_rows_colonne(int row, const TRectype &strec)
{                  
  TRectype &last_data = *_last_data;
  TString16 colname;
  TString r_totc;
  real col_value;
  bool aggiungi_perc=TRUE;
  int col=0; // contatore di colonna corrente 
  int position; // posizione fisica della colonna corrente
  // **********
  // setta i totali di riga
  if (strec.get_char(SVS_TIPO)==LINEA_DATI ||  
      strec.get_char(SVS_TIPO)==LINEA_RAFFRONTI && 
      printmask().get(F_TIPORAFFRONTO)[0]==COMP_AS_VALUE)
  {
    for (col = 0; col < _numcol_dati; col++) 
    {
      colname.format("%c%d", SVS_VALCOLNAME, col);
      
      const int idx = int(col/_col_anno); 
      if (col%_col_anno == 0) 
        _totval_riga[idx] = _totqta_riga[idx] = ZERO;
      
      _totval_riga[idx] += strec.get_real(colname);
      
      colname.format("%c%d", SVS_QTACOLNAME, col);
      _totqta_riga[idx] += strec.get_real(colname);
    }
  }
  // **********
  // setta le colonne
  
  TString pictures[3];
  TCurrency currency;
  for (int t = 0; t < 3; t++)
  {   
    TString& pic = pictures[t];
    if (t == 1)
    { 
      pic = PICTURE_QUANTITA;
      continue;
    }
    currency.set_price(t == 2);
    const int dec = currency.decimals();
    pic = PICTURE_VALORI;
    if (dec > 0)
    {                                          
      pic << ',';
      for (int d = 0; d < dec; d++) pic << '@';
      if (pic.len() > LARG_COLVAL)  
      {
        pic.ltrim(pic.len() - LARG_COLVAL);
        if (pic[0] == '.') pic[0] = '#';
      }  
    }
  }

  col = 0;
  for (int c = 0; c < _numcol_dati; c++) 
  {
    const int idx = int(c / _col_anno);
    position = POS_PRIMACOL + col*_largcol;
    for (int t = 0; t < 3; t++) // Ciclo su valore, quantita' e unitario
    {
      if (t == 0 && !_st_val)
        continue;
      if (t == 1 && !_st_qta)
        continue;
      if (t == 2 && !_st_uni)
        continue;

      const real& tot_riga = t == 1 ? _totqta_riga[idx] : _totval_riga[idx];
      colname.format("%c%d", t == 1 ? SVS_QTACOLNAME : SVS_VALCOLNAME, c);   
      
      const TString& PICT = pictures[t];
      
      real colval = strec.get_real(colname);
      if (t == 2)
      {
        colname.format("%c%d", SVS_QTACOLNAME, c); 

				const fraction valun(colval, strec.get_real(colname));
        
				colval = valun;  
      }

			TString80 colstr = colval.string(PICT);

			if (t == 1 && _st_um)
			{
				TString4 um;
				um << " ";
				um << strec.get("UMQTA");
				colstr.overwrite(um, 0);
			}

      int offset = (t - 1) * 3;
      if (offset >= 0)
        position += _larg[offset + 0] + _larg[offset + 1] + _larg[offset + 2];
      offset += 3;
 
      switch (strec.get_char(SVS_TIPO))
      {
      case LINEA_DATI:
        last_data=strec;
        set_row_atpos('R',row,colstr,position);
        break;
      case LINEA_RAFFRONTI:
        switch (printmask().get(F_TIPORAFFRONTO)[0])
        {
          case COMP_AS_VALUE:
            set_row_atpos('R',row, colstr,position);
            break;
          case COMP_AS_DIFF:
            if (t < 2) 
            {
              set_row_atpos('R',row, colstr,position);
              set_row_atpos('R',row,((real)(last_data.get_real(colname)-strec.get_real(colname))).string(PICT),position + _larg[offset]);
            }  
            break;
          case COMP_AS_PERC:
            if (t < 2) 
            {
              set_row_atpos('R', row, colstr, position);
              if (!colval.is_zero())
              {
                real p = CENTO * (last_data.get_real(colname) - colval) / colval; p.round(1);
                set_row_atpos('R',row,p.string(PICTURE_PERCENT),position + _larg[offset] );
              }  
              aggiungi_perc=FALSE;
            }  
            break;                  
        }
        break;
      case LINEA_TARGET: // v2.0
        set_row_atpos('R',row,colstr,position);
        break;
			default:
				break;
			}
          
      if (t < 2 && strec.get_char(SVS_TIPO) == LINEA_DATI)
      {
        // colonna con la percentuale rispetto al totale di riga
        if (_st_totr && aggiungi_perc && !tot_riga.is_zero()) 
        {
          const int pos = position + _larg[offset] + _larg[offset + 1];
          real p = CENTO * colval / tot_riga; p.round(1);
          set_row_atpos('R',row,p.string(PICTURE_PERCENT),pos);
        }
        // riga con i totali di colonna
        if (_st_totc && aggiungi_perc ) 
        {
          TRectype & _rec_totale=(TRectype &)_group_recs[numlevels()];
          const real tot = _rec_totale.get_real(colname);
          if (!tot.is_zero())
          { 
            real p = CENTO * colval / tot; p.round(1);
            r_totc = p.string(PICTURE_PERCENT);
            set_row_atpos('R',row+1,(const char *)r_totc,position+_largcol-LARG_COLPERC);
          }
          else
            set_row_atpos('R',row+1,"",0);
            // aggiunge comunque una riga vuota per la spaziatura
        }
        // colonna con il totale di un "anno"
        if (_st_tota && aggiungi_perc &&  (((c+1)%_col_anno)==0 || c== _numcol_dati-1)) 
        {
          col++;
          int position_a = position + _largcol;

          set_row_atpos('R',row,(const char *)tot_riga.string(PICT),position_a);
          if (_st_totr && aggiungi_perc) 
          {
            set_row_atpos('R',row,(const char *)real(100).string(PICTURE_PERCENT),position_a + _larg[offset]);
          }
        }
      }
    }  
    col++;
  }
  
  if (_st_totc) 
    ++row;
  return row;
}


void TStampa_stat::set_filled_row(char section,int row,char c,int pos,int len)
{
  const int MAX=100; // limite massimo per una singola set_row; 5 char per costruire la goto
  while (len> MAX ) 
  {
    _wrk_row.fill(c,MAX);
    set_row_atpos(section,row,(const char *)_wrk_row,pos);
    len-=MAX;
    pos+=MAX;
  }
  _wrk_row.fill(c,len);
  set_row_atpos(section,row,(const char *)_wrk_row,pos);
}



// fa le set_row per la "riga" contenente i valori delle colonne di statistica
void TStampa_stat::set_row_atpos(char section,int nrow,const char * f,int pos)
{
  TString256 ss;
  if (pos <= _colpage*_pagewidth)
  {    
    int l = strlen(f);
    const int firstpos=(_colpage-1)*_pagewidth;
    if (pos+l >= firstpos)
    { 
      // there's something to print in this page!
      if (pos+l > _colpage*_pagewidth)
      {
        l=_colpage*_pagewidth-pos;
        repeat_print();
      }
      ss.format("@%03dg",max(pos, firstpos ) % _pagewidth);
      if (pos< firstpos)
        ss.overwrite(f+firstpos-pos,5);
      else
        ss.overwrite(f,5);
      ss.cut(5+l); // !?!? this cause trouble if special couples are used at the middle of the string: 
                  // "@b" or "%%" can be split into two non-significant chars
    } 
    //else : too much to the left: already printed!
  }
  else //: too much to the right: will be printed next time! 
    repeat_print();
  switch (section)
  {
    case 'R': set_row(nrow,ss); break;
    case 'H': set_header(nrow,ss); break;
    case 'F': set_footer(nrow,ss); break;
  }
}

// stampa i totali ad ogni break di livello
int TStampa_stat::handle_levchange(int row, const int level)
{
  while (level > _last_lev_grp)
  {
    // ho lasciato un livello "basso"
    _last_lev_grp++;
    if (_last_lev_grp > _liv_riga && printmask().get_bool(F_FLAGSTOTALI+numlevels()-_last_lev_grp)
        || _last_lev_grp==numlevels())
    {  
      if (_group_recs.objptr(_last_lev_grp) == NULL)
      {
        NFCHECK("Totali inconsistenti");
      }
      else
      {
        TRectype& rec_grp1=(TRectype &)_group_recs[_last_lev_grp];
        int lencode=0;
        if (_last_lev_grp < numlevels())
        {
          TRectype & rec_grp2=(TRectype &)_group_recs[_last_lev_grp+1];
          lencode=rec_grp2.get(SVS_CODICE).len();
        }
        adjust_record(rec_grp1,lencode);
  
        set_filled_row('R',++row,'_',POS_PRIMACOL,_largcol * _numcol );
        
        partkey_name(rec_grp1.get(SVS_LEVCODE), numlevels() - _last_lev_grp - 1, _wrk_row);
        _wrk_row.insert(FR("@bTotale "), 0);

        _wrk_row << ' ' << rec_grp1.get(SVS_CODICE).mid(lencode);
        _wrk_row << ' ' << rec_grp1.get(SVS_DESCR).left(23);
        //_wrk_row.cut(LARG_COLCODICE); there are also @ chars!
        set_row_atpos('R',++row,(const char *)_wrk_row,_indent[_last_lev_grp]);
        row = set_rows_colonne(row, rec_grp1);
        if (printmask().get_bool(F_STRAFFRONTO ))
        {
          TRectype& rec_grp_raff=(TRectype &)_group_recs_raff[_last_lev_grp];
          if (_group_recs_raff.objptr(level+1))
          {
            const TRectype& rec_grp= (const TRectype&)_group_recs_raff[level+1];
            lencode = rec_grp.get(SVS_CODICE).len();
          }
          adjust_record(rec_grp_raff,lencode);
          row = set_rows_colonne(++row, rec_grp_raff);
        }
      }
    }
  }
  if (_last_lev_grp != level)
    _last_lev_grp = level;
  return row;
}

void TStampa_stat::reset_grplevels()
{
  _last_lev_grp = numlevels();    
  _liv_riga = _last_lev_grp+1;
  _group_recs.destroy();
  _group_recs_raff.destroy();
  int pos=0;
  for (int l = 0; l <= _last_lev_grp; l++)
  {
    if (printmask().get_bool(F_FLAGSTOTALI+l))
    {
      _liv_riga = _last_lev_grp-l;
      _indent[_liv_riga] = pos*2;
      pos++;
    } 
    else
      _indent[_last_lev_grp-l] = 0;
  }
}

bool TStampa_stat::user_create()
{ 
  // ************
  // files: documenti e statistiche
  open_files(LF_RIGHEDOC, LF_CONDV, LF_RCONDV, LF_ANAMAG, LF_SCONTI, LF_UMART, LF_TAB, LF_TABCOM, LF_CLIFO,
      LF_CFVEN, LF_INDSP, LF_OCCAS, LF_MOVMAG, LF_RMOVMAG,LF_PROVV, 0);
  open_files(LF_SVRIEP,LF_SVSTAT,LF_AGENTI, 0);
  
  _stats.init();
  if (_stats.frequency()==fs_nulla)
    return error_box(TR("E' necessario impostare la frequenza statistica per la ditta"));

  // ************
  // maschere
  _msk = new TMask("sv1200a");                  
  _msk->set_handler(F_CODICE, codice_handler);
  _msk->set_handler(F_MULTIPLO, multiplo_handler);
  _msk->set_handler(F_NUMERO, numero_handler);
  _msk->set_handler(F_PERIODO, periodo_handler);
  _msk->set_handler(F_DATAINI, dataini_handler);
  _msk->set_handler(F_DATAFIN, datafin_handler);
  _msk->set_handler(F_RAFFRONTO, raffronto_handler);
  _msk->set_handler(F_DATARAF, dataraf_handler);
  _msk->set_handler(F_CHIAVE, chiave_handler);  
  TMask& sm = _msk->sfield(F_CHIAVE).sheet_mask();
  sm.set_handler(S_CAMPO, campo_handler);
  sm.set_handler(S_ARTFLD, artfld_handler);  

  _print_msk  = new TMask("sv1200b");

  _export_msk = new TMask("sv1200c");
  _export_msk->set_handler(F_FILENAME, filename_handler);
  // ************
  // oggetti per la gestione delle parti del codice
  _liv_art = new TCodart_livelli;
  _liv_giac = new TCodgiac_livelli;

  _tipodoc = new TRecord_cache("%TIP");
  _catven = new TRecord_cache("CVE");
  _umart = new TRecord_cache(LF_UMART, 2);

//  _spp    = new TTable("SPP");
//  _prs    = new TTable("PRS");
  
  // ************
  // cursore di stampa
  add_file(LF_SVSTAT);
  add_cursor(new TCursor(new TRelation(LF_SVSTAT)));
  
  // cache per l'output 
  _svcache=NULL;

  set_real_picture("");       
  
  _last_data = new TRectype(LF_SVSTAT);

  return TRUE;
}
  
bool TStampa_stat::user_destroy()
{ 
  // maschere
  delete _msk;
  delete _print_msk;
  delete _export_msk;
  // livelli di codice
  delete _liv_art;
  delete _liv_giac;          
  // cancella le cache
  delete _tipodoc;
  delete _catven;
	delete _umart;
  // cancella i files
  delete _last_data;

  delete current_cursor()->relation();
  return TRUE;
} 


bool TStampa_stat::menu(MENU_TAG )
{
  TMask& m = selmask();
  TSheet_field& sheet = m.sfield(F_CHIAVE);
  TString tmp;
  KEY k;        
  m.set(F_STAMPA_VAL, "X");
  while ((k = m.run()) != K_QUIT)
  {     
    _numcol_dati = m.get_int(F_PERIODO);
    _st_val = m.get_bool(F_STAMPA_VAL);
    _st_qta = m.get_bool(F_STAMPA_QTA);
		_st_um = _st_qta && m.get_bool(F_STAMPA_UM);
    _st_uni = m.get_bool(F_STAMPA_UNI);

    _valid_types = "";
    if (m.get_bool(F_TIPOART1))
      _valid_types << RIGA_MERCE;
    if (m.get_bool(F_TIPOART2))
      _valid_types << RIGA_PRESTAZIONI;
    if (m.get_bool(F_TIPOART3))
      _valid_types << RIGA_SPESEDOC;
    if (m.get_bool(F_TIPOART4))
      _valid_types << RIGA_OMAGGI;

    set_column_frequency();
    
    _key.cut(0);     
    TRelation anamag(LF_ANAMAG);
    TRelation_description rd(anamag);
    TSheet_field & s_chiave = selmask().sfield(F_CHIAVE);
    TFilename des_file_name("sv1200.ini");
    TConfig c(des_file_name, "Descriptions");
    
    for (int r = 0; r < sheet.items(); r++)
    {
      tmp = sheet.row(r).get(0);
      if (!tmp.blank())
        _key.add(tmp);
      if (tmp == STR_ARTFIELD)                                                                 
      {
        TString16 field(s_chiave.cell(r,s_chiave.cid2index(S_ARTFLD)));

        if (c.exist(field,2))              
        {     
           TToken_string  t(c.get(field, "Descriptions", 2));
          _file_fld.add(t.get(), r);
          _key_fld.add(t.get(), r);
          _field_fld.add(t.get(), r);
        }
        if (c.exist(field,1))              
          _des_fld.add(c.get(field, "Descriptions", 1), r);
        else
        {
          int pos = field.find('[');
          TString16 sub;
          
          if (pos > 0)
          {
            sub = field.mid(pos);
            field.cut(pos);      
          }                      
            
          TString des(rd.get_field_description(field));
          des << sub;                 
          _des_fld.add(des, r);
        }
      }
    }
    
    if (k == K_SAVE)
    {
      set_ragg_per_um();
      TTable psv("PSV");
      psv.put("CODTAB", m.get(F_CODICE));
      if (psv.read() == NOERR)
        expmask().set(F_FILENAME, psv.get("S3")); // File precaricato da PSV->S3
      if (expmask().run()!=K_ESC)
      {
        TFilename fname(expmask().get(F_FILENAME));
        fname.insert("%");
        genera_file(fname);
      }
    } else {
      TFilename fname("svs");
      genera_file(fname);
      TIsamtempfile * f = new TIsamtempfile(LF_SVSTAT,fname,FALSE,TRUE);
      set_printmask();
      current_cursor()->relation()->replace(f);
      // Forza la ricostruzione del cursore...
      current_cursor()->setfilter("CODICE>=\"A\"");
      current_cursor()->setfilter("");
      do_print(1);
      current_cursor()->relation()->replace(new TLocalisamfile(LF_SVSTAT));
    }
  } 
  return FALSE;
}

void TStampa_stat::fill_field_list(TMask& m)
{                                  
  // opzioni per il tipo di dato

  m.enable(F_TIPOART4, !_stats.omaggio_is_merce());
  
  // opzioni per i campi della chiave
  TSheet_field& s = m.sfield(F_CHIAVE);
  TMask& sm = s.sheet_mask();

  TString_array& list = sm.efield(S_CAMPO).sheet()->rows_array();
  list.destroy();

  TToken_string row(80);
    
  TCodart_livelli& cal = *_liv_art;
  bool is_articolo = FALSE;
  int l;
  for (l = 0; l <= cal.last_level(); l++)
  {
    if (l && !cal.enabled(l)) 
      continue;

    is_articolo = TRUE;
    row = STR_ARTICOLO;
    if (l) row << l;
      
    row.add(TR("Codice articolo"));
    if (l) row << '[' << l << ']';
    
    list.add(row);
  }
  if (is_articolo)
  {
    row = STR_ARTFIELD;
    row.add(TR("Campo dell'anagrafica articoli"));
    list.add(row);
  }  
  TCodgiac_livelli& cgl = *_liv_giac;
  for (l = 0; l <= cgl.last_level(); l++)
  {
    if (l && !cgl.enabled(l)) 
      continue;
        
    row = STR_LIVGIAC;
    if (l) row << l;
      
    row.add(TR("Livello giacenza"));
    if (l) row << '[' << l << ']';
    list.add(row);
  }
  
  row = "D";
	row.add(TR("Tipo documento"));  
	list.add(row);
  row = "V";
	row.add(TR("Categoria vendita"));  
	list.add(row);
  row = "C";
	row.add(TR("Codice cliente"));
	list.add(row);
  row = "Z";
	row.add(TR("Codice zona"));
	list.add(row);
  row = "G";
	row.add(TR("Codice agente"));
	list.add(row);
  row = "M";
	row.add(TR("Codice magazzino"));
	list.add(row);
}

void TStampa_stat::set_frequency()
{                           
  TPeriodo::set_frequency(_stats.frequency());

  TMask& m = selmask();   

  char freq[2] = { frequency2char(_stats.frequency()), '\0' };
  m.set(F_FREQUENZA, freq); 
    
  TList_field& multiplo = (TList_field&)m.field(F_MULTIPLO);

  TToken_string codes(8), descr(80);
  switch(_stats.frequency())
  {
  case fs_giornaliera   : codes = "G|S|Q|1|2|3|4|6|A"; break;
  case fs_settimanale   : codes = "S|3|6|A"; break;
  case fs_quindicinale  : codes = "Q|1|2|3|4|6|A"; break;
  case fs_mensile       : codes = "1|2|3|4|6|A"; break;
  case fs_bimestrale    : codes = "2|4|6|A"; break;
  case fs_trimestrale   : codes = "3|6|A"; break;
  case fs_quadrimestrale: codes = "4|A"; break;
  case fs_semestrale    : codes = "6|A"; break; 
  case fs_annuale       : codes = "A"; break;
  default :break;
  } 
  
  for (const char* cod = codes.get(0); cod; cod = codes.get())
    descr.add(char2freqname(cod[0]));
    
  multiplo.replace_items(codes, descr);
  
  if (_stats.frequency() == fs_settimanale)
  {
    if (m.focus_field().dlg() == F_MULTIPLO)
      m.set_focus_field(F_NUMERO); // scappa dal campo
    multiplo.reset();
    multiplo.disable();
  }  
  else
    multiplo.enable();
    
  // setta la durata dell'anno
  //m.set(F_COL_ANNO, last_period(1992,_stats.frequency())); 
}

void TStampa_stat::on_config_change()
{
  _stats.init();  
  _liv_art->init();
  _liv_giac->init();

  set_frequency();

  fill_field_list(*_msk);
}

bool TStampa_stat::set_column_frequency()
{
  const TMask& m = selmask();
  const TDate data_inizio = m.get(F_DATAINI);
  bool ok = data_inizio.year() >= 1900;
  if (ok)                      
  {
    const TFrequenza_statistiche multiplo = char2frequency(m.get(F_MULTIPLO)[0]);
    const int numero = m.get_int(F_NUMERO);
    _freq.init(multiplo, numero, data_inizio);
  }  
  return ok;  
}          

TString& TStampa_stat::build_key(const TRectype& rec)
{   
  _last_key.cut(0);
  TString16 tmp;
  for (const char* field = _key.get(0); field; field = _key.get())
  {
    tmp = field;
    switch(tmp[0])
    {
    case 'A': 
      if (tmp[1] > '0')
      {
        const int liv = tmp[1] - '0';
        const int start = _liv_art->code_start(liv);
        const int len = _liv_art->code_length(liv);
        tmp = rec.get(SVR_CODART).mid(start, len);
        _last_key << tmp;
      }
      else
        _last_key << rec.get(SVR_CODART);
      break;
    default:
      break;
    }
  }
  return _last_key;
}

void TStampa_stat::update_file(const char * key, const char *lev_code,const char tipo_dato, const int level, const int col, TSVriep_record& curr, const TString & fld, int row)
{
  CHECK(col>=0,"Le colonne partono da 0");

  static TRectype* statrec = NULL;
  if (statrec == NULL) 
    statrec = new TRectype(LF_SVSTAT);
  
  TToken_string cachekey(key);
  cachekey.rtrim(); // necessario perch� la chiave sia buona per l'hash table
  if (cachekey.blank())
    cachekey.add(" ");
  cachekey.add(tipo_dato);

  *statrec = _svcache->get(cachekey);

  if (_svcache->io_result()!=NOERR)
  {
    // nuovo record;
    statrec->put(SVS_CODICE,key);         
    statrec->put(SVS_LEVCODE,lev_code);
    statrec->put(SVS_LIVELLO,level);
    statrec->put(SVS_TIPO,tipo_dato);
    statrec->put(SVS_UMQTA,curr.get(SVR_UMQTA));
  }
  if (*lev_code== CHR_TOTGEN) // totale generale
    statrec->put(SVS_DESCR,"");
  else                       
  {     
    TString descr(get_descrpart(lev_code,curr,fld, row));
    for (int pos = descr.find('%'); pos >=0; pos = descr.find('%', pos + 2))
      descr.insert("%", pos);                     
    if (descr.len() > SVS_LEN_DESCR)
      descr.cut(SVS_LEN_DESCR);
    statrec->put(SVS_DESCR, descr);
  }
  put_column(*statrec,col,curr);

  _svcache->put(*statrec);
}

// aggiorna la colonna del record statrec con i dati di SVriep
void TStampa_stat::put_column(TRectype& statrec, int col, const TSVriep_record& rieprec)
{
  if (col < 0 || col > SVS_NUMCOLONNE) 
    return;

  if (_st_qta || _st_uni)
  {
	  const TString16 um(rieprec.get(SVR_UMQTA));
    if (um.not_empty())
    {  
      const real fc = fc_um(statrec,rieprec);
      if (fc.is_zero())
        invalid_columns(statrec);
      else
      {
        const real qta = rieprec.get_real(SVR_QUANTITA) * fc;
        TString4 colname; colname.format("%c%d", SVS_QTACOLNAME, col);
        statrec.add(colname, qta);
        //statrec.put(SVS_TOTALERIGA,statrec.get_real(SVS_TOTALERIGA)+rieprec.get_real(SVR_QUANTITA)*fc);
      }
    } // no UM ? no valid data!
  }

  if (_st_val || _st_uni)
  {
    const real val = rieprec.get_real(SVR_VALORE);
    TString4 colname; colname.format("%c%d", SVS_VALCOLNAME, col);
    statrec.add(colname, val);
    //statrec.put(SVS_TOTALERIGA, statrec.get_real(SVS_TOTALERIGA)+rieprec.get_real(SVR_VALORE));
  }
}

// Converte le UM scrivendo la nuova UM sul record e restituendo il fattore di conv.
void TStampa_stat::standardize_um(TSVriep_record& rieprec, bool force)
{
  if (force || _ragg_per_um)
  {
    TString4 um("N.");  // Unita' di default NON valida!
    real fc;

    const char tipoart = rieprec.get_char(SVR_TIPOART);
    if (tipoart==RIGA_MERCE || tipoart==RIGA_OMAGGI)
    {
      // � un articolo (<M>erce); converto alla UM principale dell'art.
			TString key(rieprec.get(SVR_CODART));
			key << "|1";
			const TRectype& rec_umart = cache().get(LF_UMART, key);

      um = rec_umart.get(UMART_UM);
      fc = rec_umart.get_real(UMART_FC);
    } 
    else 
    {
      // non � un articolo: converte in base alla UM di riferimento in tabella
      const TRectype& ums = cache().get("%UMS", um);
      um = ums.get("S7");
      fc = ums.get_real("R10");
    }
    if (um != rieprec.get(SVS_UMQTA))
    {
      // conversione tra UM diverse
      const real qta = rieprec.get_real(SVR_QUANTITA);
      rieprec.put(SVR_QUANTITA, fc * qta);
      rieprec.put(SVR_UMQTA, um);
    }
  } 
}

// Calcola il fattore di conversione necessario per passare da rieprec a statrec
const real TStampa_stat::fc_um(const TRectype& statrec,const TSVriep_record& rieprec) 
{
  real fc = UNO;

  const TString4 statum = statrec.get(SVS_UMQTA);
  TString4 um = rieprec.get(SVR_UMQTA);

  if (um != statum)
  {
    // converte in base alla UM di riferimento in tabella
    const char tipoart = rieprec.get_char(SVR_TIPOART);
    if (tipoart==RIGA_MERCE || tipoart==RIGA_OMAGGI)
    {
      // � un articolo (<M>erce)
      TString80 art = rieprec.get(SVR_CODART); art.trim();
			
			TLocalisamfile umart(LF_UMART);
      umart.setkey(2);
      umart.put(UMART_CODART, art);
      umart.put(UMART_UM, um);
      if (umart.read() == NOERR)
      {
        fc = umart.get_real(UMART_FC);
        umart.put(UMART_UM, statum);
        if (umart.read() == NOERR)
        {
          // � una UM dell'articolo: uso i fc per convertirla
          um = umart.get(UMART_UM);
          fc /= umart.get_real(UMART_FC);
        }
      } 
      else 
      {
        error_box("L'articolo '%s' non ha l'unita' di misura %s\n"
                  "ma esistono statistiche che la utilizzano ugualmente",
                  (const char*)art, (const char*)um);
      }
    }
    if (um != statum) // Non sono riuscito a trovare una conversione certa ...
    {                 // ... allora provo con le unita' di misura standard  
      
      const TRectype& ums = cache().get("%UMS", um);
      um = ums.get("S7");
      fc = ums.get_real("R10");
    }
    if (um != statum)
    {
      // impossibile associare al totale una unit� di misura
      fc = ZERO;
    }
  }
  return fc;
}

// rende non validi i valori delle colonne
void TStampa_stat::invalid_columns(TRectype& statrec)
{
  statrec.zero(SVS_UMQTA);
  TString16 colname;
  for (int col = 0; col < SVS_NUMCOLONNE; col++)
  {
    colname.format("%C%d", SVS_QTACOLNAME, col);
    statrec.zero(colname);
  }
  //statrec.put(SVS_TOTALERIGA,0);
}

// funzione di filtro del cursore 
bool TStampa_stat::filtro_chiavi(TSVriep_record &curr)
{
  // !?!?! static 
  TString16 code_campo("  ");
  TString16 fld_campo;
  // !?!?! static 
  TString val_stringa(32,' ');

  TSheet_field & s_chiave = selmask().sfield(F_CHIAVE);
  const int num_liv_output=s_chiave.items();
  TString est1,est2;
  bool ok=TRUE;
  
  // Verifica che il tipoarticolo corrisponda con quello richiesto dall'utente
  ok = _valid_types.find(curr.get_char(SVR_TIPOART)) >= 0;

  for (int l=0; ok && l< num_liv_output; l++)
  {
    code_campo=s_chiave.cell(l,s_chiave.cid2index(S_CAMPO));
    fld_campo=s_chiave.cell(l,s_chiave.cid2index(S_ARTFLD));
    if (code_campo==STR_CLIFOR) // cliente/fornitore ha in testa il TIPOCF
    {
      // ok &= curr.get_char(SVR_TIPOCF)==; !?!?! manca la selezione dei soli cli o for
      val_stringa=get_part(code_campo,curr, fld_campo)+1;
    } else {
      val_stringa=get_part(code_campo,curr, fld_campo);
      val_stringa.trim();
    }
    if (!(est1=s_chiave.cell(l,s_chiave.cid2index(S_DAL))).blank())
    {
      if (code_campo==STR_CLIFOR) // cliente/fornitore e' numerico
        ok &= (atoi((const char *)val_stringa) >= atoi((const char *)est1));
      else
        ok &= (val_stringa>=est1);
    }
    if (ok && !(est2=s_chiave.cell(l,s_chiave.cid2index(S_AL))).blank())
    {
      if (code_campo==STR_CLIFOR) // cliente/fornitore e' numerico
        ok &= (atoi((const char *)val_stringa) <= atoi((const char *)est2));
      else
        ok &= val_stringa<=est2;
    }    
  }
  return ok;
}


// ritorna se la query � suddivisa per unit� di misura
void TStampa_stat::set_ragg_per_um()
{
  TSheet_field & s_chiave = selmask().sfield(F_CHIAVE);
  for (int l=0; l< s_chiave.items(); l++)
  {
    if (*s_chiave.cell(l,s_chiave.cid2index(S_CAMPO))==CHR_UMISURA)
    {
      _ragg_per_um=FALSE;
      return;
    }
  }
  _ragg_per_um=TRUE;
}

void TStampa_stat::genera_file(const char *outfn)
{

  TString key;  // chiave per il record del file statistiche
  const TMask& m = selmask();
  const char alg=m.get(F_TIPOCALC)[0];
  const int first_col = - (alg==ALG_MOBILI ? _col_anno-1:0);      // prima colonna dati da osservare
  const int first_raffr = -m.get_int(F_RAFFRONTO)- (alg==ALG_MOBILI ? _col_anno-1:0);// prima colonna dati da confrontare
  const int first_data = first_raffr ;       // prima colonna dati 
  TSheet_field & s_chiave = m.sfield(F_CHIAVE);


  // file di OUTPUT
  _svcache = new TStat_cache(new TIsamtempfile(LF_SVSTAT,outfn,TRUE,FALSE));
  // file di INPUT
  TLocalisamfile riep(LF_SVRIEP);
  riep.set_curr(new TSVriep_record);
  
  TSVriep_record& curr = (TSVriep_record&) riep.curr();
  const TRecfield fr_anno(curr, SVR_ANNO);
  const TRecfield fr_periodo(curr, SVR_PERIODO);
  
  const bool dok = m.get_date(F_DATARAF).ok();
  const TDate dd = m.get_date(dok ? F_DATARAF : F_DATAINI);
  
  curr.put(SVR_ANNO, dd.year());
  curr.put(SVR_PERIODO, _stats.date2period(dd));
  
  const int num_liv_output=s_chiave.items();
  const long start_status=period2long(curr.get_int(SVR_ANNO),curr.get_int(SVR_PERIODO),_stats.frequency());
  
  const TDate datafin = m.get_date(F_DATAFIN);
  const int yearfin = datafin.year();
  const int periofin = _stats.date2period(datafin);

  TProgind statusbar(period2long(datafin.year(), periofin,_stats.frequency())-start_status,
                     TR("Creazione del file di output"), FALSE, TRUE, 60);

  
  _svcache->zap();

  // **********************
  // ciclo principale: scorre il riepilogo statistiche per anno+periodo
  // e genera i risultati in una cache
  TString16 levcode;
  TString16 fld;
  
  for (int err = riep.read(_isgteq); err == NOERR && 
    (curr.get_int(SVR_ANNO) < yearfin || 
    (curr.get_int(SVR_ANNO) == yearfin && 
    curr.get_int(SVR_PERIODO) <= periofin))
      ; err = riep.next())
  {
    statusbar.setstatus(period2long(curr.get_int(SVR_ANNO),curr.get_int(SVR_PERIODO),_stats.frequency())-start_status);

    if (filtro_chiavi(curr))
    {
      const TPeriodo periodo(fr_anno, fr_periodo);
      const int col = _freq.period2column(periodo);
      
      if (col >= first_data && col < _numcol_dati)
      {
        // NOTA: eventualmente sarebbe utile forzare l'uso della UM standard se � una riga 
        // non comprendente l'articolo ......
        standardize_um(curr); 
        // costruisce il file statistiche...
        key.cut(0);
        for (int l=0; l< num_liv_output; l++)
        {      
          levcode=s_chiave.cell(l,s_chiave.cid2index(S_CAMPO));
          fld=s_chiave.cell(l,s_chiave.cid2index(S_ARTFLD));
          key << get_part(levcode,curr, fld);
          // setta il range in base all'algoritmo (Valori/progressivi/mobili)
          const int range=(alg==ALG_VALORI ? col : alg==ALG_PROGRESSIVI ? ((1+int(col/_col_anno))*_col_anno-1) : (col+_col_anno-1));
          if (col>=first_col) // periodo da osservare
          {
            if (l==0)     
              for (int c=max(col,0);c <= range ; c++)
                update_file(" ",STR_TOTGEN,LINEA_DATI,num_liv_output,c,curr, fld, l);
            if (*s_chiave.cell(l,s_chiave.cid2index(S_TOTALE))=='X')
              for (int c=max(col,0);c <= range ; c++)
                update_file(key,levcode,LINEA_DATI,num_liv_output-l-1,c,curr, fld, l);
          }
          if (first_raffr !=first_col  && col >= first_raffr &&  col<first_raffr+_numcol_dati) // periodo da confrontare
          {
            if (l==0) 
              //for (int c=max(col,0); c <= range; c++)
              for (int c=col; c <= range; c++)
                update_file(" "/*" Totale"*/,STR_TOTGEN,LINEA_RAFFRONTI, num_liv_output , c -first_raffr,curr, fld, l);
            if (*s_chiave.cell(l,s_chiave.cid2index(S_TOTALE))=='X')
              //for (int c=max(col,0); c <= range; c++)
              for (int c=col; c <= range; c++)
                update_file(key,levcode,LINEA_RAFFRONTI,num_liv_output-l-1, c -first_raffr , curr, fld, l);
          }
        }
      }
    } // filtro
  } // ciclo 
  statusbar.setstatus(period2long(datafin.year(), periofin, _stats.frequency())-start_status);
  _svcache->flush();
  delete _svcache;
}


void TStampa_stat::set_printmask()
{
  TMask  &mp=selmask();
  
  // visualizza i checkbox per i totali 
  const int nlivelli = mp.sfield(F_CHIAVE).items();  
  TString80 nomeliv;
  printmask().field(F_FLAGSTOTALI).set("X");
  TSheet_field& sc = mp.sfield(F_CHIAVE);
  for (int f=0; f<MAX_ROWS; f++)
  {
    TMask_field & flfl=printmask().field(F_FLAGSTOTALI+f+1);
    if (f<nlivelli )
    {
      partkey_name(sc.cell(f,sc.cid2index(S_CAMPO)), f, nomeliv);
      nomeliv.insert(TR("Stampa totale "), 0);
      flfl.set_prompt(nomeliv);
      flfl.show();
      if (*sc.cell(f,sc.cid2index(S_TOTALE))!=' ')
      {
        flfl.enable();
        flfl.set("X");
      } else {
        flfl.disable();
        flfl.set(" ");
      }
    }  else
      flfl.hide();
  }
  // campi per il raffronto
  if (mp.get_date(F_DATARAF).empty())
  {
    printmask().field(F_STRAFFRONTO).hide();
    printmask().field(F_TIPORAFFRONTO).hide();
  } else {
    printmask().field(F_STRAFFRONTO).show();
    printmask().field(F_TIPORAFFRONTO).show();
  } 
  // visualizza ed eventualmente abilita il campo colonne in un anno
  printmask().set(F_COLANNO, mp.get(F_COL_ANNO));
  if (mp.field(F_COL_ANNO).enabled()) // venduto, no progressivo o mobile
  {
    printmask().enable(F_COLANNO);
  } 
  else 
  {
    printmask().disable(F_COLANNO);
  }
}


const TString& TStampa_stat::partkey_name(const char *lev_code, int row, TString& park) const
{
  const int lev_code_num=atoi(lev_code+1);
  switch (*lev_code)
  {
    case CHR_TIPODOC: // tipo documento
      park=TR("tipo documento");
      break;
    case CHR_ARTICOLO: // articolo
      if (lev_code_num)
        park =_liv_art->name(lev_code_num);
      else
        park=" ";
      break;
    case CHR_LIVGIAC: // liv giac
      CHECK(lev_code_num>0,"I livelli di giacenza sono utilizzabili solo singolarmente");
      park =_liv_giac->name(lev_code_num);
      break;
    case CHR_CATVEN: // cat. vendita
      park=TR("cat. ven.");
      break;
    case CHR_CLI: // cliente 
      park=TR("cliente   ");
      break;
    case CHR_FOR: // fornitore
      park=TR("fornitore ");
      break;
    case CHR_AGENTE: // agente
      park=TR("agente    ");
      break;
    case CHR_MAGAZZ: // mag
      park=TR("magazzino");
      break;
    case CHR_ZONA: // zona
      park=TR("zona");
      break;
    case CHR_UMISURA: // unit� di misura
      park=TR("unita' di misura");
      break;
    case CHR_TOTGEN: // totale generale
      park=TR("generale");
      break;
    case CHR_ARTFIELD: // campo anagrafica
      park=_des_fld.row(row);
      break;
    default:
      NFCHECK("ai chent recognaiz the code (%s) of the key part!", lev_code);
      park = " ";
      break;
  }
  return park;
}


// strippa gli spazi dal codice di questo livello
// toglie il carattere 'C' o 'F' dal codice clientefor e lo mette come codice di livello
void TStampa_stat::adjust_record(TRectype &strec, int from) const
{
  char lev_code=strec.get_char(SVS_LEVCODE);

  if (lev_code==CHR_CLIFOR)
  {
    TString codprec = strec.get(SVS_CODICE).left(from);
    TString& s = (TString&)strec.get(SVS_CODICE).mid(from);
    while ((lev_code=s.shift())==' ');
    codprec << s;
    strec.put(SVS_LEVCODE, lev_code);
    strec.put(SVS_CODICE, codprec);
  } 
  else 
  {
    if (from)
    {
      TString codprec=strec.get(SVS_CODICE).left(from);
      TString& s = (TString&)strec.get(SVS_CODICE).mid(from);
      while ((lev_code=s.shift())==' ');
      codprec << lev_code << s;
      strec.put(SVS_CODICE,codprec);
    }
  }
}

            
const TString& TStampa_stat::get_part(TString & lev_code, TSVriep_record &rieprec, const TString & fld) // const
{
  static TString park(25, ' ');

  int lev_code_num=atoi(lev_code+1);
  switch (lev_code[0])
  {
    case CHR_TIPODOC: // tipo documento
        park = rieprec.get(SVR_TIPODOC);
        break;
    case CHR_ARTICOLO: // articolo
      if (lev_code_num==0)
      {
        park=rieprec.get(SVR_CODART);
        park.rpad(25);
        break;
      }
      else
      {
        park =_liv_art->unpack_grpcode(rieprec.get(SVR_CODART),lev_code_num);
        park.rpad(_liv_art->code_length(lev_code_num));
        break;
      }
    case CHR_ARTFIELD: // campo dell' anagrafica articolo
      {
        const char t = rieprec.get_char(SVR_TIPOART);
        TFieldref f;

        f = fld;      
				const TRectype anamag(LF_ANAMAG);
        int l = f.len(anamag);
        if (l > 25)
          l = 25;
        if (t == RIGA_MERCE || t == RIGA_OMAGGI)
        {
          const TRectype & rec = cache().get(LF_ANAMAG, rieprec.get(SVR_CODART));
          park = f.read(rec);
          park.rpad(l);
        }               
        else
          park.spaces(l); 
      }       
      break;
    case CHR_LIVGIAC: // liv giac
      CHECK(lev_code_num>0,"I livelli di giacenza sono utilizzabili solo singolarmente");
      park = _liv_giac->unpack_grpcode(rieprec.get(SVR_GIAC),lev_code_num);
      park.rpad(_liv_giac->code_length(lev_code_num));
      break;
    case CHR_CATVEN: // cat. vendita
      park=rieprec.get(SVR_CATVEN);
      park.rpad(3);
      break;
    case CHR_FOR: // cliente / fornitore
    case CHR_CLI: // cliente / fornitore
      if (rieprec.get_char(SVR_TIPOCF)>' ')
        lev_code[0]=rieprec.get_char(SVR_TIPOCF);
      park.format("%c%6ld",lev_code[0],rieprec.get_long(SVR_CODCF));
      break;
    case CHR_AGENTE: // agente
      park.format("%5s",(const char*)rieprec.get(SVR_CODAG));
      break;
    case CHR_MAGAZZ: // mag
      park=rieprec.get(SVR_MAG);
      park.rpad(3);
      break;
    case CHR_ZONA: // zona
      park=rieprec.get(SVR_ZONA);
      park.rpad(3);
      break;
    case CHR_UMISURA: // unit� di misura
      park=rieprec.get(SVR_UMQTA);
      park.rpad(3);
      break;
    default:
      NFCHECK("ai chent recognaiz the code of the key part!");
      park= " ";
  }
  if (park.blank())
    park.replace(' ','?');
  return park;
}

const TString& TStampa_stat::get_descrpart(const char *lev_code, TSVriep_record &rieprec, const TString & fld, int row)
{
  int lev_code_num=atoi(lev_code+1);
  switch (*lev_code)
  {
    case CHR_TIPODOC: // tipo documento                  
    {
      const TString16 tipodoc = rieprec.get(SVR_TIPODOC);
      const TString& descr = _tipodoc->get(tipodoc).get("S0");
      return descr;
    }  
    case CHR_ARTICOLO: // articolo
    {
      const char t = rieprec.get_char(SVR_TIPOART);
      switch (t)
      {
       case RIGA_MERCE:
       case RIGA_OMAGGI:
        if (lev_code_num==0 || lev_code_num== _liv_art->last_level())
        {
          const TRectype & rec = cache().get(LF_ANAMAG, rieprec.get(SVR_CODART));
          return rec.get(ANAMAG_DESCR);
        }
        else
        {
          return _liv_art->group_descr(rieprec.get(SVR_CODART),lev_code_num);
        }
        break;
       case RIGA_PRESTAZIONI:
          return cache().get("PRS", rieprec.get(SVR_CODART), "S0");
        break;
       case RIGA_SPESEDOC:
          return cache().get("SPP", rieprec.get(SVR_CODART), "S0");
        break;
       default:
        break;
      }
    }
    case CHR_ARTFIELD: // campo anagrafica         
      {
        if (_file_fld.objptr(row) != NULL)
        {
          TString16 tab(_file_fld.row(row));
          int file = atoi(tab);
          const TRectype & anag = cache().get(LF_ANAMAG, rieprec.get(SVR_CODART));
          TFieldref f;

          f = _key_fld.row(row);                              
          const TString & key = f.read(anag);   
          
          if (key.not_empty())
          {
            if (file != 0)        
            {
              const TRectype & rec = cache().get(file, key);
              return rec.get(_field_fld.row(row));
            }
            else
            {
              const TRectype & rec = cache().get(tab, key);
              return rec.get(_field_fld.row(row));
            }
          }
        }
        return EMPTY_STRING;
      }
      break;
    case CHR_LIVGIAC: // liv giac
      CHECK(lev_code_num>0,"I livelli di giacenza sono utilizzabili solo singolarmente");
      return _liv_giac->group_descr(rieprec.get(SVR_GIAC),lev_code_num);
    case CHR_CATVEN: // cat. vendita
      return _catven->get(rieprec.get(SVR_CATVEN)).get("S0");
    case CHR_FOR: // fornitore
    case CHR_CLI: // cliente
    {
      TToken_string key;
			
			key.add(rieprec.get_char(SVR_TIPOCF));
			key.add(rieprec.get_long(SVR_CODCF));
			return cache().get(LF_CLIFO, key, CLI_RAGSOC);
    }
    case CHR_AGENTE: // agente
			return cache().get(LF_AGENTI, rieprec.get(SVR_CODAG), AGE_RAGSOC);
    case CHR_MAGAZZ: // mag
      return cache().get("MAG", rieprec.get(SVR_MAG), "S0");
    case CHR_ZONA: // zona
      return cache().get("ZON", rieprec.get(SVR_ZONA), "S0");
    case CHR_UMISURA: // Unit� di misura
      return cache().get("%UMS", rieprec.get(SVR_UMQTA), "S0");
    default:
      NFCHECK("ai chent recognaiz the code of the key part!");
      return EMPTY_STRING;
  }
}


bool TStampa_stat::recalc_period()
{   
  bool ok = set_column_frequency();
  
  TMask& m = selmask();
  if (ok)
  {
    TDate data(m.get(F_DATAFIN));
    if (data.ok())
    {
      int col = _freq.date2column(data);
      if (col < 0) col = 0;
      if (col > 35) col = 35;
      data = app()._freq.column_last_day(col);
      m.set(F_DATAFIN, data);
      m.set(F_PERIODO, col+1);
      ok = TRUE;
    }  
    
    data = m.get(F_DATARAF);
    if (data.ok())
    {
      int col = _freq.date2column(data);
      m.set(F_RAFFRONTO, -col);
    }
  }
  return ok;
}

bool TStampa_stat::multiplo_handler(TMask_field& f, KEY k)
{   
  bool ok = TRUE;
  TMask& m = f.mask();         
  if (k == K_SPACE || k == K_ENTER)
  {
    TFrequenza_statistiche base = char2frequency(m.get(F_FREQUENZA)[0]);
    TFrequenza_statistiche freq = char2frequency(f.get()[0]);
    if (base == freq)
    {
      m.enable(F_NUMERO);
    }  
    else
    {            
      int n = divide(freq, base);
      m.set(F_NUMERO, n);
      m.disable(F_NUMERO);
    }  
    // setta la durata dell'anno
    m.set(F_COL_ANNO, last_period(1992,freq)); 
    app().recalc_period();
  }
  return ok;
} 

bool TStampa_stat::numero_handler(TMask_field& f, KEY k)
{                
  bool ok = TRUE;
  if (f.to_check(k))
    app().recalc_period();
  return ok;  
}

bool TStampa_stat::periodo_handler(TMask_field& f, KEY k)
{    
  bool ok = TRUE;
  if (f.to_check(k))
  {
    int col = atoi(f.get());
    if (col > 0 && col <= 36)
    {
      if (app().set_column_frequency())
      {
        TDate d = app()._freq.column_last_day(col-1);
        f.mask().set(F_DATAFIN, d);
      }
    }
  }
  return ok;
}

bool TStampa_stat::dataini_handler(TMask_field& f, KEY k)
{           
  bool ok = TRUE;
  if (k == K_TAB && f.focusdirty())
  {       
    TMask& m = f.mask();
    TDate data(f.get());             
    const TFrequenza_statistiche base = char2frequency(m.get(F_FREQUENZA)[0]);
    const TFrequenza_statistiche freq = char2frequency(m.get(F_MULTIPLO)[0]);
    // floor(data, divide(freq, base) == 0 ? freq : base); // perche'?
    floor(data, freq);
    f.set(data.string());
    app().recalc_period();
  }
  return ok;
}

bool TStampa_stat::datafin_handler(TMask_field& f, KEY k)
{           
  bool ok = TRUE;
  if (k == K_TAB && f.focusdirty())
  {   
    app().recalc_period();
  }
  return ok;
}

bool TStampa_stat::raffronto_handler(TMask_field& f, KEY k)
{
  if (f.to_check(k))
  {
    if (app().set_column_frequency())
    {   
      const int col = atoi(f.get());
      if (col > 0)
      {
        TDate data = app()._freq.column_first_day(-col);
        f.mask().set(F_DATARAF, data);
      }
      else
        f.mask().reset(F_DATARAF);
    }
  }
  return TRUE;
}

bool TStampa_stat::dataraf_handler(TMask_field& f, KEY k)
{
  if (f.to_check(k))
  {       
    TMask& m = f.mask();
    TDate data(f.get());             
    if (data.ok())
    {            
      if (app().set_column_frequency())
      {
        int col = app()._freq.date2column(data);
        if (col < 0) 
        {
          m.set(F_RAFFRONTO, -col);
          data = app()._freq.column_first_day(col);
          f.set(data.string());
        }  
        else
          data = botime;
      }  
    }
    if (!data.ok())
      m.reset(F_RAFFRONTO);
  }
  return TRUE;
}

bool TStampa_stat::codice_handler(TMask_field& f, KEY k)
{
  if (k == K_TAB && f.focusdirty())
  {    
    TMask& m = f.mask();
    
    if (app().set_column_frequency())
    {
      int col = m.get_int(F_RAFFRONTO);
      if (col > 0)
      {
        TDate data = app()._freq.column_first_day(-col);
        m.set(F_DATARAF, data);
      }
    }
    
    // Record corrente della tabella statistiche
    const TRectype& rec = ((TEdit_field&)f).browse()->cursor()->curr();
  
    TSheet_field& sheet = m.sfield(F_CHIAVE);
    sheet.destroy();
    
    TToken_string s1 = rec.get("S1");
    TToken_string s2 = rec.get("S2");
    TToken_string s4 = rec.get("S4");
    TString s5 = rec.get("S5");
  
    if (s4.not_empty())
      if (s4[s4.len() - 1] == '|' && s5[0] == '|') s4 << " ";
    s4 <<s5;
    
    int r = 0;
    for (const char* cod = s1.get(0); cod; cod = s1.get(), r++)
    {
      TToken_string& row = sheet.row(r);  
      row = cod;
      cod = s2.get(r);
      row.add(cod);
      row.add("");
      row.add("");
      row.add("");       
      cod = s4.get(r);
      row.add(cod);
      sheet.check_row(r);
    }
    sheet.force_update();
  }
  return TRUE;
}

bool TStampa_stat::filename_handler(TMask_field& f, KEY k)
{
  if (k == K_TAB && !f.empty())
  {                           
    TFilename fn(f.get());
    fn.ext("dbf");
    TFilename path(fn.path());
    path.rtrim(1); // Toglie la slash dal path
    if (!path.exist())
      return f.error_box(FR("Directory %s non trovato"),(const char *)path);
    else
      if (fn.exist())
        return f.yesno_box(FR("File %s esistente: sovrascrivo"), (const char*)fn);
  }
  return TRUE;
}


bool TStampa_stat::chiave_notify(TSheet_field& s, int r, KEY k)
{           
  bool ok = TRUE;
  if (k == K_INS)
    ok = s.items() < MAX_ROWS;
  else if (k == K_CTRL+ K_INS)
    s.row(r).add("X",1);
  else if (k == K_CTRL+ K_TAB)
  {
    const int maxr=s.items();
    // elimina il flag calcolo totali
    if (r<maxr)
    {
      if (*s.row(r).get(1)!=' ')
      {
       // posso tenere abilitato il totale?
       if (r>0 && *s.row(r-1).get(1)==' ')
        {
          s.row(r).add(" ",1);
          s.force_update(r);
        }
      } else {
        // disabilito i totale seguenti
        for (r++;r < maxr; r++)
          if (*s.row(r).get(1)!=' ')
          {
            s.row(r).add(" ",1);
            s.force_update(r);
          }
      }
    }
  }
    
  return ok;
}

bool TStampa_stat::test_field(const TString& cod, TMask_field& f) const
{ 
  bool ok = TRUE;
  
  switch(cod[0])  
  {              
  case CHR_ARTICOLO: 
    if (cod[1] != '\0' && !_liv_art->enabled())
      ok = f.error_box(TR("I livelli di codice articolo non sono abilitati"));
    break;  
  case CHR_LIVGIAC: 
    if (!_stats.grp_giacenza())
      ok = f.error_box(TR("Le statistiche sono raggruppate per livello di giacenza"));
    else if (!_liv_giac->enabled())
      ok = f.error_box(TR("I livelli di giacenza non sono abilitati"));
    break;  
  case CHR_CLIFOR: 
    if (!_stats.grp_cliente())
      ok = f.error_box(TR("Le statistiche sono raggruppate per cliente/fornitore"));
    break;  
  case CHR_AGENTE: 
    if (!_stats.grp_agente())
      ok = f.error_box(TR("Le statistiche sono raggruppate per agente"));
    break;  
  case CHR_MAGAZZ: 
    if (!_stats.grp_magazzino())
      ok = f.error_box(TR("Le statistiche sono raggruppate per magazzino"));
    break;  
  case CHR_ZONA: 
    if (!_stats.grp_zona())
      ok = f.error_box(TR("Le statistiche sono raggruppate per zona"));
    break;  
  default : break;
  }
  return ok;
}

bool TStampa_stat::chiave_handler(TMask_field& f, KEY k)
{       
  bool ok = TRUE;
  if (k == K_ENTER)
  {              
    TSheet_field& sheet = (TSheet_field&)f;
    TToken_string used;
    TString16 campo;
    for (int r = 0; r < sheet.items(); r++)
    {
      TToken_string& row = sheet.row(r);
      campo = row.get(0);
      
      if (campo.blank())
        continue;
      
      ok = app().test_field(campo, f);
      
      bool found = used.get_pos(campo) >= 0;
      if (!found && (campo[0] == CHR_ARTICOLO || campo[0] == CHR_LIVGIAC))
      {
        const char str[2] = { campo[0], '\0' };
        found = used.get_pos(str) >= 0;
      }
      
      if (campo[0] != CHR_ARTFIELD && found)
      {       
        ok = error_box(FR("Il codice %s inserito alla riga %d e' gia'\n"
                       "utilizzato in una riga precedente."),
                       (const char*)campo, r+1);
      }
      else
        used.add(campo);
    }
  }
  return ok;
}

bool TStampa_stat::campo_handler(TMask_field& f, KEY k)
{  
  if (f.to_check(k, TRUE))            
  {         
    const bool is_fld = f.get()== STR_ARTFIELD;
    
    if (!is_fld)
      f.mask().reset(S_ARTFLD);
    f.mask().field(S_ARTFLD).check_type(is_fld ? CHECK_REQUIRED : CHECK_NONE);
    f.mask().enable(S_ARTFLD, is_fld);
    return app().test_field(f.get(), f);
  }
  return TRUE;
}  

bool TStampa_stat::artfld_handler(TMask_field& f, KEY k)
{                 
  bool ok = TRUE;      
  switch (k)
  {       
    case K_TAB:
    case K_ENTER:
      if (!f.empty() && f.to_check(k))
      {
        const TRectype rec(LF_ANAMAG);        
        TString16 field = f.get();
        int pos = field.find('[');
        if (pos > 0)
          field.cut(pos);
        if (!rec.exist(field))
          ok = f.error_box(FR("Il campo '%s' non esiste."), (const char*)field);
      }
      break;
    case K_F9:
      {                                                      
        TRelation anamag(LF_ANAMAG);
        TRelation_description rd(anamag);
        if (rd.choose_field(f.get()))
          f.set(rd.field_name());
      }
      break;
    default:
      break;
  }
  return ok;
}

///////////////////////////////////////////////////////////
// Pseudo main
///////////////////////////////////////////////////////////

int sv1200(int argc, char* argv[])
{
  TStampa_stat mainapp;
  mainapp.run(argc, argv, TR("Stampa statistiche"));
  return 0;
}