/*
 * Module      : Data.Array.Accelerate.Debug.Clock
 * Copyright   : [2017..2020] The Accelerate Team
 * License     : BSD3
 *
 * Maintainer  : Trevor L. McDonell <trevor.mcdonell@gmail.com>
 * Stability   : experimental
 * Portability : non-portable (GHC extensions)
 *
 * Get the monotonic wall-clock time.
 *
 * Implementations for macOS and Win32 stolen from the clock package, which has
 * a BSD3 license.
 *
 * <https://github.com/corsis/clock/tree/0.5.1>
 */

#if defined(__MACH__) && defined(__APPLE__)
/*
 * macOS
 *
 * macOS 10.12 also has clock_gettime(), so it might be worthwhile to use that
 * when available.
 */
#include <time.h>
#include <mach/clock.h>
#include <mach/mach.h>

static clock_serv_t __cclock;

/* constructors with priority execute before constructors without a priority
 * value.
 *
 * constructors with a lower [numeric] priority value are executed before
 * constructors with a higher [numeric] priority.
 *
 * constructor priority values [0,100] are reserved.
 */
__attribute__((constructor(101))) void initialise_clock_service(void)
{
    host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &__cclock);
}

/* destructors without a priority execute before destructors with a priority
 *
 * destructors with a higher [numeric] priority value are executed before
 * destructors with a lower priority value.
 *
 * destructor priority values [0,100] are reserved.
 */
__attribute__((destructor(101))) void deallocate_clock_service(void)
{
    mach_port_deallocate(mach_task_self(), __cclock);
}

static void clock_darwin_gettime(struct timespec *t)
{
    // OS X does not have clock_gettime, use clock_get_time
    // see http://stackoverflow.com/questions/11680461/monotonic-clock-on-osx
    mach_timespec_t mts;
    clock_get_time(__cclock, &mts);

    t->tv_sec  = mts.tv_sec;
    t->tv_nsec = mts.tv_nsec;
}

double clock_gettime_monotonic_seconds()
{
    struct timespec t;
    clock_darwin_gettime(&t);

    return (double) t.tv_sec + (double) t.tv_nsec * 1.0E-9;
}

#elif defined(_WIN32)
/*
 * Windows
 */
#include <windows.h>

static long ticks_to_nanos(LONGLONG subsecond_time, LONGLONG frequency)
{
    return (long)((1E9 * subsecond_time) / frequency);
}

static void clock_win32_gettime_monotonic(long long* t)
{
    LARGE_INTEGER time;
    static LARGE_INTEGER frequency;
    static int hasFreq = 0;

    QueryPerformanceCounter(&time);
    if (!hasFreq)
    {
        hasFreq = 1;
        QueryPerformanceFrequency(&frequency);
    }

    // seconds
    t[0] = time.QuadPart / frequency.QuadPart;

    // nanoseconds
    t[1] = ticks_to_nanos(time.QuadPart % frequency.QuadPart, frequency.QuadPart);
}

double clock_gettime_monotonic_seconds()
{
    long long t[2];
    clock_win32_gettime_monotonic(t);

    return (double) t[0] + (double) t[1] * 1.0E-9;
}

#else
/*
 * *nix
 */
#include <time.h>

double clock_gettime_monotonic_seconds()
{
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);

    return (double) t.tv_sec + (double) t.tv_nsec * 1.0E-9;
}

#endif /* OS */


static double __program_epoch;

double clock_gettime_elapsed_seconds(void)
{
    double now  = clock_gettime_monotonic_seconds();
    double diff = now - __program_epoch;

    return diff;
}

__attribute__((constructor)) void initialise_program_epoch(void)
{
    __program_epoch = clock_gettime_monotonic_seconds();
}

