#include "timestamp.h"

#ifdef _WIN32
int clock_gettime(int dummy, struct timespec *ct) {
    static BOOL first_time = 1;
    static LARGE_INTEGER counts_per_sec;
 
    LARGE_INTEGER count;
    FILETIME ft;
    ULARGE_INTEGER epoch_time;
 
    if (first_time) {
        first_time = 0;
 
        if (QueryPerformanceFrequency(&counts_per_sec) == 0) {
            counts_per_sec.QuadPart = 0;
        }
    }
 
    if ((NULL == ct) || (counts_per_sec.QuadPart <= 0) || (0 == QueryPerformanceCounter(&count))) {
        return -1;
    }
 
    GetSystemTimeAsFileTime(&ft);
        
    // Convert FILETIME to ULARGE_INTEGER
    epoch_time.LowPart = ft.dwLowDateTime;
    epoch_time.HighPart = ft.dwHighDateTime;
     
    // Convert from 100ns intervals since Jan 1, 1601 to seconds since Jan 1, 1970
    // 116444736000000000 = 100ns intervals between 1601 and 1970
    epoch_time.QuadPart -= 116444736000000000ULL;
     
    // Convert to seconds and nanoseconds
    ct->tv_sec = (time_t)(epoch_time.QuadPart / 10000000ULL);
    ct->tv_nsec = (long)((epoch_time.QuadPart % 10000000ULL) * 100);
 
    return 0;
}

int gettimeofday(struct timeval* tv, struct timezone* tz) {
    if (tv) {
        FILETIME ft;
        SYSTEMTIME st;
        ULARGE_INTEGER uli;

        GetSystemTimeAsFileTime(&ft);

        uli.LowPart = ft.dwLowDateTime;
        uli.HighPart = ft.dwHighDateTime;

        // Convert from 100 nanosecond intervals to microseconds
        // and subtract the offset between 1601 and 1970
        uli.QuadPart /= 10; // to microseconds
        uli.QuadPart -= 11644473600000000ULL; // Offset from 1601 to 1970

        tv->tv_sec = (long)(uli.QuadPart / 1000000UL);
        tv->tv_usec = (long)(uli.QuadPart % 1000000UL);
    }

    // Timezone is not implemented on Windows
    if (tz) {
        tz->tz_minuteswest = 0;
        tz->tz_dsttime = 0;
    }

    return 0;
}
#endif


int64_t timestamp_now() {

    struct timespec tp;
    clock_gettime(CLOCK_REALTIME, &tp);

    return timestamp_from_timespec(&tp);

}

int64_t timestamp_from_timespec(const struct timespec *tp) {

    int64_t timestamp = (((int64_t)tp->tv_sec) * TIMESTAMP_ONE_SECOND) + (tp->tv_nsec / 1000);
    long leftover_ns = tp->tv_nsec % 1000;

    if (leftover_ns >= 500) {
        timestamp += 1;
    }

    return timestamp;

}

int64_t timestamp_from_double(double d) {

    int64_t sec = (int64_t)d;

    d -= sec;
    d *= TIMESTAMP_ONE_SECOND;

    int64_t frac = (int64_t)d;

    return (sec * TIMESTAMP_ONE_SECOND) + frac;

}

double timestamp_to_double(int64_t t) {

    int64_t sec  = t / TIMESTAMP_ONE_SECOND;
    int64_t frac = t % TIMESTAMP_ONE_SECOND;

    double dsec  = sec;
    double dfrac = frac;

    return dsec + (dfrac / TIMESTAMP_ONE_SECOND);

}
