/*
 * Copyright (c) 2012-2015 Christian Hansen <chansen@cpan.org>
 * <https://github.com/chansen/c-dt>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <assert.h>
#include <inttypes.h>
#include "dt_core.h"
#include "dt_util.h"

#define LEAP_YEAR(y) \
    (((y) & 3) == 0 && ((y) % 100 != 0 || (y) % 400 == 0))

#define DAYS_IN_YEAR(y) \
    (LEAP_YEAR(y) ? 366 : 365)

static const int days_preceding_month[2][13] = {
    { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
    { 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
};

static const int days_preceding_quarter[2][5] = {
    { 0, 0, 90, 181, 273 },
    { 0, 0, 91, 182, 274 }
};

dt_t
dt_from_rdn(int n) {
    return n + DT_EPOCH_OFFSET;
}

dt_t
dt_from_yd(int y, int d) {
    y--;
    if (y < 0) {
        const int n400 = 1 - y/400;
        y += n400 * 400;
        d -= n400 * 146097;
    }
    return 365 * y + y/4 - y/100 + y/400 + d + DT_EPOCH_OFFSET;
}

dt_t
dt_from_ymd(int y, int m, int d) {
    if (m < 1 || m > 12) {
        y += m / 12;
        m %= 12;
        if (m < 1)
            y--, m += 12;
    }
    assert(m >=  1);
    assert(m <= 12);
    return dt_from_yd(y, days_preceding_month[LEAP_YEAR(y)][m] + d);
}

dt_t
dt_from_yqd(int y, int q, int d) {
    if (q < 1 || q > 4) {
        y += q / 4;
        q %= 4;
        if (q < 1)
            y--, q += 4;
    }
    assert(q >= 1);
    assert(q <= 4);
    return dt_from_yd(y, days_preceding_quarter[LEAP_YEAR(y)][q] + d);
}

dt_t
dt_from_ywd(int y, int w, int d) {
    dt_t dt;

    dt  = dt_from_yd(y, 4);
    dt -= dt_dow(dt);
    dt += w * 7 + d - 7;
    return dt;
}

#ifdef DT_PARSE_ISO_TNT
/*
 * In addition to standard date/month validations we need to check
 * validity of a day range, so we have a chance to intercept
 * 32-bit dt_t overflow/underflow. So we merge `dt_valid_*` and `dt_from_*`
 * set of functions.
 */

typedef int64_t dt64_t;

static inline bool
dt64_from_yd(int y, int d, dt64_t *v, bool check) {
    if (d < 1 || d > dt_days_in_year(y))
        return false;
    int64_t y64 = y - 1;
    int64_t d64 = d;
    if (y64 < 0) {
        const int64_t n400 = 1 - y64/400;
        y64 += n400 * 400;
        d64 -= n400 * 146097;
    }
    *v = 365 * y64 + y64/4 - y64/100 + y64/400 + d64 + DT_EPOCH_OFFSET;
    if (check && (*v < INT32_MIN || *v > INT32_MAX))
        return false;
    return true;
}

bool
dt_from_yd_checked(int y, int d, dt_t *val) {
    dt64_t v = 0;
    if (!dt64_from_yd(y, d, &v, true))
           return false;
#ifndef NDEBUG
    int32_t highw = v >> 32;
    assert(highw == 0 || highw == -1);
#endif
    *val = (dt_t)v;
    return true;
}

bool
dt_from_ymd_checked(int y, int m, int d, dt_t *val) {
    if (m < 1 || m > 12)
        return false;
    if (d < 1 || d > dt_days_in_month(y, m))
        return false;
    return dt_from_yd_checked(y, days_preceding_month[LEAP_YEAR(y)][m] + d, val);
}

bool
dt_from_yqd_checked(int y, int q, int d, dt_t *val) {
    if (q < 1 || q > 4)
        return false;
    if (d < 1 || d > dt_days_in_quarter(y, q))
        return false;
    return dt_from_yd_checked(y, days_preceding_quarter[LEAP_YEAR(y)][q] + d, val);
}

bool
dt_from_ywd_checked(int y, int w, int d, dt_t *val) {
    if (d < 1 || d > 7)
        return false;
    if (w < 1 || w > dt_weeks_in_year(y))
        return false;

    /* The first week of the year always contains 4 January. */
    dt64_t dt = 0;
    bool rc = dt64_from_yd(y, 4, &dt, false);
    assert(rc);
    (void)rc;

    dt -= dt_dow(dt);
    dt += w * 7 + d - 7;
    if (dt < INT32_MIN || dt > INT32_MAX)
        return false;

    *val = (dt_t)dt;
    return true;
}
#endif


#ifndef DT_NO_SHORTCUTS
static const dt_t DT1901 = 693961 + DT_EPOCH_OFFSET; /* 1901-01-01 */
static const dt_t DT2099 = 766644 + DT_EPOCH_OFFSET; /* 2099-12-31 */
#endif

void
dt_to_yd(dt_t d, int *yp, int *dp) {
    int y, n100, n1;

    y = 0;
#ifndef DT_NO_SHORTCUTS
    /* Shortcut dates between the years 1901-2099 inclusive */
    if (d >= DT1901 && d <= DT2099) {
        d -= DT1901 - 1;
        y += (4 * d - 1) / 1461;
        d -= (1461 * y) / 4;
        y += 1901;
    }
    else
#endif
    {
        d -= DT_EPOCH_OFFSET;
        if (d < 1) {
            const int n400 = 1 - d/146097;
            y -= n400 * 400;
            d += n400 * 146097;
        }
        d--;
        y += 400 * (d / 146097);
        d %= 146097;

        n100 = d / 36524;
        y += 100 * n100;
        d %= 36524;

        y += 4 * (d / 1461);
        d %= 1461;

        n1 = d / 365;
        y += n1;
        d %= 365;

        if (n100 == 4 || n1 == 4)
            d = 366;
        else
            y++, d++;
    }
    if (yp) *yp = y;
    if (dp) *dp = (int)d;
}

void
dt_to_ymd(dt_t dt, int *yp, int *mp, int *dp) {
    int y, doy, m, l;

    dt_to_yd(dt, &y, &doy);
    l = LEAP_YEAR(y);
    m = doy < 32 ? 1 : 1 + (5 * (doy - 59 - l) + 303) / 153;

    assert(m >=  1);
    assert(m <= 12);

    if (yp) *yp = y;
    if (mp) *mp = m;
    if (dp) *dp = doy - days_preceding_month[l][m];
}

void
dt_to_yqd(dt_t dt, int *yp, int *qp, int *dp) {
    int y, doy, q, l;

    dt_to_yd(dt, &y, &doy);
    l = LEAP_YEAR(y);
    q = doy < 91 ? 1 : 1 + (5 * (doy - 59 - l) + 303) / 459;

    assert(q >= 1);
    assert(q <= 4);

    if (yp) *yp = y;
    if (qp) *qp = q;
    if (dp) *dp = doy - days_preceding_quarter[l][q];
}

void
dt_to_ywd(dt_t dt, int *yp, int *wp, int *dp) {
    int y, doy, dow;

    dt_to_yd(dt, &y, &doy);
    dow = dt_dow(dt);
    doy = doy + 4 - dow;
    if (doy < 1) {
        y--;
        doy += DAYS_IN_YEAR(y);
    }
    else if (doy > 365) {
        const int diy = DAYS_IN_YEAR(y);
        if (doy > diy) {
            doy -= diy;
            y++;
        }
    }
    if (yp) *yp = y;
    if (wp) *wp = (doy + 6) / 7;
    if (dp) *dp = dow;
}

int
dt_rdn(dt_t dt) {
    return dt - DT_EPOCH_OFFSET;
}

dt_dow_t
dt_dow(dt_t dt) {
    int dow = (dt - DT_EPOCH_OFFSET) % 7;
    if (dow < 1)
        dow += 7;
    assert(dow >= 1);
    assert(dow <= 7);
    return dow;
}

