/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    tpswait.h

Abstract:

    Wait classes. Moved out of tpsclass.h

    Contents:
        CWait
        CWaitRequest
        CWaitAddRequest
        CWaitRemoveRequest
        CWaitThreadInfo

Author:

    Richard L Firth (rfirth) 08-Aug-1998

Revision History:

    08-Aug-1998 rfirth
        Created

--*/

//
// forward declarations
//

class CWaitThreadInfo;

//
// classes
//

//
// CWait
//

class CWait : public CTimedListEntry {

private:

    HANDLE m_hObject;
    WAITORTIMERCALLBACKFUNC m_pCallback;
    LPVOID m_pContext;
    CWaitThreadInfo * m_pThreadInfo;
    DWORD m_dwFlags;

public:

    CWait(HANDLE hObject,
          WAITORTIMERCALLBACKFUNC pCallback,
          LPVOID pContext,
          DWORD dwWaitTime,
          DWORD dwFlags,
          CWaitThreadInfo * pInfo
          ) : CTimedListEntry(dwWaitTime) {
        m_hObject = hObject;
        m_pCallback = pCallback;
        m_pContext = pContext;
        m_pThreadInfo = pInfo;
        m_dwFlags = dwFlags;
    }

    CWait() {
    }

    CWait * Next(VOID) {
        return (CWait *)CTimedListEntry::Next();
    }

    CWaitThreadInfo * GetThreadInfo(VOID) const {
        return m_pThreadInfo;
    }

    VOID Execute(BOOL bTimeout) {

        //
        // execute function in this thread if required to do so, else we run
        // the callback in a non-I/O worker thread
        //

        //
        // APPCOMPAT - can't do this: the callback types for Wait & Work requests
        //          are different: one takes 2 parameters, the other one. We
        //          can't make this change until this issue is resolved with
        //          NT guys
        //

        //if (m_dwFlags & WT_EXECUTEINWAITTHREAD) {
            m_pCallback(m_pContext, bTimeout != 0);
        //} else {
        //
        //    //
        //    //  would have to allocate object from heap to hold callback
        //    //          function, context & bTimeout parameters in order to pass
        //    //          them to worker thread (we only have access to one APC
        //    //          parameter and we'd have to nominate different APC)
        //    //
        //
        //    Ie_QueueUserWorkItem((LPTHREAD_START_ROUTINE)m_pCallback,
        //                         m_pContext,
        //                         FALSE
        //                         );
        //}
    }

    HANDLE GetHandle(VOID) const {
        return m_hObject;
    }

    BOOL IsNoRemoveItem(VOID) {
        return (m_dwFlags & SRWSO_NOREMOVE) ? TRUE : FALSE;
    }
};

//
// CWaitRequest
//

class CWaitRequest {

private:

    BOOL m_bCompleted;
    CWait * m_pWait;

public:

    CWaitRequest() {
        m_bCompleted = FALSE;
    }

    CWaitRequest(CWait * pWait) {
        m_bCompleted = FALSE;
        m_pWait = pWait;
    }

    VOID SetComplete(VOID) {
        m_bCompleted = TRUE;
    }

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

    VOID SetWaitPointer(CWait * pWait) {
        m_pWait = pWait;
    }

    CWait * GetWaitPointer(VOID) const {
        return m_pWait;
    }
};

//
// CWaitAddRequest
//

class CWaitAddRequest : public CWait, public CWaitRequest {

public:

    CWaitAddRequest(HANDLE hObject,
                    WAITORTIMERCALLBACKFUNC pCallback,
                    LPVOID pContext,
                    DWORD dwWaitTime,
                    DWORD dwFlags,
                    CWaitThreadInfo * pInfo
                    ) :
                    CWait(hObject, pCallback, pContext, dwWaitTime, dwFlags, pInfo),
                    CWaitRequest()
    {
    }
};

//
// CWaitRemoveRequest
//

class CWaitRemoveRequest : public CWaitRequest {

public:

    CWaitRemoveRequest(HANDLE hWait) : CWaitRequest((CWait *)hWait) {
    }
};

//
// CWaitThreadInfo
//

class CWaitThreadInfo : public CDoubleLinkedList, public CCriticalSection {

private:

    HANDLE m_hThread;
    DWORD m_dwObjectCount;
    HANDLE m_Objects[MAXIMUM_WAIT_OBJECTS];
    CWait * m_pWaiters[MAXIMUM_WAIT_OBJECTS];
    CWait m_Waiters[MAXIMUM_WAIT_OBJECTS];
    CDoubleLinkedList m_FreeList;
    CDoubleLinkedList m_WaitList;

public:

    CWaitThreadInfo(CDoubleLinkedList * pList) {
        CDoubleLinkedList::Init();
        m_hThread = NULL;
        m_dwObjectCount = 0;
        m_FreeList.Init();
        m_WaitList.Init();
        for (int i = 0; i < ARRAY_ELEMENTS(m_Waiters); ++i) {
            m_Waiters[i].InsertTail(&m_FreeList);
        }
        InsertHead(pList);
    }

    VOID SetHandle(HANDLE hThread) {
        m_hThread = hThread;
    }

    HANDLE GetHandle(VOID) const {
        return m_hThread;
    }

    DWORD GetObjectCount(VOID) const {
        return m_dwObjectCount;
    }

    BOOL IsAvailableEntry(VOID) const {
        return m_dwObjectCount < ARRAY_ELEMENTS(m_Objects);
    }

    BOOL IsInvalidHandle(DWORD dwIndex) {

        ASSERT(dwIndex < m_dwObjectCount);

        //
        // GetHandleInformation() doesn't exist on Win95
        //
        //
        //DWORD dwHandleFlags;
        //
        //return !GetHandleInformation(m_Objects[dwIndex], &dwHandleFlags);

        DWORD status = WaitForSingleObject(m_Objects[dwIndex], 0);

        if ((status == WAIT_FAILED) && (GetLastError() == ERROR_INVALID_HANDLE)) {
//#if DBG
//char buf[128];
//wsprintf(buf, "IsInvalidHandle(%d): handle %#x is invalid\n", dwIndex, m_Objects[dwIndex]);
//OutputDebugString(buf);
//#endif
            return TRUE;
        }
        return FALSE;
    }

    VOID Compress(DWORD dwIndex, DWORD dwCount = 1) {

        ASSERT(dwCount != 0);
        ASSERT((int)m_dwObjectCount > 0);

        if ((dwIndex + dwCount) < m_dwObjectCount) {
            RtlMoveMemory(&m_Objects[dwIndex],
                          &m_Objects[dwIndex + dwCount],
                          sizeof(m_Objects[0]) * (m_dwObjectCount - (dwIndex + dwCount))
                          );
            RtlMoveMemory(&m_pWaiters[dwIndex],
                          &m_pWaiters[dwIndex + dwCount],
                          sizeof(m_pWaiters[0]) * (m_dwObjectCount - (dwIndex + dwCount))
                          );
        }
        m_dwObjectCount -= dwCount;
    }

    VOID Expand(DWORD dwIndex) {
        RtlMoveMemory(&m_Objects[dwIndex],
                      &m_Objects[dwIndex + 1],
                      sizeof(m_Objects[0]) * (m_dwObjectCount - dwIndex)
                      );
        RtlMoveMemory(&m_pWaiters[dwIndex],
                      &m_pWaiters[dwIndex + 1],
                      sizeof(m_pWaiters[0]) * (m_dwObjectCount - dwIndex)
                      );
        ++m_dwObjectCount;

        ASSERT(m_dwObjectCount <= ARRAY_ELEMENTS(m_Objects));
    }

    //DWORD BuildList(VOID) {
    //
    //    //
    //    // PERF: only rebuild from changed index
    //    //
    //
    //    m_dwObjectCount = 0;
    //    for (CWait * pWait = (CWait *)m_WaitList.Next();
    //         pWait = pWait->Next();
    //         !m_WaitList.IsHead(pWait)) {
    //        m_pWaiters[m_dwObjectCount] = pWait;
    //        m_Objects[m_dwObjectCount] = pWait->GetHandle();
    //        ++m_dwObjectCount;
    //    }
    //    return GetWaitTime();
    //}

    DWORD Wait(DWORD dwTimeout = INFINITE) {

        //
        // if no objects in list, sleep alertably for the timeout period
        //

        if (m_dwObjectCount == 0) {
            SleepEx(dwTimeout, TRUE);
            return WAIT_IO_COMPLETION;
        }

        //
        // else wait alertably for the timeout period
        //

        ASSERT(m_dwObjectCount <= ARRAY_ELEMENTS(m_Objects));

        return WaitForMultipleObjectsEx(m_dwObjectCount,
                                        m_Objects,
                                        FALSE,  // fWaitAll
                                        dwTimeout,
                                        TRUE    // fAlertable
                                        );
    }

    DWORD GetWaitTime(VOID) {

        ASSERT(m_dwObjectCount <= ARRAY_ELEMENTS(m_Objects));

        if (m_dwObjectCount != 0) {

            CWait * pWaiter = m_pWaiters[0];
            DWORD dwWaitTime = pWaiter->GetWaitTime();

            if (dwWaitTime != INFINITE) {

                DWORD dwTimeNow = GetTickCount();
                DWORD dwTimeStamp = pWaiter->GetTimeStamp();

                if (dwTimeNow > dwTimeStamp + dwWaitTime) {

                    //
                    // first object expired already
                    //

                    return 0;
                }

                //
                // number of milliseconds until next waiter expires
                //

                return (dwTimeStamp + dwWaitTime) - dwTimeNow;
            }
        }

        //
        // nothing in list
        //

        return INFINITE;
    }

    CWait * GetFreeWaiter(VOID) {
        return (CWait *)m_FreeList.RemoveHead();
    }

    VOID InsertWaiter(CWait * pWait) {

        DWORD dwIndex = 0;
        BOOL bAtEnd = TRUE;
        CDoubleLinkedListEntry * pHead = m_WaitList.Head();

        ASSERT(m_dwObjectCount <= ARRAY_ELEMENTS(m_Objects));

        if ((m_dwObjectCount != 0) && !pWait->IsInfiniteTimeout()) {

            //
            // not infinite timeout. Find place in list to insert this object
            //

            //
            // PERF: typically, new wait will be longer than most currently in
            //       list, so should start from end of non-infinite timeouts
            //       and work backwards
            //

            for (; dwIndex < m_dwObjectCount; ++dwIndex) {
                if (pWait->ExpiryTime() < m_pWaiters[dwIndex]->ExpiryTime()) {
                    pHead = m_pWaiters[dwIndex]->Head();
                    bAtEnd = (dwIndex == (m_dwObjectCount - 1));
                    break;
                }
            }
        }

        //
        // insert the new wait object at the correct location
        //

        pWait->InsertTail(pHead);
        if (!bAtEnd && (m_dwObjectCount != 0)) {
            Expand(dwIndex);
        } else {
            dwIndex = m_dwObjectCount;
            ++m_dwObjectCount;
        }

        //
        // update object list and pointer list
        //

        m_Objects[dwIndex] = pWait->GetHandle();
        m_pWaiters[dwIndex] = pWait;
    }

    VOID RemoveWaiter(CWait * pWait, DWORD dwIndex) {

        //
        // remove the waiter from the wait list and add it back to the
        // free list
        //

        pWait->Remove();
        pWait->InsertTail(&m_FreeList);

        //
        // if the object was not at the end of the list then compress
        // the list
        //

        if (dwIndex != (m_dwObjectCount - 1)) {
            Compress(dwIndex, 1);
        } else {
            --m_dwObjectCount;
        }
    }

    VOID RemoveWaiter(DWORD dwIndex) {

        ASSERT(m_dwObjectCount <= ARRAY_ELEMENTS(m_Objects));

        RemoveWaiter(m_pWaiters[dwIndex], dwIndex);
    }

    BOOL RemoveWaiter(CWait * pWait) {

        ASSERT(m_dwObjectCount <= ARRAY_ELEMENTS(m_Objects));

        for (DWORD dwIndex = 0; dwIndex < m_dwObjectCount; ++dwIndex) {
            if (m_pWaiters[dwIndex] == pWait) {
                RemoveWaiter(pWait, dwIndex);
                return TRUE;
            }
        }
        return FALSE;
    }

    VOID ProcessTimeouts(VOID) {

        DWORD dwTimeNow = GetTickCount();
        DWORD dwCount = 0;

        ASSERT(m_dwObjectCount <= ARRAY_ELEMENTS(m_Objects));

        while (dwCount < m_dwObjectCount) {

            CWait * pWait = m_pWaiters[dwCount];

            //
            // if waiter has expired, invoke its callback then remove it from
            // the wait list and add back to the free list
            //

            if (pWait->IsTimedOut(dwTimeNow)) {
                pWait->Execute(TRUE);
                pWait->Remove();
                pWait->InsertTail(&m_FreeList);
                ++dwCount;
            } else {

                //
                // quit loop at first non-timed-out entry
                //

                break;
            }
        }

        ASSERT(dwCount != 0);

        if (dwCount != 0) {
            Compress(0, dwCount);
        }
    }

    VOID PurgeInvalidHandles(VOID) {

        DWORD dwCount = 0;
        DWORD dwIndex = 0;
        DWORD dwIndexStart = 0;

        ASSERT(m_dwObjectCount <= ARRAY_ELEMENTS(m_Objects));

        while (dwIndex < m_dwObjectCount) {

            CWait * pWait = m_pWaiters[dwIndex];

            //
            // if handle has become invalid, invoke the callback then remove it
            // from the wait list and add back to the free list
            //

            if (IsInvalidHandle(dwIndex)) {
                pWait->Execute(FALSE);
                pWait->Remove();
                pWait->InsertTail(&m_FreeList);
                if (dwIndexStart == 0) {
                    dwIndexStart = dwIndex;
                }
                ++dwCount;
            } else if (dwCount != 0) {
                Compress(dwIndexStart, dwCount);
                dwIndex = dwIndexStart - 1;
                dwIndexStart = 0;
                dwCount = 0;
            }
            ++dwIndex;
        }
        if (dwCount != 0) {
            Compress(dwIndexStart, dwCount);
        }
    }

    VOID ProcessCompletion(DWORD dwIndex) {

        CWait * pWait = m_pWaiters[dwIndex];

        pWait->Execute(FALSE);
        if (!pWait->IsNoRemoveItem()) {
            RemoveWaiter(dwIndex);
        }
    }
};
