//
// 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 "Device.h"
#include "Service.h"
#include "HttpRequest.h"

static wchar_t pwszServiceElement[] = 
    L"<urn:schemas-upnp-org:device-1-0>"
    L"<serviceList>"
    L"<urn:schemas-upnp-org:device-1-0>"
    L"<service>";

static wchar_t pwszDeviceElement[] = 
    L"<urn:schemas-upnp-org:device-1-0>"
    L"<deviceList>"
    L"<urn:schemas-upnp-org:device-1-0>"
    L"<device>";

static wchar_t pwszIconElement[] = 
    L"<urn:schemas-upnp-org:device-1-0>"
    L"<iconList>"
    L"<urn:schemas-upnp-org:device-1-0>"
    L"<icon>";


// startElement
HRESULT STDMETHODCALLTYPE Device::startElement( 
    /* [in] */ const wchar_t __RPC_FAR *pwchNamespaceUri,
    /* [in] */ int cchNamespaceUri,
    /* [in] */ const wchar_t __RPC_FAR *pwchLocalName,
    /* [in] */ int cchLocalName,
    /* [in] */ const wchar_t __RPC_FAR *pwchQName,
    /* [in] */ int cchQName,
    /* [in] */ ISAXAttributes __RPC_FAR *pAttributes)
{
    SAXContentHandler::startElement(pwchNamespaceUri, cchNamespaceUri, pwchLocalName, cchLocalName, pwchQName, cchQName, pAttributes);
    
    if(m_pTempDevice)
        // parsing section for nested device
        m_pTempDevice->startElement(pwchNamespaceUri, cchNamespaceUri, pwchLocalName, cchLocalName, pwchQName, cchQName, pAttributes);
    else
    {
        // Device element
        if(pwszDeviceElement == m_strFullElementName)
        {
            // start of new device (nested device)
            // this section will be parsed by new device object
            m_pTempDevice = new Device(m_pDeviceDescription, m_pRootDevice, this);
            if(!m_pTempDevice)
                return ERROR_OUTOFMEMORY;
            
            // simulate startDocument for the nested device
            m_pTempDevice->startDocument();
        }

        // Service element
        if(pwszServiceElement == m_strFullElementName)
        {
            // starting to parse service
            m_strServiceType.resize(0);
            m_strServiceId.resize(0);
            m_strServiceDescriptionURL.resize(0);
            m_strServiceControlURL.resize(0);
            m_strServiceEventSubURL.resize(0);
        }

        // Icon element
        if(pwszIconElement == m_strFullElementName)
        {
            // starting parsing icon
            m_strIconWidth.resize(0);
            m_strIconHeight.resize(0);
            m_strIconDepth.resize(0);
            m_strIconURL.resize(0);
            m_strIconMimeType.resize(0);
        }
    }

    return S_OK;
}


// endElement
HRESULT STDMETHODCALLTYPE Device::endElement( 
    /* [in] */ const wchar_t __RPC_FAR *pwchNamespaceUri,
    /* [in] */ int cchNamespaceUri,
    /* [in] */ const wchar_t __RPC_FAR *pwchLocalName,
    /* [in] */ int cchLocalName,
    /* [in] */ const wchar_t __RPC_FAR *pwchQName,
    /* [in] */ int cchQName)
{
    // Device element
    if(pwszDeviceElement == m_strFullElementName)
    {
        // finished parsing nested device;
        // simulate endDocument
        m_pTempDevice->endDocument();
        
        // add the device to collection
        if(m_pDevices)
            m_pDevices->AddItem(m_pTempDevice->m_strUDN, m_pTempDevice);
        
        m_bHasChildren = true;
        m_pTempDevice = NULL;
    }

    if(m_pTempDevice)
        // parsing section for nested device
        m_pTempDevice->endElement(pwchNamespaceUri, cchNamespaceUri, pwchLocalName, cchLocalName, pwchQName, cchQName);
    else
    {
        // Service element
        if(pwszServiceElement == m_strFullElementName)
        {
            // trim white spaces
            m_strServiceType.trim(L"\n\r\t ");
            m_strServiceId.trim(L"\n\r\t ");
            m_strServiceDescriptionURL.trim(L"\n\r\t ");
            m_strServiceControlURL.trim(L"\n\r\t ");
            m_strServiceEventSubURL.trim(L"\n\r\t ");

            // convert URLs to absolute
            MakeAbsoluteURL(&m_strServiceDescriptionURL);
            MakeAbsoluteURL(&m_strServiceControlURL);
            MakeAbsoluteURL(&m_strServiceEventSubURL);
            
            // create Service object and add to collection
            if(Service *pService = new Service(m_strUDN, m_strServiceType, m_strServiceId, m_strServiceDescriptionURL, 
                                               m_strServiceControlURL, m_strServiceEventSubURL, m_pDeviceDescription->m_nLifeTime, &m_pDeviceDescription->m_strBaseURL))
                if(m_pServices)
                    m_pServices->AddItem(m_strServiceId, pService);
        }

        // Icon element
        if(pwszIconElement == m_strFullElementName)
        {
            // trim white spaces
            m_strIconWidth.trim(L"\n\r\t ");
            m_strIconHeight.trim(L"\n\r\t ");
            m_strIconDepth.trim(L"\n\r\t ");

            // add icon to the list
            m_listIcons.push_back(icon(m_strIconMimeType, m_strIconWidth, m_strIconHeight, m_strIconDepth, m_strIconURL));
        }
    }

    return SAXContentHandler::endElement(pwchNamespaceUri, cchNamespaceUri, pwchLocalName, cchLocalName, pwchQName, cchQName);
}


// characters
HRESULT STDMETHODCALLTYPE Device::characters( 
    /* [in] */ const wchar_t __RPC_FAR *pwchChars,
    /* [in] */ int cchChars)
{
    if(m_pTempDevice)
        // parsing section for nested device
        m_pTempDevice->characters(pwchChars, cchChars);
    else
    {
        //
        // device properties
        //
        // deviceType
        if(L"<urn:schemas-upnp-org:device-1-0><deviceType>" == m_strFullElementName)
            m_strDeviceType.append(pwchChars, cchChars);

        // friendlyName
        if(L"<urn:schemas-upnp-org:device-1-0><friendlyName>" == m_strFullElementName)
            m_strFriendlyName.append(pwchChars, cchChars);

        // manufacturer
        if(L"<urn:schemas-upnp-org:device-1-0><manufacturer>" == m_strFullElementName)
            m_strManufacturer.append(pwchChars, cchChars);

        // manufacturerURL
        if(L"<urn:schemas-upnp-org:device-1-0><manufacturerURL>" == m_strFullElementName)
            m_strManufacturerURL.append(pwchChars, cchChars);

        // modelDescription
        if(L"<urn:schemas-upnp-org:device-1-0><modelDescription>" == m_strFullElementName)
            m_strModelDescription.append(pwchChars, cchChars);

        // modelName
        if(L"<urn:schemas-upnp-org:device-1-0><modelName>" == m_strFullElementName)
            m_strModelName.append(pwchChars, cchChars);

        // modelNumber
        if(L"<urn:schemas-upnp-org:device-1-0><modelNumber>" == m_strFullElementName)
            m_strModelNumber.append(pwchChars, cchChars);

        // modelURL
        if(L"<urn:schemas-upnp-org:device-1-0><modelURL>" == m_strFullElementName)
            m_strModelURL.append(pwchChars, cchChars);

        // serialNumber
        if(L"<urn:schemas-upnp-org:device-1-0><serialNumber>" == m_strFullElementName)
            m_strSerialNumber.append(pwchChars, cchChars);

        // UDN
        if(L"<urn:schemas-upnp-org:device-1-0><UDN>" == m_strFullElementName)
            m_strUDN.append(pwchChars, cchChars);

        // UPC
        if(L"<urn:schemas-upnp-org:device-1-0><UPC>" == m_strFullElementName)
            m_strUPC.append(pwchChars, cchChars);

        // presentationURL
        if(L"<urn:schemas-upnp-org:device-1-0><presentationURL>" == m_strFullElementName)
            m_strPresentationURL.append(pwchChars, cchChars);

        //
        // icon properties
        //
        static const int nIconLength = sizeof(pwszIconElement)/sizeof(*pwszIconElement) - 1;

        if(0 == wcsncmp(pwszIconElement, m_strFullElementName, nIconLength))
        {           
            // mimetype
            if(0 == wcscmp(L"<urn:schemas-upnp-org:device-1-0><mimetype>", static_cast<LPCWSTR>(m_strFullElementName) + nIconLength))
                m_strIconMimeType.append(pwchChars, cchChars);

            // width
            if(0 == wcscmp(L"<urn:schemas-upnp-org:device-1-0><width>", static_cast<LPCWSTR>(m_strFullElementName) + nIconLength))
                m_strIconWidth.append(pwchChars, cchChars);

            // height
            if(0 == wcscmp(L"<urn:schemas-upnp-org:device-1-0><height>", static_cast<LPCWSTR>(m_strFullElementName) + nIconLength))
                m_strIconHeight.append(pwchChars, cchChars);

            // depth
            if(0 == wcscmp(L"<urn:schemas-upnp-org:device-1-0><depth>", static_cast<LPCWSTR>(m_strFullElementName) + nIconLength))
                m_strIconDepth.append(pwchChars, cchChars);

            // url
            if(0 == wcscmp(L"<urn:schemas-upnp-org:device-1-0><url>", static_cast<LPCWSTR>(m_strFullElementName) + nIconLength))
                m_strIconURL.append(pwchChars, cchChars);
        }

        //
        // service properties
        //
        static const int nServiceLength = sizeof(pwszServiceElement)/sizeof(*pwszServiceElement) - 1;

        if(0 == wcsncmp(pwszServiceElement, m_strFullElementName, nServiceLength))
        {           
            // serviceType
            if(0 == wcscmp(L"<urn:schemas-upnp-org:device-1-0><serviceType>", static_cast<LPCWSTR>(m_strFullElementName) + nServiceLength))
                m_strServiceType.append(pwchChars, cchChars);

            // serviceId
            if(0 == wcscmp(L"<urn:schemas-upnp-org:device-1-0><serviceId>", static_cast<LPCWSTR>(m_strFullElementName) + nServiceLength))
                m_strServiceId.append(pwchChars, cchChars);

            // SCPDURL
            if(0 == wcscmp(L"<urn:schemas-upnp-org:device-1-0><SCPDURL>", static_cast<LPCWSTR>(m_strFullElementName) + nServiceLength))
                m_strServiceDescriptionURL.append(pwchChars, cchChars);

            // controlURL
            if(0 == wcscmp(L"<urn:schemas-upnp-org:device-1-0><controlURL>", static_cast<LPCWSTR>(m_strFullElementName) + nServiceLength))
                m_strServiceControlURL.append(pwchChars, cchChars);

            // eventSubURL
            if(0 == wcscmp(L"<urn:schemas-upnp-org:device-1-0><eventSubURL>", static_cast<LPCWSTR>(m_strFullElementName) + nServiceLength))
                m_strServiceEventSubURL.append(pwchChars, cchChars);
        }
    }

    return S_OK;
}


// endDocument
HRESULT STDMETHODCALLTYPE Device::endDocument(void)
{
    // we finished parsing XML so now we can trim white spaces from string members
    m_strDeviceType.trim(L"\n\r\t ");
    m_strFriendlyName.trim(L"\n\r\t ");
    m_strManufacturer.trim(L"\n\r\t ");
    m_strManufacturerURL.trim(L"\n\r\t ");
    m_strModelDescription.trim(L"\n\r\t ");
    m_strModelName.trim(L"\n\r\t ");
    m_strModelNumber.trim(L"\n\r\t ");
    m_strModelURL.trim(L"\n\r\t ");
    m_strSerialNumber.trim(L"\n\r\t ");
    m_strUDN.trim(L"\n\r\t ");
    m_strUPC.trim(L"\n\r\t ");
    m_strPresentationURL.trim(L"\n\r\t ");

    m_pDeviceDescription->DeviceAddedCallback(this);

    return S_OK;
}


// FixDeviceURL
void Device::FixDeviceURL()
{
    // convert all device URLs to absolute
    MakeAbsoluteURL(&m_strManufacturerURL);
    MakeAbsoluteURL(&m_strModelURL);
    MakeAbsoluteURL(&m_strPresentationURL);
    
    // icon URL for all icons
    for(ce::list<icon>::iterator it = m_listIcons.begin(), itEnd = m_listIcons.end(); it != itEnd; ++it)
        MakeAbsoluteURL(&it->m_strURL);
}


// MakeAbsoluteURL
void Device::MakeAbsoluteURL(ce::wstring* pstr)
{
    ce::string  strRelativeURL(INTERNET_MAX_URL_LENGTH);
    ce::string  strAbsoluteURL(INTERNET_MAX_URL_LENGTH);
    DWORD       dw;

    if(!pstr->empty())
    {
        wcstombs(strRelativeURL.get_buffer(), *pstr, strRelativeURL.capacity());
        
        if(InternetCombineUrlA(m_pDeviceDescription->m_strBaseURL, strRelativeURL, strAbsoluteURL.get_buffer(), &(dw = strAbsoluteURL.capacity()), 0))
        {
            pstr->reserve(strAbsoluteURL.length() + 1);
            mbstowcs(pstr->get_buffer(), strAbsoluteURL, pstr->capacity());
        }
    }
}
