/*****************************************************************************
 * $LastChangedDate: 2011-04-09 21:58:06 -0400 (Sat, 09 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Timer calls registered functors, pauses the program, and provides time functions.
 *//*
 * LEGAL:   COPYRIGHT (C) 2006 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define BASE_TIMER_CC 1
#include "base/module.hh"
#include "base/auto_flag.hh"
#include "base/timer.hh"
#include "base/time_clock.hh"

namespace base {

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////  TickFunctor  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
TickFunctor::TickFunctor( void )
:   mRegistered(false),
    mMillisecFreq(TimerDefs::MILLISECONDS_NEVER),
    mMillisecCalled(TimerDefs::MILLISECONDS_NEVER),
    mDisablement(CAN_DISABLE)
{
    SET_TYPESIG(this,TYPESIG_TICK_FUNCTOR);

    // Members will be assigned by Timer::Register().
}

TickFunctor::~TickFunctor()
{
    INVALIDATE_TYPESIG(this,TYPESIG_TICK_FUNCTOR);
}

/*****************************************************************************
 * If at or past the time to call this functor, then call it.
 *****************************************************************************/
void
TickFunctor::CallTickIfNowIsTheTime( const Milliseconds millisecElapsed )
{
CHECK_TYPESIG(this,TYPESIG_TICK_FUNCTOR);
ASSERT( mRegistered );

    // If functor was never called, call it now.
    // Else call if elapsed time minus last time is greater/equal frequency.
    bool ifTimeToCall = false;
    if ( mMillisecCalled == TimerDefs::MILLISECONDS_NEVER )
    {
        // Never called before.
        ifTimeToCall = true;
    }
    else
    {
        // Was called before.
        // Compute time between now and last time functor was called.
        const Milliseconds delta = millisecElapsed - mMillisecCalled;
        ASSERT( delta >= Milliseconds(0) );
        ifTimeToCall = (delta >= mMillisecFreq);
    }

    // Call Tick()?
    if ( ifTimeToCall )
    {
        mMillisecCalled = millisecElapsed;  // remember time when it was called
        Tick( millisecElapsed );            // invoke functor
    }
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////  Timer  /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

DEFINE_SINGLETON_CLASS_VARS( Timer )

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Timer::Timer( void )
:   mEnabled(true),
    mFunctors(),
    mPendingRegister(),
    mPendingUnregister(),
    mTimeTotal(0),
    mTimeVirtual(0),
    mTimeTotalWhenPauseStarted(0),
    mTimeVirtualWhenPauseStarted(0),
    mTimeLostWhilePaused(0)
{
    ASSERT_RUN_ONCE;  // singleton
}

Timer::~Timer()
{
    ASSERT_RUN_ONCE;
}

/*****************************************************************************
 * Timer can be disabled to pause/freeze program.
 *****************************************************************************/
void
Timer::Enable( const bool enable )
{
    if ( mEnabled and (not enable) )
    {
        // Program will be paused.
        // Record the time when pausing started.
        mTimeTotalWhenPauseStarted   = mTimeTotal;
        mTimeVirtualWhenPauseStarted = mTimeVirtual;
    }
    else if ( (not mEnabled) and enable )
    {
        // Program will be unpaused.
        // Account for lost time while paused.

        // Compute the lost time during this instance of pausing.
        const Milliseconds delta = mTimeTotal - mTimeTotalWhenPauseStarted;
        ASSERT( delta >= Milliseconds(0) );

        // Accumulate all the lost time while paused.
        mTimeLostWhilePaused = mTimeLostWhilePaused + delta;
    }
    else
    {
        ASSERT(false);
    }

    mEnabled = enable;
}

/*****************************************************************************
 * Register a tick functor.
 * NOTE: Don't re-register the same functor.
 * @param   functor
 *          Tick functor.
 * @param   millisecFreq
 *          How frequently to invoke the functor.
 *          If equal to MILLISECONDS_NEVER then NOP.
 * @param   disablement
 *          Pass CAN_DISABLE or CANNOT_DISABLE.
 *          Default is "can disable".
 * Implementation:
 * Registering is done without disrupting the iteration of Tick().
 *****************************************************************************/
void
Timer::Register( shptr<TickFunctor> functor,
                 const Milliseconds millisecFreq,
                 const TickFunctor::eDisablement disablement )
{
CHECK_TYPESIG(functor,TYPESIG_TICK_FUNCTOR);
ASSERT( not functor->mRegistered );  // re-registering is an error
ASSERT( (millisecFreq >= TimerDefs::MILLISECONDS_MIN) or (millisecFreq == TimerDefs::MILLISECONDS_NEVER) );  // MILLISECONDS_NEVER is ok
ASSERT( (millisecFreq <= TimerDefs::MILLISECONDS_MAX) or (millisecFreq == TimerDefs::MILLISECONDS_NEVER) );  // MILLISECONDS_NEVER is ok

    // Pend the action to avoid disrupting the iteration of Tick().
    // MILLISECONDS_NEVER is a special value which means don't really register.
    if ( millisecFreq != TimerDefs::MILLISECONDS_NEVER )
    {
        // Fill hidden state of TickFunctor.
        functor->mRegistered     = true;
        functor->mMillisecFreq   = millisecFreq;
        functor->mMillisecCalled = TimerDefs::MILLISECONDS_NEVER;
        functor->mDisablement    = disablement;

        // Pend.
        mPendingRegister.push_back( functor );
    }
}

/*****************************************************************************
 * Unregister a tick functor.
 * A functor is allowed to unregister itself (see header comments).
 * Implementation:
 * Unregistering is done without disrupting the iteration of Tick().
 *****************************************************************************/
void
Timer::Unregister( shptr<TickFunctor> functor )
{
CHECK_TYPESIG(functor,TYPESIG_TICK_FUNCTOR);
ASSERT( functor->mRegistered );  // re-unregistering is an error

    // Immediately mark the functor as unregistered
    // else Tick() could wrongly call an unregistered functor.
    functor->mRegistered = false;

    // Pend the action to avoid disrupting the iteration of Tick().
    mPendingUnregister.push_back( functor );
}

/*****************************************************************************
 * INTEGRATION:
 * The Timer singleton must be pulsed externally.
 * An external module must call Timer::Tick() periodically
 * (such as the program's main loop).
 *****************************************************************************/
void
Timer::Tick( void )
{
    const Milliseconds elapsedTime = GET_TIME_CLOCK().GetElapsedTime();
    TickUpdateTime( elapsedTime );
    TickDoPending();
    TickCallFunctors();
}

// Update time.
void
Timer::TickUpdateTime( const Milliseconds elapsedTime )
{
    // Compute total elapsed time.
    mTimeTotal = elapsedTime;

    // Compute virtual elapsed time (total time minus time lost while paused).
    // While paused, then mTimeVirtual/GetElapsedTime() stay frozen.
    if ( EX( mEnabled ) )
        mTimeVirtual = mTimeTotal - mTimeLostWhilePaused;

#if DEBUG
    // Catch traveling backwards in time
    // else Einstein will stick his tongue out at you.
    PERSISTENT Milliseconds sDebugPrevTotalTime(0);    // no need to lock
    PERSISTENT Milliseconds sDebugPrevVirtualTime(0);  // as only main thread should be running this
    ASSERT( mTimeTotal   >= sDebugPrevTotalTime );
    ASSERT( mTimeVirtual >= sDebugPrevVirtualTime );
    sDebugPrevTotalTime   = mTimeTotal;
    sDebugPrevVirtualTime = mTimeVirtual;
    ASSERT( mTimeVirtual <= mTimeTotal );  // virtual time should exceed total time
#endif
}

// Do pending registering/unregistering of functors.
void
Timer::TickDoPending( void )
{
    // Do pending registering.
    for ( PendingFunctors::iterator iter = mPendingRegister.begin(); iter != mPendingRegister.end(); ++iter )
    {
    CHECK_TYPESIG(*iter,TYPESIG_TICK_FUNCTOR);
    ASSERT( mFunctors.find( *iter ) == mFunctors.end() );  // shouldn't re-register

        mFunctors.insert( *iter );
    }
    mPendingRegister.clear();  // clear

    // Do pending unregistering.
    for ( PendingFunctors::iterator iter = mPendingUnregister.begin(); iter != mPendingUnregister.end(); ++iter )
    {
    CHECK_TYPESIG(*iter,TYPESIG_TICK_FUNCTOR);
    ASSERT( mFunctors.find( *iter ) != mFunctors.end() );  // shouldn't re-register

        mFunctors.erase( *iter );
    }
    mPendingUnregister.clear();  // clear
}

// Call timer-tick functors.
void
Timer::TickCallFunctors( void )
{
    // This is the C++ exception handler that will actually be invoked by C++ exception
    // during a GLUT timer-tick.  The top-level handler in mod_sim_main.cc won't be invoked,
    // since the C++ run-time cannot unwind stack frames from C code (in GLUT).
    // But adding another "try" block allows unwinding the stack from this point.
    try
    {
        // Iterate thru functors.
        // Call registered functors if their time has come.
        // Functors could unregister themselves while this is iterating!
        // Register()/Unregister() are designed to avoid disrupting this iteration.
        for ( Functors::iterator iter = mFunctors.begin();
              iter != mFunctors.end();
              ++iter )
        {
            // Invoke this functor if its time has come.
            // Some functorss must be called even if Timer is disabled.
            shptr<TickFunctor> functor = *iter;
            CHECK_TYPESIG(functor,TYPESIG_TICK_FUNCTOR);
            if ( EX( functor->mRegistered ) )
            {
                const bool cannotDisableFunctor = (functor->mDisablement == TickFunctor::CANNOT_DISABLE);
                if ( EX( mEnabled or cannotDisableFunctor ) )  // if Timer enabled or functor cannot be disabled
                {
                    // Time stops while program is paused,
                    // except for functors that cannot be disabled.
                    //
                    // SUBTLE: GetElapsedTime() has to be called every time (not pre-assigned)
                    // in order to recompute elapsed time minus paused time (lost time).
                    // What happens is that one timer functor goes to the Lua keyboard handler
                    // which can pause/unpause and thus calls Timer::Enable() during this loop.
                    const Milliseconds milliseconds = cannotDisableFunctor ? GetElapsedTimeTotal()  // total elapsed time
                                                                           : GetElapsedTime();      // minus paused time
                    functor->CallTickIfNowIsTheTime( milliseconds );
                }
            }
        }
    }
    catch ( const std::exception& ex )
    {
        PrintException( ex, "timer-tick" );
        throw;  // rethrow
    }
}

} // namespace base
