/*
** The original version of this file is in the public domain, so clarified as of
** 1996-06-05 by Arthur David Olson.
*/

/*
** Leap second handling from Bradley White.
** POSIX-style TZ environment variable handling from Guy Harris.
** Updated to use int64_t's instead of system-dependent definitions of int64_t
** and struct tm by A. Jesse Jiryu Davis for MongoDB, Inc.
*/

#include <bson/bson-timegm-private.h>

#include <bson/compat.h>
#include <bson/macros.h>

#include <errno.h>
#include <stdint.h> /* for INT64_MAX and INT64_MIN */
#include <string.h>

#if 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
#define ATTRIBUTE_PURE __attribute__((__pure__))
#else
#define ATTRIBUTE_PURE /* empty */
#endif

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshift-negative-value"
#endif

/* The minimum and maximum finite time values.  */
static int64_t const time_t_min = INT64_MIN;
static int64_t const time_t_max = INT64_MAX;

#ifdef __clang__
#pragma clang diagnostic pop
#pragma clang diagnostic pop
#endif

#ifndef TZ_MAX_TIMES
#define TZ_MAX_TIMES 2000
#endif /* !defined TZ_MAX_TIMES */

#ifndef TZ_MAX_TYPES
/* This must be at least 17 for Europe/Samara and Europe/Vilnius.  */
#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
#endif                   /* !defined TZ_MAX_TYPES */

#ifndef TZ_MAX_CHARS
#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */
                        /* (limited by what unsigned chars can hold) */
#endif                  /* !defined TZ_MAX_CHARS */

#ifndef TZ_MAX_LEAPS
#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */
#endif                  /* !defined TZ_MAX_LEAPS */

#define SECSPERMIN 60
#define MINSPERHOUR 60
#define HOURSPERDAY 24
#define DAYSPERWEEK 7
#define DAYSPERNYEAR 365
#define DAYSPERLYEAR 366
#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
#define SECSPERDAY ((int_fast32_t)SECSPERHOUR * HOURSPERDAY)
#define MONSPERYEAR 12

#define TM_YEAR_BASE 1900

#define EPOCH_YEAR 1970
#define EPOCH_WDAY 4 /* TM_THURSDAY */

#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))

struct ttinfo {            /* time type information */
   int_fast32_t tt_gmtoff; /* UT offset in seconds */
   int tt_isdst;           /* used to set tm_isdst */
   int tt_abbrind;         /* abbreviation list index */
   int tt_ttisstd;         /* true if transition is std time */
   int tt_ttisgmt;         /* true if transition is UT */
};

struct lsinfo {          /* leap second information */
   int64_t ls_trans;     /* transition time */
   int_fast64_t ls_corr; /* correction to apply */
};

#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b))

#ifdef TZNAME_MAX
#define MY_TZNAME_MAX TZNAME_MAX
#endif /* defined TZNAME_MAX */
#ifndef TZNAME_MAX
#define MY_TZNAME_MAX 255
#endif /* !defined TZNAME_MAX */

struct state {
   int leapcnt;
   int timecnt;
   int typecnt;
   int charcnt;
   int goback;
   int goahead;
   int64_t ats[TZ_MAX_TIMES];
   unsigned char types[TZ_MAX_TIMES];
   struct ttinfo ttis[TZ_MAX_TYPES];
   char chars[BIGGEST(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1)))];
   struct lsinfo lsis[TZ_MAX_LEAPS];
   int defaulttype; /* for early times or if no transitions */
};

struct rule {
   int r_type;          /* type of rule--see below */
   int r_day;           /* day number of rule */
   int r_week;          /* week number of rule */
   int r_mon;           /* month number of rule */
   int_fast32_t r_time; /* transition time of rule */
};

/*
** Prototypes for static functions.
*/

static void
gmtload(struct state *const sp);
static struct bson_tm *
gmtsub(const int64_t *const timep, const int_fast32_t offset, struct bson_tm *const tmp);
static int64_t
increment_overflow(int64_t *const ip, int64_t j);
static int64_t
leaps_thru_end_of(const int64_t y) ATTRIBUTE_PURE;
static int64_t
increment_overflow32(int_fast32_t *const lp, int64_t const m);
static int64_t
normalize_overflow32(int_fast32_t *const tensptr, int64_t *const unitsptr, const int64_t base);
static int64_t
normalize_overflow(int64_t *const tensptr, int64_t *const unitsptr, const int64_t base);
static int64_t
time1(struct bson_tm *const tmp,
      struct bson_tm *(*const funcp)(const int64_t *, int_fast32_t, struct bson_tm *),
      const int_fast32_t offset);
static int64_t
time2(struct bson_tm *const tmp,
      struct bson_tm *(*const funcp)(const int64_t *, int_fast32_t, struct bson_tm *),
      const int_fast32_t offset,
      int64_t *const okayp);
static int64_t
time2sub(struct bson_tm *const tmp,
         struct bson_tm *(*const funcp)(const int64_t *, int_fast32_t, struct bson_tm *),
         const int_fast32_t offset,
         int64_t *const okayp,
         const int64_t do_norm_secs);
static struct bson_tm *
timesub(const int64_t *const timep, const int_fast32_t offset, const struct state *const sp, struct bson_tm *const tmp);
static int64_t
tmcomp(const struct bson_tm *const atmp, const struct bson_tm *const btmp);

static struct state gmtmem;
#define gmtptr (&gmtmem)

static int gmt_is_set;

static const int mon_lengths[2][MONSPERYEAR] = {{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
                                                {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};

static const int year_lengths[2] = {DAYSPERNYEAR, DAYSPERLYEAR};

static void
gmtload(struct state *const sp)
{
   memset(sp, 0, sizeof(struct state));
   sp->typecnt = 1;
   sp->charcnt = 4;
   sp->chars[0] = 'G';
   sp->chars[1] = 'M';
   sp->chars[2] = 'T';
}

/*
** gmtsub is to gmtime as localsub is to localtime.
*/

static struct bson_tm *
gmtsub(const int64_t *const timep, const int_fast32_t offset, struct bson_tm *const tmp)
{
   struct bson_tm *result;

   if (!gmt_is_set) {
      gmt_is_set = true;
      gmtload(gmtptr);
   }
   result = timesub(timep, offset, gmtptr, tmp);
   return result;
}

/*
** Return the number of leap years through the end of the given year
** where, to make the math easy, the answer for year zero is defined as zero.
*/

static int64_t
leaps_thru_end_of(const int64_t y)
{
   return (y >= 0) ? (y / 4 - y / 100 + y / 400) : -(leaps_thru_end_of(-(y + 1)) + 1);
}

static struct bson_tm *
timesub(const int64_t *const timep, const int_fast32_t offset, const struct state *const sp, struct bson_tm *const tmp)
{
   const struct lsinfo *lp;
   int64_t tdays;
   int64_t idays; /* unsigned would be so 2003 */
   int_fast64_t rem;
   int64_t y;
   const int (*ip)[MONSPERYEAR];
   int_fast64_t corr;
   int64_t hit;
   int64_t i;

   corr = 0;
   hit = 0;
   i = (sp == NULL) ? 0 : sp->leapcnt;
   while (--i >= 0) {
      lp = &sp->lsis[i];
      if (*timep >= lp->ls_trans) {
         if (*timep == lp->ls_trans) {
            hit = ((i == 0 && lp->ls_corr > 0) || lp->ls_corr > sp->lsis[i - 1].ls_corr);
            if (hit)
               while (i > 0 && sp->lsis[i].ls_trans == sp->lsis[i - 1].ls_trans + 1 &&
                      sp->lsis[i].ls_corr == sp->lsis[i - 1].ls_corr + 1) {
                  ++hit;
                  --i;
               }
         }
         corr = lp->ls_corr;
         break;
      }
   }
   y = EPOCH_YEAR;
   tdays = *timep / SECSPERDAY;
   rem = *timep - tdays * SECSPERDAY;
   while (tdays < 0 || tdays >= year_lengths[isleap(y)]) {
      int64_t newy;
      int64_t tdelta;
      int64_t idelta;
      int64_t leapdays;

      tdelta = tdays / DAYSPERLYEAR;
      idelta = tdelta;
      if (idelta == 0)
         idelta = (tdays < 0) ? -1 : 1;
      newy = y;
      if (increment_overflow(&newy, idelta))
         return NULL;
      leapdays = leaps_thru_end_of(newy - 1) - leaps_thru_end_of(y - 1);
      tdays -= ((int64_t)newy - y) * DAYSPERNYEAR;
      tdays -= leapdays;
      y = newy;
   }
   {
      int_fast32_t seconds;

      seconds = (int_fast32_t)(tdays * SECSPERDAY);
      tdays = seconds / SECSPERDAY;
      rem += seconds - tdays * SECSPERDAY;
   }
   /*
   ** Given the range, we can now fearlessly cast...
   */
   idays = (int64_t)tdays;
   rem += offset - corr;
   while (rem < 0) {
      rem += SECSPERDAY;
      --idays;
   }
   while (rem >= SECSPERDAY) {
      rem -= SECSPERDAY;
      ++idays;
   }
   while (idays < 0) {
      if (increment_overflow(&y, -1))
         return NULL;
      idays += year_lengths[isleap(y)];
   }
   while (idays >= year_lengths[isleap(y)]) {
      idays -= year_lengths[isleap(y)];
      if (increment_overflow(&y, 1))
         return NULL;
   }
   tmp->tm_year = y;
   if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
      return NULL;
   tmp->tm_yday = idays;
   /*
   ** The "extra" mods below avoid overflow problems.
   */
   tmp->tm_wday = EPOCH_WDAY + ((y - EPOCH_YEAR) % DAYSPERWEEK) * (DAYSPERNYEAR % DAYSPERWEEK) +
                  leaps_thru_end_of(y - 1) - leaps_thru_end_of(EPOCH_YEAR - 1) + idays;
   tmp->tm_wday %= DAYSPERWEEK;
   if (tmp->tm_wday < 0)
      tmp->tm_wday += DAYSPERWEEK;
   tmp->tm_hour = (int64_t)(rem / SECSPERHOUR);
   rem %= SECSPERHOUR;
   tmp->tm_min = (int64_t)(rem / SECSPERMIN);
   /*
   ** A positive leap second requires a special
   ** representation. This uses "... ??:59:60" et seq.
   */
   tmp->tm_sec = (int64_t)(rem % SECSPERMIN) + hit;
   ip = mon_lengths + (isleap(y) ? 1 : 0);
   tmp->tm_mon = 0;
   while (idays >= (*ip)[tmp->tm_mon]) {
      idays -= (*ip)[tmp->tm_mon++];
      BSON_ASSERT(tmp->tm_mon < MONSPERYEAR);
   }
   tmp->tm_mday = (int64_t)(idays + 1);
   tmp->tm_isdst = 0;
#ifdef TM_GMTOFF
   tmp->TM_GMTOFF = offset;
#endif /* defined TM_GMTOFF */
   return tmp;
}

/*
** Adapted from code provided by Robert Elz, who writes:
**	The "best" way to do mktime I think is based on an idea of Bob
**	Kridle's (so its said...) from a long time ago.
**	It does a binary search of the int64_t space. Since int64_t's are
**	just 32 bits, its a max of 32 iterations (even at 64 bits it
**	would still be very reasonable).
*/

#ifndef WRONG
#define WRONG (-1)
#endif /* !defined WRONG */

/*
** Normalize logic courtesy Paul Eggert.
*/

static int64_t
increment_overflow(int64_t *const ip, int64_t j)
{
   int64_t const i = *ip;

   /*
   ** If i >= 0 there can only be overflow if i + j > INT_MAX
   ** or if j > INT_MAX - i; given i >= 0, INT_MAX - i cannot overflow.
   ** If i < 0 there can only be overflow if i + j < INT_MIN
   ** or if j < INT_MIN - i; given i < 0, INT_MIN - i cannot overflow.
   */
   if ((i >= 0) ? (j > INT_MAX - i) : (j < INT_MIN - i))
      return true;
   *ip += j;
   return false;
}

static int64_t
increment_overflow32(int_fast32_t *const lp, int64_t const m)
{
   int_fast32_t const l = *lp;

   if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l))
      return true;
   *lp += (int_fast32_t)m;
   return false;
}

static int64_t
normalize_overflow(int64_t *const tensptr, int64_t *const unitsptr, const int64_t base)
{
   int64_t tensdelta;

   tensdelta = (*unitsptr >= 0) ? (*unitsptr / base) : (-1 - (-1 - *unitsptr) / base);
   *unitsptr -= tensdelta * base;
   return increment_overflow(tensptr, tensdelta);
}

static int64_t
normalize_overflow32(int_fast32_t *const tensptr, int64_t *const unitsptr, const int64_t base)
{
   int64_t tensdelta;

   tensdelta = (*unitsptr >= 0) ? (*unitsptr / base) : (-1 - (-1 - *unitsptr) / base);
   *unitsptr -= tensdelta * base;
   return increment_overflow32(tensptr, tensdelta);
}

static int64_t
tmcomp(const struct bson_tm *const atmp, const struct bson_tm *const btmp)
{
   int64_t result;

   if (atmp->tm_year != btmp->tm_year)
      return atmp->tm_year < btmp->tm_year ? -1 : 1;
   if ((result = (atmp->tm_mon - btmp->tm_mon)) == 0 && (result = (atmp->tm_mday - btmp->tm_mday)) == 0 &&
       (result = (atmp->tm_hour - btmp->tm_hour)) == 0 && (result = (atmp->tm_min - btmp->tm_min)) == 0)
      result = atmp->tm_sec - btmp->tm_sec;
   return result;
}

static int64_t
time2sub(struct bson_tm *const tmp,
         struct bson_tm *(*const funcp)(const int64_t *, int_fast32_t, struct bson_tm *),
         const int_fast32_t offset,
         int64_t *const okayp,
         const int64_t do_norm_secs)
{
   const struct state *sp;
   int64_t dir;
   int64_t i, j;
   int64_t saved_seconds;
   int_fast32_t li;
   int64_t lo;
   int64_t hi;
   int_fast32_t y;
   int64_t newt;
   int64_t t;
   struct bson_tm yourtm, mytm;

   *okayp = false;
   yourtm = *tmp;
   if (do_norm_secs) {
      if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec, SECSPERMIN))
         return WRONG;
   }
   if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR))
      return WRONG;
   if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY))
      return WRONG;
   y = (int_fast32_t)yourtm.tm_year;
   if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR))
      return WRONG;
   /*
   ** Turn y into an actual year number for now.
   ** It is converted back to an offset from TM_YEAR_BASE later.
   */
   if (increment_overflow32(&y, TM_YEAR_BASE))
      return WRONG;
   while (yourtm.tm_mday <= 0) {
      if (increment_overflow32(&y, -1))
         return WRONG;
      li = y + (1 < yourtm.tm_mon);
      yourtm.tm_mday += year_lengths[isleap(li)];
   }
   while (yourtm.tm_mday > DAYSPERLYEAR) {
      li = y + (1 < yourtm.tm_mon);
      yourtm.tm_mday -= year_lengths[isleap(li)];
      if (increment_overflow32(&y, 1))
         return WRONG;
   }
   for (;;) {
      i = mon_lengths[isleap(y)][yourtm.tm_mon];
      if (yourtm.tm_mday <= i)
         break;
      yourtm.tm_mday -= i;
      if (++yourtm.tm_mon >= MONSPERYEAR) {
         yourtm.tm_mon = 0;
         if (increment_overflow32(&y, 1))
            return WRONG;
      }
   }
   if (increment_overflow32(&y, -TM_YEAR_BASE))
      return WRONG;
   yourtm.tm_year = y;
   if (yourtm.tm_year != y)
      return WRONG;
   if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN)
      saved_seconds = 0;
   else if (y + TM_YEAR_BASE < EPOCH_YEAR) {
      /*
      ** We can't set tm_sec to 0, because that might push the
      ** time below the minimum representable time.
      ** Set tm_sec to 59 instead.
      ** This assumes that the minimum representable time is
      ** not in the same minute that a leap second was deleted from,
      ** which is a safer assumption than using 58 would be.
      */
      if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN))
         return WRONG;
      saved_seconds = yourtm.tm_sec;
      yourtm.tm_sec = SECSPERMIN - 1;
   } else {
      saved_seconds = yourtm.tm_sec;
      yourtm.tm_sec = 0;
   }
   /*
   ** Do a binary search.
   */
   lo = INT64_MIN;
   hi = INT64_MAX;

   for (;;) {
      t = lo / 2 + hi / 2;
      if (t < lo)
         t = lo;
      else if (t > hi)
         t = hi;
      if ((*funcp)(&t, offset, &mytm) == NULL) {
         /*
         ** Assume that t is too extreme to be represented in
         ** a struct bson_tm; arrange things so that it is less
         ** extreme on the next pass.
         */
         dir = (t > 0) ? 1 : -1;
      } else
         dir = tmcomp(&mytm, &yourtm);
      if (dir != 0) {
         if (t == lo) {
            if (t == time_t_max)
               return WRONG;
            ++t;
            ++lo;
         } else if (t == hi) {
            if (t == time_t_min)
               return WRONG;
            --t;
            --hi;
         }
         if (lo > hi)
            return WRONG;
         if (dir > 0)
            hi = t;
         else
            lo = t;
         continue;
      }
      if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst)
         break;
      /*
      ** Right time, wrong type.
      ** Hunt for right time, right type.
      ** It's okay to guess wrong since the guess
      ** gets checked.
      */
      sp = (const struct state *)gmtptr;
      if (sp == NULL)
         return WRONG;
      for (i = sp->typecnt - 1; i >= 0; --i) {
         if (sp->ttis[i].tt_isdst != yourtm.tm_isdst)
            continue;
         for (j = sp->typecnt - 1; j >= 0; --j) {
            if (sp->ttis[j].tt_isdst == yourtm.tm_isdst)
               continue;
            newt = t + sp->ttis[j].tt_gmtoff - sp->ttis[i].tt_gmtoff;
            if ((*funcp)(&newt, offset, &mytm) == NULL)
               continue;
            if (tmcomp(&mytm, &yourtm) != 0)
               continue;
            if (mytm.tm_isdst != yourtm.tm_isdst)
               continue;
            /*
            ** We have a match.
            */
            t = newt;
            goto label;
         }
      }
      return WRONG;
   }
label:
   newt = t + saved_seconds;
   if ((newt < t) != (saved_seconds < 0))
      return WRONG;
   t = newt;
   if ((*funcp)(&t, offset, tmp))
      *okayp = true;
   return t;
}

static int64_t
time2(struct bson_tm *const tmp,
      struct bson_tm *(*const funcp)(const int64_t *, int_fast32_t, struct bson_tm *),
      const int_fast32_t offset,
      int64_t *const okayp)
{
   int64_t t;

   /*
   ** First try without normalization of seconds
   ** (in case tm_sec contains a value associated with a leap second).
   ** If that fails, try with normalization of seconds.
   */
   t = time2sub(tmp, funcp, offset, okayp, false);
   return *okayp ? t : time2sub(tmp, funcp, offset, okayp, true);
}

static int64_t
time1(struct bson_tm *const tmp,
      struct bson_tm *(*const funcp)(const int64_t *, int_fast32_t, struct bson_tm *),
      const int_fast32_t offset)
{
   int64_t t;
   const struct state *sp;
   int64_t samei, otheri;
   int64_t sameind, otherind;
   int64_t i;
   int64_t nseen;
   int64_t seen[TZ_MAX_TYPES];
   int64_t types[TZ_MAX_TYPES];
   int64_t okay;

   if (tmp == NULL) {
      errno = EINVAL;
      return WRONG;
   }
   if (tmp->tm_isdst > 1)
      tmp->tm_isdst = 1;
   t = time2(tmp, funcp, offset, &okay);
   if (okay)
      return t;
   if (tmp->tm_isdst < 0)
#ifdef PCTS
      /*
      ** POSIX Conformance Test Suite code courtesy Grant Sullivan.
      */
      tmp->tm_isdst = 0; /* reset to std and try again */
#else
      return t;
#endif /* !defined PCTS */
   /*
   ** We're supposed to assume that somebody took a time of one type
   ** and did some math on it that yielded a "struct tm" that's bad.
   ** We try to divine the type they started from and adjust to the
   ** type they need.
   */
   sp = (const struct state *)gmtptr;
   if (sp == NULL)
      return WRONG;
   for (i = 0; i < sp->typecnt; ++i)
      seen[i] = false;
   nseen = 0;
   for (i = sp->timecnt - 1; i >= 0; --i)
      if (!seen[sp->types[i]]) {
         seen[sp->types[i]] = true;
         types[nseen++] = sp->types[i];
      }
   for (sameind = 0; sameind < nseen; ++sameind) {
      samei = types[sameind];
      if (sp->ttis[samei].tt_isdst != tmp->tm_isdst)
         continue;
      for (otherind = 0; otherind < nseen; ++otherind) {
         otheri = types[otherind];
         if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst)
            continue;
         tmp->tm_sec += sp->ttis[otheri].tt_gmtoff - sp->ttis[samei].tt_gmtoff;
         tmp->tm_isdst = !tmp->tm_isdst;
         t = time2(tmp, funcp, offset, &okay);
         if (okay)
            return t;
         tmp->tm_sec -= sp->ttis[otheri].tt_gmtoff - sp->ttis[samei].tt_gmtoff;
         tmp->tm_isdst = !tmp->tm_isdst;
      }
   }
   return WRONG;
}

int64_t
_bson_timegm(struct bson_tm *const tmp)
{
   if (tmp != NULL)
      tmp->tm_isdst = 0;
   return time1(tmp, gmtsub, 0L);
}
