/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2017 by Michael Sevakis
 *
 * Copyright (C) 2012 by Bertrik Sikken
 *
 * Based on code from: rtc_as3514.c
 * Copyright (C) 2007 by Barry Wardell
 *
 * 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.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "time.h"

/* Constants that mark Thursday, 1 January 1970 */
#define UNIX_EPOCH_DAY_NUM  134774
#define UNIX_EPOCH_YEAR     (1601 - 1900)

/* Last day number it can do */
#define MAX_DAY_NUM         551879

/* d is days since epoch start, Monday, 1 January 1601 (d = 0)
 *
 * That date is the beginning of a full 400 year cycle and so simplifies the
 * calculations a bit, not requiring offsets before divisions to shift the
 * leap year cycle.
 *
 * It can handle dates up through Sunday, 31 December 3111 (d = 551879).
 *
 * time_t can't get near the limits anyway for now but algorithm can be
 * altered slightly to increase range if even needed.
 */
static void get_tmdate(unsigned int d, struct tm *tm)
{
    static const unsigned short mon_yday[13] =
    {
        /* year day of 1st of month (non-leap)
      +31  +28  +31  +30  +31  +30  +31  +31  +30  +31  +30  +31  +31 */
        0,  31,  59,  90, 120, 151, 181, 212, 243, 273, 304, 334, 365
    };

    unsigned int x0, x1, x2, x3; /* scratch variables */

    /* calculate year from day number */
    x0 = d / 1460;
    x1 = x0 / 25;
    x2 = x1 / 4;

    unsigned int y = (d - x0 + x1 - x2) / 365;

    tm->tm_year = y + UNIX_EPOCH_YEAR;          /* year - 1900 */

    /* calculate year day from year number and day number */
    x0 = y / 4;
    x1 = x0 / 25;
    x2 = x1 / 4;

    unsigned int yday = d - x0 + x1 - x2 - y * 365;

    tm->tm_yday = x3 = yday;                    /* 0..364/365 */

    /* check if leap year; adjust February->March transition if so rather
       than keeping a leap year version of mon_yday[] */
    if (y - x0 * 4 == 3 && (x0 - x1 * 25 != 24 || x1 - x2 * 4 == 3)) {
        /* retard month lookup to make year day 59 into 29 Feb, both to make
           year day 60 into 01 March, lagging one day for remainder of year */
        if (x3 >= mon_yday[2] && --x3 >= mon_yday[2]) {
            yday--;
        }
    }

    /* stab approximately at current month based on year day; advance if
       it fell short (never initially more than 1 short). */
    x0 = x3 / 32;
    if (mon_yday[x0 + 1] <= x3) {
        x0++;
    }

    tm->tm_mon  = x0;                           /* 0..11 */
    tm->tm_mday = yday - mon_yday[x0] + 1;      /* 1..31 */
    tm->tm_wday = (d + 1) % 7;                  /* 0..6 */
}

struct tm * gmtime_r(const time_t *timep, struct tm *tm)
{
    time_t t = *timep;

    int d = t / 86400;             /* day number (-24856..24855) */
    int s = t - (time_t)d * 86400; /* second # of day (0..86399) */

    if (s < 0) {
        /* round towards 0 -> floored division */
        d--;
        s += 86400;
    }

    unsigned int x;

    x = s / 3600;
    tm->tm_hour = x;    /* 0..23 */

    s -= x * 3600;
    x = s / 60;
    tm->tm_min = x;     /* 0..59 */

    s -= x * 60;
    tm->tm_sec = s;     /* 0..59 */

    /* not implemented right now */
    tm->tm_isdst = -1;

    get_tmdate(d + UNIX_EPOCH_DAY_NUM, tm);

    return tm;
}

struct tm * gmtime(const time_t *timep)
{
    static struct tm time;
    return gmtime_r(timep, &time);
}
