/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    tpstimer.h

Abstract:

    Timer classes. Moved out of tpsclass.h

    Contents:
        CTimer
        CTimerQueueEntry
        CTimerQueueList
        CTimerQueue
        CTimerRequest
        CTimerQueueDeleteRequest
        CTimerAddRequest
        CTimerChangeRequest
        CTimerCancelRequest

Author:

    Richard L Firth (rfirth) 08-Aug-1998

Revision History:

    08-Aug-1998 rfirth
        Created

--*/

//
// manifests
//

#define TPS_TIMER_IN_USE    0x80000000
#define TPS_TIMER_CANCELLED 0x40000000

//
// external data
//

extern LONG g_UID;

//
// classes
//

//
// CTimer
//

class CTimer {

private:

    HANDLE m_hQueue;    // address of owning queue
    HANDLE m_hTimer;    // timer ordinal
    WAITORTIMERCALLBACKFUNC m_pfnCallback;
    LPVOID m_pContext;
    DWORD m_dwPeriod;
    DWORD m_dwFlags;

public:

    CTimer(HANDLE hQueue,
           WAITORTIMERCALLBACKFUNC pfnCallback,
           LPVOID pContext,
           DWORD dwPeriod,
           DWORD dwFlags
           )
    {
        m_hQueue = hQueue;

        //
        // FEATURE - (prevent this scenario) not industrial-strength: can have 2 timers with same ID
        //

        m_hTimer = IntToPtr(InterlockedIncrement(&g_UID));
        m_pfnCallback = pfnCallback;
        m_pContext = pContext;
        m_dwPeriod = dwPeriod;
        m_dwFlags = dwFlags;
    }

    HANDLE GetHandle(VOID) const {
        return m_hTimer;
    }

    HANDLE GetQueue(VOID) const {
        return m_hQueue;
    }

    VOID Execute(VOID) {
        if (m_dwFlags & TPS_EXECUTEIO) {

            //
            // NT code does nothing with this flag. We should queue
            // request to I/O worker thread
            //

            ASSERT(!(m_dwFlags & TPS_EXECUTEIO));

        }
        m_pfnCallback(m_pContext, TRUE);
    }

    VOID SetPeriod(DWORD dwPeriod) {
        m_dwPeriod = dwPeriod;
    }

    DWORD GetPeriod(VOID) const {
        return m_dwPeriod;
    }

    BOOL IsOneShot(VOID) {
        return GetPeriod() == 0;
    }

    VOID SetInUse(VOID) {
        m_dwFlags |= TPS_TIMER_IN_USE;
    }

    VOID ResetInUse(VOID) {
        m_dwFlags &= ~TPS_TIMER_IN_USE;
    }

    BOOL IsInUse(VOID) {
        return (m_dwFlags & TPS_TIMER_IN_USE) ? TRUE : FALSE;
    }

    VOID SetCancelled(VOID) {
        m_dwFlags |= TPS_TIMER_CANCELLED;
    }

    BOOL IsCancelled(VOID) {
        return (m_dwFlags & TPS_TIMER_CANCELLED) ? TRUE : FALSE;
    }
};

//
// CTimerQueueEntry
//

class CTimerQueueEntry : public CTimedListEntry, public CTimer {

private:

public:

    CDoubleLinkedList m_TimerList;

    CTimerQueueEntry(HANDLE hQueue,
                     WAITORTIMERCALLBACKFUNC pfnCallback,
                     LPVOID pContext,
                     DWORD dwDueTime,
                     DWORD dwPeriod,
                     DWORD dwFlags
                     ) :
                     CTimedListEntry(dwDueTime),
                     CTimer(hQueue,
                            pfnCallback,
                            pContext,
                            dwPeriod,
                            dwFlags
                            )
    {
        CDoubleLinkedListEntry::Init();
        m_TimerList.Init();
    }

    ~CTimerQueueEntry() {
        m_TimerList.Remove();
    }

    VOID SetPeriodicTime(VOID) {
        SetTimeStamp(ExpiryTime());
        SetWaitTime(GetPeriod());
    }

    CDoubleLinkedList * TimerListHead(VOID) {
        return m_TimerList.Head();
    }
};

//
// CTimerQueueList
//

class CTimerQueueList {

private:

    CDoubleLinkedList m_QueueList;
    CDoubleLinkedList m_TimerList;

public:

    VOID Init(VOID) {
        m_QueueList.Init();
        m_TimerList.Init();
    }

    CDoubleLinkedList * QueueListHead(VOID) {
        return m_QueueList.Head();
    }

    CDoubleLinkedList * TimerListHead(VOID) {
        return m_TimerList.Head();
    }

    CDoubleLinkedListEntry * FindQueue(CDoubleLinkedListEntry * pEntry) {
        return m_QueueList.FindEntry(pEntry);
    }

    BOOL Wait(VOID) {

        DWORD dwWaitTime = INFINITE;
        CTimedListEntry * pTimer = (CTimedListEntry * )m_TimerList.Next();

        ASSERT(pTimer != NULL);

        if (pTimer != (CTimedListEntry * )m_TimerList.Head()) {
            dwWaitTime = pTimer->TimeToWait();
        }

        //
        //  HACKHACK (tnoonan):  Can't just check for 0 since
        //  Win95 will always return WAIT_TIMEOUT (despite what
        //  the docs say).
        //

        DWORD dwResult = SleepEx(dwWaitTime, TRUE);

        return (dwResult == 0) || (dwResult == WAIT_TIMEOUT);
    }

    VOID ProcessCompletions(VOID) {

        //
        // run down list of all timers; for each expired timer, execute its
        // completion handler. If one-shot timer, delete it, else reset the
        // timer and re-insert it in the list
        //
        // If a timer is re-inserted further down the list, we may visit it
        // again before we have completed the traversal. This is OK: either it
        // has already expired, in which case we execute again, or it hasn't
        // expired, in which case we terminate the traversal
        //

        CTimerQueueEntry * pTimer;
        CTimerQueueEntry * pNext = (CTimerQueueEntry *)m_TimerList.Next();

        do {
            pTimer = pNext;
            if ((pTimer == (CTimerQueueEntry *)m_TimerList.Head())
            || !pTimer->IsTimedOut()) {
                break;
            }
            pNext = (CTimerQueueEntry * )pTimer->Next();
            pTimer->Remove();
            pTimer->SetInUse();
            pTimer->Execute();
            if (pTimer->IsOneShot() || pTimer->IsCancelled()) {
                delete pTimer;
            } else {
                pTimer->SetPeriodicTime();
                pTimer->ResetInUse();
                pTimer->InsertBack(m_TimerList.Head());
            }
        } while (TRUE);
    }
};

//
// CTimerQueue
//

class CTimerQueue : public CDoubleLinkedList {

private:

    CDoubleLinkedList m_TimerList;

public:

    CTimerQueue(CTimerQueueList * pList) {
        CDoubleLinkedList::Init();
        m_TimerList.Init();
        InsertTail(pList->QueueListHead());
    }

    ~CTimerQueue() {
        Remove();
    }

    CDoubleLinkedList * TimerListHead(VOID) {
        return m_TimerList.Head();
    }

    CTimerQueueEntry * FindTimer(HANDLE hTimer) {

        CDoubleLinkedListEntry * pEntry;

        for (pEntry = m_TimerList.Next();
             pEntry != m_TimerList.Head();
             pEntry = pEntry->Next()) {

            CTimerQueueEntry * pTimer;

            pTimer = CONTAINING_RECORD(pEntry, CTimerQueueEntry, m_TimerList);
            if (pTimer->GetHandle() == hTimer) {
                return pTimer;
            }
        }
        return NULL;
    }

    VOID DeleteTimers(VOID) {

        CDoubleLinkedListEntry * pEntry;

        for (pEntry = m_TimerList.Next();
             pEntry != m_TimerList.Head();
             pEntry = m_TimerList.Next()) {

            CTimerQueueEntry * pTimer;

            pTimer = CONTAINING_RECORD(pEntry, CTimerQueueEntry, m_TimerList);

            //
            // remove timer from global timer list (linked on CDoubleLinkedList)
            //

            pTimer->Remove();

            //
            // timer will be removed from m_TimerList by its destructor
            //

            delete pTimer;
        }
    }
};

//
// CTimerRequest
//

class CTimerRequest {

private:

    BOOL m_bCompleted;
    DWORD m_dwStatus;

public:

    CTimerRequest() {
        m_bCompleted = FALSE;
        m_dwStatus = ERROR_SUCCESS;
    }

    VOID SetComplete(VOID) {
        m_bCompleted = TRUE;
    }

    VOID WaitForCompletion(VOID) {
        while (!m_bCompleted) {
            SleepEx(0, TRUE);
        }
    }

    VOID SetStatus(DWORD dwStatus) {
        m_dwStatus = dwStatus;
    }

    VOID SetCompletionStatus(DWORD dwStatus) {
        SetStatus(dwStatus);
        SetComplete();
    }

    BOOL SetThreadStatus(VOID) {
        if (m_dwStatus == ERROR_SUCCESS) {
            return TRUE;
        }
        SetLastError(m_dwStatus);
        return FALSE;
    }
};

//
// CTimerQueueDeleteRequest
//

class CTimerQueueDeleteRequest : public CTimerRequest {

private:

    HANDLE m_hQueue;

public:

    CTimerQueueDeleteRequest(HANDLE hQueue) : CTimerRequest() {
        m_hQueue = hQueue;
    }

    HANDLE GetQueue(VOID) const {
        return m_hQueue;
    }
};

//
// CTimerAddRequest
//

class CTimerAddRequest : public CTimerQueueEntry, public CTimerRequest {

public:

    CTimerAddRequest(HANDLE hQueue,
                     WAITORTIMERCALLBACKFUNC pfnCallback,
                     LPVOID pContext,
                     DWORD dwDueTime,
                     DWORD dwPeriod,
                     DWORD dwFlags
                     ) :
                     CTimerQueueEntry(hQueue,
                                      pfnCallback,
                                      pContext,
                                      dwDueTime,
                                      dwPeriod,
                                      dwFlags
                                      ),
                     CTimerRequest()
    {
    }

    HANDLE GetHandle(VOID) const {
        return CTimer::GetHandle();
    }

    CTimerQueue * GetQueue(VOID) const {
        return (CTimerQueue *)CTimer::GetQueue();
    }
};

//
// CTimerChangeRequest
//

class CTimerChangeRequest : public CTimerRequest {

private:

    HANDLE m_hQueue;
    HANDLE m_hTimer;
    DWORD m_dwDueTime;
    DWORD m_dwPeriod;

public:

    CTimerChangeRequest(HANDLE hQueue,
                        HANDLE hTimer,
                        DWORD dwDueTime,
                        DWORD dwPeriod
                        ) :
                        CTimerRequest()
    {
        m_hQueue = hQueue;
        m_hTimer = hTimer;
        m_dwDueTime = dwDueTime;
        m_dwPeriod = dwPeriod;
    }

    HANDLE GetQueue(VOID) const {
        return m_hQueue;
    }

    HANDLE GetTimer(VOID) const {
        return m_hTimer;
    }

    DWORD GetDueTime(VOID) const {
        return m_dwDueTime;
    }

    DWORD GetPeriod(VOID) const {
        return m_dwPeriod;
    }
};

//
// CTimerCancelRequest
//

class CTimerCancelRequest : public CTimerRequest {

private:

    HANDLE m_hQueue;
    HANDLE m_hTimer;

public:

    CTimerCancelRequest(HANDLE hQueue, HANDLE hTimer) : CTimerRequest() {
        m_hQueue = hQueue;
        m_hTimer = hTimer;
    }

    HANDLE GetQueue(VOID) const {
        return m_hQueue;
    }

    HANDLE GetTimer(VOID) const {
        return m_hTimer;
    }
};