#include "priv.h"
#include "schedule.h"

// debug stuff for tracking critical section owners.....
#ifdef DEBUG
#define DECLARE_CRITICAL_SECTION(x)         CRITICAL_SECTION x;                              \
                                            DWORD dwThread##x;

#define STATIC_DECLARE_CRITICAL_SECTION(x)  static CRITICAL_SECTION x;                      \
                                            static DWORD dwThread##x;

#define STATIC_INIT_CRITICAL_SECTION(c,x)   CRITICAL_SECTION c::x = {0};             \
                                            DWORD c::dwThread##x;

#define ASSERT_CRITICAL_SECTION(x)          ASSERT( dwThread##x == GetCurrentThreadId() );

#define OBJECT_ASSERT_CRITICAL_SECTION(o,x) ASSERT( o->dwThread##x == GetCurrentThreadId() );

#define ENTER_CRITICAL_SECTION(x)           EnterCriticalSection(&x);                        \
                                            dwThread##x = GetCurrentThreadId();

#define OBJECT_ENTER_CRITICAL_SECTION(o,x)  EnterCriticalSection(&o->x);                        \
                                            o->dwThread##x = GetCurrentThreadId();

#define LEAVE_CRITICAL_SECTION(x)           ASSERT_CRITICAL_SECTION(x);    \
                                            LeaveCriticalSection(&x);

#define OBJECT_LEAVE_CRITICAL_SECTION(o,x)  OBJECT_ASSERT_CRITICAL_SECTION(o,x);    \
                                            LeaveCriticalSection(&o->x);
#else
#define DECLARE_CRITICAL_SECTION(x)         CRITICAL_SECTION x;

#define STATIC_DECLARE_CRITICAL_SECTION(x)  static CRITICAL_SECTION x;

#define STATIC_INIT_CRITICAL_SECTION(c,x)   CRITICAL_SECTION c::x = {0};

#define ASSERT_CRITICAL_SECTION(x)

#define OBJECT_ASSERT_CRITICAL_SECTION(o,x)

#define ENTER_CRITICAL_SECTION(x)           EnterCriticalSection(&x);

#define OBJECT_ENTER_CRITICAL_SECTION(o,x)  EnterCriticalSection(&o->x);

#define LEAVE_CRITICAL_SECTION(x)           LeaveCriticalSection(&x);

#define OBJECT_LEAVE_CRITICAL_SECTION(o,x)  LeaveCriticalSection(&o->x);
#endif

#define TF_SCHEDULER     0x20

// struct to hold the details for each task that is to be executed....
struct TaskNode
{
    LPRUNNABLETASK pTask;
    TASKOWNERID toid;
    DWORD dwPriority;
    DWORD_PTR dwLParam;
    BOOL fSuspended;
};


class CShellTaskScheduler : public IShellTaskScheduler2
{
    public:
        CShellTaskScheduler( HRESULT * pHr );
        ~CShellTaskScheduler();

        STDMETHOD (QueryInterface) (REFIID riid, LPVOID * ppvObj );
        STDMETHOD_(ULONG, AddRef)( void );
        STDMETHOD_(ULONG,Release)( void );

        STDMETHOD (AddTask)(IRunnableTask * pTask,
                   REFTASKOWNERID rtoid,
                   DWORD_PTR lParam,
                   DWORD dwPriority );
        STDMETHOD (RemoveTasks)( REFTASKOWNERID rtoid,
                   DWORD_PTR dwLParam,
                   BOOL fWaitIfRunning );
        STDMETHOD (Status)( DWORD dwStatus, DWORD dwThreadTimeout );
        STDMETHOD_(UINT, CountTasks)(REFTASKOWNERID rtoid);

        STDMETHOD (AddTask2)(IRunnableTask * pTask,
                   REFTASKOWNERID rtoid,
                   DWORD_PTR lParam,
                   DWORD dwPriority,
                   DWORD grfFlags);
        STDMETHOD (MoveTask)(REFTASKOWNERID rtoid,
                   DWORD_PTR dwLParam,
                   DWORD dwPriority,
                   DWORD grfFlags );

    protected:

        // data held by a task scheduler to refer to the current worker that it has....
        struct WorkerData
        {
            BOOL Init(CShellTaskScheduler *pts);

            // this (pThis) is used to pass the controlling
            // object back and forth to the thread, so that threads can be moved
            // back and forth from objects as they need them.
            CShellTaskScheduler *   pThis;

#ifdef DEBUG
            DWORD                   dwThreadID;
#endif
        };

        friend UINT CShellTaskScheduler_ThreadProc( LPVOID pParam );
        friend int CALLBACK ListDestroyCallback( LPVOID p, LPVOID pData );

        VOID _KillScheduler( BOOL bKillCurTask );
        BOOL _WakeScheduler( void );

        BOOL _RemoveTasksFromList( REFTASKOWNERID rtoid, DWORD_PTR dwLParam );


        // create a worker thread data block that can be associated with a task scheduler....
        WorkerData * FetchWorker( void );

        // from a worker thread, let go of the scheduler it is associated...
        static BOOL ReleaseWorker( WorkerData * pThread );


        /***********PERINSTANCE DATA ************/
        DECLARE_CRITICAL_SECTION( m_csListLock )
        HDPA m_hTaskList;

        WorkerData * m_pWorkerThread;

        // the currently running task...
        TaskNode * m_pRunning;

        // a semaphore that counts, so that all waiters canbe released...
        HANDLE m_hCurTaskEnded;

        DWORD m_dwStatus;

        int m_iSignalCurTask;                // - tell the thread to signal when the
                                             //   current task is finished if non-zero
                                             //   the other thread will signal the 
                                             //   handle as many times as this variable
                                             //   holds.
        BOOL m_fEmptyQueueAndSleep;          // - tell the thread to empty itself and
                                             //   go to sleep (usually it is dying....

        int m_iGoToSleep;                    // - tell the tread to go to sleep without emptying the queue

        long m_cRef;

#ifdef DEBUG
        void AssertForNoOneWaiting( void )
        {
            // no one should be queued for waiting
            ASSERT( m_iSignalCurTask == 0 );

            // release the semaphore by zero to get the current count....
            LONG lPrevCount = 0;
            ReleaseSemaphore( m_hCurTaskEnded, 0, &lPrevCount );
            ASSERT( lPrevCount == 0 );
        };
#endif
        
        void IWantToKnowWhenCurTaskDone( void )
        {
            m_iSignalCurTask ++;
        };
};

// private messages sent to the scheduler thread...
#define WM_SCH_WAKEUP       WM_USER + 0x600
#define WM_SCH_TERMINATE    WM_USER + 0x601

STDAPI CShellTaskScheduler_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
    if ( pUnkOuter )
    {
        return CLASS_E_NOAGGREGATION;
    }

    HRESULT hr = NOERROR;
    CShellTaskScheduler * pScheduler = new CShellTaskScheduler( & hr );
    if ( !pScheduler )
    {
        return E_OUTOFMEMORY;
    }
    if ( FAILED( hr ))
    {
        delete pScheduler;
        return hr;
    }

    *ppunk = SAFECAST(pScheduler, IShellTaskScheduler *);
    return NOERROR;
}

// Global ExplorerTaskScheduler object that is used by multiple components.
IShellTaskScheduler * g_pTaskScheduler = NULL;


// This is the class factory routine for creating the one and only ExplorerTaskScheduler object.
// We have a static object (g_pTaskScheduler) that everyone who wants to use it shares.
STDAPI CSharedTaskScheduler_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
    HRESULT hr = NOERROR;

    if (pUnkOuter)
        return CLASS_E_NOAGGREGATION;

    ENTERCRITICAL;
    if (g_pTaskScheduler)
    {
        g_pTaskScheduler->AddRef();
    }
    else
    {
        hr = CShellTaskScheduler_CreateInstance(NULL, (LPUNKNOWN*)&g_pTaskScheduler, NULL);
        if (SUCCEEDED(hr))
        {
            // set timeout to be 1 minute.....
            g_pTaskScheduler->Status( ITSSFLAG_KILL_ON_DESTROY, 1 * 60 * 1000 );

            // keep an additional ref for us..
            g_pTaskScheduler->AddRef();
        }
    }

    *ppunk = SAFECAST(g_pTaskScheduler, IShellTaskScheduler*);
    LEAVECRITICAL;

    return hr;
}

STDAPI SHIsThereASystemScheduler( void )
{
    return ( g_pTaskScheduler ? S_OK : S_FALSE );
}

// use CoCreateInstance - thread pool removes need for global scheduler
STDAPI SHGetSystemScheduler( LPSHELLTASKSCHEDULER * ppScheduler )
{
    if ( !ppScheduler )
    {
        return E_INVALIDARG;
    }

    return CSharedTaskScheduler_CreateInstance(NULL, (IUnknown **)ppScheduler, NULL );
}

// use CoCreateInstance - thread pool removes need for global scheduler
STDAPI SHFreeSystemScheduler( void )
{
    TraceMsg(TF_SCHEDULER, "SHfss: g_pTaskSched=%x", g_pTaskScheduler);

    IShellTaskScheduler * pSched;

    ENTERCRITICAL;
    pSched = g_pTaskScheduler;
    g_pTaskScheduler = NULL;
    LEAVECRITICAL;
    if ( pSched )
    {
        // assume the scheduler is empty....
        pSched->RemoveTasks( TOID_NULL, ITSAT_DEFAULT_LPARAM, FALSE );

        pSched->Release();
    }
    return NOERROR;
}

#ifdef DEBUG
STDAPI_(void) SHValidateEmptySystemScheduler()
{
    if ( g_pTaskScheduler )
    {
        ASSERT( g_pTaskScheduler->CountTasks( TOID_NULL ) == 0 );
    }
}
#endif

int InsertInPriorityOrder( HDPA hTaskList, TaskNode * pNewNode, BOOL fBefore );
int CALLBACK ListDestroyCallback( LPVOID p, LPVOID pData )
{
    ASSERT( p != NULL );
    if ( ! p )
    {
        TraceMsg( TF_ERROR, "ListDestroyCallback() - p is NULL!" );
        return TRUE;
    }

    CShellTaskScheduler * pThis = (CShellTaskScheduler *) pData;
    ASSERT( pThis );
    if ( ! pThis )
    {
        TraceMsg( TF_ERROR, "ListDestroyCallback() - pThis is NULL!" );
        return TRUE;
    }

    TaskNode * pNode = (TaskNode *) p;
    ASSERT( pNode != NULL );
    ASSERT( pNode->pTask != NULL );

#ifdef DEBUG
    if ( pThis->m_pWorkerThread )
    {
        // notify the thread that we are emptying the list from here, so remove these
        // items from its mem track list
    }
#endif

    // if it is suspended, kill it. If it is not suspended, then it has
    // probably never been started..
    if ( pNode->fSuspended )
    {
        pNode->pTask->Kill( pThis->m_dwStatus == ITSSFLAG_COMPLETE_ON_DESTROY );
    }
    pNode->pTask->Release();
    delete pNode;

    return TRUE;
}

STDMETHODIMP CShellTaskScheduler::QueryInterface( REFIID riid, LPVOID * ppvObj )
{
    static const QITAB qit[] = {
        QITABENT(CShellTaskScheduler, IShellTaskScheduler),
        QITABENT(CShellTaskScheduler, IShellTaskScheduler2),
        { 0 },
    };

    return QISearch(this, qit, riid, ppvObj);
}

STDMETHODIMP_ (ULONG) CShellTaskScheduler::AddRef()
{
    InterlockedIncrement( &m_cRef );
    return m_cRef;
}
STDMETHODIMP_ (ULONG) CShellTaskScheduler::Release()
{
    if (0 == m_cRef)
    {
        AssertMsg(0, TEXT("CShellTaskScheduler::Release called too many times!"));
        return 0;
    }

    if (InterlockedDecrement( &m_cRef ) == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

CShellTaskScheduler::CShellTaskScheduler( HRESULT * pHr) : m_cRef(1)
{
    InitializeCriticalSection( &m_csListLock );

    ASSERT(m_pWorkerThread == NULL);
    ASSERT(m_pRunning == NULL);

    m_dwStatus = ITSSFLAG_COMPLETE_ON_DESTROY;

    // grow queue by five each time...
    m_hTaskList = DPA_Create( 5 );
    if ( !m_hTaskList )
    {
        *pHr = E_OUTOFMEMORY;
    }

    m_hCurTaskEnded = CreateSemaphoreWrap( NULL, 0, 0xffff, NULL );
    if ( !m_hCurTaskEnded )
    {
        *pHr = E_FAIL;
    }

    DllAddRef();

}

CShellTaskScheduler::~CShellTaskScheduler()
{
    // if we don't have a tasklist and semaphore (constructor failure), we can't have a workerthread
    ASSERT((m_hTaskList && m_hCurTaskEnded) || !m_pWorkerThread);

    // but if we have a task list...
    if ( m_hTaskList )
    {
        EnterCriticalSection( &m_csListLock );

        // if we have a background worker thread, then it MUST be doing something as we
        // are now in the crit section so it can't go away
        if ( m_pWorkerThread )
        {
            // we tell the object we need to know when it has done with its stuff....
            // we reuse the event we already have...
            m_fEmptyQueueAndSleep = TRUE;

#ifdef DEBUG
            AssertForNoOneWaiting();
#endif

            IWantToKnowWhenCurTaskDone();
            
            // tell the cur task to go away.....
            TraceMsg(TF_SCHEDULER, "(%x)csts.dtor: call _KillScheduler", GetCurrentThreadId());
            _KillScheduler( m_dwStatus == ITSSFLAG_KILL_ON_DESTROY );

            // free the thread. At this point there is always
            LeaveCriticalSection( &m_csListLock );

            TraceMsg(TF_SCHEDULER, "csts.dtor: call u.WFSMT(m_hCurTaskEnded=%x)", m_hCurTaskEnded);

            DWORD dwRes = SHWaitForSendMessageThread(m_hCurTaskEnded, INFINITE);
            ASSERT(dwRes == WAIT_OBJECT_0);
            TraceMsg(TF_SCHEDULER, "csts.dtor: u.WFSMT() done");

            ASSERT( !m_pWorkerThread );
        }
        else
        {
            LeaveCriticalSection( &m_csListLock );
        }

        // empty the list incase it is not empty (it should be)
        DPA_EnumCallback( m_hTaskList, ListDestroyCallback, this );
        DPA_DeleteAllPtrs( m_hTaskList );

        DPA_Destroy( m_hTaskList );
        m_hTaskList = NULL;
    }

    if ( m_hCurTaskEnded )
        CloseHandle( m_hCurTaskEnded );

    DeleteCriticalSection( &m_csListLock );

    DllRelease();

}

STDMETHODIMP CShellTaskScheduler::AddTask( IRunnableTask * pTask,
                                      REFTASKOWNERID rtoid,
                                      DWORD_PTR dwLParam,
                                      DWORD dwPriority )
{
    return AddTask2(pTask, rtoid, dwLParam, dwPriority, ITSSFLAG_TASK_PLACEINBACK);
}

STDMETHODIMP CShellTaskScheduler::AddTask2( IRunnableTask * pTask,
                                      REFTASKOWNERID rtoid,
                                      DWORD_PTR dwLParam,
                                      DWORD dwPriority,
                                      DWORD grfFlags )
{                                      
    if ( !pTask )
        return E_INVALIDARG;

    HRESULT hr = E_OUTOFMEMORY;   // assume failure

    TaskNode * pNewNode = new TaskNode;
    if ( pNewNode )
    {
        pNewNode->pTask = pTask;
        pTask->AddRef();
        pNewNode->toid = rtoid;
        pNewNode->dwPriority = dwPriority;
        pNewNode->dwLParam = dwLParam;
        pNewNode->fSuspended = FALSE;

        EnterCriticalSection( &m_csListLock );

        int iPos = -1;

        if (grfFlags & ITSSFLAG_TASK_PLACEINFRONT)
        {
            iPos = InsertInPriorityOrder( m_hTaskList, pNewNode, TRUE );
        }
        else if (grfFlags & ITSSFLAG_TASK_PLACEINBACK)
        {
            iPos = InsertInPriorityOrder( m_hTaskList, pNewNode, FALSE );
        }

        if ( iPos != -1 && m_pRunning )
        {
            if ( m_pRunning->dwPriority < dwPriority )
            {
                // try to suspend the current task. If this works, the task will
                // return to the scheduler with E_PENDING. It will then be added
                // suspended in the queue to be Resumed later....
                m_pRunning->pTask->Suspend();
            }
        }

        BOOL bRes = FALSE;

        if ( iPos != -1 )
        {
            // get a worker thread and awaken it...
            // we do this in the crit section because we need to test m_pWorkerThread and
            // to save us from releasing and grabbing it again...
            bRes = _WakeScheduler();

#ifdef DEBUG
            if ( bRes && m_pWorkerThread )
            {
                //
                // We are putting this memory block in a linked list and it will most likely be freed
                // from the background thread. Remove it from the per-thread memory list to avoid
                // detecting it as a memory leak.
                //
                // WARNING - WARNING - WARNING:
                // We cannot...
                // assume that when pTask is Released it will be deleted, so move it
                // to the other thread's memory list.
                // 
                // This will be incorrect some of the time and we don't want to investigate
                // fake leaks. -BryanSt
                //transfer_to_thread_memlist( m_pWorkerThread->dwThreadID, pNewNode->pTask );
            }
#endif
        }
        LeaveCriticalSection( &m_csListLock );

        // we failed to add it to the list
        if ( iPos == -1 )
        {
            // we failed to add it to the list, must have been a memory failure...
            pTask->Release();       // for the AddRef above
            delete pNewNode;
            goto Leave;
        }

        hr = bRes ? NOERROR : E_FAIL;
    }
Leave:
    return hr;
}

STDMETHODIMP CShellTaskScheduler::RemoveTasks( REFTASKOWNERID rtoid,
                                               DWORD_PTR dwLParam,
                                               BOOL fWaitIfRunning )
{
    BOOL fRemoveAll = IsEqualGUID( TOID_NULL, rtoid );
    BOOL fAllItems = (dwLParam == ITSAT_DEFAULT_LPARAM );
    BOOL fWaitOnHandle = FALSE;

    // note, this ignores the current
    EnterCriticalSection( &m_csListLock );

    _RemoveTasksFromList( rtoid, dwLParam );

    if ( m_pRunning && ( fWaitIfRunning || m_dwStatus == ITSSFLAG_KILL_ON_DESTROY ))
    {
        // kill the current task ...
        if (( fRemoveAll || IsEqualGUID( rtoid, m_pRunning->toid )) &&
            ( fAllItems || dwLParam == m_pRunning->dwLParam ))
        {
            ASSERT( m_pRunning->pTask );
            if ( m_dwStatus == ITSSFLAG_KILL_ON_DESTROY )
            {
                m_pRunning->pTask->Kill( fWaitIfRunning );
            }

            // definitive support for waiting until they are done...
            // (note, only do it is there is a task running, otherwise we'll sit
            // on a handle that will never fire)
            if ( fWaitIfRunning )
            {
                IWantToKnowWhenCurTaskDone();

                // don't use this directly outside of the cirtical section because it can change...
                ASSERT ( m_iSignalCurTask );

                fWaitOnHandle = TRUE;
                m_iGoToSleep++;
            }
        }
    }

    LeaveCriticalSection( &m_csListLock );

    // now wait if we need to......
    if ( fWaitOnHandle )
    {
        DWORD dwRes = SHWaitForSendMessageThread(m_hCurTaskEnded, INFINITE);
        ASSERT(dwRes == WAIT_OBJECT_0);

        EnterCriticalSection( &m_csListLock );

        // Remove tasks that might have been added while the last task was finishing
        _RemoveTasksFromList( rtoid, dwLParam );

        m_iGoToSleep--;
        // See if we need to wake the thread now.
        if ( m_iGoToSleep == 0 && DPA_GetPtrCount( m_hTaskList ) > 0 )
            _WakeScheduler();

        LeaveCriticalSection( &m_csListLock );
    }

    return NOERROR;
}

BOOL CShellTaskScheduler::_RemoveTasksFromList( REFTASKOWNERID rtoid, DWORD_PTR dwLParam )
{
    // assumes that we are already holding the critical section
    
    BOOL fRemoveAll = IsEqualGUID( TOID_NULL, rtoid );
    BOOL fAllItems = (dwLParam == ITSAT_DEFAULT_LPARAM );
    int iIndex = 0;

    do
    {
        TaskNode * pNode = (TaskNode *) DPA_GetPtr( m_hTaskList, iIndex );
        if ( !pNode )
        {
            break;
        }

        ASSERT( pNode );
        ASSERT( pNode->pTask );

        if (( fRemoveAll || IsEqualGUID( pNode->toid, rtoid )) && ( fAllItems || dwLParam == pNode->dwLParam ))
        {
            // remove it
            DPA_DeletePtr( m_hTaskList, iIndex );

            if ( pNode->fSuspended )
            {
                // kill it just incase....
                pNode->pTask->Kill( FALSE );
            }
            pNode->pTask->Release();
            delete pNode;
        }
        else
        {
            iIndex ++;
        }
    }
    while ( TRUE );
    return TRUE;
}

//
// CShellTaskScheduler::MoveTask
//
STDMETHODIMP CShellTaskScheduler::MoveTask( REFTASKOWNERID rtoid,
                                            DWORD_PTR dwLParam,
                                            DWORD dwPriority,
                                            DWORD grfFlags )
{
    int  iInsert;
    int  iIndex;
    BOOL fMoveAll  = IsEqualGUID( TOID_NULL, rtoid );
    BOOL fAllItems = (dwLParam == ITSAT_DEFAULT_LPARAM );
    BOOL bMatch    = FALSE ;
    int  iIndexStart;
    int  iIndexInc;

    EnterCriticalSection( &m_csListLock );

    // Init direction of search
    if (grfFlags & ITSSFLAG_TASK_PLACEINFRONT)
    {
        iIndexStart = 0;
        iInsert = DPA_GetPtrCount( m_hTaskList );
        iIndexInc = 1;
    }
    else if (grfFlags & ITSSFLAG_TASK_PLACEINBACK)
    {
        iIndexStart = iInsert = DPA_GetPtrCount( m_hTaskList );
        iIndexInc = -1;
    }

    // Find insert point (based on priority)
    iIndex = 0;
    do
    {
        TaskNode * pNode = (TaskNode *) DPA_GetPtr( m_hTaskList, iIndex );
        if ( !pNode )
        {
            break;
        }

        if (grfFlags & ITSSFLAG_TASK_PLACEINFRONT)
        {
            if (pNode->dwPriority <= dwPriority)
            {
                iInsert = iIndex;
                break;
            }
        }
        else if (grfFlags & ITSSFLAG_TASK_PLACEINBACK)
        {
            if (pNode->dwPriority > dwPriority)
            {
                iInsert = iIndex;
            }
            else
            {
                break;
            }
        }

        iIndex++;
    }
    while (TRUE);

    // Now try and locate any items.
    iIndex = iIndexStart;
    do
    {
        TaskNode * pNode = (TaskNode *) DPA_GetPtr( m_hTaskList, iIndex );
        if ( !pNode )
        {
            break;
        }

        if (( fMoveAll || IsEqualGUID( pNode->toid, rtoid )) && 
            ( fAllItems || dwLParam == pNode->dwLParam ))
        {
            bMatch = TRUE;

            // Can we move this node?
            if ( iIndex != iInsert )
            {
                int iPos = DPA_InsertPtr( m_hTaskList, iInsert, pNode );
                if (iPos != -1)
                {
                    if ( iIndex > iInsert )
                    {
                        DPA_DeletePtr( m_hTaskList, iIndex + 1);  // Will have shifted one
                    }
                    else
                    {
                        DPA_DeletePtr( m_hTaskList, iIndex);
                    }
                }
            }
        }
        iIndex += iIndexInc;
    }
    while ( !bMatch );
    
    LeaveCriticalSection( &m_csListLock );

    return (bMatch ? S_OK : S_FALSE);
}

BOOL CShellTaskScheduler::_WakeScheduler( )
{
    // assume we are in the object's critsection.....

    if ( NULL == m_pWorkerThread )
    {
        // we need a worker quick ....
        m_pWorkerThread = FetchWorker();
    }

    return ( NULL != m_pWorkerThread );
}

VOID CShellTaskScheduler::_KillScheduler( BOOL bKillCurTask )
{
    // assumes that we are already holding the critical section
    if ( m_pRunning != NULL && bKillCurTask )
    {
        ASSERT( m_pRunning->pTask );

        // tell the currently running task that it should die
        // quickly, because we are a separate thread than the
        // one that is running the task, it can be notified
        m_pRunning->pTask->Kill( FALSE );
    }
}

UINT CShellTaskScheduler_ThreadProc( LPVOID pParam )
{
    // make sure we have a message QUEUE // BOGUS - why do we need this?
    MSG msg;
    PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE );

    ASSERT( pParam );

    HRESULT hrInit = SHCoInitialize();

    CShellTaskScheduler::WorkerData * pWorker = (CShellTaskScheduler::WorkerData *) pParam;
    DWORD dwRes = 0;

    TraceMsg(TF_SCHEDULER, "(?%x)ShellTaskScheduler::Thread Started", GetCurrentThreadId());

#ifdef DEBUG
    pWorker->dwThreadID = GetCurrentThreadId();
#endif

    // figure out who we are attatched to (where the queue is we get tasks from)
    CShellTaskScheduler * pThis = pWorker->pThis;

    // we must always have a valid parent object at this point....
    ASSERT( pThis && IS_VALID_WRITE_PTR( pThis, CShellTaskScheduler ));

    do
    {
        MSG msg;
        HRESULT hr = NOERROR;
        TaskNode * pTask = NULL;

        OBJECT_ENTER_CRITICAL_SECTION( pThis, m_csListLock );

        // this means we are being told to quit...
        if ( pThis->m_fEmptyQueueAndSleep )
        {
            // we are being told to empty the queue .....
            DPA_EnumCallback( pThis->m_hTaskList, ListDestroyCallback, pThis );
            DPA_DeleteAllPtrs( pThis->m_hTaskList );
        }
        else if ( !pThis->m_iGoToSleep )
        {
            // get the first item...
            pTask = (TaskNode *) DPA_GetPtr( pThis->m_hTaskList, 0 );
        }

        if ( pTask )
        {
            // remove from the list...
            DPA_DeletePtr( pThis->m_hTaskList, 0 );
        }
        pThis->m_pRunning = pTask;

        OBJECT_LEAVE_CRITICAL_SECTION( pThis, m_csListLock );

        if ( pTask == NULL )
        {
            // cache the scheduler pointer, as we need it to leave the crit section
            CShellTaskScheduler * pScheduler = pThis;

            // queue is empty, go back on the thread pool.....
            // we are about to enter a deep deep sleep/coma, so remove us from the object....
            OBJECT_ENTER_CRITICAL_SECTION( pScheduler, m_csListLock );

            HANDLE hSleep = pThis->m_fEmptyQueueAndSleep ? pThis->m_hCurTaskEnded : NULL;
            BOOL fEmptyAndLeave = pThis->m_fEmptyQueueAndSleep;

            // make sure they didn't just add something to the queue, or have we been asked to go to sleep
            if ( pThis->m_iGoToSleep || DPA_GetPtrCount( pThis->m_hTaskList ) == 0)
            {
                if ( CShellTaskScheduler::ReleaseWorker( pWorker ))
                {
                    pThis = NULL;
                }
            }
            OBJECT_LEAVE_CRITICAL_SECTION( pScheduler, m_csListLock );

            if ( pThis && !fEmptyAndLeave )
            {
                // they must have added something at the last moment...
                continue;
            }

            // we are being emptied, tell them we are no longer attatched....
            if ( hSleep )
            {
                ReleaseSemaphore( hSleep, 1, NULL);
            }

            break;
        }
        else
        {
#ifndef DEBUG
            //__try
            {
#endif
                if ( pTask->fSuspended )
                {
                    pTask->fSuspended = FALSE;
                    hr = pTask->pTask->Resume();
                }
                else
                {
                    // run the task...
                    hr = pTask->pTask->Run( );
                }
#ifndef DEBUG
            }
            //__except( EXCEPTION_EXECUTE_HANDLER )
           // {
                // ignore it.... and pray we are fine...
            //}
           // __endexcept
#endif

            BOOL fEmptyQueue;
            OBJECT_ENTER_CRITICAL_SECTION( pThis, m_csListLock );
            {
                pThis->m_pRunning = NULL;

                // check to see if we have been asked to notify them
                // on completion....
                // NOTE: the NOT clause is needed so that we release ourselves
                // NOTE: and signal them at the right point, if we do it here,
                // NOTE: they leave us stranded, delete the crit section and
                // NOTE: we fault.
                if ( pThis->m_iSignalCurTask && !pThis->m_fEmptyQueueAndSleep )
                {
                    LONG lPrevCount = 0;

                    // release all those that are waiting. (we are using a semaphore
                    // because we are a free threaded object and God knows how many
                    // threads are waiting, and he passed on the information in the
                    // iSignalCurTask variable
                    ReleaseSemaphore( pThis->m_hCurTaskEnded, pThis->m_iSignalCurTask, &lPrevCount );

                    // reset the count.
                    pThis->m_iSignalCurTask = 0;
                }
                fEmptyQueue = pThis->m_fEmptyQueueAndSleep;
            }
            OBJECT_LEAVE_CRITICAL_SECTION( pThis, m_csListLock );

            if ( hr != E_PENDING || fEmptyQueue )
            {
                ULONG cRef = pTask->pTask->Release();
                delete pTask;
                pTask = NULL;
            }

            // empty the message queue...
            while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ))
            {
                {
#ifdef DEBUG
                    if (msg.message == WM_ENDSESSION)
                        TraceMsg(TF_SCHEDULER, "(?%x)csts.tp: peek #2 got WM_ENDESSION", GetCurrentThreadId());
#endif
                    TranslateMessage( &msg );
                    DispatchMessage( &msg );
                }
            }

            ASSERT( pThis && IS_VALID_WRITE_PTR( pThis, CShellTaskScheduler ));

            // the task must have been suspended because a higher priority
            // task has been added to the queue..... (this only works if the
            // task supports the Suspend() method).
            if ( hr == E_PENDING && pTask && !fEmptyQueue )
            {
                // put the task on the Suspended Queue ....
                pTask->fSuspended = TRUE;
                OBJECT_ENTER_CRITICAL_SECTION( pThis, m_csListLock );
                int iIndex = InsertInPriorityOrder( pThis->m_hTaskList, pTask, TRUE );
                OBJECT_LEAVE_CRITICAL_SECTION( pThis, m_csListLock );

                if ( iIndex == -1 )
                {
                    // we are so low on memory, kill it...
                    pTask->pTask->Kill( FALSE );
                    pTask->pTask->Release();
                    delete pTask;
                }
                pTask = NULL;
           }
       }
    }
    while ( TRUE );

    TraceMsg(TF_SCHEDULER, "(?%x)ShellTaskScheduler::Thread Ended", GetCurrentThreadId());
    SHCoUninitialize(hrInit);

    return 0;
}


STDMETHODIMP CShellTaskScheduler::Status( DWORD dwStatus, DWORD dwThreadTimeout )
{
    m_dwStatus = dwStatus & ITSSFLAG_FLAGS_MASK;
    if ( dwThreadTimeout != ITSS_THREAD_TIMEOUT_NO_CHANGE )
    {
/*
 * We don't support thread termination or pool timeout any more

        if ( dwStatus & ITSSFLAG_THREAD_TERMINATE_TIMEOUT )
        {
            m_dwThreadRlsKillTimeout = dwThreadTimeout;
        }
        else if ( dwStatus & ITSSFLAG_THREAD_POOL_TIMEOUT )
        {
            CShellTaskScheduler::s_dwComaTimeout = dwThreadTimeout;
        }
*/
    }
    return NOERROR;
}

STDMETHODIMP_(UINT) CShellTaskScheduler::CountTasks(REFTASKOWNERID rtoid)
{
    UINT iMatch = 0;
    BOOL fMatchAll = IsEqualGUID( TOID_NULL, rtoid );

    ENTER_CRITICAL_SECTION( m_csListLock );
    if ( fMatchAll )
    {
        iMatch = DPA_GetPtrCount( m_hTaskList );
    }
    else
    {
        int iIndex = 0;
        do
        {
            TaskNode * pNode = (TaskNode * )DPA_GetPtr( m_hTaskList, iIndex ++ );
            if ( !pNode )
            {
                break;
            }

            if ( IsEqualGUID( pNode->toid, rtoid ))
            {
                iMatch ++;
            }
        }
        while ( TRUE );
    }

    if ( m_pRunning )
    {
        if ( fMatchAll || IsEqualGUID( rtoid, m_pRunning->toid ))
        {
            iMatch ++;
        }
    }

    LEAVE_CRITICAL_SECTION( m_csListLock );

    return iMatch;

}


////////////////////////////////////////////////////////////////////////////////////
int InsertInPriorityOrder( HDPA hTaskList, TaskNode * pNewNode, BOOL fStart )
{
    // routine assumes that we are thread safe, therfore grab the crit-section
    // prior to calling this function

    int iPos = -1;
    int iIndex = 0;
    do
    {
        TaskNode * pNode = (TaskNode *) DPA_GetPtr( hTaskList, iIndex );
        if ( !pNode )
        {
            break;
        }

        // the fStart allows us to either add it before other tasks of the same
        // priority or after.
        if ((( pNode->dwPriority < pNewNode->dwPriority ) && !fStart ) || (( pNode->dwPriority <= pNewNode->dwPriority ) && fStart ))
        {
            iPos = DPA_InsertPtr( hTaskList, iIndex, pNewNode );
            break;
        }
        iIndex ++;
    }
    while ( TRUE );

    if ( iPos == -1 )
    {
        // add item to end of list...
        iPos = DPA_AppendPtr( hTaskList, pNewNode );
    }

    return iPos;
}


CShellTaskScheduler::WorkerData * CShellTaskScheduler::FetchWorker()
{
    WorkerData * pWorker = new WorkerData;

    if ( pWorker )
    {
        // call to Shlwapi thread pool
        if ( pWorker->Init(this) && SHQueueUserWorkItem( (LPTHREAD_START_ROUTINE)CShellTaskScheduler_ThreadProc,
                                                     pWorker,
                                                     0,
                                                     (DWORD_PTR)NULL,
                                                     (DWORD_PTR *)NULL,
                                                     "browseui.dll",
                                                     TPS_LONGEXECTIME | TPS_DEMANDTHREAD
                                                     ) )
        {
            return pWorker;
        }
        else
            delete pWorker;
    }

    return NULL;
}


// used by main thread proc to release its link the the task scheduler because it
// has run out of things to do....
BOOL CShellTaskScheduler::ReleaseWorker( WorkerData * pWorker )
{
    ASSERT( pWorker && IS_VALID_WRITE_PTR( pWorker, WorkerData ));

    CShellTaskScheduler * pThis = pWorker->pThis;

    OBJECT_ASSERT_CRITICAL_SECTION( pThis, m_csListLock );

    ASSERT( pWorker && IS_VALID_WRITE_PTR( pWorker, CShellTaskScheduler::WorkerData ));

    if ( DPA_GetPtrCount( pThis->m_hTaskList ) > 0 )
    {
        // something was added to the queue at the last minute....
        return FALSE;
    }

    // we assume we have entered the critsection of pThis
    pThis->m_pWorkerThread = NULL;
    pWorker->pThis = NULL;

    delete pWorker;

    return TRUE;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CShellTaskScheduler::WorkerData::Init(CShellTaskScheduler *pts)
{
    pThis = pts;

    return TRUE;
}
