//  worker.cpp
//
//      Implementation of the worker thread object
//

#include "priv.h"

// Do not build this file if on Win9X or NT4
#ifndef DOWNLEVEL_PLATFORM

#include "resource.h"
#include "worker.h"


//--------------------------------------------------------------------
//
//
//  CWorkerThread class
//
//
//--------------------------------------------------------------------


//  CWorkerThread constructor

CWorkerThread::CWorkerThread() : _cRef(1)
{
    ASSERT(NULL == _hthreadWorker);
    ASSERT(NULL == _hwndWorker);
    ASSERT(FALSE == _fKillWorker);
    ASSERT(NULL == _pwe);
    ASSERT(0 == _cRefLockWorker);

    InitializeCriticalSection(&_csWorker);
    DllAddRef();
}


//  CWorkerThread destructor

CWorkerThread::~CWorkerThread()
{
    ASSERT(0 == _cRefLockWorker);

    if (IsWindow(_hwndWorker))
    {
        DestroyWindow(_hwndWorker);
    }

    SetListenerWT(NULL);
    DeleteCriticalSection(&_csWorker);

    DllRelease();
}


BOOL CWorkerThread::PostWorkerMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    BOOL bRet = FALSE;

    _LockWorker();
    // Only if we are not being killed, and the worker window is valid we
    // post the message, we don't want to post to NULL window (desktop)
    if (!_fKillWorker && _hwndWorker)
        bRet = PostMessage(_hwndWorker, uMsg, wParam, lParam);
    _UnlockWorker();
    return bRet;
}

/*--------------------------------------------------------------------
Purpose: IUnknown::QueryInterface
*/
STDMETHODIMP CWorkerThread::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CWorkerThread, IARPWorker),
        { 0 },
    };

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


STDMETHODIMP_(ULONG) CWorkerThread::AddRef()
{
    LONG cRef = InterlockedIncrement(&_cRef);
    TraceAddRef(CWorkerThread, cRef);
    return cRef;
}


STDMETHODIMP_(ULONG) CWorkerThread::Release()
{
    LONG cRef = InterlockedDecrement(&_cRef);
    TraceRelease(CWorkerThread, cRef);
    if (cRef)
        return cRef;

    delete this;
    return 0;
}

/*-------------------------------------------------------------------------
Purpose: Sets the event listener so the worker thread can fire
         events incrementally.
*/
HRESULT CWorkerThread::SetListenerWT(IWorkerEvent * pwe)
{
    _LockWorker();
    {
        // We have to protect _pwe because it can be accessed by this
        // thread and the main thread.

        // Don't AddRef the event listener or we'll have a circular
        // reference.
        _pwe = pwe;
    }
    _UnlockWorker();

    return S_OK;
}


/*-------------------------------------------------------------------------
Purpose: Start the thread
*/
HRESULT CWorkerThread::StartWT(int iPriority)
{
    DWORD thid;     // Not used but we have to pass something in

    // Create a hidden top-level window to post messages to from the
    // worker thread
    _hwndWorker = SHCreateWorkerWindow(_WorkerWndProcWrapper, NULL, 0, 0, NULL, this);

    if (_hwndWorker)
    {
        AddRef(); // AddRef myself, the background thread is responsible of releasing
        // this ref count

        // Kick off the worker thread to do the slow enumeration.
        _hthreadWorker = CreateThread(NULL, 0,
                                      (LPTHREAD_START_ROUTINE)_ThreadStartProcWrapper,
                                      (LPVOID)this, CREATE_SUSPENDED, &thid);

        if (_hthreadWorker)
        {
            // Demote the priority so it doesn't interfere with the
            // initial HTML databinding.
            SetThreadPriority(_hthreadWorker, iPriority);
            ResumeThread(_hthreadWorker);
        }
        else
        {
            // Release my refcount in case of failure
            Release();

            // If we can't create the background thread, don't bother with the window
            DestroyWindow(_hwndWorker);
            _hwndWorker = NULL;
        }
    }
    return (_hthreadWorker != NULL) ? S_OK : E_FAIL;
}


/*-------------------------------------------------------------------------
Purpose: Kills the worker thread if one is around
*/

HRESULT CWorkerThread::KillWT(void)
{
    MSG msg;

    // I should never call KillWT to kill myself
    ASSERT(_hthreadWorker != GetCurrentThread());

    TraceMsg(TF_TASKS, "[%x] Killing worker thread...", _dwThreadId);

    // Tell the worker thread to stop when it can
    // Do this inside the critical section because we don't want random messages
    // get posted to the worker window after this.
    _LockWorker();
    _fKillWorker = TRUE;
    _UnlockWorker();

    // If we have no worker thread, nothing to do
    if (_hthreadWorker)
    {
        // Now wait for the worker to stop
        if (WaitForSingleObject(_hthreadWorker, 10000) == WAIT_TIMEOUT)
            TraceMsg(TF_ERROR, "[%x] Worker thread termination wait timed out!", _dwThreadId);
        else
            TraceMsg(TF_TASKS, "[%x] Worker thread wait exited cleanly", _dwThreadId);

        // Now that the thread is stopped, release our hold so all its memory can go away
        CloseHandle(_hthreadWorker);
        _hthreadWorker = NULL;
    }

    // Make sure that all messages to our worker HWND get processed
    if (_hwndWorker)
    {
        while (PeekMessage(&msg, _hwndWorker, 0, 0, PM_REMOVE))
            DispatchMessage(&msg);

        DestroyWindow(_hwndWorker);
        _hwndWorker = NULL;
    }
    SetListenerWT(NULL);

    return S_OK;
}



//--------------------------------------------------------------------
// Private methods



void CWorkerThread::_LockWorker(void)
{
    EnterCriticalSection(&_csWorker);
    DEBUG_CODE( _cRefLockWorker++; )
}

void CWorkerThread::_UnlockWorker(void)
{
    DEBUG_CODE( _cRefLockWorker--; )
    LeaveCriticalSection(&_csWorker);
}


/*-------------------------------------------------------------------------
Purpose: Static wndproc wrapper. Calls the real non-static WndProc.
*/
LRESULT
CALLBACK
CWorkerThread::_WorkerWndProcWrapper(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    if (uMsg == WM_DESTROY)
        SetWindowLongPtr(hwnd, 0, 0);
    else
    {
        CWorkerThread * pWorker = (CWorkerThread*)GetWindowLongPtr(hwnd, 0);
        if (pWorker)
            return pWorker->_WorkerWndProc(hwnd, uMsg, wParam, lParam);
    }

    return 0;
}


/*-------------------------------------------------------------------------
Purpose: Used to fire events back to Trident since they can't be fired from
         the worker thread.
*/
LRESULT
CWorkerThread::_WorkerWndProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch (uMsg)
    {
    case WORKERWIN_FIRE_ROW_READY:
        // Posted by worker thread when async data is ready
        _LockWorker();
        {
            if (_pwe)
            {
                TraceMsg(TF_TASKS, "[%x] Firing event for row #%d", _dwThreadId, wParam);
                _pwe->FireOnDataReady((LONG)wParam);
            }
        }
        _UnlockWorker();
        return 0;

    case WORKERWIN_FIRE_FINISHED:
        // Posted by worker thread when thread is finished enumerating
        // async data.
        _LockWorker();
        {
            if (_pwe)
            {
                TraceMsg(TF_TASKS, "[%x] Firing finished", _dwThreadId);
                _pwe->FireOnFinished();
            }
        }
        _UnlockWorker();
        return 0;

    case WORKERWIN_FIRE_DATASETCHANGED:
        // Posted by worker thread when matrix array finished enumerating
        _LockWorker();
        {
            if (_pwe)
            {
                TraceMsg(TF_TASKS, "[%x] Firing DatasetChanged", _dwThreadId);
                _pwe->FireOnDatasetChanged();
            }
        }
        _UnlockWorker();
        return 0;

    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


/*-------------------------------------------------------------------------
Purpose: Start and exit of worker thread to do slow app information
*/
DWORD
CALLBACK
CWorkerThread::_ThreadStartProcWrapper(
    LPVOID lpParam          // this pointer of object since wrapper is static
    )
{
    CWorkerThread * pwt = (CWorkerThread *)lpParam;

    pwt->_dwThreadId = GetCurrentThreadId();

    return pwt->_ThreadStartProc();
}


/*-------------------------------------------------------------------------
Purpose: Contains the code run on the worker thread where we get the slow
         information about applications
*/
DWORD
CWorkerThread::_ThreadStartProc()
{
    // Don't bother killing the worker window here, let the main thread take care
    // of the life time of the worker window.

    // Signal that we don't have a worker thread anymore. Prevents race
    // conditions.
    _fKillWorker = FALSE;

    TraceMsg(TF_TASKS, "[%x] Exiting worker thread", _dwThreadId);

    // Release the ref count to "this" object at end of thread, be it CMtxArray or CDataSrc  because we
    // AddRef()ed before this thread started.
    Release();
    return 0;
}

#endif //DOWNLEVEL_PLATFORM