/***************************************************************************
    begin       : Sun Nov 23 2003
    copyright   : (C) 2019 by Martin Preuss
    email       : martin@libchipcard.de

 ***************************************************************************
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Lesser General Public            *
 *   License as published by the Free Software Foundation; either          *
 *   version 2.1 of the License, or (at your option) any later version.    *
 *                                                                         *
 *   This library is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   Lesser General Public License for more details.                       *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with this library; if not, write to the Free Software   *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston,                 *
 *   MA  02111-1307  USA                                                   *
 *                                                                         *
 ***************************************************************************/


#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#define DISABLE_DEBUGLOG


#include "gwentime_p.h"
#include <gwenhywfar/gwentime.h>
#include <gwenhywfar/debug.h>

#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>


GWEN_LIST_FUNCTIONS(GWEN_TIME_TMPLCHAR, GWEN_TimeTmplChar)


/* ------------------------------------------------------------------------------------------------
 * forward declarations
 * ------------------------------------------------------------------------------------------------
 */


static GWEN_TIME *GWEN_Time__fromString(const char *s, const char *tmpl, int inUtc);


/* ------------------------------------------------------------------------------------------------
 * implementations
 * ------------------------------------------------------------------------------------------------
 */



GWEN_TIME *GWEN_CurrentTime(void)
{
  GWEN_TIME *t;

  GWEN_NEW_OBJECT(GWEN_TIME, t);
  if (GWEN_Time__GetCurrentTime(t)) {
    DBG_ERROR(GWEN_LOGDOMAIN, "Could not get current time");
    GWEN_Time_free(t);
    return 0;
  }
  return t;
}



GWEN_TIME *GWEN_Time_fromSeconds(uint32_t secs)
{
  GWEN_TIME *t;

  GWEN_NEW_OBJECT(GWEN_TIME, t);
  t->secs=secs;
  return t;
}



int GWEN_Time_AddSeconds(GWEN_TIME *ti,
                         uint32_t secs)
{
  uint32_t i;

  assert(ti);
  i=ti->secs+secs;
  if (i<ti->secs) {
    DBG_INFO(GWEN_LOGDOMAIN,
             "Overflow when adding %u seconds", secs);
    return GWEN_ERROR_INVALID;
  }
  ti->secs=i;
  return 0;
}



int GWEN_Time_SubSeconds(GWEN_TIME *ti,
                         uint32_t secs)
{
  assert(ti);

  if (ti->secs<secs) {
    DBG_INFO(GWEN_LOGDOMAIN,
             "Underflow when subtracting %u seconds",
             secs);
    return GWEN_ERROR_INVALID;
  }
  ti->secs-=secs;
  return 0;
}


void GWEN_Time__SetSecsAndMSecs(GWEN_TIME *ti,
                                uint32_t secs,
                                uint32_t msecs)
{
  assert(ti);
  ti->secs=secs;
  ti->msecs=msecs;
}



int GWEN_Time_toDb(const GWEN_TIME *t, GWEN_DB_NODE *db)
{
  GWEN_DB_NODE *dbT;
  int i1, i2, i3;

  assert(t);
  assert(db);
  dbT=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_DEFAULT, "date");
  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "inUtc", 1);

  assert(dbT);
  if (GWEN_Time_GetBrokenDownUtcDate(t, &i1, &i2, &i3)) {
    DBG_INFO(GWEN_LOGDOMAIN, "Could not break down date");
    return -1;
  }
  GWEN_DB_SetIntValue(dbT, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "day", i1);
  GWEN_DB_SetIntValue(dbT, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "month", i2+1);
  GWEN_DB_SetIntValue(dbT, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "year", i3);

  dbT=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_DEFAULT, "time");
  assert(dbT);
  if (GWEN_Time_GetBrokenDownUtcTime(t, &i1, &i2, &i3)) {
    DBG_INFO(GWEN_LOGDOMAIN, "Could not break down time");
    return -1;
  }
  GWEN_DB_SetIntValue(dbT, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "hour", i1);
  GWEN_DB_SetIntValue(dbT, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "min", i2);
  GWEN_DB_SetIntValue(dbT, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "sec", i3);

  return 0;
}



GWEN_TIME *GWEN_Time_fromDb(GWEN_DB_NODE *db)
{
  GWEN_TIME *t;
  GWEN_DB_NODE *dbT;
  int day, month, year;
  int hour, min, sec;
  int inUtc;

  day=month=year=0;
  hour=min=sec=0;

  inUtc=GWEN_DB_GetIntValue(db, "inUtc", 0, 0);
  dbT=GWEN_DB_GetGroup(db, GWEN_PATH_FLAGS_NAMEMUSTEXIST, "date");
  if (dbT) {
    day=GWEN_DB_GetIntValue(dbT, "day", 0, 0);
    month=GWEN_DB_GetIntValue(dbT, "month", 0, 1)-1;
    year=GWEN_DB_GetIntValue(dbT, "year", 0, 0);
    if (!day || !year) {
      DBG_INFO(GWEN_LOGDOMAIN, "Bad date in DB");
      return 0;
    }
  }

  dbT=GWEN_DB_GetGroup(db, GWEN_PATH_FLAGS_NAMEMUSTEXIST, "time");
  if (dbT) {
    hour=GWEN_DB_GetIntValue(dbT, "hour", 0, 0);
    min=GWEN_DB_GetIntValue(dbT, "min", 0, 0);
    sec=GWEN_DB_GetIntValue(dbT, "sec", 0, 0);
  }

  DBG_VERBOUS(GWEN_LOGDOMAIN,
              "Creating time from this: %04d/%02d/%02d - %02d:%02d:%02d (%d)",
              year, month, day, hour, min, sec, inUtc);
  t=GWEN_Time_new(year, month, day, hour, min, sec, inUtc);
  if (!t) {
    DBG_INFO(GWEN_LOGDOMAIN, "Bad date/time");
    return 0;
  }

  return t;
}



GWEN_TIME *GWEN_Time__fromString(const char *s, const char *tmpl, int inUtc)
{
  int year, month, day;
  int hour, min, sec;
  const char *p;
  const char *t;
  GWEN_TIME *gwt;

  assert(s);
  assert(tmpl);
  year=month=day=0;
  hour=min=sec=0;

  p=s;
  t=tmpl;
  while (*t && *p) {
    int i;

    if (*t=='*') {
      t++;
      if (!*t) {
        DBG_ERROR(GWEN_LOGDOMAIN, "Bad pattern: Must not end with \"*\"");
        return 0;
      }
      i=0;
      while (*p) {
        if (!isdigit((int)*p))
          break;
        if (*p==*t)
          break;
        i*=10;
        i+=(*p)-'0';
        p++;
      } /* while */
    }
    else {
      if (isdigit((int)*p))
        i=(*p)-'0';
      else
        i=-1;
      p++;
    }

    if (i==-1 && strchr("YMDhms", *t)!=NULL) {
      DBG_INFO(GWEN_LOGDOMAIN,
               "No more digits at [%s], continuing", t);
      p--;
    }
    else {
      switch (*t) {
      case 'Y':
        if (i==-1) {
          DBG_INFO(GWEN_LOGDOMAIN, "here");
          return 0;
        }
        year*=10;
        year+=i;
        break;
      case 'M':
        if (i==-1) {
          DBG_INFO(GWEN_LOGDOMAIN, "here");
          return 0;
        }
        month*=10;
        month+=i;
        break;
      case 'D':
        if (i==-1) {
          DBG_INFO(GWEN_LOGDOMAIN, "here");
          return 0;
        }
        day*=10;
        day+=i;
        break;
      case 'h':
        if (i==-1) {
          DBG_INFO(GWEN_LOGDOMAIN, "here");
          return 0;
        }
        hour*=10;
        hour+=i;
        break;
      case 'm':
        if (i==-1) {
          DBG_INFO(GWEN_LOGDOMAIN, "here");
          return 0;
        }
        min*=10;
        min+=i;
        break;
      case 's':
        if (i==-1) {
          DBG_INFO(GWEN_LOGDOMAIN, "here");
          return 0;
        }
        sec*=10;
        sec+=i;
        break;
      default:
        DBG_VERBOUS(GWEN_LOGDOMAIN,
                    "Unknown character in template, will skip in both strings");
        break;
      }
    }
    t++;
  } /* while */

  if (year<100)
    year+=2000;
  if (day==0)
    day=1;

  DBG_DEBUG(GWEN_LOGDOMAIN,
            "Got this date/time: %04d/%02d/%02d, %02d:%02d:%02d",
            year, month-1, day, hour, min, sec);

  /* get time in local time */
  gwt=GWEN_Time_new(year, month-1, day, hour, min, sec, inUtc);
  if (!gwt) {
    DBG_INFO(GWEN_LOGDOMAIN, "here");
    return 0;
  }
  return gwt;
}



GWEN_TIME *GWEN_Time_fromString(const char *s, const char *tmpl)
{
  return GWEN_Time__fromString(s, tmpl, 0);
}



GWEN_TIME *GWEN_Time_fromUtcString(const char *s, const char *tmpl)
{
  return GWEN_Time__fromString(s, tmpl, 1);
}



GWEN_TIME *GWEN_Time_new(int year,
                         int month,
                         int day,
                         int hour,
                         int min,
                         int sec,
                         int inUtc)
{
  uint32_t s;

  if (inUtc)
    s=GWEN_Time__mktimeUtc(year, month, day, hour, min, sec);
  else {
    struct tm ti;
    struct tm *tp;
    time_t tt;

    tt=time(0);
    tp=localtime(&tt);
    assert(tp);
    memmove(&ti, tp, sizeof(ti));
    ti.tm_sec=sec;
    ti.tm_min=min;
    ti.tm_hour=hour;
    if (year<100) {
      if (year<72)
        year+=2000;
      year+=1900;
    }
    ti.tm_year=year-1900;
    ti.tm_mon=month;
    ti.tm_mday=day;
    ti.tm_yday=0;
    ti.tm_wday=0;
    tt=mktime(&ti);
    assert(tt!=(time_t)-1);
    s=(uint32_t)tt;
  }
  return GWEN_Time_fromSeconds(s);
}



uint32_t GWEN_Time__mktimeUtc(int year,
                              int month,
                              int day,
                              int hour,
                              int min,
                              int sec)
{
  uint32_t result;
  int i;
  int isLeap;
  const uint32_t hoursecs=60*60;
  const uint32_t daysecs=24*hoursecs;
  const uint32_t yearsecs=365*daysecs;
  const uint32_t monthDays[12]= {
    31, 28, 31, 30,
    31, 30, 31, 31,
    30, 31, 30, 31
  };

  result=(year-1970)*yearsecs;

  for (i=1970; i<year; i++)
    if ((((i % 4)==0) &&
         ((i % 100)!=0)) ||
        ((i % 400)==0))
      result+=daysecs;

  isLeap=((((year % 4)==0) &&
           ((year % 100)!=0)) ||
          ((year % 400)==0));

  for (i=0; i<month; i++)
    if (isLeap && i==1)
      result+=29*daysecs;
    else
      result+=monthDays[i]*daysecs;

  result+=(day-1)*daysecs;
  result+=(hour*hoursecs);
  result+=min*60;
  result+=sec;

  return result;
}



GWEN_TIME *GWEN_Time_dup(const GWEN_TIME *t)
{
  GWEN_TIME *newT;

  assert(t);
  GWEN_NEW_OBJECT(GWEN_TIME, newT);
  newT->secs=t->secs;
  newT->msecs=t->msecs;
  return newT;
}



void GWEN_Time_free(GWEN_TIME *t)
{
  if (t) {
    GWEN_FREE_OBJECT(t);
  }
}



double GWEN_Time_Diff(const GWEN_TIME *t1, const GWEN_TIME *t0)
{
  double d;

  assert(t1);
  assert(t0);

  d=1000.0*((double)(t1->secs)-(double)(t0->secs));
  d+=(double)((double)(t1->msecs)-(double)(t0->msecs));

  return d;
}



double GWEN_Time_DiffSeconds(const GWEN_TIME *t1, const GWEN_TIME *t0)
{
  double d;

  assert(t1);
  assert(t0);

  d=(double)(t1->secs)-(double)(t0->secs);
  d+=((double)((double)(t1->msecs)-(double)(t0->msecs)))/1000.0;

  return d;
}



int GWEN_Time_Compare(const GWEN_TIME *t1, const GWEN_TIME *t0)
{
  if (t1 && t0) {
    if (t1->secs<t0->secs)
      return -1;
    else if (t1->secs>t0->secs)
      return 1;
    else {
      if (t1->msecs<t0->msecs)
        return -1;
      else if (t1->msecs>t0->msecs)
        return 1;
      else
        return 0;
    }
  }
  else if (t1)
    return 1;
  else if (t0)
    return -1;

  return 0;
}



double GWEN_Time_Milliseconds(const GWEN_TIME *t)
{
  assert(t);
  return (double)((t->secs*1000)+(t->msecs));
}



uint32_t GWEN_Time_Seconds(const GWEN_TIME *t)
{
  assert(t);
  return t->secs;
}



int GWEN_Time_GetBrokenDownTime(const GWEN_TIME *t,
                                int *hours,
                                int *mins,
                                int *secs)
{
  struct tm *tb;
  time_t tt;

  assert(t);
  tt=t->secs;
  tb=localtime(&tt);
  if (!tb) {
    DBG_ERROR(GWEN_LOGDOMAIN, "localtime(): %s", strerror(errno));
    return -1;
  }
  *hours=tb->tm_hour;
  *mins=tb->tm_min;
  *secs=tb->tm_sec;
  return 0;
}



int GWEN_Time_GetBrokenDownUtcTime(const GWEN_TIME *t,
                                   int *hours,
                                   int *mins,
                                   int *secs)
{
  struct tm *tb;
  time_t tt;

  assert(t);
  tt=t->secs;
  tb=gmtime(&tt);
  if (!tb) {
    DBG_ERROR(GWEN_LOGDOMAIN, "gmtime(): %s", strerror(errno));
    return -1;
  }
  *hours=tb->tm_hour;
  *mins=tb->tm_min;
  *secs=tb->tm_sec;
  return 0;
}



int GWEN_Time_GetBrokenDownDate(const GWEN_TIME *t,
                                int *days,
                                int *month,
                                int *year)
{
  struct tm *tb;
  time_t tt;

  assert(t);
  tt=t->secs;
  tb=localtime(&tt);
  if (!tb) {
    DBG_ERROR(GWEN_LOGDOMAIN, "localtime(): %s", strerror(errno));
    return -1;
  }
  *days=tb->tm_mday;
  *month=tb->tm_mon;
  *year=tb->tm_year+1900;
  return 0;
}



int GWEN_Time_GetBrokenDownUtcDate(const GWEN_TIME *t,
                                   int *days,
                                   int *month,
                                   int *year)
{
  struct tm *tb;
  time_t tt;

  assert(t);
  tt=t->secs;
  tb=gmtime(&tt);
  if (!tb) {
    DBG_ERROR(GWEN_LOGDOMAIN, "gmtime(): %s", strerror(errno));
    return -1;
  }
  *days=tb->tm_mday;
  *month=tb->tm_mon;
  *year=tb->tm_year+1900;
  return 0;
}



#if 0
/* TODO: compiler says "function returns an aggregate" */
struct tm GWEN_Time_toTm(const GWEN_TIME *t)
{
  struct tm *tb;
  time_t tt;

  assert(t);
  tt=t->secs;
  tb=localtime(&tt);
  return *tb;
}
#endif


time_t GWEN_Time_toTime_t(const GWEN_TIME *t)
{
  assert(t);
  return t->secs;
}




GWEN_TIME_TMPLCHAR *GWEN_TimeTmplChar_new(char c)
{
  GWEN_TIME_TMPLCHAR *e;

  GWEN_NEW_OBJECT(GWEN_TIME_TMPLCHAR, e);
  GWEN_LIST_INIT(GWEN_TIME_TMPLCHAR, e);
  e->character=c;
  return e;
}



void GWEN_TimeTmplChar_free(GWEN_TIME_TMPLCHAR *e)
{
  if (e) {
    free(e->content);
    GWEN_LIST_FINI(GWEN_TIME_TMPLCHAR, e);
    GWEN_FREE_OBJECT(e);
  }
}


GWEN_TIME_TMPLCHAR *GWEN_Time__findTmplChar(GWEN_TIME_TMPLCHAR_LIST *ll,
                                            char c)
{
  GWEN_TIME_TMPLCHAR *e;

  e=GWEN_TimeTmplChar_List_First(ll);
  while (e) {
    if (e->character==c)
      break;
    e=GWEN_TimeTmplChar_List_Next(e);
  }

  return e;
}




void GWEN_Time__sampleTmplChars(GWEN_UNUSED const GWEN_TIME *t, const char *tmpl,
                                GWEN_UNUSED GWEN_BUFFER *buf,
                                GWEN_TIME_TMPLCHAR_LIST *ll)
{
  const char *s;

  s=tmpl;
  while (*s) {
    if (strchr("YMDhms", *s)) {
      GWEN_TIME_TMPLCHAR *e;

      e=GWEN_Time__findTmplChar(ll, *s);
      if (!e) {
        /* new entry, create it */
        e=GWEN_TimeTmplChar_new(*s);
        GWEN_TimeTmplChar_List_Add(e, ll);
      }
      assert(e);
      e->count++;
    }
    else {
      DBG_DEBUG(GWEN_LOGDOMAIN, "Unknown character in template (%02x)",
                *s);
    }
    s++;
  }
}



void GWEN_Time__fillTmplChars(const GWEN_TIME *t,
                              GWEN_TIME_TMPLCHAR_LIST *ll,
                              int useUtc)
{
  GWEN_TIME_TMPLCHAR *e;
  int year, month, day, hour, minute, second;

  if (useUtc) {
    GWEN_Time_GetBrokenDownUtcDate(t, &day, &month, &year);
    GWEN_Time_GetBrokenDownUtcTime(t, &hour, &minute, &second);
  }
  else {
    GWEN_Time_GetBrokenDownDate(t, &day, &month, &year);
    GWEN_Time_GetBrokenDownTime(t, &hour, &minute, &second);
  }

  e=GWEN_TimeTmplChar_List_First(ll);
  while (e) {
    int v;
    char buffer[32];

    switch (e->character) {
    case 'Y':
      v=year;
      break;
    case 'M':
      v=month+1;
      break;
    case 'D':
      v=day;
      break;
    case 'h':
      v=hour;
      break;
    case 'm':
      v=minute;
      break;
    case 's':
      v=second;
      break;
    default:
      v=-1;
      break;
    }
    if (v==-1) {
      DBG_ERROR(GWEN_LOGDOMAIN, "Unknown character, should not happen here");
      abort();
    }
    buffer[0]=0;
    snprintf(buffer, sizeof(buffer)-1, "%0*d", GWEN_TIME_TMPL_MAX_COUNT, v);
    buffer[sizeof(buffer)-1]=0;
    e->content=strdup(buffer);
    e->nextChar=strlen(e->content)-(e->count);
    e=GWEN_TimeTmplChar_List_Next(e);
  }
}




int GWEN_Time__toString(const GWEN_TIME *t, const char *tmpl,
                        GWEN_BUFFER *buf, int useUtc)
{
  GWEN_TIME_TMPLCHAR_LIST *ll;
  const char *s;

  ll=GWEN_TimeTmplChar_List_new();
  GWEN_Time__sampleTmplChars(t, tmpl, buf, ll);
  GWEN_Time__fillTmplChars(t, ll, useUtc);

  s=tmpl;
  while (*s) {
    if (strchr("YMDhms", *s)) {
      GWEN_TIME_TMPLCHAR *e;
      char c;

      e=GWEN_Time__findTmplChar(ll, *s);
      assert(e);
      assert(e->content);
      if (s[1]=='*') {
        /* append full string */
        GWEN_Buffer_AppendString(buf, e->content);
        /* skip asterisk */
        s++;
      }
      else {
        c=e->content[e->nextChar++];
        assert(c);
        GWEN_Buffer_AppendByte(buf, c);
      }
    }
    else
      GWEN_Buffer_AppendByte(buf, *s);
    s++;
  }
  GWEN_TimeTmplChar_List_free(ll);
  return 0;
}



int GWEN_Time_toString(const GWEN_TIME *t, const char *tmpl,
                       GWEN_BUFFER *buf)
{
  return GWEN_Time__toString(t, tmpl, buf, 0);
}



int GWEN_Time_toUtcString(const GWEN_TIME *t, const char *tmpl,
                          GWEN_BUFFER *buf)
{
  return GWEN_Time__toString(t, tmpl, buf, 1);
}










