/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS WDM Streaming ActiveMovie Proxy
 * FILE:            dll/directx/ksproxy/clockforward.cpp
 * PURPOSE:         IKsClockForwarder interface
 *
 * PROGRAMMERS:     Johannes Anderwald (johannes.anderwald@reactos.org)
 */
#include "precomp.h"

const GUID IID_IKsClockForwarder              = {0x877e4352, 0x6fea, 0x11d0, {0xb8, 0x63, 0x00, 0xaa, 0x00, 0xa2, 0x16, 0xa1}};

DWORD WINAPI CKsClockForwarder_ThreadStartup(LPVOID lpParameter);

class CKsClockForwarder : public IDistributorNotify,
                          public IKsObject
{
public:
    STDMETHODIMP QueryInterface( REFIID InterfaceId, PVOID* Interface);

    STDMETHODIMP_(ULONG) AddRef()
    {
        InterlockedIncrement(&m_Ref);
        return m_Ref;
    }
    STDMETHODIMP_(ULONG) Release()
    {
        InterlockedDecrement(&m_Ref);

        if (!m_Ref)
        {
            delete this;
            return 0;
        }
        return m_Ref;
    }

    // IDistributorNotify interface
    HRESULT STDMETHODCALLTYPE Stop();
    HRESULT STDMETHODCALLTYPE Pause();
    HRESULT STDMETHODCALLTYPE Run(REFERENCE_TIME tStart);
    HRESULT STDMETHODCALLTYPE SetSyncSource(IReferenceClock *pClock);
    HRESULT STDMETHODCALLTYPE NotifyGraphChange();

    // IKsObject interface
    HANDLE STDMETHODCALLTYPE KsGetObjectHandle();

    CKsClockForwarder(HANDLE handle);
    virtual ~CKsClockForwarder(){};
    HRESULT STDMETHODCALLTYPE SetClockState(KSSTATE State);
protected:
    LONG m_Ref;
    HANDLE m_Handle;
    IReferenceClock * m_Clock;
    HANDLE m_hEvent;
    HANDLE m_hThread;
    BOOL m_ThreadStarted;
    BOOL m_PendingStop;
    BOOL m_ForceStart;
    KSSTATE m_State;
    REFERENCE_TIME m_Time;

    friend DWORD WINAPI CKsClockForwarder_ThreadStartup(LPVOID lpParameter);
};

CKsClockForwarder::CKsClockForwarder(
    HANDLE handle) : m_Ref(0),
                     m_Handle(handle),
                     m_Clock(0),
                     m_hEvent(NULL),
                     m_hThread(NULL),
                     m_ThreadStarted(FALSE),
                     m_PendingStop(FALSE),
                     m_ForceStart(FALSE),
                     m_State(KSSTATE_STOP),
                     m_Time(0)
{
}

HRESULT
STDMETHODCALLTYPE
CKsClockForwarder::QueryInterface(
    IN  REFIID refiid,
    OUT PVOID* Output)
{
    if (IsEqualGUID(refiid, IID_IUnknown))
    {
        *Output = PVOID(this);
        reinterpret_cast<IUnknown*>(*Output)->AddRef();
        return NOERROR;
    }
    if (IsEqualGUID(refiid, IID_IKsObject) ||
        IsEqualGUID(refiid, IID_IKsClockForwarder))
    {
        *Output = (IKsObject*)(this);
        reinterpret_cast<IKsObject*>(*Output)->AddRef();
        return NOERROR;
    }

    if (IsEqualGUID(refiid, IID_IDistributorNotify))
    {
        *Output = (IDistributorNotify*)(this);
        reinterpret_cast<IDistributorNotify*>(*Output)->AddRef();
        return NOERROR;
    }

    return E_NOINTERFACE;
}

//-------------------------------------------------------------------
// IDistributorNotify interface
//


HRESULT
STDMETHODCALLTYPE
CKsClockForwarder::Stop()
{
#ifdef KSPROXY_TRACE
    WCHAR Buffer[200];
    swprintf(Buffer, L"CKsClockForwarder::Stop m_ThreadStarted %u m_PendingStop %u m_hThread %p m_hEvent %p m_Handle %p\n", m_ThreadStarted, m_PendingStop, m_hThread, m_hEvent, m_Handle);
    OutputDebugStringW(Buffer);
#endif

    m_Time = 0;
    if (m_ThreadStarted)
    {
        // signal pending stop
        m_PendingStop = true;

        assert(m_hThread);
        assert(m_hEvent);

        // set stop event
        SetEvent(m_hEvent);

        // wait untill the thread has finished
        WaitForSingleObject(m_hThread, INFINITE);

        // close thread handle
        CloseHandle(m_hThread);

        // zero handle
        m_hThread = NULL;
    }

    if (m_hEvent)
    {
        // close stop event
        CloseHandle(m_hEvent);
        m_hEvent = NULL;
    }

    m_PendingStop = false;

    SetClockState(KSSTATE_STOP);
    return NOERROR;
}

HRESULT
STDMETHODCALLTYPE
CKsClockForwarder::Pause()
{
#ifdef KSPROXY_TRACE
    OutputDebugString("CKsClockForwarder::Pause\n");
#endif

    if (!m_hEvent)
    {
        m_hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
        if (!m_hEvent)
            return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, GetLastError());
    }

    if (m_State <= KSSTATE_PAUSE)
    {
        if (m_State == KSSTATE_STOP)
            SetClockState(KSSTATE_ACQUIRE);

        if (m_State == KSSTATE_ACQUIRE)
            SetClockState(KSSTATE_PAUSE);
    }
    else
    {
        if (!m_ForceStart)
        {
            SetClockState(KSSTATE_PAUSE);
        }
    }

    if (!m_hThread)
    {
        m_hThread = CreateThread(NULL, 0, CKsClockForwarder_ThreadStartup, (LPVOID)this, 0, NULL);
        if (!m_hThread)
            return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, GetLastError());
    }

    return NOERROR;
}

HRESULT
STDMETHODCALLTYPE
CKsClockForwarder::Run(
    REFERENCE_TIME tStart)
{
#ifdef KSPROXY_TRACE
    OutputDebugString("CKsClockForwarder::Run\n");
#endif

    m_Time = tStart;

    if (!m_hEvent || !m_hThread)
    {
        m_ForceStart = TRUE;
        HRESULT hr = Pause();
        m_ForceStart = FALSE;

        if (FAILED(hr))
            return hr;
    }

    assert(m_hThread);

    SetClockState(KSSTATE_RUN);
    SetEvent(m_hEvent);

    return NOERROR;
}

HRESULT
STDMETHODCALLTYPE
CKsClockForwarder::SetSyncSource(
    IReferenceClock *pClock)
{
#ifdef KSPROXY_TRACE
    OutputDebugString("CKsClockForwarder::SetSyncSource\n");
#endif

    if (pClock)
        pClock->AddRef();

    if (m_Clock)
        m_Clock->Release();


    m_Clock = pClock;
    return NOERROR;
}

HRESULT
STDMETHODCALLTYPE
CKsClockForwarder::NotifyGraphChange()
{
#ifdef KSPROXY_TRACE
    OutputDebugString("CKsClockForwarder::NotifyGraphChange\n");
#endif

    return NOERROR;
}

//-------------------------------------------------------------------
// IKsObject interface
//

HANDLE
STDMETHODCALLTYPE
CKsClockForwarder::KsGetObjectHandle()
{
    return m_Handle;
}

//-------------------------------------------------------------------
HRESULT
STDMETHODCALLTYPE
CKsClockForwarder::SetClockState(KSSTATE State)
{
    KSPROPERTY Property;
    ULONG BytesReturned;

    Property.Set = KSPROPSETID_Clock;
    Property.Id = KSPROPERTY_CLOCK_STATE;
    Property.Flags = KSPROPERTY_TYPE_SET;

    HRESULT hr = KsSynchronousDeviceControl(m_Handle, IOCTL_KS_PROPERTY, (PVOID)&Property, sizeof(KSPROPERTY), &State, sizeof(KSSTATE), &BytesReturned);
    if (SUCCEEDED(hr))
        m_State = State;

#ifdef KSPROXY_TRACE
    WCHAR Buffer[100];
    swprintf(Buffer, L"CKsClockForwarder::SetClockState m_State %u State %u hr %lx\n", m_State, State, hr);
    OutputDebugStringW(Buffer);
#endif

    return hr;
}

DWORD
WINAPI
CKsClockForwarder_ThreadStartup(LPVOID lpParameter)
{
    REFERENCE_TIME Time;
    ULONG BytesReturned;

    CKsClockForwarder * Fwd = (CKsClockForwarder*)lpParameter;

    Fwd->m_ThreadStarted = TRUE;

    do
    {
        if (Fwd->m_PendingStop)
            break;

        if (Fwd->m_State != KSSTATE_RUN)
            WaitForSingleObject(Fwd->m_hEvent, INFINITE);

        KSPROPERTY Property;
        Property.Set = KSPROPSETID_Clock;
        Property.Id = KSPROPERTY_CLOCK_TIME;
        Property.Flags = KSPROPERTY_TYPE_SET;

        Fwd->m_Clock->GetTime(&Time);
        Time -= Fwd->m_Time;

        KsSynchronousDeviceControl(Fwd->m_Handle, IOCTL_KS_PROPERTY, (PVOID)&Property, sizeof(KSPROPERTY), &Time, sizeof(REFERENCE_TIME), &BytesReturned);
    }
    while(TRUE);

    Fwd->m_ThreadStarted = FALSE;
    return NOERROR;
}

HRESULT
WINAPI
CKsClockForwarder_Constructor(
    IUnknown * pUnkOuter,
    REFIID riid,
    LPVOID * ppv)
{
    HRESULT hr;
    HANDLE handle;

#ifdef KSPROXY_TRACE
    OutputDebugStringW(L"CKsClockForwarder_Constructor\n");
#endif

    // open default clock
    hr = KsOpenDefaultDevice(KSCATEGORY_CLOCK, GENERIC_READ | GENERIC_WRITE, &handle);

    if (hr != NOERROR)
    {
#ifdef KSPROXY_TRACE
         OutputDebugString("CKsClockForwarder_Constructor failed to open device\n");
#endif
         return hr;
    }

    CKsClockForwarder * clock = new CKsClockForwarder(handle);

    if (!clock)
    {
        // free clock handle
        CloseHandle(handle);
        return E_OUTOFMEMORY;
    }

    if (FAILED(clock->QueryInterface(riid, ppv)))
    {
        /* not supported */
        delete clock;
        return E_NOINTERFACE;
    }

    return NOERROR;
}