#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>

#define __DATE_CPP
#include <date.h>
#ifndef FOXPRO
#include <real.h>
#endif
#include <strings.h>
#include <utility.h>
#if XVT_OS != XVT_OS_SCOUNIX
#include <dos.h>
#endif

#define DAYYEAR  365
#define DAYBIAS  36525L

///////////////////////////////////////////////////////////
// Utility functions
///////////////////////////////////////////////////////////

// @doc EXTERNAL

HIDDEN TDate __tmp_date;
HIDDEN char __date_tmp_string[128];
HIDDEN const byte _days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};


TDate::TDate(const TDate &d) : _val(d._val) 
{}

TDate::TDate(long l) : _val(l)
{
  if (_val == TODAY)
  {
#if XVT_OS == XVT_OS_SCOUNIX
#ifndef FOXPRO
    long lt;
    struct tm *timeloc;
    FILE *f;                             
    
    sprintf(_int_s1, "date.%-d", getuid());
    if ((f = fopen(_int_s1, "r")) == NULL)
    {
      if (time(&lt) == -1) return ;
      timeloc = localtime(&lt) ;
      _val = makedata(timeloc->tm_mday, timeloc->tm_mon+1, timeloc->tm_year);
    }
    else
    {
      fscanf(f, "%ld", _val);
      fclose(f);
    }         
#endif
#else
    int junk;
    union REGS inregs, outregs;
    
    inregs.h.ah = 0x2A;
    junk = intdos(&inregs, &outregs);
    _val = makedata(outregs.h.dl, outregs.h.dh, outregs.x.cx);
#endif
  } else
    if (_val == 0)
      _val = NULLDATE;
    else
      if (_val < 1000000L)
      {
        long wd = _val;
        int cnt = 1900;
        if (wd > DAYBIAS)
        {
          wd -= DAYBIAS;
          cnt += 100;
        }
        else
          while (wd < 0)
          {
            cnt -= 100;
            wd += DAYBIAS;
          }
        int m, y, leap;
        
        for(y = 0; wd > DAYYEAR + (leap = ((y % 4 ) == 0)); y++)
          wd -= (DAYYEAR + leap);
        for(m = 0; wd > (_days_in_month[m] + (leap && (m == 1))); m++)
          wd -= (_days_in_month[m] + (leap && (m == 1)));
        _val = makedata((int) wd, m+1, y+cnt);
      }
}


TDate::TDate(const char* s)
{
  _val = NULLDATE;
  
  const int len = (s && *s) ? strlen(s) : 0;
  if (len != 8 && len != 10)
    return;
  
  int d = 0, m = 0, y = 0;
  if (len == 8)
  {          
    for (int i = 0; i < 8; i++)
      if (!isdigit(s[i])) break;
    if (i == 8)
    {
      TString16 str(s);
      d = atoi(str+6); str.cut(6);
      m = atoi(str+4); str.cut(4);
      y = atoi(str+0);
    }  
  } 
  else
    if (len == 10)
    {
      if (s[2] == s[5] && !isdigit(s[2]))
      {
        d = atoi(s);
        m = atoi(&s[3]);
        y = atoi(&s[6]);
      }
    } 
  
#ifdef DBG
  if (d < 1 || d > 31 || m < 1 || m > 12 && y < 0)
    yesnofatal_box("Lamentati con Guy se la data %s non viene accettata!", s);
#endif    
  _val = makedata(d, m, y);
}

TDate::TDate(int day, int month, int year)
{
  if (day >= 1 && day <= 31 && month >= 1 && month <= 12 && year > 0)
    _val = makedata(day, month, year);
  else
    _val = NULLDATE;
}       

int TDate::last_day(int month, int year)
  // parse_filastrok(
  //    "trenta giorni ha novembre
  //    con april, giugno e settembre
  //    son ventotto case uno
  //    per default ce n'ha trentuno");
{
  int d;
  switch(month)
  {
  case 4:
  case 6:
  case 9:
  case 11:
    d = 30;
    break;
  case 2:
    d = year % 4 ? 28 : 29;
    break;
  default:
    d = 31;
    break;
  }
  return d;
}

void TDate::set_end_month()             
{
  _val = makedata(last_day(month(),year()),month(),year());                    
}

bool TDate::is_end_month()
{
  return day() == last_day(month(),year());
}

int TDate::wday() const
{
  // day of week (1=lunedi)
  // DDJ algorithm (4/1995)  Della serie: "non e' colpa mia se funziona".
  int m = month();
  int d = day(); 
  int y = year(); 
  
  if (m <= 2)      // Gennaio e Febbraio sono gli ultimi mesi dell'anno scorso
  {
    y --;
    m += 12;
  }         
  
  return ((d + 2*m + 3*(m+1)/5 + y + y/4 - y/100 + y/400) % 7) + 1; // Pure magic
}

void  TDate::set_day(int n)   
{ 
   CHECK(n > 0 && n < 32, "TDate::set_day: giorno insensato");
  _val = makedata(n, month(), year()); 
}  
void  TDate::set_month(int n) 
{ 
  CHECK(n > 0 && n < 13, "TDate::set_month: mese impossibile");
  _val = makedata(day(), n, year());   
}  
void  TDate::set_year(int n)  { _val = makedata(day(), month(), n);  }  

TDate::operator const char*() const
{
  return string();
}


TDate& TDate::operator =(const char* s)
{
  return *this = TDate(s);
}


void TDate::print_on(ostream& out) const
{ 
  out << string();
}


void TDate::read_from(istream& in)
{
  char s[256];
  in >> s;
  TDate d(s);
  _val = d._val;
}

// @mfunc Ritorna la data in formato di stringa (anche in formato ANSI)
//
// @rdesc Se si tratta di una data valida ritorna la stringa secondo il
//        formato scelto (vedi <t TDate_mgafmt>), altrimenti ritorna ""
char* TDate::string(
  TDate_mgafmt yearf,     // @parm Formato per l'anno (default def)
  char sep,               // @parm Carattere separatore (default '-')
  TDate_mgafmt dayf,      // @parm Formato per il giorno (default def)
  TDate_mgafmt monthf,    // @parm Formato per il mese (default def)
  TDate_order ord) const  // @parm Ordine con la quale visualizzare la data
  //       (vedi <t TDate_order>; default gma_date)
{ 
  if (!ok() || *this == botime) 
    return "";
  
  if (yearf == ANSI)
  {
    yearf = full;
    ord = amg_date;
    sep='\0';
  }  

  TString df(2), yf(4), mf(2);
  bool letterflag = FALSE;

  // format day
  if (dayf == letters)
  {
#ifndef FOXPRO    
    const real ddd(day());
    df = ddd.string("LETTERE");
    letterflag = TRUE;
#endif
  }
  else if (dayf == weekday)
  {
    df = format("%s %d", itow(wday()), day());       
    letterflag = TRUE;
  }
  else 
    df = format(dayf == brief ? "%d" : "%02d", day());
  
  // format year
  if (yearf == letters)
  {           
#ifndef FOXPRO
    const real ddd(year());
    yf = ddd.string("LETTERE");
    letterflag = TRUE;
#endif
  }
  else 
    if (yearf == brief)
      yf = format("%02d", year() % 100);
    else
      yf = format("%04d", year());
  
  // format month
  if (monthf == letters)
  {
    letterflag = TRUE;
    mf = itom(month());   
  }
  else if (monthf == quarter)
  {                             
    if (ord < m_date) ord = ma_date;
    mf = format("%do trimestre", (month() / 3) + 1);
  }
  else
    mf = format(monthf == brief ? "%d" : "%02d", month());
  
  if ((letterflag && sep == '-') || sep == 'S') 
    sep = ' '; 

  // build date string  
  
  TFixed_string dfm(__date_tmp_string, 128);
  dfm.cut(0);
  
  switch (ord)
  { 
  case mga_date:    
    dfm << mf << sep << df << sep << yf;
    break;
  case amg_date:                        
    dfm << yf << sep << mf << sep << df;
    break;
  case a_date:                        
    dfm << yf;
    break;
  case m_date:
    dfm << mf;
    break;
  case g_date:
    dfm << df;
    break;
  case ma_date: 
    dfm << mf << sep << yf;      
    break;
  default: 
    dfm << df << sep << mf << sep << yf;
    break;
  }
  
  return __date_tmp_string;
}

long TDate::date2julian() const
{                      
  const int d = day(), m = month(), y = year();
  
  return (long)(d - 32076)
    + 1461L * (y + 4800L + (m - 14) / 12) / 4
      + 367 * ( m - 2 - (m - 14) / 12 * 12) / 12
        - 3 * ((y + 4900L + (m - 14) / 12) / 100) / 4
          + 1;
  
}

long TDate::julian2date(long julian) const
{
  long x, z, m, d, y;
  const long daysPer400Years = 146097L;
  const long fudgedDaysPer4000Years = 1460970L + 31;

  x = julian + 68569L;
  z = 4 * x / daysPer400Years;
  x = x - (daysPer400Years * z + 3) / 4;
  y = 4000 * (x + 1) / fudgedDaysPer4000Years;
  x = x - 1461 * y / 4 + 31;
  m = 80 * x / 2447;
  d = x - 2447 * m / 80;
  x = m / 11;
  m = m + 2 - 12 * x;
  y = 100 * (z - 49) + y + x;
  return makedata((int) d, (int) m, (int) y);
}

int TDate::day() const
{
  return (int) (_val % 100L);
}


int TDate::month() const
{
  return (int) ((_val % 10000L) / 100L);
}


int TDate::year() const

{ 
  return (int) (_val / 10000L);
}


void TDate::addmonth(int nmonth)

{
  const int wday = day();
  int wmonth = month() + nmonth, wyear = year();
  
  if (wmonth > 12)
  {
    wmonth -= 12;
    wyear++;
  }
  _val = makedata(wday, wmonth, wyear);
}


void TDate::addyear(int nyear)

{
  const int wday = day(), wmonth = month();
  int wyear = year() + nyear;
  
  _val = makedata(wday, wmonth, wyear);
}


bool TDate::isdate(const char* s)
{ 
  const int len = strlen(s);
  if (len != 8 && len != 10)
    return FALSE;
  
  int d = 0, m = 0, y = 0;
  if (len == 8)
  {          
    for (int i = 0; i < 8; i++)
      if (!isdigit(s[i])) break;
    if (i == 8)
    {
      TString16 str(s);
      d = atoi(str+6); str.cut(6);
      m = atoi(str+4); str.cut(4);
      y = atoi(str+0);
    }  
  } 
  else
    if (len == 10)
    {
      if (s[2] == s[5] && !isdigit(s[2]))
      {
        d = atoi(s);
        m = atoi(&s[3]);
        y = atoi(&s[6]);
      }
    } 
  
  if (d < 1 || d > 31 ||
      m < 1 || m > 12 ||
      y < 0)
    return FALSE;

  return d <= _days_in_month[m - 1] || (m == 2 && (y % 4 == 0) && d == 29);
}


bool TDate::ok() const
{
  return _val > 0;
}


// @func TDate& | operator + | Incrementa la data di un certo numero di giorni
TDate& operator +(
  const TDate& a,  // @parm Data a cui aggiungere i giorni
  long nday)       // @parm Numero di giorni da aggiungere

  // @syntax operator + (const TDate& a, long nday)
  // @syntax operator + (long nday, const TDate& a)
  //
  // @comm E' indifferente quale parametro viene passato per primo

{
  __tmp_date = a.julian2date(a.date2julian() + nday);
  return __tmp_date;
}


TDate& operator +(const long nday, const TDate& b)

{
  __tmp_date = b.julian2date(b.date2julian() + nday);
  return __tmp_date;    
}


// @func TDate& | operator - | Decrementa la data di un certo numero di giorni
TDate& operator -(
  const TDate& a,  // @parm Data da decrementare
  long nday)       // @parm Numero di giorni da togliere

{
  __tmp_date = a.julian2date(a.date2julian() - nday);
  return __tmp_date;
}

// @func TDate& | operator - | Calcola la differenza tra due date
long operator -(
  const TDate& a,  // @parm Data da decrementare
  const TDate& b)  // @parm Data da sottrarre
{
  const long diff = a.date2julian() - b.date2julian();
  return diff;
}

// @func Scambia la data <p a> con la data <p b>
void swap(
  TDate& a,  // @parm Prima data da scambiare
  TDate& b)  // @parm Seconda data da scambiare

{
  __tmp_date = b;
  b = a;
  a = __tmp_date;
}


// @func Ritorna la data piu' piccola tra <p a> e <p b>
const TDate& fnc_min(
  const TDate& a, // @parm Prima data da confrontare
  const TDate& b) // @parm Secondo data da confrontare

{
  if (a < b) return a;
  else return b;
}

// @func Ritorna la data piu' grande tra <p a> e <p b>
const TDate& fnc_max(
  const TDate& a,  // @parm Prima data da confrontare
  const TDate& b)  // @parm Secondo data da confrontare

{
  if (a > b) return a;
  else return b;
}

///////////////////////////////////////////////////////////
// TFormatted_date
///////////////////////////////////////////////////////////

TFormatted_date::TFormatted_date(int day, int month, int year, const char* form)
: TDate(day, month, year)
{
  set_format(form);
}

TFormatted_date::TFormatted_date(const TDate& d, const char* form)
: TDate(d)
{ 
  set_format(form); 
}

TFormatted_date::TFormatted_date(const TFormatted_date& d)
: TDate(d)
{  
  set_format(d._format); 
}

// @mfunc Permette di stabilire il criterio di formattazione delle date

void TFormatted_date::set_format(
  const char* f) // @parm Stringa di 5 caratteri che indica il formato della data
  
  // @comm Ogni carattere del parametro <p f>   permette di settare un tipo di formattazione
  //             della data:
  //             <nl>1° carattere -<gt> FORMATO. Puo' assumere i seguenti valori:
  //             <nl>   1 = giorno-mese-anno
  //             <nl>   2 = mese-anno-giorno
  //             <nl>   3 = anno-giorno-mese
  //             <nl>   4 = solo anno
  //             <nl>   5 = solo mese
  //             <nl>   6 = solo giorno
  //             <nl>   7 = mese-anno
  //             <nl><nl>2° carattere -<gt> Formato GIORNO. Puo assumere i seguenti valori:
  //             <nl>   2 = formato normale (es. 4)
  //             <nl>   4 = formato con 0 (es. 04)
  //             <nl>   5 = lettere (es. quattro)
  //             <nl>   6 = giorno della settimana
  //             <nl><nl>3° carattere -<gt> Formato MESE. Puo assumere i seguenti valori:
  //             <nl>   2 = formato normale (es. 4)
  //             <nl>   4 = formato con 0 (es. 04)
  //             <nl>   5 = lettere (es. quattro)
  //             <nl>   7 = trimestre
  //             <nl><nl>4° carattere -<gt> Formato ANNO. Puo' assumere i seguenti valori:
  //             <nl>   2 = breve (es. 95)
  //             <nl>   4 = lungo (es. 1995)
  //       <nl><nl>5° carattere -<gt> Carattere SEPARATORE. Puo' essere un carattere o lo spazio

{
  CHECKS(memchr(f, 0, 5) == NULL, "Bad date format ", f);
  memcpy(_format, f, 5);
}

const char* TFormatted_date::string() const
{ 
  TDate_mgafmt yearf  = (TDate_mgafmt)(_format[3] - '0');  
  char         sep    = _format[4]; 
  TDate_mgafmt dayf   = (TDate_mgafmt)(_format[1] - '0');            
  TDate_mgafmt monthf = (TDate_mgafmt)(_format[2] - '0'); 
  TDate_order  ord    = (TDate_order)(_format[0] - '0'); 

  return TDate::string(yearf, sep, dayf, monthf, ord);
}


///////////////////////////////////////////////////////////
// Utility functions
///////////////////////////////////////////////////////////

// @func Converte un numero da 1 a 12 nel corrispondente mese
const char* itom(
  byte m) // @parm Numero del mese da convertire in parole (da 1 a 12)

  // @comm Se il parametro <p m> e' maggiore di 12 viene calcolato il nome del
  //       mese corrispondente a tale cifra (es. 15 = "Marzo")
{
  const char* nomi[12] =
  {
    "Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno",
    "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"
    };

  return nomi[(m-1) % 12];
}

// @func Ritorna il nome del giorno (1-7)
const char* itow(
  byte d) // @parm Numero del giorna da convertire in parole (da 1 a 7)

  // @comm Come primo giorno della setimana e' preso il Lunedi.
  //       <nl>Se il parametro <p d> e' maggiore di 7 viene calcolato il nome del
  //       giorno corrispondente a tale cifra (es. 15 = "Lunedi")
{
  const char* nomi[7] =
  { "Lunedi", "Martedi", "Mercoledi",   "Giovedi", "Venerdi",
    "Sabato", "Domenica" };
  return nomi[(d-1) % 7];
}

