/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS BDA Proxy
 * FILE:            dll/directx/bdaplgin/classfactory.cpp
 * PURPOSE:         ClassFactory interface
 *
 * PROGRAMMERS:     Johannes Anderwald (johannes.anderwald@reactos.org)
 */

#include "precomp.h"

#ifndef _MSC_VER
const GUID KSPROPSETID_BdaPinControl = {0x0ded49d5, 0xa8b7, 0x4d5d, {0x97, 0xa1, 0x12, 0xb0, 0xc1, 0x95, 0x87, 0x4d}};
const GUID IID_IBDA_PinControl       = {0x0DED49D5, 0xA8B7, 0x4d5d, {0x97, 0xA1, 0x12, 0xB0, 0xC1, 0x95, 0x87, 0x4D}};
const GUID IID_IPin = {0x56a86891, 0x0ad4, 0x11ce, {0xb0, 0x3a, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70}};
#endif


class CBDAPinControl : public IBDA_PinControl
{
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;
    }
    // IBDA_PinControl methods
    HRESULT STDMETHODCALLTYPE GetPinID(ULONG *pulPinID);
    HRESULT STDMETHODCALLTYPE GetPinType(ULONG *pulPinType);
    HRESULT STDMETHODCALLTYPE RegistrationContext(ULONG *pulRegistrationCtx);


    CBDAPinControl(HANDLE hFile, IBDA_NetworkProvider * pProvider, IPin * pConnectedPin, ULONG RegistrationCtx) : m_Ref(0), m_Handle(hFile), m_pProvider(pProvider), m_pConnectedPin(pConnectedPin), m_RegistrationCtx(RegistrationCtx){};
    virtual ~CBDAPinControl()
    {
        //m_pConnectedPin->Release();
        //m_pProvider->Release();
    };

protected:
    LONG m_Ref;
    HANDLE m_Handle;
    IBDA_NetworkProvider * m_pProvider;
    IPin * m_pConnectedPin;
    ULONG m_RegistrationCtx;
};

HRESULT
STDMETHODCALLTYPE
CBDAPinControl::QueryInterface(
    IN  REFIID refiid,
    OUT PVOID* Output)
{
    *Output = NULL;
    if (IsEqualGUID(refiid, IID_IUnknown))
    {
        *Output = PVOID(this);
        reinterpret_cast<IUnknown*>(*Output)->AddRef();
        return NOERROR;
    }
    if (IsEqualGUID(refiid, IID_IBDA_PinControl))
    {
        *Output = (IBDA_PinControl*)(this);
        reinterpret_cast<IBDA_PinControl*>(*Output)->AddRef();
        return NOERROR;
    }
DebugBreak();
    return E_NOINTERFACE;
}
//-------------------------------------------------------------------
// IBDA_PinControl methods
//
HRESULT
STDMETHODCALLTYPE
CBDAPinControl::GetPinID(ULONG *pulPinID)
{
    KSPROPERTY Property;
    ULONG BytesReturned;
    HRESULT hr;

    // setup request
    Property.Set = KSPROPSETID_BdaPinControl;
    Property.Id = KSPROPERTY_BDA_PIN_ID;
    Property.Flags = KSPROPERTY_TYPE_GET;

    // perform request
    hr = KsSynchronousDeviceControl(m_Handle, IOCTL_KS_PROPERTY, (PVOID)&Property, sizeof(KSPROPERTY), pulPinID, sizeof(ULONG), &BytesReturned);

#ifdef BDAPLGIN_TRACE
    WCHAR Buffer[100];
    swprintf(Buffer, L"CBDAPinControl::GetPinID: hr %lx pulPinID %lu BytesReturned %lx\n", hr, *pulPinID, BytesReturned);
    OutputDebugStringW(Buffer);
#endif

    return hr;
}

HRESULT
STDMETHODCALLTYPE
CBDAPinControl::GetPinType(ULONG *pulPinType)
{
    KSPROPERTY Property;
    ULONG BytesReturned;
    HRESULT hr;

    // setup request
    Property.Set = KSPROPSETID_BdaPinControl;
    Property.Id = KSPROPERTY_BDA_PIN_TYPE;
    Property.Flags = KSPROPERTY_TYPE_GET;

    // perform request
    hr = KsSynchronousDeviceControl(m_Handle, IOCTL_KS_PROPERTY, (PVOID)&Property, sizeof(KSPROPERTY), pulPinType, sizeof(ULONG), &BytesReturned);

#ifdef BDAPLGIN_TRACE
    WCHAR Buffer[100];
    swprintf(Buffer, L"CBDAPinControl::GetPinType: hr %lx pulPinType %lu BytesReturned %lx\n", hr, *pulPinType, BytesReturned);
    OutputDebugStringW(Buffer);
#endif

    return hr;
}

HRESULT
STDMETHODCALLTYPE
CBDAPinControl::RegistrationContext(ULONG *pulRegistrationCtx)
{
#ifdef BDAPLGIN_TRACE
    OutputDebugStringW(L"CBDAPinControl::RegistrationContext\n");
#endif

    if (!pulRegistrationCtx)
    {
        // invalid argument
        return E_POINTER;
    }

    if (m_RegistrationCtx)
    {
        // is registered
        *pulRegistrationCtx = m_RegistrationCtx;
        return NOERROR;
    }

    //pin not registered
    return E_FAIL;
}

//-------------------------------------------------------------------
HRESULT
GetNetworkProviderFromGraph(
    IFilterGraph * pGraph,
    IBDA_NetworkProvider ** pOutNetworkProvider)
{
    IEnumFilters *pEnumFilters = NULL;
    IBaseFilter * ppFilter[1];
    IBDA_NetworkProvider * pNetworkProvider = NULL;
    HRESULT hr;

    // get IEnumFilters interface
    hr = pGraph->EnumFilters(&pEnumFilters);

    if (FAILED(hr))
    {
        //clean up
        *pOutNetworkProvider = NULL;
        return hr;
    }

    while(pEnumFilters->Next(1, ppFilter, NULL) == S_OK)
    {
        // check if that filter supports the IBDA_NetworkProvider interface
        hr = ppFilter[0]->QueryInterface(IID_IBDA_NetworkProvider, (void**)&pNetworkProvider);

        // release IBaseFilter
        ppFilter[0]->Release();

        if (SUCCEEDED(hr))
            break;
    }

    // release IEnumFilters interface
    pEnumFilters->Release();

    //store result
    *pOutNetworkProvider = pNetworkProvider;

    if (pNetworkProvider)
        return S_OK;
    else
        return E_FAIL;
}

HRESULT
CBDAPinControl_RealConstructor(
    HANDLE hPin,
    IBDA_NetworkProvider *pNetworkProvider,
    IPin * pConnectedPin,
    ULONG RegistrationCtx,
    IUnknown * pUnkOuter,
    REFIID riid,
    LPVOID * ppv)
{
    CBDAPinControl * handler = new CBDAPinControl(hPin, pNetworkProvider, pConnectedPin, RegistrationCtx);

#ifdef BDAPLGIN_TRACE
    OutputDebugStringW(L"CBDAPinControl_fnConstructor\n");
#endif

    if (!handler)
        return E_OUTOFMEMORY;

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

    return NOERROR;
}

HRESULT
WINAPI
CBDAPinControl_fnConstructor(
    IUnknown * pUnkOuter,
    REFIID riid,
    LPVOID * ppv)
{
    IPin * pConnectedPin = NULL;
    IBDA_NetworkProvider * pNetworkProvider = NULL;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    HRESULT hr;
    IKsObject * pObject = NULL;
    IPin * pPin = NULL;
    IUnknown * pUnknown = NULL;
    PIN_INFO PinInfo;
    FILTER_INFO FilterInfo;
    ULONG RegistrationCtx = 0;

    if (!pUnkOuter)
        return E_POINTER;

    OutputDebugStringW(L"CBDAPinControl_fnConstructor\n");
    //DebugBreak();

    // query for IKsObject interface
    hr = pUnkOuter->QueryInterface(IID_IKsObject, (void**)&pObject);

    if (FAILED(hr))
        return hr;

    // query for IPin interface
    hr = pObject->QueryInterface(IID_IPin, (void**)&pPin);

    if (FAILED(hr))
    {
        //clean up
       pObject->Release();
       return hr;
    }

    // get pin info
    hr = pPin->QueryPinInfo(&PinInfo);

    if (FAILED(hr))
    {
        //clean up
       pObject->Release();
       pPin->Release();
       return hr;
    }

    if (!PinInfo.pFilter)
    {
        //clean up
       pObject->Release();
       pPin->Release();
       return hr;
    }

    // sanity checks
    assert(PinInfo.pFilter != NULL);

    // query filter info
    hr = PinInfo.pFilter->QueryFilterInfo(&FilterInfo);

    // sanity check
    assert(FilterInfo.pGraph != NULL);

    // get network provider interface
    hr = GetNetworkProviderFromGraph(FilterInfo.pGraph, &pNetworkProvider);

    if (SUCCEEDED(hr))
    {
        if (PinInfo.dir == PINDIR_OUTPUT)
        {
            // get connected pin handle
            hr = pPin->ConnectedTo(&pConnectedPin);
            if (SUCCEEDED(hr))
            {
                // get file handle
                hFile = pObject->KsGetObjectHandle();
                if (hFile)
                {
                    hr = CBDAPinControl_RealConstructor(hFile, pNetworkProvider, pConnectedPin, 0, pUnkOuter, riid, ppv);
                    if (SUCCEEDED(hr))
                    {
                        // set to null to prevent releasing
                        pNetworkProvider = NULL;
                        pConnectedPin = NULL;
                    }
                }
                else
                {
                    // expected file handle
                    hr = E_UNEXPECTED;
                }
            }
        }
        else
        {
            // get IUnknown from base filter
            hr = PinInfo.pFilter->QueryInterface(IID_IUnknown, (void**)&pUnknown);
            if (SUCCEEDED(hr))
            {
                // register device filter
                OutputDebugStringW(L"CBDAPinControl_fnConstructor registering device filter with network provider\n");
                hr = pNetworkProvider->RegisterDeviceFilter(pUnknown, &RegistrationCtx);
                if (SUCCEEDED(hr))
                {
                    // get file handle
                    hFile = pObject->KsGetObjectHandle();
                    if (hFile)
                    {
                        hr = CBDAPinControl_RealConstructor(hFile, pNetworkProvider, NULL, RegistrationCtx, pUnkOuter, riid, ppv);
                        if (SUCCEEDED(hr))
                        {
                            // set to null to prevent releasing
                            pNetworkProvider = NULL;
                        }
                    }
                    else
                    {
                        // expected file handle
                        hr = E_UNEXPECTED;
                    }
                }
                else
                {
                    WCHAR Buffer[100];
                    swprintf(Buffer, L"CBDAPinControl_fnConstructor failed to register filter with %lx\n", hr);
                    OutputDebugStringW(Buffer);
                }
            }
        }
    }

    // release IFilterGraph interface
    FilterInfo.pGraph->Release();

    // release IBaseFilter interface
    PinInfo.pFilter->Release();

    // release IPin 
    pPin->Release();

    // release IKsObject
    pObject->Release();


    if (pNetworkProvider)
    {
        // release network provider
        pNetworkProvider->Release();
    }

    if (pConnectedPin)
    {
        // release connected pin
        pConnectedPin->Release();
    }

    if (pUnknown)
    {
        // release filter
        pUnknown->Release();
    }

    return hr;
}
