/****************************************************************************
 * libs/libc/time/lib_timegm.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <time.h>
#include <debug.h>

#include <nuttx/clock.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/****************************************************************************
 * Private Type Declarations
 ****************************************************************************/

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Public Constant Data
 ****************************************************************************/

/****************************************************************************
 * Public Data
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const int g_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}
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static void adjust(FAR int *tenx, FAR int *x, int base)
{
  while (*x < 0)
    {
      *x += base;
      (*tenx)--;
    }

  while (*x > (base - 1))
    {
      *x -= base;
      (*tenx)++;
    }
}

static void normalize(FAR struct tm *tm)
{
  bool leapyear = false;
  int year;

  for (; ; )
    {
      /* Adjust mon field */

      adjust(&tm->tm_year, &tm->tm_mon, MONSPERYEAR);

      /* Get an absolute year */

      year = tm->tm_year + TM_YEAR_BASE;

      /* Is this a leap year? */

      leapyear = clock_isleapyear(year);

      /* Adjust mday field */

      while (tm->tm_mday < 1)
        {
          tm->tm_mon--;
          if (tm->tm_mon < 0)
            {
              tm->tm_mday += g_mon_lengths[leapyear][TM_DECEMBER];
              break;
            }

          tm->tm_mday += g_mon_lengths[leapyear][tm->tm_mon];
        }

      if (tm->tm_mon < 0)
        {
          continue;
        }

      while (tm->tm_mday > g_mon_lengths[leapyear][tm->tm_mon])
        {
          tm->tm_mday -= g_mon_lengths[leapyear][tm->tm_mon];
          tm->tm_mon++;
          if (tm->tm_mon > (MONSPERYEAR - 1))
            {
              break;
            }
        }

      if (tm->tm_mon > (MONSPERYEAR - 1))
        {
          continue;
        }

      /* Adjust seconds field */

      adjust(&tm->tm_min, &tm->tm_sec, SECSPERMIN);

      /* Adjust minutes field */

      adjust(&tm->tm_hour, &tm->tm_min, MINSPERHOUR);

      /* Adjust hours field */

      while (tm->tm_hour < 0)
        {
          tm->tm_hour += HOURSPERDAY;
          tm->tm_mday--;

          if (tm->tm_mday < 1)
            {
              break;
            }
        }

      if (tm->tm_mday < 1)
        {
          continue;
        }

      while (tm->tm_hour > (HOURSPERDAY - 1))
        {
          tm->tm_hour -= HOURSPERDAY;
          tm->tm_mday++;

          if (tm->tm_mday > g_mon_lengths[leapyear][tm->tm_mon])
            {
              break;
            }
        }

      if (tm->tm_mday > g_mon_lengths[leapyear][tm->tm_mon])
        {
          continue;
        }

      break;
    }

  /* Update the years field */

  tm->tm_year = year - TM_YEAR_BASE;

  /* Determine the day of the year; -1 because the mday is 1-indexed */

  tm->tm_yday = tm->tm_mday - 1 + clock_daysbeforemonth(tm->tm_mon,
                                                        leapyear);

  /* Finally calculate the weekday */

  tm->tm_wday = clock_dayoftheweek(tm->tm_mday, tm->tm_mon + 1, year);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name:  timegm
 *
 * Description:
 *   Time conversion (based on the POSIX API)
 *
 ****************************************************************************/

time_t timegm(FAR struct tm *tp)
{
  time_t ret;
  time_t jdn;

  /* Normalize struct tm */

  normalize(tp);

  /* Get the EPOCH-relative julian date from the calendar year,
   * month, and date
   */

  jdn = clock_calendar2utc(tp->tm_year + TM_YEAR_BASE, tp->tm_mon,
                           tp->tm_mday);
  linfo("jdn=%d tm_year=%d tm_mon=%d tm_mday=%d\n",
        (int)jdn, tp->tm_year, tp->tm_mon, tp->tm_mday);

  /* Return the seconds into the julian day. */

  ret = ((jdn * 24 + tp->tm_hour) * 60 + tp->tm_min) * 60 + tp->tm_sec;
  linfo("ret=%d tm_hour=%d tm_min=%d tm_sec=%d\n",
        (int)ret, tp->tm_hour, tp->tm_min, tp->tm_sec);

  return ret;
}

time_t mktime(FAR struct tm *tp)
{
  return timegm(tp);
}