/*********************************************************************
 *
 * AUTHORIZATION TO USE AND DISTRIBUTE
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: 
 *
 * (1) source code distributions retain this paragraph in its entirety, 
 *  
 * (2) distributions including binary code include this paragraph in
 *     its entirety in the documentation or other materials provided 
 *     with the distribution, and 
 *
 * (3) all advertising materials mentioning features or use of this 
 *     software display the following acknowledgment:
 * 
 *      "This product includes software written and developed 
 *       by Brian Adamson and Joe Macker of the Naval Research 
 *       Laboratory (NRL)." 
 *         
 *  The name of NRL, the name(s) of NRL  employee(s), or any entity
 *  of the United States Government may not be used to endorse or
 *  promote  products derived from this software, nor does the 
 *  inclusion of the NRL written and developed software  directly or
 *  indirectly suggest NRL or United States  Government endorsement
 *  of this product.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ********************************************************************/
 
 
#ifndef _PROTOCOL_TIMER
#define _PROTOCOL_TIMER

#include "sysdefs.h"    // for GetSystemTime() function
#include "debug.h"

#include <stdio.h>
#include <stdlib.h>     // for rand() routines
#include <math.h>

// Some constants used for precision timers
const double PRECISION_TIME_THRESHOLD =   60.0;    // this must be less that PRECISION_HALF_TIME
const double PRECISION_TIME_MODULUS =     2048.0;  // ~34 minutes 
const double PRECISION_HALF_TIME =        1024.0;  // ~17 min = max possible precision timeout
const long PRECISION_SECS_MAX =           0x7ff;   // used to mask seconds properly



// Pick a random number from 0..max
inline double UniformRand(double max)
{
  return (max * ((double)rand() / (double)RAND_MAX));
}

// Pick a random number from 0..max
// (truncated exponential dist. lambda = log(groupSize) + 1)
inline double ExponentialRand(double max, double groupSize)
{
    double lambda = log(groupSize) + 1;
    double x = UniformRand(lambda/max)+lambda/(max*(exp(lambda)-1));
    return ((max/lambda)*log(x*(exp(lambda)-1)*(max/lambda)));   
}

inline void SeedRandomGenerator()
{
    struct timeval  tv;
    ::GetSystemTime(&tv);
    srand(tv.tv_usec);
}


// Typedefs to make syntax cleaner
class ProtocolTimerOwner {};
typedef bool (ProtocolTimerOwner::*ProtocolTimeoutFunc)();

class ProtocolTimer
{
    friend class ProtocolTimerList;
    friend class LongProtocolTimerList;
    friend class PrecisionProtocolTimerList;
    friend class ProtocolTimerMgr;
    
    // Members
    private:                          
        double                  timeout;    // next expiration time
        int                     repeat_count; // remaining repeats
        
        double                  interval;   // Timer interval
        int                     repeats;    // Total number of repeats, 
                                            // -1 = forever, 0 = one shot
        
        ProtocolTimerOwner      *owner;       // Class to which this timer belongs
        ProtocolTimeoutFunc     timeout_func; // Offset pointer to timeout action
        
        class ProtocolTimerList *list;
        class ProtocolTimer     *prev;
        class ProtocolTimer     *next;
        
    // Methods
    public:
        ProtocolTimer();
        ~ProtocolTimer();
        void Init(double theInterval, int numRepeats, 
                  ProtocolTimerOwner *theOwner, 
                  ProtocolTimeoutFunc theAction)
        {
            interval = theInterval;
            repeats = repeat_count = numRepeats;
            owner = theOwner; 
            timeout_func = theAction;
        }
        double Interval() const {return interval;}
        void SetInterval(double delay) {interval = delay;}
        int Repeats() const {return repeats;}
        void SetRepeats(int numRepeats) {repeats = numRepeats;}
        void ResetRepeats() {repeat_count = repeats;}
        void Reset();
        void DecrementRepeatCount()
            {repeat_count = (repeat_count > 0) ? repeat_count-1 : repeat_count;}
        int  RepeatCount() const {return repeat_count;}
        void Deactivate();
        bool IsActive() const {return(list != NULL);}
        double TimeRemaining() const;
    
    private:
        bool DoTimeout() {return (owner->*timeout_func)();}
    
};  // end ProtocolTimer

class ProtocolTimerList
{
    friend class ProtocolTimerMgr;
    
    public:
        void Init(class ProtocolTimerMgr* theMgr) {mgr = theMgr;}
        class ProtocolTimerMgr* Mgr() {return mgr;}
    
    protected:
        ProtocolTimerList();
        void RemoveTimer(ProtocolTimer* theTimer);
        
        class ProtocolTimerMgr*     mgr;
        ProtocolTimer*              head;
        ProtocolTimer*              tail;
        
};  // end ProtocolTimerList


class LongProtocolTimerList : public ProtocolTimerList
{
    public:
        LongProtocolTimerList();
        void InsertTimer(ProtocolTimer* theTimer);
        void DoTimers();
        static double GetCurrentTime() 
        {
            struct timeval tv;
            ::GetSystemTime(&tv);
            return ((double)tv.tv_sec);
        }
        static double DeltaTime(double t1, double t2) 
            {return (t1 - t2);}
        double TimeRemaining();    
};  // end LongProtocolTimerList


class PrecisionProtocolTimerList : public ProtocolTimerList
{
    public:
        PrecisionProtocolTimerList();
        void InsertTimer(ProtocolTimer* theTimer);
        void DoTimers();
        static double GetCurrentTime()
        {
            struct timeval current;
            GetSystemTime(&current);
            current.tv_sec &= PRECISION_SECS_MAX;
            return (current.tv_sec +
                    (((double)current.tv_usec)/((double)1.0e+06)));
            
        }
        // t1 - t2
        static double DeltaTime(double t1, double t2)
        {
            double delta = t1 - t2;
            return ((delta < -PRECISION_HALF_TIME) ?
                        (delta + PRECISION_TIME_MODULUS) :
                        ((delta < PRECISION_HALF_TIME) ?
                            delta : delta - PRECISION_TIME_MODULUS));
        }
        // t1 + t2
        static double TotalTime(double t1, double t2)
        {
            double total = t1 + t2;
            return ((total >= PRECISION_TIME_MODULUS) ?
                        (total - PRECISION_TIME_MODULUS) :
                        total);
        }
        double TimeRemaining();
};  // end ProtocolPrecisionTimerList


#ifndef _INSTALL_CMDS
#define _INSTALL_CMDS
// Possible Installer "Actions"
enum ProtocolTimerInstallCmd
{
    PROTOCOL_TIMER_INSTALL = 0, 
    PROTOCOL_TIMER_MODIFY  = 1, 
    PROTOCOL_TIMER_REMOVE  = 2
};
#endif // _INSTALL_CMDS

// Function pointer for user supplied timer "installer"
typedef bool (ProtocolTimerInstallFunc)(ProtocolTimerInstallCmd cmd,
                                        double                  delay,
                                        ProtocolTimerMgr*       timerMgr, 
                                        const void*             installData);

class ProtocolTimerMgr
{
    public:
    // Methods
        ProtocolTimerMgr();
        ~ProtocolTimerMgr();
        void SetInstaller(ProtocolTimerInstallFunc* installFunc, 
                          const void*               installData)
        {
            installer_func = installFunc;
            installer_data = installData;
        }
        void SetUserData(const void* userData) {user_data = userData;}
        const void* GetUserData() {return user_data;}
        
        void InstallTimer(ProtocolTimer* theTimer);
        void ReinstallTimer(ProtocolTimer* theTimer, double now);
        void RemoveTimer(ProtocolTimer* theTimer);
        
        // Check timer list and execute timeout actions as appropriate
        void DoTimers() 
        {
            // Assume user timer needs is one shot
            DMSG(10, "ProtocolTimerMgr::DoTimers() entered ...\n");
            update_pending = true;
            timer_timeout = -1.0;
            precision_list.DoTimers();
            Update();
            update_pending = false;
            DMSG(10, "ProtocolTimerMgr::DoTimers() exiting ...\n");
        }
        // Differential time until next timer fires (in seconds)
        double TimeRemaining() {return precision_list.TimeRemaining();}        
        void Halt();
        
    private: 
    // Members    
        PrecisionProtocolTimerList  precision_list;
        LongProtocolTimerList       long_list;
        ProtocolTimer               long_base_timer;
        
        ProtocolTimerInstallFunc*   installer_func;
        const void*                 installer_data;
        const void*                 user_data;
        double                      timer_timeout;
        bool                        update_pending;
    // Methods
        bool DoLongTimers()
        {
            long_list.DoTimers();
            return (NULL != long_list.head);
        }
        double NextTimeout()
        {
            return (precision_list.head ? precision_list.head->timeout : -1.0);
        }
        void Update();
};  // end ProtocolTimerMgr

#endif // _PROTOCOL_TIMER
