/*
**  OSSP tai - Time Handling
**  Copyright (c) 2002-2003 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2002-2003 The OSSP Project <http://www.ossp.org/>
**  Copyright (c) 2002-2003 Cable & Wireless Deutschland <http://www.cw.com/de/>
**
**  This file is part of OSSP tai, a time handling library
**  which can be found at http://www.ossp.org/pkg/lib/tai/.
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  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.
**
**  tai_lib.c: API implementation
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>

#include "tai.h"
#include "tai_p.h"

tai_rc_t tai_create(tai_t **ptai)
{
    tai_t *tai;
    tai_rc_t rv;

    if ((tai = (tai_t *)malloc(sizeof(tai_t))) == NULL)
        return TAI_ERR_SYS;
    if ((rv = tai_import(tai, TAI_TYPE_TIMET, 0)) != TAI_OK) {
        free(tai);
        return rv;
    }
    *ptai = tai;
    return TAI_OK;
}

tai_rc_t tai_destroy(tai_t *tai)
{
    if (tai == NULL)
        return TAI_ERR_ARG;
    free(tai);
    return TAI_OK;
}

#if 0
static time_t tai_utcoffset(void)
{
    time_t tL, tU;
    struct tm *tmL, *tmU;
    time_t offset;

    /* determine local time (UTC based) */
    tL = time(NULL); /* theoretically this could be a constant value, too */

    /* unpack local time (local time based) */
    tmL = localtime(&tL);

    /* unpack local time (UTC based) */
    tmU = gmtime(&tL);

    /* pack UTC-based local time back into local time */
    tU  = mktime(tmU);

    /* local time adjustment for Daylight Saving Time (DST) */
    if (tmL->tm_isdst)
        tL += (60*60);

    /* finally calculate the UTC offset */
    offset = (tL - tU);

    return offset;
}
#endif

tai_rc_t tai_import(tai_t *tai, tai_type_t type, ...)
{
    va_list ap;

    if (tai == NULL)
        return TAI_ERR_ARG;
    va_start(ap, type); 
    if (type == TAI_TYPE_TIMET) {
        /* import from time_t */
#ifdef HAVE_GMTIME_R
        struct tm stmbuf;
#endif
        struct tm *stm;
        time_t t;
        t = (time_t)va_arg(ap, long);
#ifdef HAVE_GMTIME_R
        stm = gmtime_r(&t, &stmbuf);
#else
        stm = gmtime(&t);
#endif
        tai->tai_sec    = stm->tm_sec;
        tai->tai_min    = stm->tm_min;
        tai->tai_hour   = stm->tm_hour;
        tai->tai_mday   = stm->tm_mday;
        tai->tai_mon    = stm->tm_mon;
        tai->tai_year   = stm->tm_year;
        tai->tai_wday   = stm->tm_wday;
        tai->tai_yday   = stm->tm_yday;
        tai->tai_isdst  = stm->tm_isdst;
#ifdef HAVE_TM_GMTOFF
        tai->tai_gmtoff = stm->tm_gmtoff; /* implicitly 0 */
#else
        tai->tai_gmtoff = 0;              /* explicitly 0 */
#endif
    }
    else if (type == TAI_TYPE_STRUCTTM) {
        /* import from struct tm */
        struct tm *stm;
        stm = (struct tm *)va_arg(ap, void *);
#define TM_ISRANGE(var,field,min,max) \
    (var->tm_##field >= (min) && var->tm_##field <= (max))
        if (!TM_ISRANGE(stm,sec,0,61)   ||
            !TM_ISRANGE(stm,min,0,59)   ||
            !TM_ISRANGE(stm,hour,0,23)  ||
            !TM_ISRANGE(stm,mday,1,31)  ||
            !TM_ISRANGE(stm,mon,0,11)   ||
            !TM_ISRANGE(stm,wday,0,6)   ||
            !TM_ISRANGE(stm,yday,0,365) ||
            !TM_ISRANGE(stm,isdst,0,1))
            return TAI_ERR_ARG;
        tai->tai_sec    = stm->tm_sec;
        tai->tai_min    = stm->tm_min;
        tai->tai_hour   = stm->tm_hour;
        tai->tai_mday   = stm->tm_mday;
        tai->tai_mon    = stm->tm_mon;
        tai->tai_year   = stm->tm_year;
        tai->tai_wday   = stm->tm_wday;
        tai->tai_yday   = stm->tm_yday;
        tai->tai_isdst  = stm->tm_isdst;
#ifdef HAVE_TM_GMTOFF
        tai->tai_gmtoff = stm->tm_gmtoff;
#else
        tai->tai_gmtoff = 0;
#endif
    }
    else if (type == TAI_TYPE_UNIX) {
        /* import from time(3) FIXME */
        time_t t = time(NULL);
        tai->sec  = tai_ui64_n2i((unsigned long)t);
        tai->asec = tai_ui64_n2i((unsigned long)0);
        tai_tai2cal(tai, 7200);
    }
    else
        return TAI_ERR_IMP; /* FIXME */
    va_end(ap); 
    return TAI_OK;
}

tai_rc_t tai_export(tai_t *tai, tai_type_t type, ...)
{
    va_list ap;

    if (tai == NULL)
        return TAI_ERR_ARG;
    va_start(ap, type); 
    if (type == TAI_TYPE_TIMET) {
        time_t *pt;
        struct tm stmbuf;
        struct tm *stm;
#ifndef HAVE_TIMEGM
        time_t t;
#endif
        pt = (time_t *)va_arg(ap, void *);
        stm = &stmbuf;
        stm->tm_sec    = tai->tai_sec;
        stm->tm_min    = tai->tai_min;
        stm->tm_hour   = tai->tai_hour;
        stm->tm_mday   = tai->tai_mday;
        stm->tm_mon    = tai->tai_mon;
        stm->tm_year   = tai->tai_year;
        stm->tm_wday   = tai->tai_wday;
        stm->tm_yday   = tai->tai_yday;
        stm->tm_isdst  = tai->tai_isdst;
#ifdef HAVE_TM_GMTOFF
        stm->tm_gmtoff = tai->tai_gmtoff;
#endif
#ifdef HAVE_TIMEGM
        /* non-standard timegm(3) makes our life easy */
        *pt = timegm(stm);
#else
        /* standard mktime(3) calculated relative to local timezone,
           so we have to post-adjust result */
        *pt = mktime(stm);
        t = 12*60*60*2; /* max offset of 12 hours plus safety */
        *pt += (mktime(localtime(&t)) - mktime(gmtime(&t)));
#endif
    }
    else if (type == TAI_TYPE_STRUCTTM) {
        struct tm *stm;
        
        stm = (struct tm *)va_arg(ap, void *);
        stm->tm_sec    = tai->tai_sec;
        stm->tm_min    = tai->tai_min;
        stm->tm_hour   = tai->tai_hour;
        stm->tm_mday   = tai->tai_mday;
        stm->tm_mon    = tai->tai_mon;
        stm->tm_year   = tai->tai_year;
        stm->tm_wday   = tai->tai_wday;
        stm->tm_yday   = tai->tai_yday;
        stm->tm_isdst  = tai->tai_isdst;
#ifdef HAVE_TM_GMTOFF
        stm->tm_gmtoff = tai->tai_gmtoff;
#endif
    }
    else
        return TAI_ERR_IMP; /* FIXME */
    va_end(ap); 
    return TAI_OK;
}

tai_rc_t tai_format(tai_t *tai, char *buf_ptr, size_t buf_len, const char *fmt)
{
    struct tm stm;
    tai_rc_t rv;

    if (tai == NULL || buf_ptr == NULL || buf_len == 0 || fmt == NULL)
        return TAI_ERR_ARG;
    if ((rv = tai_export(tai, TAI_TYPE_STRUCTTM, &stm)) != TAI_OK)
        return rv;
    if (tai_format_int(buf_ptr, buf_len, fmt, &stm) == 0)
        return TAI_ERR_FMT;
    return TAI_OK;
}

tai_rc_t tai_parse(tai_t *tai, const char *buf_ptr, size_t buf_len, const char *fmt)
{
    struct tm stm;
    char *cp;
    char *cp2;
    tai_rc_t rv;

    if (tai == NULL || buf_ptr == NULL || buf_len == 0 || fmt == NULL)
        return TAI_ERR_ARG;
    if ((cp = malloc(buf_len+1)) == NULL)
        return TAI_ERR_SYS;
    memmove(cp, buf_ptr, buf_len);
    cp[buf_len] = '\0';
    memset(&stm, 0, sizeof(struct tm));
    cp2 = tai_parse_int(cp, fmt, &stm);
    free(cp);
    if (cp2 == NULL)
        return TAI_ERR_PRS;
    if ((rv = tai_import(tai, TAI_TYPE_STRUCTTM, &stm)) != TAI_OK)
        return rv;
    return TAI_OK;
}

tai_rc_t tai_op(tai_t *tai, tai_op_t op, ...)
{
    tai_rc_t rc = TAI_ERR_IMP;
    va_list ap;

    if (tai == NULL)
        return TAI_ERR_ARG;
    va_start(ap, op); 
    if ((op == TAI_OP_NE) || (op == TAI_OP_EQ) || (op == TAI_OP_LT) || (op == TAI_OP_LE) || (op == TAI_OP_GT) || (op == TAI_OP_GE)) {
        tai_t *tai1, *tai2;
        unsigned long s1, s2;

        tai1 = tai;
        tai2 = (tai_t *)va_arg(ap, void *);
        s1 = tai1->tai_sec + (60 * tai1->tai_min) + (60 * 60 * tai1->tai_hour) + (60 * 60 * 24 * tai1->tai_yday) + (60 * 60 * 24 * 365 * tai1->tai_year) + tai1->tai_gmtoff;
        s2 = tai2->tai_sec + (60 * tai2->tai_min) + (60 * 60 * tai2->tai_hour) + (60 * 60 * 24 * tai2->tai_yday) + (60 * 60 * 24 * 365 * tai2->tai_year) + tai2->tai_gmtoff;
        switch (op) {
            case TAI_OP_NE: rc = (s2 != s1 ? TAI_OK : TAI_FALSE); break;
            case TAI_OP_EQ: rc = (s2 == s1 ? TAI_OK : TAI_FALSE); break;
            case TAI_OP_LT: rc = (s2 <  s1 ? TAI_OK : TAI_FALSE); break;
            case TAI_OP_LE: rc = (s2 <= s1 ? TAI_OK : TAI_FALSE); break;
            case TAI_OP_GT: rc = (s2 >  s1 ? TAI_OK : TAI_FALSE); break;
            case TAI_OP_GE: rc = (s2 >= s1 ? TAI_OK : TAI_FALSE); break;
            default: break; /* FIXME */
        }
    }
    va_end(ap); 
    return rc;
}
