/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
 * Qwt Widget Library
 * Copyright (C) 1997   Josef Wilgen
 * Copyright (C) 2002   Uwe Rathmann
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the Qwt License, Version 1.0
 *****************************************************************************/

#include "qwt_system_clock.h"
#include <qdatetime.h>

#if !defined(Q_OS_WIN)
#include <unistd.h>
#endif

#if defined(Q_OS_MAC)
#include <stdint.h>
#include <mach/mach_time.h>
#define QWT_HIGH_RESOLUTION_CLOCK
#elif defined(_POSIX_TIMERS)
#include <time.h>
#define QWT_HIGH_RESOLUTION_CLOCK
#elif defined(Q_OS_WIN)
#define QWT_HIGH_RESOLUTION_CLOCK
#include <qt_windows.h>
#endif

#if defined(QWT_HIGH_RESOLUTION_CLOCK)

class QwtHighResolutionClock
{
public:
    QwtHighResolutionClock();

    void start();
    double restart();
    double elapsed() const;

    bool isNull() const;

    static double precision();

private:

#if defined(Q_OS_MAC)
    static double msecsTo( uint64_t, uint64_t );

    uint64_t d_timeStamp;
#elif defined(_POSIX_TIMERS)

    static double msecsTo( const struct timespec &,
        const struct timespec & );

    static bool isMonotonic();

    struct timespec d_timeStamp;
    clockid_t d_clockId;

#elif defined(Q_OS_WIN)

    LARGE_INTEGER d_startTicks;
    LARGE_INTEGER d_ticksPerSecond;
#endif
};

#if defined(Q_OS_MAC)
QwtHighResolutionClock::QwtHighResolutionClock():
    d_timeStamp( 0 )
{
}

double QwtHighResolutionClock::precision()
{
    return 1e-6;
}

void QwtHighResolutionClock::start()
{
    d_timeStamp = mach_absolute_time();
}

double QwtHighResolutionClock::restart()
{
    const uint64_t timeStamp = mach_absolute_time();
    const double elapsed = msecsTo( d_timeStamp, timeStamp );
    d_timeStamp = timeStamp;

    return elapsed;
}

double QwtHighResolutionClock::elapsed() const
{
    return msecsTo( d_timeStamp, mach_absolute_time() );
}

bool QwtHighResolutionClock::isNull() const
{
    return d_timeStamp == 0;
}

double QwtHighResolutionClock::msecsTo(
    uint64_t from, uint64_t to )
{
    const uint64_t difference = to - from;

    static double conversion = 0.0;
    if ( conversion == 0.0 )
    {
        mach_timebase_info_data_t info;
        kern_return_t err = mach_timebase_info( &info );

        //Convert the timebase into ms
        if ( err == 0  )
            conversion = 1e-6 * ( double ) info.numer / ( double ) info.denom;
    }

    return conversion * ( double ) difference;
}

#elif defined(_POSIX_TIMERS)

QwtHighResolutionClock::QwtHighResolutionClock()
{
    d_clockId = isMonotonic() ? CLOCK_MONOTONIC : CLOCK_REALTIME;
    d_timeStamp.tv_sec = d_timeStamp.tv_nsec = 0;
}

double QwtHighResolutionClock::precision()
{
    struct timespec resolution;

    int clockId = isMonotonic() ? CLOCK_MONOTONIC : CLOCK_REALTIME;
    ::clock_getres( clockId, &resolution );

    return resolution.tv_nsec / 1e3;
}

inline bool QwtHighResolutionClock::isNull() const
{
    return d_timeStamp.tv_sec <= 0 && d_timeStamp.tv_nsec <= 0;
}

inline void QwtHighResolutionClock::start()
{
    ::clock_gettime( d_clockId, &d_timeStamp );
}

double QwtHighResolutionClock::restart()
{
    struct timespec timeStamp;
    ::clock_gettime( d_clockId, &timeStamp );

    const double elapsed = msecsTo( d_timeStamp, timeStamp );

    d_timeStamp = timeStamp;
    return elapsed;
}

inline double QwtHighResolutionClock::elapsed() const
{
    struct timespec timeStamp;
    ::clock_gettime( d_clockId, &timeStamp );

    return msecsTo( d_timeStamp, timeStamp );
}

inline double QwtHighResolutionClock::msecsTo(
    const struct timespec &t1, const struct timespec &t2 )
{
    return ( t2.tv_sec - t1.tv_sec ) * 1e3
        + ( t2.tv_nsec - t1.tv_nsec ) * 1e-6;
}

bool QwtHighResolutionClock::isMonotonic()
{
    // code copied from qcore_unix.cpp

#if (_POSIX_MONOTONIC_CLOCK-0 > 0)
    return true;
#else
    static int returnValue = 0;

    if ( returnValue == 0 )
    {
#if (_POSIX_MONOTONIC_CLOCK-0 < 0) || !defined(_SC_MONOTONIC_CLOCK)
        returnValue = -1;
#elif (_POSIX_MONOTONIC_CLOCK == 0)
        // detect if the system support monotonic timers
        const long x = sysconf( _SC_MONOTONIC_CLOCK );
        returnValue = ( x >= 200112L ) ? 1 : -1;
#endif
    }

    return returnValue != -1;
#endif
}

#elif defined(Q_OS_WIN)

QwtHighResolutionClock::QwtHighResolutionClock()
{
    d_startTicks.QuadPart = 0;
    QueryPerformanceFrequency( &d_ticksPerSecond );
}

double QwtHighResolutionClock::precision()
{
    LARGE_INTEGER ticks;
    if ( QueryPerformanceFrequency( &ticks ) && ticks.QuadPart > 0 )
        return 1e3 / ticks.QuadPart;

    return 0.0;
}

inline bool QwtHighResolutionClock::isNull() const
{
    return d_startTicks.QuadPart <= 0;
}

inline void QwtHighResolutionClock::start()
{
    QueryPerformanceCounter( &d_startTicks );
}

inline double QwtHighResolutionClock::restart()
{
    LARGE_INTEGER ticks;
    QueryPerformanceCounter( &ticks );

    const double dt = ticks.QuadPart - d_startTicks.QuadPart;
    d_startTicks = ticks;

    return dt / d_ticksPerSecond.QuadPart * 1e3;
}

inline double QwtHighResolutionClock::elapsed() const
{
    LARGE_INTEGER ticks;
    QueryPerformanceCounter( &ticks );

    const double dt = ticks.QuadPart - d_startTicks.QuadPart;
    return dt / d_ticksPerSecond.QuadPart * 1e3;
}

#endif

#endif // QWT_HIGH_RESOLUTION_CLOCK

class QwtSystemClock::PrivateData
{
public:
#if defined(QWT_HIGH_RESOLUTION_CLOCK)
    QwtHighResolutionClock *clock;
#endif
    QTime time;
};

//!  Constructs a null clock object.
QwtSystemClock::QwtSystemClock()
{
    d_data = new PrivateData;

#if defined(QWT_HIGH_RESOLUTION_CLOCK)
    d_data->clock = NULL;
    if ( QwtHighResolutionClock::precision() > 0.0 )
        d_data->clock = new QwtHighResolutionClock;
#endif
}

//! Destructor
QwtSystemClock::~QwtSystemClock()
{
#if defined(QWT_HIGH_RESOLUTION_CLOCK)
    delete d_data->clock;
#endif
    delete d_data;
}

/*!
  \return true if the clock has never been started.
*/
bool QwtSystemClock::isNull() const
{
#if defined(QWT_HIGH_RESOLUTION_CLOCK)
    if ( d_data->clock )
        return d_data->clock->isNull();
#endif

    return d_data->time.isNull();
}

/*!
  Sets the start time to the current time.
*/
void QwtSystemClock::start()
{
#if defined(QWT_HIGH_RESOLUTION_CLOCK)
    if ( d_data->clock )
    {
        d_data->clock->start();
        return;
    }
#endif

    d_data->time.start();
}

/*!
  The start time to the current time and
  return the time, that is elapsed since the
  previous start time.
*/
double QwtSystemClock::restart()
{
#if defined(QWT_HIGH_RESOLUTION_CLOCK)
    if ( d_data->clock )
        return d_data->clock->restart();
#endif

    return d_data->time.restart();
}

/*!
  \return Number of milliseconds that have elapsed since the last time
          start() or restart() was called or 0.0 for null clocks.
*/
double QwtSystemClock::elapsed() const
{
    double elapsed = 0.0;

#if defined(QWT_HIGH_RESOLUTION_CLOCK)
    if ( d_data->clock )
    {
        if ( !d_data->clock->isNull() )
            elapsed = d_data->clock->elapsed();

        return elapsed;
    }
#endif

    if ( !d_data->time.isNull() )
        elapsed = d_data->time.elapsed();

    return elapsed;
}

/*!
  \return Accuracy of the system clock in milliseconds.
*/
double QwtSystemClock::precision()
{
    static double prec = 0.0;
    if ( prec <= 0.0 )
    {
#if defined(QWT_HIGH_RESOLUTION_CLOCK)
        prec = QwtHighResolutionClock::precision();
#endif
        if ( prec <= 0.0 )
            prec = 1.0; // QTime offers 1 ms
    }

    return prec;
}