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

#define __DATE_CPP
#include "date.h"
#include "real.h"

#define DAYYEAR  365
#define DAYBIAS  36525L

static const byte _days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 
inline bool is_leap(int year)
{ return (year % 4) == 0; }

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

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

TDate::TDate(long l) : _val(l)
{
  if (_val == TODAY)
  {
  	_val = NULLDATE;
    time_t lt;
    if (time(&lt) != -1) 
    {
      struct tm * timeloc = localtime(&lt);
      if (timeloc != NULL)
        _val = makedata(timeloc->tm_mday, timeloc->tm_mon+1, timeloc->tm_year + 1900);
    }
  } 
  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 = is_leap(y)); 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(((const char *)str)+6); str.cut(6);
      m = atoi(((const char *)str)+4); str.cut(4);
      y = atoi(((const char *)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 = _days_in_month[month-1];
  if (month == 2 && is_leap(year))
    d++;
  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());
}

bool TDate::empty() const
{
  return _val == 0;
}

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);
}

TDate& TDate::operator =(long val)
{ 
  if (val < 0L)
    *this = TDate(val);
  else
    _val = val; 
  return *this;
}

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;
}

TObject* TDate::dup() const
{
  TDate * d = new TDate(*this);
  return d;
}

// @doc EXTERNAL

// @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';
  }  

  TString80 df, yf, mf;
  bool letterflag = FALSE;

  // format day
  if (dayf == letters)
  {
    const real ddd(day());
    df = ddd.string("LETTERE");
    letterflag = TRUE;
  }
  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)
  {           
    const real ddd(year());
    yf = ddd.string("LETTERE");
    letterflag = TRUE;
  }
  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("trimestre %d", (month()/3)+1);
  }
  else
    mf.format(monthf == brief ? "%d" : "%02d", month());
  
  if ((letterflag && sep == '-') || sep == 'S') 
    sep = ' '; 

  // build date string  
  
  TString& dfm = get_tmp_string(80);
 
  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 dfm.get_buffer();
}

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);
}


int TDate::week() const
{ 
  TDate y(1, 1, year());
  const int w = y.wday();
  if (w > 1) y -= w-1;
  return int((*this - y) / 7 + 1);
}

TDate& TDate::operator +=(long nday)
{
  const long d = day() + nday;
  if (d > 0 && d < 29)
    _val += nday;
  else
    _val = julian2date(date2julian() + nday); 
  return *this; 
}

void TDate::get_week_year(int &weekd, int &yeard, bool complete)
{
  weekd = week();
  yeard = year();
  const int wday = TDate(1,1,yeard).wday();
  if (complete)
  {
  	if (wday != 1)
    	weekd --;
	  if (weekd <= 0)
	  {
	    weekd = 52;
	    yeard --; 
	  } 
	}
	else
	{  
  	if (weekd > 52)
	  {
	    weekd = 1;
	    yeard ++; 
	  } 
	}  
}

void TDate::addmonth(int nmonth)
{
  const int wday = day();
  int wyear = year();
  int wmonth = month() + nmonth;
  while (wmonth > 12)
  {
    wmonth -= 12;
    wyear++;
  }
  _val = makedata(wday, wmonth, wyear);
}

void TDate::addyear(int nyear)
{
  const int wday = day();
  const int wmonth = month();
  const 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(((const char *)str)+6); str.cut(6);
      m = atoi(((const char *)str)+4); str.cut(4);
      y = atoi(((const char *)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 <= last_day(m,y);
}


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


// @doc EXTERNAL

// @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
{
  TDate tmp(a);
  tmp += nday;
  return tmp;
}


TDate operator +(const long nday, const TDate& b)
{
  TDate tmp(b);
  tmp += nday;
  return tmp;    
}

// @doc EXTERNAL

// @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
{
  TDate tmp(a);
  tmp += -nday;
  return tmp;
}

// @doc EXTERNAL

// @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;
}

// @doc EXTERNAL

// @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

{
  const TDate tmp = b;
  b = a;
  a = tmp;
}

// @doc EXTERNAL

// @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;
}

// @doc EXTERNAL

// @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); 
}

// @doc EXTERNAL

// @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 o essere omesso (sep= char nullo)

{
  CHECKS(memchr(f, 0, 4) == NULL, "Bad date format (you must define all the first 4 chars) :", 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
///////////////////////////////////////////////////////////

// @doc EXTERNAL

// @func Converte un numero da 1 a 12 nel corrispondente mese
const char* itom(
  int 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")
{
  CHECK(m>=1, "Il mese indicato deve essere un numero da 1 a 12 ");
  const char* const nomi[12] =
  {
    "Gennaio", "Febbraio", "Marzo",     "Aprile",  "Maggio",   "Giugno",
    "Luglio",  "Agosto",   "Settembre", "Ottobre", "Novembre", "Dicembre"
  };
  return nomi[(m-1) % 12];
}

// @doc EXTERNAL

// @func Ritorna il nome del giorno (1-7)
const char* itow(
  int 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")
{
  CHECKD(d >= 1 && d <= 7, "Bad week day ", d);
  const char* const nomi[7] =
  { "Luned�",  "Marted�", "Mercoled�", "Gioved�", "Venerd�", "Sabato", "Domenica" };
  return nomi[(d-1) % 7];
}