//
// Date.cpp
//

#define DBG_LEVEL 0
#include <Raym/Log.h>
#include <Raym/Date.h>

#ifdef _WIN32

#if defined(_MSC_VER) || defined(_MSC_EXTEIO)
    #define DELTA_EPOCH_IN_MICROSECS  11644473600000000Ui64
#else
    #define DELTA_EPOCH_IN_MICROSECS  11644473600000000ULL
#endif

static int gettimeofday(struct timeval *tv, struct timezone *tz)
{
    FILETIME ft;
    unsigned __int64 tmpres = 0;
    static int tzflag;

    if (NULL != tv)
    {
        GetSystemTimeAsFileTime(&ft);

        tmpres = ft.dwHighDateTime;
        tmpres <<= 32;
        tmpres |= ft.dwLowDateTime;

        /*converting file time to unix epoch*/
        tmpres /= 10;  /*convert into microseconds*/
        tmpres -= DELTA_EPOCH_IN_MICROSECS;
        tv->tv_sec = (long)(tmpres / 1000000UL);
        tv->tv_usec = (long)(tmpres % 1000000UL);
    }

    if (NULL != tz)
    {
/*
        if (!tzflag)
        {
            _tzset();
            tzflag++;
        }
        tz->tz_minuteswest = _get_timezone / 60;
        tz->tz_dsttime = _daylight;
*/
    }

    return 0;
}

#endif // _WIN32

namespace Raym
{

Date::Date()
{
    DebugLog2("Date::Date()");

    _time.tv_sec = 0;
    _time.tv_usec = 0;
    _zone.tz_minuteswest = 0;
    _zone.tz_dsttime = 0;
}

Date::~Date()
{
    _time.tv_sec = 0;
    _time.tv_usec = 0;
    _zone.tz_minuteswest = 0;
    _zone.tz_dsttime = 0;

    DebugLog2("Date::~Date()");
}

Date *Date::alloc()
{
    DebugLog2("Date::alloc()");

    return new Date();
}

Date *Date::date()
{
    DebugLog2("Date::date()");

    Date *result = NULL;
    result = Date::alloc()->init();
    if (result != NULL)
    {
        result->autorelease();
    }
    return result;
}

Date *Date::dateWithTimeIntervalSinceNow(TimeInterval seconds)
{
    DebugLog2("Date::dateWithTimeIntervalSinceNow(seconds)");

    Date *result = NULL;
    result = Date::alloc()->initWithTimeIntervalSinceNow(seconds);
    if (result != NULL)
    {
        result->autorelease();
    }
    return result;
}

Date *Date::init()
{
    DebugLog2("Date::init()");

    if (gettimeofday(&_time, &_zone) != 0)
    {
        return NULL;
    }
    return this;
}

Date *Date::initWithTimeInterval(TimeInterval seconds, Date *refDate)
{
    DebugLog2("Date::initWithTimeInterval(seconds,refDate)");

    if (refDate == NULL)
    {
        return NULL;
    }

    _time = refDate->_time;
    _zone = refDate->_zone;

    _time.tv_sec += (int)seconds;
    _time.tv_usec += ((int)(seconds * 1000000)) % 1000000;
    if (_time.tv_usec >= 1000000)
    {
        _time.tv_sec += 1;
        _time.tv_usec -= 1000000;
    }

    return this;
}

Date *Date::initWithTimeIntervalSinceNow(TimeInterval seconds)
{
    DebugLog2("Date::initWithTimeIntervalSinceNow(seconds)");

    if (gettimeofday(&_time, &_zone) != 0)
    {
        return NULL;
    }

    _time.tv_sec += (int)seconds;
    _time.tv_usec += ((int)(seconds * 1000000)) % 1000000;
    if (_time.tv_usec >= 1000000)
    {
        _time.tv_sec += 1;
        _time.tv_usec -= 1000000;
    }

    return this;
}

TimeInterval Date::timeIntervalSinceDate(Date *anotherDate)
{
    DebugLog2("Date::timeIntervalSinceDate(anotherDate)");

    if (anotherDate == NULL)
    {
        abort();
    }

    TimeInterval self, another;
    self = _time.tv_usec;
    self = _time.tv_sec + self / 1000000;
    another = anotherDate->_time.tv_usec;
    another = anotherDate->_time.tv_sec + another / 1000000;

    return self - another;
}

Date *Date::dateByAddingTimeInterval(TimeInterval seconds)
{
    DebugLog2("Date::dateByAddingTimeInterval(seconds)");

    Date *result = Date::alloc()->initWithTimeInterval(seconds, this);
    if (result != NULL)
    {
        result->autorelease();
    }
    return result;
}

ComparisonResult Date::compare(Date *anotherDate)
{
    DebugLog2("Date::compare(anotherDate)");

    if (!anotherDate)
    {
        abort();
    }
    if ((_time.tv_sec < anotherDate->_time.tv_sec) ||
        ((_time.tv_sec == anotherDate->_time.tv_sec) && (_time.tv_usec < anotherDate->_time.tv_usec)))
    {
        return OrderedAscending;
    }
    if ((_time.tv_sec == anotherDate->_time.tv_sec) && (_time.tv_usec == anotherDate->_time.tv_usec))
    {
        return OrderedSame;
    }
    return OrderedDescending;
}

const char *Date::className()
{
    return "Date";
}

} // Raym
