//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
#include "pch.h"
#pragma hdrstop

#include "com_macros.h"

#include "DeviceDescription.h"
#include "Device.h"

// DeviceDescription
DeviceDescription::DeviceDescription(UINT nLifeTime/* = 0*/)
    : m_pTempDevice(NULL),
      m_pRootDevice(NULL),
      m_nPass(0),
      m_ReadyState(READYSTATE_UNINITIALIZED),
      m_hrLoadResult(E_UNEXPECTED),
      m_bAbort(false),
      m_nLifeTime(nLifeTime),
      m_bParsedRootElement(false),
      m_punkCallback(NULL)
{
}


// ~DeviceDescription
DeviceDescription::~DeviceDescription()
{
    delete m_pRootDevice;
    delete m_pTempDevice;

    if(m_punkCallback)
        m_punkCallback->Release();
}


// get_ReadyState
STDMETHODIMP DeviceDescription::get_ReadyState(/* [retval][out] */ LONG *plReadyState)
{
    CHECK_POINTER(plReadyState);

    ce::gate<ce::critical_section> _gate(m_cs);

    *plReadyState = m_ReadyState;

    return S_OK;
}


// Load
STDMETHODIMP DeviceDescription::Load(/* [in] */ BSTR bstrUrl)
{
    CHECK_POINTER(bstrUrl);

    {
        ce::gate<ce::critical_section> _gate(m_cs);

        if(m_ReadyState == READYSTATE_LOADING)
            return E_FAIL;

        if(m_pRootDevice != NULL)
            return E_FAIL;

        m_ReadyState = READYSTATE_LOADING;
        
        m_hrLoadResult = E_PENDING;
    }

    m_strUrl = bstrUrl;

    LoadImpl();

    return m_hrLoadResult;
}


// LoadAsync
STDMETHODIMP DeviceDescription::LoadAsync(/* [in] */ BSTR bstrUrl, /* [in] */ IUnknown *punkCallback)
{
    CHECK_POINTER(punkCallback);
    CHECK_POINTER(bstrUrl);

    {
        ce::gate<ce::critical_section> _gate(m_cs);

        if(m_ReadyState == READYSTATE_LOADING)
            return E_FAIL;

        if(m_pRootDevice != NULL)
            return E_FAIL;

        m_ReadyState = READYSTATE_LOADING;

        m_hrLoadResult = E_PENDING;
    }

    HRESULT hr;

    if(SUCCEEDED(hr = punkCallback->QueryInterface(IID_IUnknown, (void**)&m_punkCallback)))
    {
        m_strUrl = bstrUrl;

        HANDLE hThread = CreateThread(NULL, 0, LoadAsyncThread, this, 0, NULL);
        CloseHandle(hThread);
    }

    return hr;
}


// LoadAsyncThread
DWORD WINAPI DeviceDescription::LoadAsyncThread(LPVOID lpParameter)
{
    DeviceDescription* pThis = (DeviceDescription*)lpParameter;

    Assert(pThis);

    pThis->LoadImpl();

    return 0;
}


// LoadImpl
void DeviceDescription::LoadImpl()
{
    ce::string  strURL;

    ce::WideCharToMultiByte(CP_ACP, m_strUrl, -1, &strURL);
    
    HRESULT hrParseResult = Parse(strURL);

    IUPnPDescriptionDocumentCallback*   pCallback = NULL;
    IDispatch*                          pDispatch = NULL;

    {
        ce::gate<ce::critical_section> _gate(m_cs);

        if(m_bAbort)
            m_hrLoadResult = E_ABORT;
        else
            m_hrLoadResult = hrParseResult;
            
        if(FAILED(m_hrLoadResult))
        {
            m_bAbort = false;

            delete m_pRootDevice;
            m_pRootDevice = NULL;

            delete m_pTempDevice;
            m_pTempDevice = NULL;

            m_listDevices.erase(m_listDevices.begin(), m_listDevices.end());
        }

        m_ReadyState = READYSTATE_COMPLETE;

        if(m_punkCallback)
        {
            if(FAILED(m_punkCallback->QueryInterface(IID_IUPnPDescriptionDocumentCallback, (void**)&pCallback)))
                m_punkCallback->QueryInterface(IID_IDispatch, (void**)&pDispatch);

            m_punkCallback->Release();
            m_punkCallback = NULL;
        }
    }

    if(pCallback)
    {
        pCallback->LoadComplete(m_hrLoadResult);
        pCallback->Release();
    }
    else
        if(pDispatch)
        {
            DISPPARAMS  DispParams = {0};
            VARIANT     vtArg1;
            UINT        uArgErr;

            DispParams.cArgs = 1;
            DispParams.rgvarg = &vtArg1;

            V_VT(&vtArg1) = VT_I4;
            V_I4(&vtArg1) = m_hrLoadResult;

            pDispatch->Invoke(DISPID_VALUE, IID_NULL, 0, DISPATCH_METHOD, &DispParams, NULL, NULL, &uArgErr);
            pDispatch->Release();
        }
}


// get_LoadResult
STDMETHODIMP DeviceDescription::get_LoadResult(/* [retval][out] */ long *phrError)
{
    CHECK_POINTER(phrError);

    ce::gate<ce::critical_section> _gate(m_cs);

    *phrError = m_hrLoadResult;

    return S_OK;
}


// Abort
STDMETHODIMP DeviceDescription::Abort(void)
{
    ce::gate<ce::critical_section> _gate(m_cs);

    if(m_ReadyState == READYSTATE_LOADING)
        m_bAbort = true;

    return S_OK;
}


// RootDevice
STDMETHODIMP DeviceDescription::RootDevice(/* [retval][out] */ IUPnPDevice **ppudRootDevice)
{
    CHECK_POINTER(ppudRootDevice);

    ce::gate<ce::critical_section> _gate(m_cs);

    if(m_ReadyState != READYSTATE_COMPLETE)
        return E_FAIL;

    if(m_pRootDevice)
        return m_pRootDevice->QueryInterface(IID_IUPnPDevice, (void**)ppudRootDevice);
    else
        return E_FAIL;
}


// DeviceByUDN
STDMETHODIMP DeviceDescription::DeviceByUDN(/* [in] */ BSTR bstrUDN, /* [retval][out] */ IUPnPDevice** ppDevice)
{
    ce::gate<ce::critical_section> _gate(m_cs);

    if(m_ReadyState != READYSTATE_COMPLETE)
        return E_FAIL;

    for(ce::list<Device*>::iterator it = m_listDevices.begin(), itEnd = m_listDevices.end(); it != itEnd; ++it)
        if((*it)->m_strUDN == bstrUDN)
            return (*it)->QueryInterface(IID_IUPnPDevice, (void**)ppDevice);

    return E_FAIL;
}
