/***************************************************************************
                          Fl_Date_Time.cpp  -  description
                             -------------------
    begin                : Tue Dec 14 1999
    copyright            : (C) 1999 by Alexey Parshin
    email                : alexeyp@m7.tts-sf.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <config.h>

#include <efltk/Fl_Date_Time.h>
#include <efltk/Fl_Util.h>

// For NLS stuff
#include "../core/fl_internal.h"

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

#ifndef _WIN32
# include <sys/time.h>
#else
# include <windows.h>
#endif

static const char *dayname[] = {
   "Sunday", "Monday", "Tuesday", "Wednesday",
   "Thursday", "Friday", "Saturday"
};

static const char *mname[] = {
   "January", "February", "March", "April", "May", "June",
   "July", "August", "September", "October", "November", "December"
};

static const short _monthDays[2][12] = {
   {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
   {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} //Leap year
};

static const short _monthDaySums[2][12] = {
   {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
   {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335} //Leap year
};

#define DateDelta 693594

char Fl_Date_Time::dateInputFormat[32];
char Fl_Date_Time::timeInputFormat[32];
char Fl_Date_Time::dateFormat[32];
char Fl_Date_Time::datePartsOrder[4];
char Fl_Date_Time::timeFormat[32];
bool Fl_Date_Time::time24Mode;
char Fl_Date_Time::dateSeparator;
char Fl_Date_Time::timeSeparator;
double Fl_Date_Time::dateTimeOffset;

static void upperCase(char *dest, const char *src, int dest_len) {
   int i = 0;
   int len = strlen(src);
   if(len>dest_len) len=dest_len; //No buffer overflow.
   for (; i < len; i++)
      dest[i] = (char)toupper( src[i] );
   dest[i] = '\0';
}

static int trimRight(char *s) {
   int len = strlen(s);
   while( (len--) >= 0) {
      if( (unsigned char)s[len] > 32 ) {
         len++;
         s[len] = '\0';
         break;
      }
   }
   return len;
}

static char parseDateOrTime(char *format,char *dt) {
   char  separator[] = " ";

   // Cut-off trailing non-digit characters
   int   len = strlen(dt);
   char *ptr = dt + len - 1;
   while (!isdigit(*ptr)) ptr--;
   *(ptr+1) = 0;
   // find a separator char
   ptr = dt;
   while (isalnum(*ptr) || *ptr == ' ') ptr++;
   separator[0] = *ptr;
   ptr = strtok(dt,separator);
   strcpy(format,"");

   const char *pattern;
   while (ptr) {
      int number = atoi(ptr);
      switch (number) {
      case 10: pattern = "HH";   // hour (12-hour mode)
               Fl_Date_Time::time24Mode = false;
               break;
      case 22: pattern = "HH";	// hour (24-hour mode)
               Fl_Date_Time::time24Mode = true;
               break;
      case 48: pattern = "NN";	// minute
               break;
      case 59: pattern = "SS";	// second
               break;
      case 17: pattern = "DD";   // day
               strcat(Fl_Date_Time::datePartsOrder,"D");	
               break;
      case 6:  pattern = "MM";   // month
               strcat(Fl_Date_Time::datePartsOrder,"M");	
               break;
      case 2000:
      case 0:  pattern = "YYYY"; // year
               strcat(Fl_Date_Time::datePartsOrder,"Y");	
          break;
      default: pattern = NULL; break;
      }
      if (pattern) {
         strcat(format,pattern);
         strcat(format,separator);
      }
      ptr = strtok(NULL,separator);
   }
   len = strlen(format);
   if (len)
      format[len-1] = 0;

   return separator[0];
}

class Fl_Date_Time_Format {
public:
   Fl_Date_Time_Format();
};

static void buildDateInputFormat() {
   Fl_Date_Time::dateInputFormat[0] = 0;
   char separator[] = { Fl_Date_Time::dateSeparator, 0 };
   for (int i = 0; i < 3; i++) 
   switch (Fl_Date_Time::datePartsOrder[i]) {
   case 'D':   strcat(Fl_Date_Time::dateInputFormat,"39\\");
               strcat(Fl_Date_Time::dateInputFormat,separator);
               break;
   case 'M':   strcat(Fl_Date_Time::dateInputFormat,"19\\");
               strcat(Fl_Date_Time::dateInputFormat,separator);
               break;
   case 'Y':   strcat(Fl_Date_Time::dateInputFormat,"2999\\");
               strcat(Fl_Date_Time::dateInputFormat,separator);
               break;
   }
   int len = strlen(Fl_Date_Time::dateInputFormat);
   if (len) 
      Fl_Date_Time::dateInputFormat[len-2] = 0;
}

static void buildTimeInputFormat() {
   if (Fl_Date_Time::time24Mode)
         strcpy(Fl_Date_Time::timeInputFormat,"29\\:59");
   else  strcpy(Fl_Date_Time::timeInputFormat,"19\\:59T\\M");
}

Fl_Date_Time_Format::Fl_Date_Time_Format() {
   char  dateBuffer[32];
   char  timeBuffer[32];
   // make a special date and time - today :)
   struct tm t;
   t.tm_year = 100;	// since 1900, -> 2000
   t.tm_mon  = 5;  	// June (January=0)
   t.tm_mday = 17;
   t.tm_hour = 22;
   t.tm_min  = 48;
   t.tm_sec  = 59;

   t.tm_wday = 0; // Sunday

   // Build local data and time
   strftime(timeBuffer,32,"%X",&t);
   strftime(dateBuffer,32,"%x",&t);

   // Build local date and time formats
   Fl_Date_Time::datePartsOrder[0] = 0;
   Fl_Date_Time::time24Mode = false; // to be determined below
   Fl_Date_Time::dateSeparator = parseDateOrTime(Fl_Date_Time::dateFormat,dateBuffer);
   Fl_Date_Time::timeSeparator = parseDateOrTime(Fl_Date_Time::timeFormat,timeBuffer);
   if (!Fl_Date_Time::time24Mode)
      strcat(Fl_Date_Time::timeFormat,"AM");
   buildDateInputFormat();
   buildTimeInputFormat();
}

// This is the only instance to Fl_Date_Time_Format. 
static Fl_Date_Time_Format dateFormat;

bool Fl_Date_Time::is_leap_year(const short year) {
   return ((year&3) == 0 && year%100 != 0 || year%400 == 0);
}

void Fl_Date_Time::encode_date(double &dt,short year,short month,short day) {
   if (year == 0 && month == 0 && day == 0) {
      dt = 0;
      return;
   }
   if (month < 1 || month > 12) {
      dt = 0;
      return;
   }
   int yearKind = is_leap_year(year);
   if (day < 1 || day > _monthDays[yearKind][month-1]) {
      dt = 0;
      return;
   }      
      
   if (year <= 0 || year > 9999) {
      dt = 0;
      return;
   }

   day += _monthDaySums[yearKind][month-1];
   int i = year - 1;
   dt = i * 365 + i / 4 - i / 100 + i / 400 + day - DateDelta;
}

void Fl_Date_Time::encode_date(double &dt,const char *dat) {
   char     bdat[64];
   short    datePart[7], partNumber = 0;
   char     *ptr = NULL;
   int i;

   memset(datePart,0,sizeof(datePart));
   upperCase(bdat, dat, sizeof(bdat));

   if (strcmp(bdat,"TODAY") == 0) {
      dt = Date();        // Sets the current date
      return;
   } else {
      int len = strlen(bdat);
      for(i = 0; i <= len && partNumber < 7; i++) {
         char c = bdat[i];
         if (c == dateSeparator || c == timeSeparator || c == ' ' || c == 0) {
            if (c == timeSeparator && partNumber < 3) partNumber = 3;
            if (ptr) { // end of token
               bdat[i] = 0;
               datePart[partNumber] = (short)atoi(ptr);
               partNumber++;
               ptr = NULL;
            }
         } else {
            if (!isdigit(c)) {
               dt = 0;
               return;
            }
            if (!ptr) ptr = bdat + i;
         }
      }
      if (partNumber < 3) { // Not enough date parts
         dt = 0;
         return;
      }
      short month=0, day=0, year=0;
      for(i = 0; i < 3; i++)
      switch (datePartsOrder[i]) {
      case 'M': month = datePart[i]; break;
      case 'D': day   = datePart[i]; break;
      case 'Y': year  = datePart[i]; break;
      }
      if (year < 100) {
         if (year < 35) year = short(year + 2000);
         else           year = short(year + 1900);
      }
      double dd;
      encode_date(dd,year,month,day);
      if (partNumber > 3) { // Time part included into string
         double d;
         encode_time(d,datePart[3],datePart[4],datePart[5],datePart[6]);
         dd += d;
      }
      dt = dd;

   }
}

void Fl_Date_Time::encode_time(double& dt,short h,short m,short s,short ms) {
   dt = (h + ((m + (s + ms / 100.0) / 60.0) / 60.0)) / 24.0;
}

void Fl_Date_Time::encode_time(double& dt,const char *tim) {
   char  bdat[32];
   short timePart[4] = { 0, 0, 0, 0},
         partNumber = 0;
   char  *ptr = NULL;
   bool  afternoon = false;

   upperCase(bdat, tim, sizeof(bdat));

   if (!trimRight(bdat)) {
      dt = 0;
      return;
   }

   if (strcmp(bdat,"TIME") == 0) {
      dt = Time();        // Sets the current date
      return;
   } else {
      char *p = strstr(bdat,"AM");
      if (p) {
         *p = 0;
      } else {
         p = strstr(bdat,"PM");
         if (p) {
            *p = 0;
            afternoon = true;
         }
      }
      trimRight(bdat);
      int len = strlen(bdat);
      for (int i = 0; i <= len && partNumber < 4; i++) {
         char c = bdat[i];
         if (c == timeSeparator || c == ' ' || c == '.' || c == 0) {
            if (ptr) { // end of token
               bdat[i] = 0;
               timePart[partNumber] = (short) atoi(ptr);
               partNumber++;
               ptr = NULL;
            }
         } else {
            if (!isdigit(c)) {
               dt = 0;
               return;
            }
            if (!ptr) ptr = bdat + i;
         }
      }
      if (afternoon && timePart[0] != 12)
         timePart[0] = short(timePart[0] + 12);
      encode_time(dt,timePart[0],timePart[1],timePart[2],timePart[3]);
   }
}

const int S1 = 24 * 60 * 60; // seconds in 1 day

void Fl_Date_Time::decode_time(const double dt,short& h,short& m,short& s,short& ms) {
   double t = dt - (int) dt;

   int secs = int(t * S1 + 0.5);
   h = short(secs / 3600);
   secs = secs % 3600;
   m = short(secs / 60);
   secs = secs % 60;
   s = short(secs);
   ms = 0;
}

const int D1 = 365;              // Days in 1 year
const int D4 = D1 * 4 + 1;       // Days in 4 years
const int D100 = D4 * 25 - 1;    // Days in 100 years
const int D400 = D100 * 4 + 1;   // Days in 400 years

static void DivMod(int op1, int op2, int& div, int& mod) {
   div = op1 / op2;
   mod = op1 % op2;
}

void Fl_Date_Time::decode_date(const double dat, short& year, short& month, short& day) {
   int Y, M, D, I;
   int T = (int) dat + DateDelta;

   T--;
   Y = 1;
   while (T >= D400) {
      T -= D400;
      Y += 400;
   }

   DivMod(T, D100, I, D);
   if (I == 4) {
      I--;
      D += D100;
   }

   Y += I * 100;
   DivMod(D, D4, I, D);
   Y += I * 4;
   DivMod(D, D1, I, D);
   if (I == 4) {
      I--;
      D += D1;
   }
   Y += I;
   year = Y;
   //year  = short (Y + 1900);

   int leapYear = is_leap_year(short(year));
   for (M = 0;;M++) {
      I = _monthDays[leapYear][M];
      if (D < I)
         break;
      D -= I;
   }

   month = short (M + 1);
   day   = short (D + 1);
}

//----------------------------------------------------------------
// Constructors
//----------------------------------------------------------------
Fl_Date_Time::Fl_Date_Time (short year,short month,short day,short hour,short minute,short second) {
   double t;
   int i;
   // NLS stuff
   for (i=0; i<7;i++) dayname[i]=_(dayname[i]);
   for (i=0; i<12;i++) mname[i]=_(mname[i]);   
  
   encode_date(m_dateTime,year,month,day);
   encode_time(t,hour,minute,second);
   m_dateTime += t;
}

Fl_Date_Time::Fl_Date_Time (const char * dat) {

   int i;
   // NLS stuff
   for (i=0; i<7;i++) dayname[i]=_(dayname[i]);
   for (i=0; i<12;i++) mname[i]=_(mname[i]);   

   Fl_String  s1( Fl_String(dat).trim() );
   Fl_String  s2;

   if (!*dat) {
      m_dateTime = 0;
      return;
   }
   int      p = s1.pos(" ");
   if (p >= 0) {
      s2 = s1.sub_str(p+1,20).trim();
      s1[p] = 0;
   }
   if ( strchr(s1.c_str(),dateSeparator) ) {
      encode_date(m_dateTime, s1.c_str());
      if ( strchr(s2.c_str(),timeSeparator) ) {
         double dt;
         encode_time(dt, s2.c_str());
         m_dateTime += dt;
      }
   }
   else  encode_time(m_dateTime, s1.c_str());
}

Fl_Date_Time::Fl_Date_Time (const Fl_Date_Time &dt) {
   int i;
   // NLS stuff
   for (i=0; i<7;i++) dayname[i]=_(dayname[i]);
   for (i=0; i<12;i++) mname[i]=_(mname[i]);   

   m_dateTime = dt.m_dateTime;
}

Fl_Date_Time::Fl_Date_Time (const double dt) {
   int i;
   // NLS stuff
   for (i=0; i<7;i++) dayname[i]=_(dayname[i]);
   for (i=0; i<12;i++) mname[i]=_(mname[i]);   

   m_dateTime = dt;
}
//----------------------------------------------------------------
// Assignments
//----------------------------------------------------------------
void Fl_Date_Time::operator = (const Fl_Date_Time &dt) {
   m_dateTime = dt.m_dateTime;
}

void Fl_Date_Time::operator = (const char * dat) {
   encode_date(m_dateTime, dat);
}

//----------------------------------------------------------------
// Conversion operations
//----------------------------------------------------------------
// Fl_Date_Time::operator int (void) { return (int) dateTime; }

Fl_Date_Time::operator double (void) const { return m_dateTime; }

//----------------------------------------------------------------
// Date Arithmetic
//----------------------------------------------------------------
Fl_Date_Time Fl_Date_Time::operator + (int i) {
    return Fl_Date_Time(m_dateTime + i);
}

Fl_Date_Time Fl_Date_Time::operator - (int i) {
    return Fl_Date_Time(m_dateTime - i);
}

Fl_Date_Time Fl_Date_Time::operator + (Fl_Date_Time& dt) {
    return Fl_Date_Time(m_dateTime + dt.m_dateTime);
}

Fl_Date_Time Fl_Date_Time::operator - (Fl_Date_Time& dt) {
    return Fl_Date_Time(m_dateTime - dt.m_dateTime);
}

Fl_Date_Time& Fl_Date_Time::operator += (int i) {
    m_dateTime += i;
    return *this;
}

Fl_Date_Time& Fl_Date_Time::operator -= (int i) {
    m_dateTime -= i;
    return *this;
}

Fl_Date_Time& Fl_Date_Time::operator += (Fl_Date_Time& dt) {
    m_dateTime += dt.m_dateTime;
    return *this;
}

Fl_Date_Time& Fl_Date_Time::operator -= (Fl_Date_Time& dt) {
    m_dateTime -= dt.m_dateTime;
    return *this;
}

Fl_Date_Time& Fl_Date_Time::operator ++() {
    m_dateTime += 1;
    return *this;
}

Fl_Date_Time &Fl_Date_Time::operator ++(int) {
    m_dateTime += 1;
    return *this;
}

Fl_Date_Time &Fl_Date_Time::operator --() {
    m_dateTime -= 1;
    return *this;
}

Fl_Date_Time &Fl_Date_Time::operator --(int) {
    m_dateTime -= 1;
    return *this;
}

//----------------------------------------------------------------
// Format routine
//----------------------------------------------------------------
void Fl_Date_Time::format_date (char *str) const {
   char *ptr = str;
   short month, day, year;

   if (m_dateTime == 0) {
      *str = 0;
      return;
   }
   decode_date(m_dateTime,year,month,day);
   for (int i = 0; i < 3; i++) {
      switch (datePartsOrder[i]) {
      case 'M':   sprintf(ptr,"%02i%c",month,dateSeparator);
                  break;
      case 'D':   sprintf(ptr,"%02i%c",day,dateSeparator);
                  break;
      case 'Y':   sprintf(ptr,"%04i%c",year,dateSeparator);
                  break;
      }
      ptr += strlen(ptr);
   }
   *(ptr-1) = 0;
}

void Fl_Date_Time::format_time (char *str,bool ampm) const {
   short h,m,s,ms;

   if (m_dateTime == 0) {
      *str = 0;
      return;
   }
   decode_time(m_dateTime,h,m,s,ms);
   if (ampm) {
      char format[] = "%02i%c%02iAM";
      if (h > 11) format[10] = 'P';
      sprintf(str,format,h%12,timeSeparator,m);
   }
   else  sprintf(str,"%02i%c%02i%c%02i",h,timeSeparator,m,timeSeparator,s);
}
//----------------------------------------------------------------
//  Miscellaneous Routines
//----------------------------------------------------------------
short Fl_Date_Time::day_of_year( void ) const {
    Fl_Date_Time temp( 1, 1, year() );

    return (short) (m_dateTime - temp.m_dateTime);
}

Fl_Date_Time Fl_Date_Time::convert(const long tt) {
   struct tm *t = localtime((time_t*)&tt);
   double dat,tim;
   encode_date(dat,short(t->tm_year+1900),short(t->tm_mon+1),short(t->tm_mday));
   encode_time(tim,short(t->tm_hour),short(t->tm_min),short(t->tm_sec),short(0));
   return dat + tim;
}

#ifdef _WIN32
#define FILETIME_1970 0x019db1ded53e8000
const BYTE DWLEN = sizeof(DWORD) * 8;
/* Code ripped from some xntp implementation on http://src.openresources.com. */
long get_usec()
{
  FILETIME ft;
  __int64 msec;
  GetSystemTimeAsFileTime(&ft);
  msec = (__int64) ft.dwHighDateTime << DWLEN | ft.dwLowDateTime;
  msec = (msec - FILETIME_1970) / 10;
  return (long) (msec % 1000000);
}
#endif

// Get the current system time
Fl_Date_Time Fl_Date_Time::System() {
   time_t tt;
   time(&tt);
   double datetime = convert(tt);
#ifndef _WIN32
   timeval tp;
   gettimeofday(&tp,0L);
   double mcsec = tp.tv_usec / 1000000.0 / (3600 * 24);
#else
   // This works now!
   double mcsec = get_usec() / 1000000.0 / (3600 * 24);
#endif
   return datetime + mcsec;
}

// Get the current system time with optional synchronization offset
Fl_Date_Time Fl_Date_Time::Now() {
   return (double)Fl_Date_Time::System() + Fl_Date_Time::dateTimeOffset;
}

// Set the synchronization offset
void Fl_Date_Time::Now(Fl_Date_Time dt) {
   Fl_Date_Time::dateTimeOffset = (double)dt - (double)Fl_Date_Time::System(); 
}

Fl_Date_Time Fl_Date_Time::Date() {
   double dat = Now();
   return double(int(dat));
}

Fl_Date_Time Fl_Date_Time::Time() {
   double dat = Now();
   return dat - int(dat);
}

short Fl_Date_Time::days_in_month() const {
   short y, m, d;
   decode_date(m_dateTime,y,m,d);
   return _monthDays[is_leap_year(y)][m-1];
}

unsigned Fl_Date_Time::date() const {
   return unsigned(m_dateTime);
}

short Fl_Date_Time::day() const {
   short y, m, d;
   decode_date(m_dateTime,y,m,d);
   return d;
}

short Fl_Date_Time::month() const {
   short y, m, d;
   decode_date(m_dateTime,y,m,d);
   return m;
}

short Fl_Date_Time::year() const {
   short y, m, d;
   decode_date(m_dateTime,y,m,d);
   return y;
}

short Fl_Date_Time::day_of_week (void) const {
   return short((int(m_dateTime) - 1) % 7 + 1);
}

Fl_String Fl_Date_Time::day_name (void) const {
   return dayname[day_of_week() - 1];
}

Fl_String Fl_Date_Time::month_name() const {
   return mname[month()-1];
}

Fl_String Fl_Date_Time::date_string() const {	
   char  buffer[32];
   format_date(buffer);
   return Fl_String(buffer);
}

Fl_String Fl_Date_Time::time_string() const {
   char  buffer[32];
   format_time(buffer,!time24Mode);
   return Fl_String(buffer);
}

void Fl_Date_Time::decode_date(short *y,short *m,short *d) const {
   decode_date(m_dateTime,*y,*m,*d);
}

void Fl_Date_Time::decode_time(short *h,short *m,short *s,short *ms) const {
   decode_time(m_dateTime,*h,*m,*s,*ms);
}

