//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 1995
//
//  File:       proccomm.cxx
//
//  Contents:   Implementation of the CProcessComm class
//
//----------------------------------------------------------------------------

#include "headers.hxx"

DeclareTag(tagProcComm, "MTScript", "IScriptedProcess communication");

CProcessComm::CProcessComm(CMTScript *pMT)
{
    _ulRefs = 1;

    _pMT = pMT;

    Assert(_pSink == NULL);
    Assert(_pSH == NULL);
    Assert(_pProc == NULL);
    TraceTag((tagProcComm, "CProcessComm this(%x)", this));
}

CProcessComm::~CProcessComm()
{
    TraceTag((tagProcComm, "%p: Destroyed this(%x)", _pProc, this));

    if (_pProc)
    {
        _pProc->SetProcComm(NULL);
    }

    ReleaseInterface(_pSink);
    ReleaseInterface(_pSH);
    ReleaseInterface(_pProc);
}

HRESULT
CProcessComm::QueryInterface(REFIID iid, void **ppv)
{
    if (iid == IID_IUnknown || iid == IID_IScriptedProcess)
    {
        *ppv = (IScriptedProcess *)this;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    ((IUnknown *)*ppv)->AddRef();
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Member:     CProcessComm::SetProcessID, public
//
//  Synopsis:   Called by the remote process to tell us who it is.  We use
//              the information to match it with the CProcessThread and
//              CScriptHost object that created it.
//
//  Arguments:  [lProcessID] -- Process ID of the calling process
//              [pszEnvID]   -- Value of the __MTSCRIPT_ENV_ID environment
//                              variable.
//
//  Returns:    S_OK, E_INVALIDARG if a match could not be made.
//
//----------------------------------------------------------------------------

STDMETHODIMP
CProcessComm::SetProcessID(long lProcessID, wchar_t *pszEnvID)
{
    CProcessThread **ppProc;
    int              cProcs;
    long             lEnvID;
    wchar_t         *pch;

    if (!pszEnvID)
    {
        return E_INVALIDARG;
    }

    if (_pProc)
    {
        TraceTag((tagProcComm, "%p: Got duplicate call to SetProcessID! (id=%d)", _pProc, lProcessID));
        return E_UNEXPECTED;
    }

    lEnvID = wcstol(pszEnvID, &pch, 10);

    LOCK_LOCALS(_pMT);

    for (ppProc = _pMT->_aryProcesses, cProcs = _pMT->_aryProcesses.Size();
         cProcs;
         ppProc++, cProcs--)
    {
        if ((*ppProc)->IsOwner(lProcessID, lEnvID))
        {
            break;
        }
    }

    if (cProcs == 0)
    {
        return E_INVALIDARG;
    }

    // We don't allow more than one process to connect to a single
    // CProcessThread. This could happen if more than one child process of
    // the one we launched with RunLocalCommand tries to connect. All but the
    // first one will get an error back.

    if ((*ppProc)->GetProcComm())
    {
        TraceTag((tagProcComm, "%p: Got duplicate call to SetProcessID! (id=%d)", *ppProc, lProcessID));
        return E_UNEXPECTED;
    }

    _pProc = *ppProc;
    _pProc->AddRef();

    _pSH = _pProc->ScriptHost();
    _pSH->AddRef();

    _pProc->SetProcComm(this);

    TraceTag((tagProcComm, "Proc:%p, this:%p: Received SetProcessID call: %d, %ls", _pProc, this, lProcessID, pszEnvID));

    _pMT->PostToThread(_pSH, MD_PROCESSCONNECTED, &_pProc, sizeof(CProcessComm*));

    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Member:     CProcessComm::SendData, public
//
//  Synopsis:   Called by the remote process when it wants to fire an event
//              into the script.
//
//  Arguments:  [pszType]  -- String giving name of data
//              [pszData]  -- String giving data
//              [plReturn] -- Return value from event handler
//
//  Returns:    HRESULT
//
//  Notes:      This uses the same data structures as CMachine::Exec and is
//              basically the same code.
//
//----------------------------------------------------------------------------

STDMETHODIMP
CProcessComm::SendData(wchar_t * pszType,
                       wchar_t * pszData,
                       long    * plReturn)
{
    // We create an event object for each call on this method. While this
    // may have a cost, it makes this method thread-safe. If we cached an
    // event object then we would have to synchronize access to that event
    // object which could be even more expensive.
    MACHPROC_EVENT_DATA   med;
    MACHPROC_EVENT_DATA * pmed;
    VARIANT           vRet;
    VARIANT           vLong;
    HRESULT           hr = S_OK;

    TraceTag((tagProcComm, "%p: SendData call received: (%ls, %ls)", _pProc, pszType, pszData));

    VariantInit(&vRet);
    VariantInit(&vLong);

    if (!_pSH)
    {
        return E_UNEXPECTED;
    }

    if (!plReturn)
    {
        return E_INVALIDARG;
    }

    med.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (med.hEvent == NULL)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    med.bstrCmd     = SysAllocString(pszType);
    med.bstrParams  = SysAllocString(pszData);
    med.dwProcId    = _pProc->ProcId();
    med.pvReturn    = &vRet;
    med.dwGITCookie = 0;
    med.hrReturn    = S_OK;

    pmed = &med;

    _pMT->PostToThread(_pSH,
                       MD_PROCESSDATA,
                       (LPVOID)&pmed,
                       sizeof(MACHPROC_EVENT_DATA*));

    // We can do WaitForSingleObject because we are in OLE's multi-threaded
    // apartment and don't need to handle messages from our event loop.
    WaitForSingleObject(med.hEvent, INFINITE);

    if (med.hrReturn != S_OK)
    {
        hr = med.hrReturn;
        goto Cleanup;
    }

    if (VariantChangeType(&vLong, &vRet, 0, VT_I4) != S_OK)
    {
        *plReturn = -1;
    }
    else
    {
        *plReturn = V_I4(&vLong);
    }

Cleanup:
    VariantClear(&vRet);
    VariantClear(&vLong);

    SysFreeString(med.bstrCmd);
    SysFreeString(med.bstrParams);

    CloseHandle(med.hEvent);

    TraceTag((tagProcComm, "%p: SendData is returning %x", _pProc, hr));

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     CProcessComm::SetExitCode, public
//
//  Synopsis:   Sets the exit code which will be given to the script for
//              this process. This will override the actual exit code of
//              the process.
//
//  Arguments:  [lExitCode] -- New exit code.
//
//----------------------------------------------------------------------------

STDMETHODIMP
CProcessComm::SetExitCode(long lExitCode)
{
    if (!_pProc)
    {
        return E_UNEXPECTED;
    }

    TraceTag((tagProcComm, "%p: Process set exit code of %d", _pProc, lExitCode));

    _pProc->SetExitCode((DWORD)lExitCode);

    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Member:     CProcessComm::SetProcessSink, public
//
//  Synopsis:   Sets the sink interface so the script can call back into
//              the remote process.
//
//  Arguments:  [pSink] -- Sink interface
//
//  Returns:    HRESULT
//
//  Notes:      Clear the sink by calling SetProcessSink with NULL before
//              shutting down.
//
//----------------------------------------------------------------------------

STDMETHODIMP
CProcessComm::SetProcessSink(IScriptedProcessSink *pSink)
{
    ReleaseInterface(_pSink);

    _pSink = pSink;

    TraceTag((tagProcComm, "%p: Received new process sink (%p)", _pProc, pSink));

    if (pSink)
    {
        pSink->AddRef();
    }

    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Member:     CProcessComm::SendToProcess, public
//
//  Synopsis:   Sends a command to the remote process as requested by the
//              script engine.
//
//  Arguments:  [pmed] -- Pointer to cross-thread data structure.
//
//----------------------------------------------------------------------------

void
CProcessComm::SendToProcess(MACHPROC_EVENT_DATA *pmed)
{
    long    lReturn     = 0;
    HRESULT hr          = S_OK;

    TraceTag((tagProcComm, "%p: Making call to ReceiveData. Params=(%ls, %ls)",
              _pProc, pmed->bstrCmd, pmed->bstrParams));

    if (_pSink)
    {
        hr = _pSink->ReceiveData(pmed->bstrCmd, pmed->bstrParams, &lReturn);
    }

    V_VT(pmed->pvReturn) = VT_I4;
    V_I4(pmed->pvReturn) = lReturn;

    TraceTag((tagProcComm, "%p: Call to ReceiveData returned %x", _pProc, hr));

    pmed->hrReturn = hr;

    SetEvent(pmed->hEvent);

    return;
}