// Copyright (c) 1997-2001 Microsoft Corporation, All Rights Reserved
/*---------------------------------------------------------
Filename: timer.cpp
Written By: B.Rajeev
----------------------------------------------------------*/

#include "precomp.h"
#include "common.h"
#include "sync.h"
#include "timer.h"
#include "message.h"
#include "dummy.h"

#include "flow.h"
#include "frame.h"
#include "ssent.h"
#include "idmap.h"
#include "opreg.h"

#include "session.h"

SnmpClThreadObject *Timer :: g_timerThread = NULL ;
UINT Timer :: g_SnmpWmTimer = SNMP_WM_TIMER ;

// static CriticalSection and CMap
CriticalSection Timer::timer_CriticalSection;
TimerMapping Timer::timer_mapping;

TimerEventId Timer :: next_timer_event_id = ILLEGAL_TIMER_EVENT_ID+1 ;
Window *SnmpTimerObject :: window = NULL ;
CMap <UINT_PTR,UINT_PTR,SnmpTimerObject *,SnmpTimerObject *> SnmpTimerObject :: timerMap ;

SnmpClThreadObject :: SnmpClThreadObject () : SnmpThreadObject ( "SnmpCl" ) 
{
}

void SnmpClThreadObject :: Initialise ()
{
}
 
void SnmpClThreadObject :: Uninitialise ()
{
    delete SnmpTimerObject :: window ;
    SnmpTimerObject :: window = NULL ;
    delete this ;
}

SnmpClTrapThreadObject :: SnmpClTrapThreadObject () : SnmpThreadObject ( "SnmpClTrapThread" ) 
{
}

void SnmpClTrapThreadObject :: Initialise ()
{
DebugMacro4( 

    SnmpDebugLog :: s_SnmpDebugLog->WriteFileAndLine (  

        __FILE__,__LINE__,
        L"SnmpClTrapThreadObject::Initialise: Initialised!!\n"

    ) ;
)
}
 
void SnmpClTrapThreadObject :: Uninitialise ()
{
DebugMacro4( 

    SnmpDebugLog :: s_SnmpDebugLog->WriteFileAndLine (  

        __FILE__,__LINE__,
        L"SnmpClTrapThreadObject::Uninitialise: About to destroy trap thread\n"

    ) ;
)
    delete this ;

DebugMacro4( 

    SnmpDebugLog :: s_SnmpDebugLog->WriteFileAndLine (  

        __FILE__,__LINE__,
        L"SnmpClTrapThreadObject::Uninitialise: Trap thread destroyed!!\n"

    ) ;
)
}


Timer::Timer(SnmpImpSession &session)
{
    Timer::session = &session;
}

BOOL Timer::CreateCriticalSection()
{
    return TRUE;
}

void Timer::DestroyCriticalSection()
{
}

BOOL Timer::InitializeStaticComponents()
{
    return CreateCriticalSection();
}

void Timer::DestroyStaticComponents()
{
    DestroyCriticalSection();
}

// generates and returns a new event id
// associates the pair (event_id, waiting_message)
// creates the timer event
TimerEventId Timer::SetTimerEvent(UINT timeout_value)
{
    TimerEventId suggested_event_id = next_timer_event_id++;
    if ( suggested_event_id == ILLEGAL_TIMER_EVENT_ID )
       suggested_event_id = next_timer_event_id++;

    // let the dummy session receive the window messages for timer events
    TimerEventId event_id = 
        SnmpSetTimer( session->m_SessionWindow.GetWindowHandle(), suggested_event_id, 
                  timeout_value, NULL );

    if ( (event_id == ILLEGAL_TIMER_EVENT_ID) ||
         (event_id != suggested_event_id) )
         throw GeneralException(Snmp_Error, Snmp_Local_Error,__FILE__,__LINE__);

    return event_id;
}

// generates and returns a new event id
// associates the pair (event_id, waiting_message)
// creates the timer event
void Timer::SetMessageTimerEvent(WaitingMessage &waiting_message)
{
    CriticalSectionLock session_lock(session->session_CriticalSection);

    if ( !session_lock.GetLock(INFINITE) )
        return; // no use throwing exception

    TimerEventId event_id = session->timer_event_id;
    // register the timer event in both the instance CMap and the global CMap
    waiting_message_mapping.AddTail ( &waiting_message ) ;

    session_lock.UnLock();   

    CriticalSectionLock timer_lock(Timer::timer_CriticalSection);

    if ( !timer_lock.GetLock(INFINITE) )
        throw GeneralException ( Snmp_Error , Snmp_Local_Error,__FILE__,__LINE__ ) ;

    timer_mapping[event_id] = this;

    timer_lock.UnLock();   

}

// Removes the association (event_id, waiting_message)
// and also kills the registered timer event
void Timer::CancelMessageTimer(WaitingMessage &waiting_message,TimerEventId event_id)
{
    CriticalSectionLock session_lock(session->session_CriticalSection);

    if ( !session_lock.GetLock(INFINITE) )
        return; // no use throwing exception

    // remove the timer event from the instance CMap

    POSITION t_Position = waiting_message_mapping.GetHeadPosition () ;
    while ( t_Position )
    {
        POSITION t_OldPosition = t_Position ;
        WaitingMessage *t_Message = waiting_message_mapping.GetNext ( t_Position ) ;
        if ( t_Message == & waiting_message )
        {
            waiting_message_mapping.RemoveAt(t_OldPosition);
            break ;
        }
    }

    session_lock.UnLock();   

DebugMacro4( 

    SnmpDebugLog :: s_SnmpDebugLog->WriteFileAndLine (  

        __FILE__,__LINE__,
        L"Cancelled Message TimerEvent %d\n", event_id
    ) ;
)

}


// Removes the association (event_id, waiting_message)
// and also kills the registered timer event
void Timer::CancelTimer(TimerEventId event_id)
{
    CriticalSectionLock timer_lock(Timer::timer_CriticalSection);

    if ( !timer_lock.GetLock(INFINITE) )
        throw GeneralException ( Snmp_Error , Snmp_Local_Error,__FILE__,__LINE__ ) ;

    // remove the timer event from the global CMap
    timer_mapping.RemoveKey(event_id);

    timer_lock.UnLock();   

    SnmpKillTimer(NULL, event_id);

DebugMacro4( 

    SnmpDebugLog :: s_SnmpDebugLog->WriteFileAndLine (  

        __FILE__,__LINE__,
        L"Cancelled TimerEvent %d\n", event_id
    ) ;
)
}

// it determines the corresponding Timer and calls 
// its TimerEventNotification with the appropriate parameters
void CALLBACK Timer::HandleGlobalEvent(HWND hWnd ,UINT message,
                                       UINT_PTR idEvent, DWORD dwTime)
{
    CriticalSectionLock timer_lock(Timer::timer_CriticalSection);

    if ( !timer_lock.GetLock(INFINITE) )
        throw GeneralException ( Snmp_Error , Snmp_Local_Error,__FILE__,__LINE__ ) ;

    Timer *timer;
    TimerEventId event_id = idEvent;
    BOOL found = timer_mapping.Lookup(event_id, timer);

    timer_lock.UnLock();   

    // if no such timer event, return
    if ( !found )
        return;

    // let the timer handle the event
    timer->TimerEventNotification(event_id);

    return;
}


// used by the event handler to notify the timer event.
// it must notify the corresponding waiting message
void Timer::TimerEventNotification(TimerEventId event_id)
{
    CriticalSectionLock session_lock(session->session_CriticalSection);

    if ( !session_lock.GetLock(INFINITE) )
        return; // no use throwing exception

    WaitingMessage *waiting_message;

    // identify the waiting message corresponding to
    // the event_id. if no such event, ignore it

    POSITION t_Position = waiting_message_mapping.GetHeadPosition () ;
    while ( t_Position )
    {
        waiting_message = waiting_message_mapping.GetNext ( t_Position ) ;
        // notify the waiting message of the event
        waiting_message->TimerNotification();
    }

    // session_lock.UnLock();   The lock may be released at this point
}

// remove all the (timer_event_id, timer) associations
// from the static mapping data structure
Timer::~Timer(void)
{
    WaitingMessage *waiting_message;

    POSITION current = waiting_message_mapping.GetHeadPosition();

    while ( current != NULL )
    {
        waiting_message = waiting_message_mapping.GetNext(current);

        TimerEventId event_id = waiting_message->GetTimerEventId () ;

        CriticalSectionLock timer_lock(Timer::timer_CriticalSection);

        if ( !timer_lock.GetLock(INFINITE) )
            throw GeneralException ( Snmp_Error , Snmp_Local_Error,__FILE__,__LINE__ ) ;

        timer_mapping.RemoveKey ( event_id  );

        timer_lock.UnLock(); 

        SnmpKillTimer(NULL, event_id );
    }

    waiting_message_mapping.RemoveAll();
}

SnmpTimerObject :: SnmpTimerObject (

    HWND hWndArg,               // handle of window for timer messages
    UINT_PTR timerIdArg,            // timer identifier
    UINT elapsedArg,            // time-out value
    TIMERPROC lpTimerFuncArg    // address of timer procedure

) : hWnd ( hWndArg ) ,
    timerId ( timerIdArg ) ,
    lpTimerFunc ( lpTimerFuncArg ) 
{
    if ( ! window )
        window = new Window ;

    timerId = SetTimer ( 

        window->GetWindowHandle(), 
        timerId , 
        elapsedArg , 
        lpTimerFunc 
    ) ;

    CriticalSectionLock session_lock(Timer::timer_CriticalSection);

    if ( !session_lock.GetLock(INFINITE) )
        throw GeneralException ( Snmp_Error , Snmp_Local_Error,__FILE__,__LINE__ ) ;

    if ( timerId ) 
    {
        timerMap [ timerId ] = this ;
    }
    else
        throw GeneralException ( Snmp_Error , Snmp_Local_Error,__FILE__,__LINE__ ) ;
}

SnmpTimerObject :: ~SnmpTimerObject ()
{
    CriticalSectionLock session_lock(Timer::timer_CriticalSection);

    if ( !session_lock.GetLock(INFINITE) )
        throw GeneralException ( Snmp_Error , Snmp_Local_Error,__FILE__,__LINE__ ) ;

    timerMap.RemoveKey ( timerId );

    if (window)
    {
        KillTimer ( window->GetWindowHandle () , timerId ) ;
    }
}

void SnmpTimerObject :: TimerNotification ( HWND hWnd , UINT timerId )
{
    :: WaitPostMessage ( hWnd , Timer :: g_SnmpWmTimer , timerId , 0 ) ;
}

SnmpSetTimerObject :: SnmpSetTimerObject (

    HWND hWndArg,               // handle of window for timer messages
    UINT_PTR nIDEventArg,           // timer identifier
    UINT uElapseArg,            // time-out value
    TIMERPROC lpTimerFuncArg    // address of timer procedure

) : hWnd ( hWndArg ) ,
    timerId ( nIDEventArg ) ,
    elapsedTime ( uElapseArg ) ,
    lpTimerFunc ( lpTimerFuncArg ) 
{
}

SnmpSetTimerObject :: ~SnmpSetTimerObject ()
{
}

void SnmpSetTimerObject :: Process ()
{
    SnmpTimerObject *object = new SnmpTimerObject ( 

        hWnd ,
        timerId ,
        elapsedTime ,
        lpTimerFunc 
    ) ;

    Complete () ;
}

SnmpKillTimerObject :: SnmpKillTimerObject (

    HWND hWndArg ,              // handle of window that installed timer
    UINT_PTR uIDEventArg            // timer identifier

) : hWnd ( hWndArg ) ,
    timerId ( uIDEventArg ) , 
    status ( TRUE )
{
}

void SnmpKillTimerObject :: Process ()
{
    CriticalSectionLock session_lock(Timer::timer_CriticalSection);

    if ( !session_lock.GetLock(INFINITE) )
        throw GeneralException ( Snmp_Error , Snmp_Local_Error,__FILE__,__LINE__ ) ;

    SnmpTimerObject *object ;
    if ( SnmpTimerObject :: timerMap.Lookup ( timerId , object ) )
    {
        delete object ;
    }
    else
    {
        status = FALSE ;
    }

    Complete () ;   
}

UINT_PTR SnmpSetTimer (

    HWND hWnd,              // handle of window for timer messages
    UINT_PTR nIDEvent,          // timer identifier
    UINT uElapse,           // time-out value,
    TIMERPROC lpTimerFunc   // address of timer procedure
)
{
    SnmpSetTimerObject object ( hWnd , nIDEvent , uElapse , lpTimerFunc ) ;
    Timer :: g_timerThread->ScheduleTask ( object ) ;
    object.Exec () ;
    if ( object.Wait () )
    {
        Timer :: g_timerThread->ReapTask ( object ) ;
        return object.GetTimerId () ;
    }
    else
    {
        Timer :: g_timerThread->ReapTask ( object ) ;
        return FALSE ;
    }
}

BOOL SnmpKillTimer (

    HWND hWnd,      // handle of window that installed timer
    UINT_PTR uIDEvent   // timer identifier
)
{
    SnmpKillTimerObject object ( hWnd , uIDEvent ) ;

    Timer :: g_timerThread->ScheduleTask ( object ) ;

    object.Exec () ;
    if ( object.Wait () )
    {
        Timer :: g_timerThread->ReapTask ( object ) ;
        return object.GetStatus () ;
    }
    else
    {
        Timer :: g_timerThread->ReapTask ( object ) ;
        return FALSE ;
    }
}