/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   System-specific time functions.
 * @verbatim
 * -----------------------------------------------------------------------------
 * Notes:
 * - Two ways to measure time on a computer are counters and clocks.
 *   Counters are preferred since clocks are vulnerable skew from time adjustments.
 * - 64-bits of milliseconds can define 300 million years (less than FreeBSD's uptime).
 * - Counters (and the program) will stop while the system sleeps, of course.
 * - The counters used here are 64-bits and supposedly will never overflow.
 *   A possibility is a bad implementation that uses a hardware counter with less bits
 *   and the OS doesn't correct overflow (kind of OS bug that should be noticed and fixed).
 * - GetCurrentTime() is a macro in Windows, hence the long name GetCurrentMilliseconds().
 * -----------------------------------------------------------------------------
 * POSIX:
 * How to test POSIX features at compile-time:
 * #if POSIX_FLAG >= 0  // WRONG: if POSIX_FLAG is undefined preprocessor
 *                      // defaults it as 0 so test incorrectly becomes true.
 * man sysconf:
 * "For options, typically, there is a  constant  _POSIX_FOO  that  may  be
 * defined in <unistd.h>.  If it is undefined, one should ask at run time.
 * If it is defined to -1, then the option is not supported.   If it is
 * defined to 0, then relevant functions and headers exist, but one has to
 * ask at runtime what degree of support is available.  If it is defined
 * to a value other than -1 or 0, then the option is supported."
 * -----------------------------------------------------------------------------
 * @endverbatim
 *//*
 * LEGAL:   COPYRIGHT (C) 2009 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define BASE_TIME_CLOCK_CC 1
#if VERBOSE
#define TIME_CLOCK_VERBOSE 1
#endif
#if OS_UNIX  // includes Apple
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#endif
#if OS_APPLE
#include <CoreServices/CoreServices.h>
#endif
#if OS_WINDOWS
#include <windows.h>
#endif
#include "base/module.hh"
#include "base/conf.hh"
#include "base/stream.hh"
#include "base/time.hh"
#include "base/time_clock.hh"
#include "base/stream.hh"

namespace base {

// Eg a timer frequency of 500 milliseconds is 2 Hz (100/500) which is inadequate.
// More frequent means lower milliseconds.
const uint         TIME_CLOCK_DESIRED_HZ = 100;                                              ///< desired frequency/Hz
const Milliseconds TIME_CLOCK_ADEQUATE_TIMER_MILLISECONDS( 1000 / TIME_CLOCK_DESIRED_HZ );   ///< reject timer that is less frequent
const Milliseconds TICK_CLOCK_CORRECTED_DELTA_MILLISECONDS( 1000 / TIME_CLOCK_DESIRED_HZ );  ///< artificial time-delta if calendar-clock was skewed

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  functions  /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Specifically for counters (not calendar-clocks).
 * Compute elapsed seconds as delta of start and current counter.
 * Throws exception if counter overflowed.
 *****************************************************************************/
INTERN Milliseconds
ComputeElapsedTimeFromCounter( const Milliseconds startMilliseconds,
                               const Milliseconds currentMilliseconds )
{
    // Subtract start time from current time so that the first tick will be relative to zero.
    const Milliseconds elapsedMilliseconds = currentMilliseconds - startMilliseconds;
    if ( UX( elapsedMilliseconds < Milliseconds(0) ) )
        throw( "TimeClock: Counter overflow detected" );
    return elapsedMilliseconds;
}

/*****************************************************************************
 * Print the incorrect time-delta caused by clock-skew.
 *****************************************************************************/
INTERN void
PrintErrorTimeDelta( const Milliseconds deltaMilliseconds )
{
    const fpx deltaSeconds = deltaMilliseconds.FPX() / 1000.0;
    const fpx deltaMinutes = deltaSeconds / 60.0;

    if ( deltaMilliseconds < Milliseconds(0) )
        CERROR << "WARNING: Negative clock-skew detected, correcting. " << std::endl;
    else
        CERROR << "WARNING: Excessive positive clock-skew detected (or slow system?), correcting. " << std::endl;

    CERROR << "Skewed time-delta = milliseconds=" << deltaMilliseconds << " minutes=" << deltaMinutes << std::endl;
}

/*****************************************************************************
 * Detect/correct clock-skew in a delta of time.
 *****************************************************************************/
INTERN Milliseconds
CorrectClockSkew( const Milliseconds delta )
{
ASSERT( BASE_CONF.mTimeClockSkewMilliseconds > Milliseconds(0) );
ASSERT( BASE_CONF.mTimeClockSkewMilliseconds < Milliseconds(10*MILLION) );

    // delta = currentTick - prevTick
    // mTimeClockSkewMilliseconds should be a few minutes.
    // TICK_CLOCK_CORRECTED_DELTA_MILLISECONDS should be a few milliseconds (a render frame).

    if ( UX( (delta < Milliseconds(0))                           // if calendar-time was adjusted backwards
          or (delta > BASE_CONF.mTimeClockSkewMilliseconds) ) )  // or calendar-time was adjusted forward too much
    {
        PrintErrorTimeDelta( delta );
        return TICK_CLOCK_CORRECTED_DELTA_MILLISECONDS;
    }
    else
    {
        // Ok, not skewed.
        return delta;
    }
}

/*****************************************************************************
 * Convert UNIX time structs to Milliseconds class.
 *****************************************************************************/
#if OS_UNIX
INTERN Milliseconds
ConvertToMilliseconds( const struct timeval& tv )
{
    fpx microseconds  = tv.tv_sec * 1000000.0;  // million
        microseconds += tv.tv_usec;
    fpx milliseconds = microseconds / 1000.0;
    ASSERT( milliseconds >= 0.0 );
    return Milliseconds(milliseconds);
}

#if defined(_POSIX_MONOTONIC_CLOCK) && (_POSIX_MONOTONIC_CLOCK >= 0)
INTERN Milliseconds
ConvertToMilliseconds( const struct timespec& ts )
{
    fpx nanoseconds  = ts.tv_sec * 1000000000.0;  // billion
        nanoseconds += ts.tv_nsec;
    fpx milliseconds = nanoseconds / 1000000.0;  // million
    ASSERT( milliseconds >= 0.0 );
    return Milliseconds(milliseconds);
}
#endif

#endif // OS_UNIX

////////////////////////////////////////////////////////////////////////////////
////////////////////  ElapsedTimeFunctorUnixPosixMono  /////////////////////////
////////////////////////////////////////////////////////////////////////////////

// POSIX #defines are -1 if unsupported, 0 if possibly supported, >0 if definitely supported.
// "#if POSIX_FLAG >= 0" is wrong when POSIX_FLAG is undefined as C preprocessor defaults it as 0.
#if OS_UNIX
#if defined(_POSIX_MONOTONIC_CLOCK) && (_POSIX_MONOTONIC_CLOCK >= 0)

////////////////////////////////////////////////////////////////////////////////
/// @brief Get elapsed time using POSIX clock_gettime(CLOCK_MONOTONIC).
///
/// Preferred UNIX time function.
/// POSIX CLOCK_MONOTONIC should be equivalent to a counter
/// and be unaffected by adjustments to calendar-time
/// Tested on Linux 2.6 and FreeBSD 7.1 (untested on XENIX).
///
class ElapsedTimeFunctorUnixPosixMono : public TimeClock::ElapsedTimeFunctor
{
PREVENT_COPYING(ElapsedTimeFunctorUnixPosixMono)
public:
    ElapsedTimeFunctorUnixPosixMono( void )
    :   mStartMilliseconds(ElapsedTimeFunctorUnixPosixMono::GetCurrentMilliseconds())
    {
        // NOP
    }

    virtual ~ElapsedTimeFunctorUnixPosixMono( void )
    {
        // NOP
    }

    // Determine if POSIX clock_gettime() is available and if it has adequate frequency.
    virtual bool IfAvailable( void )
    {
        struct timespec freq;
        if ( clock_getres( CLOCK_MONOTONIC, &freq ) == 0 )
        {
            // clock_getres() succeeded but is its frequency adequate?
            const Milliseconds milliseconds = ConvertToMilliseconds( freq );
            #if TIME_CLOCK_VERBOSE
            CDEBUG << "TimeClock: clock_getres(CLOCK_MONOTONIC) timespec.tv_sec="
                   << freq.tv_sec << " tv_nsec=" << freq.tv_nsec << std::endl;
            #endif

            // More frequent means lower milliseconds.
            if ( milliseconds <= TIME_CLOCK_ADEQUATE_TIMER_MILLISECONDS )
            {
                #if TIME_CLOCK_VERBOSE
                CDEBUG << "TimeClock: Selected POSIX CLOCK_MONOTONIC." << std::endl;
                #endif
                return true;
            }
            else
            {
                #if TIME_CLOCK_VERBOSE
                CDEBUG << "TimeClock: Rejecting POSIX CLOCK_MONOTONIC (too infrequent)." << std::endl;
                #endif
                return false;
            }
        }
        else
        {
            return false;  // clock_getres() failed
        }
    }

    Milliseconds GetElapsedTime( void )
    {
        return ComputeElapsedTimeFromCounter( mStartMilliseconds, GetCurrentMilliseconds() );
    }

private:
    CLASS_METHOD Milliseconds GetCurrentMilliseconds( void )
    {
        struct timespec ts;
        if ( UX( clock_gettime( CLOCK_MONOTONIC, &ts ) != 0 ) )
            throw Exception( "TimeClock: clock_gettime(CLOCK_MONOTONIC) failed" );
        return ConvertToMilliseconds( ts );
    }

private:
    const Milliseconds  mStartMilliseconds;
};

#endif // defined(_POSIX_MONOTONIC_CLOCK) && (_POSIX_MONOTONIC_CLOCK >= 0)
#endif // OS_UNIX

////////////////////////////////////////////////////////////////////////////////
///////////////////  ElapsedTimeFunctorUnixGettimeofday  ///////////////////////
////////////////////////////////////////////////////////////////////////////////

#if OS_UNIX

////////////////////////////////////////////////////////////////////////////////
/// @brief Get elapsed time using gettimeofday().
///
/// Default UNIX time function.
/// Because of possibility of adjusting calendar-time,
/// this has to detect and correct clock-skew.
/// To tolerate clock-skew, this computes and corrects a time-delta,
/// then accumulates every time-delta as the elapsed time.
/// Simply subtracting a start time from current time is vulnerable to clock-skew.
///
class ElapsedTimeFunctorUnixGettimeofday : public TimeClock::ElapsedTimeFunctor
{
PREVENT_COPYING(ElapsedTimeFunctorUnixGettimeofday)
public:
    ElapsedTimeFunctorUnixGettimeofday( void )
    :   mElapsedMilliseconds(0),
        mPrevMilliseconds(ElapsedTimeFunctorUnixGettimeofday::GetCurrentMilliseconds())
    {
        // NOP
    }

    virtual ~ElapsedTimeFunctorUnixGettimeofday( void )
    {
        // NOP
    }

    virtual bool IfAvailable( void )
    {
        #if TIME_CLOCK_VERBOSE
        CDEBUG << "TimeClock: Selected UNIX gettimeofday()." << std::endl;
        #endif
        return true;  // gettimeofday() should be always available
    }

    Milliseconds GetElapsedTime( void )
    {
        // Get current tick (calendar-time).
        const Milliseconds currentMilliseconds = GetCurrentMilliseconds();

        // Compute delta between current and previous tick.
        Milliseconds deltaMilliseconds = currentMilliseconds - mPrevMilliseconds;

        // Detect/correct clock-skew.
        deltaMilliseconds = CorrectClockSkew( deltaMilliseconds );

        // Add corrected time-delta to the accumulating elapsed time.
        mElapsedMilliseconds = mElapsedMilliseconds + deltaMilliseconds;

        #if TIME_CLOCK_VERBOSE
        CDEBUG << "TimeClock[gettimeofday]: elapsed=" << mElapsedMilliseconds << " delta=" << deltaMilliseconds << std::endl;
        #endif

        // Next become previous.
        mPrevMilliseconds = currentMilliseconds;

        return mElapsedMilliseconds;
    }

private:
    CLASS_METHOD Milliseconds GetCurrentMilliseconds( void )
    {
        struct timeval tv;
        if ( UX( gettimeofday( &tv, NULL ) != 0 ) )
            throw Exception( "TimeClock: gettimeofday() failed" );
        return ConvertToMilliseconds( tv );
    }

private:
    Milliseconds    mElapsedMilliseconds;   ///< accumulates
    Milliseconds    mPrevMilliseconds;      ///< previous tick
};

#endif // OS_UNIX

////////////////////////////////////////////////////////////////////////////////
////////////////////////  ElapsedTimeFunctorApple  /////////////////////////////
////////////////////////////////////////////////////////////////////////////////

#if OS_APPLE

////////////////////////////////////////////////////////////////////////////////
/// @brief Get elapsed time using Apple Microseconds().
///
/// BTW, base::Microseconds is a class here.
///
/// Microseconds()
/// Determines the number of microseconds that have elapsed since system startup time.
/// void Microseconds( UnsignedWide* microTickCount );
/// 
/// struct UnsignedWide {
///   unsigned long  hi;
///   unsigned long  lo;
/// };
///
class ElapsedTimeFunctorApple : public TimeClock::ElapsedTimeFunctor
{
PREVENT_COPYING(ElapsedTimeFunctorApple)
public:
    ElapsedTimeFunctorApple( void )
    :   mStartMilliseconds(ElapsedTimeFunctorApple::GetCurrentMilliseconds())
    {
        // NOP
    }

    virtual ~ElapsedTimeFunctorApple( void )
    {
        // NOP
    }

    virtual bool IfAvailable( void )
    {
        #if TIME_CLOCK_VERBOSE
        CDEBUG << "TimeClock: Selected Apple Microseconds()." << std::endl;
        #endif
        return true;
    }

    Milliseconds GetElapsedTime( void )
    {
        return ComputeElapsedTimeFromCounter( mStartMilliseconds, GetCurrentMilliseconds() );
    }

private:
    CLASS_METHOD Milliseconds GetCurrentMilliseconds( void )
    {
        // Get time using Apple function.
        UnsignedWide microsecondsApple;
        microsecondsApple.lo = microsecondsApple.hi = 0;
        ::Microseconds( &microsecondsApple );

        // Interim step: convert to Microseconds type.
        // This is tested at tests/base/src/timeUnits.cc
        const Microseconds microseconds(  (uint64(microsecondsApple.lo) & (0xffffffffULL))
                                       | ((uint64(microsecondsApple.hi) & (0xffffffffULL)) << 32) );

        // Convert to Milliseconds type.
        return Milliseconds( Microseconds2Milliseconds<TimeInt>( microseconds.INT() ) );
    }

private:
    const Milliseconds mStartMilliseconds;
};

#endif // OS_APPLE

////////////////////////////////////////////////////////////////////////////////
///////////////////////  ElapsedTimeFunctorWindows  ////////////////////////////
////////////////////////////////////////////////////////////////////////////////

#if OS_WINDOWS

////////////////////////////////////////////////////////////////////////////////
/// @brief Get elapsed time using Windows "high-resolution performance counter".
///
/// This should be an ever-incrementing counter rather than an adjustable calendar-clock.
/// Tested that QueryPerformanceCounter() isn't affected by adjusting calendar-time.
///
/// LARGE_INTEGER is a C union.
/// LARGE_INTEGER.QuadPart is a 64-bit int (long long).
///
class ElapsedTimeFunctorWindows : public TimeClock::ElapsedTimeFunctor
{
PREVENT_COPYING(ElapsedTimeFunctorWindows)
public:
    ElapsedTimeFunctorWindows( void )
    :   mTimerFreq(0),
        mStartMilliseconds(0)
    {
        mTimerFreq         = GetTimerFreq();            // -- ORDER-DEPENDENT --
        mStartMilliseconds = GetCurrentMilliseconds();  // these cannot be CLASS_METHOD
    }

    virtual ~ElapsedTimeFunctorWindows( void )
    {
        // NOP
    }

    virtual bool IfAvailable( void )
    {
        #if TIME_CLOCK_VERBOSE
        CDEBUG << "TimeClock: Selected Windows QueryPerformanceCounter()." << std::endl;
        #endif
        return true;
    }

    Milliseconds GetElapsedTime( void )
    {
        return ComputeElapsedTimeFromCounter( mStartMilliseconds, GetCurrentMilliseconds() );
    }

private:
    // These cannot be CLASS_METHOD because of dependency on mTimerFreq.

    // Determine frequency/Hz of high-resolution timer.
    uint64 GetTimerFreq( void )
    {
        LARGE_INTEGER timerFreqInt;
        if ( UX( not QueryPerformanceFrequency( &timerFreqInt ) ) )
            throw Exception( "TimeClock: QueryPerformanceFrequency failed" );
        return uint64( timerFreqInt.QuadPart );
    }

    // Would've named this GetCurrentTime() but it's a macro in Windows.
    Milliseconds GetCurrentMilliseconds( void )
    {
        return Ticks2Milliseconds( GetCurrentTick() );
    }

    uint64 GetCurrentTick( void )
    {
        LARGE_INTEGER tickInt;
        if ( UX( not QueryPerformanceCounter( &tickInt ) ) )
            throw( "TimeClock: QueryPerformanceCounter failed" );
        return uint64( tickInt.QuadPart );
    }

    Milliseconds Ticks2Milliseconds( const uint64 ticks )
    {
    ASSERT( mTimerFreq > 0 );

        // First divide to multiply a smaller number.
      //return Milliseconds( ticks / mTimerFreq * 1000 );  // causes truncation
        return Milliseconds( fpx(ticks) / fpx(mTimerFreq) * 1000.0 );
    }

private:
    uint64        mTimerFreq;             // -- ORDER-DEPENDENT --  freq/Hz
    Milliseconds  mStartMilliseconds;     // -- ORDER-DEPENDENT --
};

#endif // OS_WINDOWS

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  TimeClock  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

TimeClock* TimeClock::msInstance;

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
TimeClock::TimeClock( void )
:   mElapsedTimeFunctor(TimeClock::SelectElapsedTimeFunctor())
{
    // NOP
}

// Select the best available time function.
shptr<TimeClock::ElapsedTimeFunctor>
TimeClock::SelectElapsedTimeFunctor( void )
{
    // -- The initialization of ElapsedTimeFunctor might throw an exception. --
    // -- Catch exceptions of optional ElapsedTimeFunctors. --

//..............................................................................
// UNIX and Apple.
#if OS_UNIX

    // Was cmdline arg -timer-gettimeofday passed?
    if ( BASE_CONF.mTimerUnixGettimeofday )
        return new ElapsedTimeFunctorUnixGettimeofday;

#if OS_APPLE
    // Preferred/optional time function.
    try {
    shptr<ElapsedTimeFunctor> functor = new ElapsedTimeFunctorApple;
    if ( functor->IfAvailable() )
        return functor;
    } catch ( const Exception& ) { }
#endif

#if defined(_POSIX_MONOTONIC_CLOCK) && (_POSIX_MONOTONIC_CLOCK >= 0)
    // Preferred/optional time function.
    try {
    shptr<ElapsedTimeFunctor> functor = new ElapsedTimeFunctorUnixPosixMono;
    if ( functor->IfAvailable() )
        return functor;
    } catch ( const Exception& ) { }
#endif

    {
    // Default UNIX time function.
    shptr<ElapsedTimeFunctor> functor = new ElapsedTimeFunctorUnixGettimeofday;
    if ( not functor->IfAvailable() )
        throw( "No UNIX elapsed time method is available" );  // very unexpected
    return functor;
    }

#endif // UNIX
//..............................................................................
// Windows.
#if OS_WINDOWS

    {
    // Default Windows time function.
    shptr<ElapsedTimeFunctor> functor = new ElapsedTimeFunctorWindows;
    if ( not functor->IfAvailable() )
        throw( "No elapsed time method is available" );
    return functor;
    }

#endif // OS_WINDOWS
//..............................................................................
}

TimeClock::~TimeClock()
{
    // NOP
}

/*****************************************************************************
 * @return Elapsed time since progam startup independent of any adjustments to calendar-time.
 * Almost equivalent to a function that gets the count of timer ticks.
 * THROWS EXCEPTION.
 *****************************************************************************/
Milliseconds
TimeClock::GetElapsedTime( void )
{
    const Milliseconds elapsedMilliseconds = mElapsedTimeFunctor->GetElapsedTime();
#if TIME_CLOCK_VERBOSE
    CLOG << "TimeClock: elapsed time = " << elapsedMilliseconds << " milliseconds" << std::endl;
#endif
    return elapsedMilliseconds;
}

} // namespace base
