/////////////////////////////////////////////////////////////////////////////////////
// TuningSpaceContainer.cpp : Implementation of CSystemTuningSpaces
// Copyright (c) Microsoft Corporation 1999-2000.

#include "stdafx.h"

#include "TuningSpaceContainer.h"
#include "rgsbag.h"
#include "ATSCTS.h"
#include "AnalogTVTS.h"
#include "AuxiliaryInTs.h"
#include "AnalogRadioTS.h"
#include "dvbts.h"
#include "dvbsts.h"

DEFINE_EXTERN_OBJECT_ENTRY(CLSID_SystemTuningSpaces, CSystemTuningSpaces)
DEFINE_EXTERN_OBJECT_ENTRY(CLSID_ATSCTuningSpace, CATSCTS)
DEFINE_EXTERN_OBJECT_ENTRY(CLSID_AnalogTVTuningSpace, CAnalogTVTS)
DEFINE_EXTERN_OBJECT_ENTRY(CLSID_AuxInTuningSpace, CAuxInTS)
DEFINE_EXTERN_OBJECT_ENTRY(CLSID_AnalogRadioTuningSpace, CAnalogRadioTS)
DEFINE_EXTERN_OBJECT_ENTRY(CLSID_DVBTuningSpace, CDVBTS)
DEFINE_EXTERN_OBJECT_ENTRY(CLSID_DVBSTuningSpace, CDVBSTS)

#define MAX_COUNT_NAME OLESTR("Max Count")
namespace BDATuningModel {

typedef CComQIPtr<ITuningSpaceContainer> PQTuningSpaceContainer;

class CAutoMutex {
public:
    const static int MAX_MUTEX_WAIT = 5000;
	CAutoMutex(HANDLE hMutex) throw(ComException) : m_hMutex(hMutex) {
        if (WaitForSingleObject(m_hMutex, MAX_MUTEX_WAIT) != WAIT_OBJECT_0)
            THROWCOM(E_FAIL);
    }

    ~CAutoMutex() throw(ComException) {
        if (!ReleaseMutex(m_hMutex))
            THROWCOM(E_FAIL);
    }

private:
    HANDLE m_hMutex;
};

// to avoid deadlock, always grab the objects critsec via ATL_LOCK before
// grabbing the registry section mutex.

/////////////////////////////////////////////////////////////////////////////
// CSystemTuningSpaces

HRESULT
CSystemTuningSpaces::FinalConstruct(void)
{
    // set up to serialize access to this point in the registry
    CString cs;
    cs.LoadString(IDS_MUTNAME);
    m_hMutex = CreateMutex(NULL, FALSE, cs);
    if (!m_hMutex)
    {
        return Error(IDS_E_NOMUTEX, __uuidof(ITuningSpaceContainer), HRESULT_FROM_WIN32(GetLastError()));
    }
    try {
        // wait for exclusive access
        CAutoMutex mutex(m_hMutex);

            // this must only be done once
        _ASSERT(!m_pFactory);

        // get the property bag class factory
        HRESULT hr = m_pFactory.CoCreateInstance(__uuidof(CreatePropBagOnRegKey));
        if (FAILED(hr))
        {
            return Error(IDS_E_NOPROPBAGFACTORY, __uuidof(ITuningSpaceContainer), hr);
        }

        hr = OpenRootKeyAndBag(KEY_READ);
        if (FAILED(hr)) {
            return Error(IDS_E_NOREGACCESS, __uuidof(ITuningSpaceContainer), hr);
        }

	    PQPropertyBag pb(m_pTSBag);
	    if (!pb) {
		    return E_UNEXPECTED;
	    }

        // discover the maximum possible number of tuning spaces that currently exist
        ULONG cTSPropCount;
        hr = m_pTSBag->CountProperties(&cTSPropCount);
        if (FAILED(hr))
        {
            return Error(IDS_E_CANNOTQUERYKEY, __uuidof(ITuningSpaceContainer), hr);
        }

        // allocate space to hold the tuning space object information entries
        PROPBAG2 *rgPROPBAG2 = new PROPBAG2[cTSPropCount];
        if (!rgPROPBAG2)
        {
            return Error(IDS_E_OUTOFMEMORY, __uuidof(ITuningSpaceContainer), E_OUTOFMEMORY);
        }

        ULONG cpb2Lim;

        // get all the property info structs at once
        hr = m_pTSBag->GetPropertyInfo(0, cTSPropCount, rgPROPBAG2, &cpb2Lim);
        if (FAILED(hr))
        {
            return Error(IDS_E_CANNOTQUERYKEY, __uuidof(ITuningSpaceContainer), hr);
        }
        _ASSERT(cTSPropCount == cpb2Lim);

	    HRESULT hrc = NOERROR;
        // go through the list of properties
        for (ULONG ipb2 = 0; ipb2 < cpb2Lim; ++ipb2)
        {
            // only deal with ones that represent sub-objects (keys)
            if (rgPROPBAG2[ipb2].vt == VT_UNKNOWN)
            {
                USES_CONVERSION;
                LPTSTR pstrName = OLE2T(rgPROPBAG2[ipb2].pstrName);
                TCHAR* pchStop;

                // check for a valid tuning space identifier
                ULONG idx = _tcstoul(pstrName, &pchStop, 10);
                if (idx != 0 && idx != ULONG_MAX && *pchStop == 0)
                {
                    CComVariant var;

                    // read the property from the bag (instantiating the tuning space object)
                    HRESULT hr2;
                    hr = m_pTSBag->Read(1, &rgPROPBAG2[ipb2], NULL, &var, &hr2);
				    if (FAILED(hr)) {
					    // even if the read fails, we should keep going.  
					    // a) this is the easiest way to prevent memory leaks from rgPROPBAG2
					    // b) a bad 3rd party uninstall could leave us with tuning space data
					    //    but no tuning space class to instantiate for that data.  we shouldn't
					    //    allow this to prevent use of other tuning spaces.
					    hrc = hr;
				    } else {
                        _ASSERT(var.vt == VT_UNKNOWN || var.vt == VT_DISPATCH);
					    PQTuningSpace pTS(((var.vt == VT_UNKNOWN) ? var.punkVal : var.pdispVal));
					    CComBSTR UniqueName(GetUniqueName(pTS));
					    if (!UniqueName.Length()) {
						    // return Error(IDS_E_NOUNIQUENAME, __uuidof(ITuningSpace), E_UNEXPECTED);
                            // seanmcd 01/04/04  don't allow a corrupt tuning space to prevent
                            // use of the rest of them.  treat this as a read failure as per the above
                            // comment
                            // but remove it from the collection otherwise we've got a name/idx
                            // cache inconsistency problem
                            hrc = hr = E_UNEXPECTED; // indicate error to delete corrupt TS below
                        } else {
					        m_mapTuningSpaces[idx] = var;
					        m_mapTuningSpaceNames[UniqueName] = idx;
                        }
    #if 0
                        // the following code has been tested and works, but i don't want to
                        // turn it on because stress testing can cause false registry
                        // read failures that resolve themselves later when the system isn't under
                        // stress and i don't want to risk deleting a good tuning space just
                        // because of a bogus read error.
                        if (FAILED(hr)) {
                            // delete the corrupt TS
                            CComVariant var2;
                            var2.vt = VT_UNKNOWN;
                            var2.punkVal = NULL;
                            // can't do anything about a failure so ignore it
                            m_pTSBag->Write(1, &rgPROPBAG2[ipb2], &var2);
                        }
    #endif
				    }
                }
            }

            // free space allocated within rgPROPBAG2 by GetPropertyInfo
            CoTaskMemFree(rgPROPBAG2[ipb2].pstrName);
        }
        delete [] rgPROPBAG2;
	    _ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());

	    CComVariant v;
	    v.vt = VT_UI4;
	    hr = pb->Read(MAX_COUNT_NAME, &v, NULL);
	    if (SUCCEEDED(hr)) {
		    if (v.vt != VT_UI4) {
			    hr = ::VariantChangeType(&v, &v, 0, VT_UI4);
			    if (FAILED(hr)) {
				    return E_UNEXPECTED;
			    }
		    }
		    m_MaxCount = max(v.lVal, m_mapTuningSpaces.size());
		    if (m_MaxCount != v.lVal) {
			    //someone has added stuff to the registry by hand, by defn this is secure
			    // so just update max_count to be consistent
			    hr = put_MaxCount(m_MaxCount);
			    if (FAILED(hr)) {
				    return E_UNEXPECTED;
			    }
		    }
	    } else {
		    m_MaxCount = max(DEFAULT_MAX_COUNT, m_mapTuningSpaces.size());
	    }

    #if 0
        // we'd like to return some indicator that not all of the tuning spaces we're successfully
        // read.  but ATL's base CreateInstance method has a check that deletes the object if
        // the return code != S_OK which S_FALSE triggers.  this results in a successful return code 
        // with a NULL object pointer being returned which crashes clients(specifically the network
        // provider).
	    if (FAILED(hrc)) {
		    return Error(IDS_S_INCOMPLETE_LOAD, __uuidof(ITuningSpace), S_FALSE);
	    }
    #endif

        return NOERROR;
    } CATCHCOM();
}

void CSystemTuningSpaces::FinalRelease()
{
	_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
    if (m_hMutex)
        CloseHandle(m_hMutex);
}

STDMETHODIMP CSystemTuningSpaces::InterfaceSupportsErrorInfo(REFIID riid)
{
	static const IID* arr[] = 
	{
		&IID_ITuningSpaceContainer
	};
	for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		if (InlineIsEqualGUID(*arr[i],riid))
			return S_OK;
	}
	return S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////////////
HRESULT CSystemTuningSpaces::OpenRootKeyAndBag(REGSAM DesiredAccess) {
    CString cs;
    cs.LoadString(IDS_TSREGKEY);
    // make sure our entry exists
    LONG lRes = m_RootKey.Create(HKEY_LOCAL_MACHINE, cs, NULL, REG_OPTION_NON_VOLATILE, DesiredAccess);
    if (lRes != ERROR_SUCCESS) {
        return HRESULT_FROM_WIN32(lRes);
    }
    m_CurrentAccess = DesiredAccess;
    // create a property bag for this portion of the registry
    HRESULT hr = m_pFactory->Create
        ( m_RootKey, 0,
          0,
          m_CurrentAccess,
          __uuidof(IPropertyBag2),
          reinterpret_cast<void **>(&m_pTSBag)
        );
    if (FAILED(hr))
    {
        return Error(IDS_E_CANNOTCREATEPROPBAG, __uuidof(ITuningSpaceContainer), hr);
    }
    return NOERROR;
}

HRESULT CSystemTuningSpaces::ChangeAccess(REGSAM NewAccess) {
    if (m_CurrentAccess == NewAccess) {
        return NOERROR;
    }
    m_RootKey.Close();
    m_pTSBag.Release();
    HRESULT hr = OpenRootKeyAndBag(NewAccess);
    if (FAILED(hr)) {
        return Error(IDS_E_NOREGACCESS, __uuidof(ITuningSpaceContainer), hr);
    }
    return NOERROR;
}

CComBSTR CSystemTuningSpaces::GetUniqueName(ITuningSpace* pTS) {
// don't assert map size equality here.  this function is used to create the name map and will
// always fail during finalconstrcut()
//	_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
    CComBSTR un;
    HRESULT hr = pTS->get_UniqueName(&un);
    if (FAILED(hr)) {
		THROWCOM(hr);
	}
    return un;
}

ULONG CSystemTuningSpaces::GetID(CComBSTR& un) {
	_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
    TuningSpaceNames_t::iterator i = m_mapTuningSpaceNames.find(un);
    if (i == m_mapTuningSpaceNames.end()) {
        return 0;
    }
    return (*i).second;
}

HRESULT CSystemTuningSpaces::DeleteID(ULONG id) {
	_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
    HRESULT hr = ChangeAccess(KEY_READ | KEY_WRITE);
    if (FAILED(hr)) {
        return hr;
    }
    OLECHAR idstr[66];
    _ltow(id, idstr, 10);
    VARIANT v;
    v.vt = VT_EMPTY;
    PQPropertyBag p(m_pTSBag);
    if (!p) {
        return Error(IDS_E_NOREGACCESS, __uuidof(IPropertyBag), E_UNEXPECTED);
    }
	USES_CONVERSION;
    hr = p->Write(idstr, &v);
    if (FAILED(hr)) {
        return Error(IDS_E_NOREGACCESS, __uuidof(ITuningSpaceContainer), E_UNEXPECTED);
    }
    return NOERROR;
}

HRESULT CSystemTuningSpaces::Add(CComBSTR& UniqueName, long PreferredID, PQTuningSpace pTS, VARIANT *pvarIndex) {
    try {
        CAutoMutex mutex(m_hMutex);
	    _ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
	    int newcount = m_mapTuningSpaces.size() + 1;
        if (!PreferredID || m_mapTuningSpaces.find(PreferredID) == m_mapTuningSpaces.end()) {
            // verify no unique name conflict
            TuningSpaceNames_t::iterator in;
            in = m_mapTuningSpaceNames.find(UniqueName);
            if (in != m_mapTuningSpaceNames.end()) {
                return Error(IDS_E_DUPLICATETS, __uuidof(ITuningSpace), HRESULT_FROM_WIN32(ERROR_DUP_NAME));
            }

            // hunt for first available unused id
            // start with 1, id 0 is invalid for a tuning space
            for (PreferredID = 1;
                 m_mapTuningSpaces.find(PreferredID) != m_mapTuningSpaces.end(); 
                 ++PreferredID) {

            }
        } else {
		    // this is the case for complete replacement via idx.
            // delete existing data for this id in preparation for overwriting it.
		    // they may also be changing the unique name at this point.
            HRESULT hr = DeleteID(PreferredID);
            if (FAILED(hr)){
                return hr;
            }
		    newcount--;
        }
	    if (newcount > m_MaxCount) {
		    return Error(IDS_E_MAXCOUNTEXCEEDED, __uuidof(ITuningSpaceContainer), STG_E_MEDIUMFULL);
	    }

        HRESULT hr = ChangeAccess(KEY_READ | KEY_WRITE);
        if (FAILED(hr)) {
            return hr;
        }

        OLECHAR idstr[66];
        _ltow(PreferredID, idstr, 10);

        PQPropertyBag p(m_pTSBag);
        if (!p) {
            return Error(IDS_E_NOREGACCESS, __uuidof(IPropertyBag), E_UNEXPECTED);
        }
	    USES_CONVERSION;
        VARIANT v;
        v.vt = VT_UNKNOWN;
        v.punkVal = pTS;
        hr = p->Write(idstr, &v);
	    if (FAILED(hr)) {
            return Error(IDS_E_NOREGACCESS, __uuidof(ITuningSpaceContainer), hr);
	    }

        PQTuningSpace newTS;
        hr = pTS->Clone(&newTS);
        if (FAILED(hr)) {
            return hr;
        }
        m_mapTuningSpaces[PreferredID] = newTS;
        m_mapTuningSpaceNames[UniqueName] = PreferredID;
        if (pvarIndex) {
            VARTYPE savevt = pvarIndex->vt;
            VariantClear(pvarIndex);
            switch(savevt) {
            case VT_BSTR:
                pvarIndex->vt = VT_BSTR;
                return newTS->get_UniqueName(&pvarIndex->bstrVal);
            default:
                pvarIndex->vt = VT_I4;
                pvarIndex->ulVal = PreferredID;
                return NOERROR;
            }
        }
        return NOERROR;
    } CATCHCOM();
}


HRESULT CSystemTuningSpaces::Find(TuningSpaceContainer_t::iterator &its, CComBSTR& UniqueName, TuningSpaceNames_t::iterator &itn) {
	ATL_LOCK();
	_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
    if (its == m_mapTuningSpaces.end()) {
        return Error(IDS_E_NO_TS_MATCH, __uuidof(ITuningSpace), E_FAIL);
    }
    _ASSERT(((*its).second.vt == VT_UNKNOWN) || ((*its).second.vt == VT_DISPATCH));
    PQTuningSpace pTS((*its).second.punkVal);
    if (!pTS) {
        return Error(IDS_E_NO_TS_MATCH, __uuidof(ITuningSpaceContainer), E_UNEXPECTED);
    }
    UniqueName = GetUniqueName(pTS);
    if (!UniqueName.Length()) {
        return Error(IDS_E_NOUNIQUENAME, __uuidof(ITuningSpace), E_UNEXPECTED);
    }
    itn = m_mapTuningSpaceNames.find(UniqueName);
    _ASSERT(itn != m_mapTuningSpaceNames.end());  // cache inconsistency, in container but not in names
    return NOERROR;
}

HRESULT CSystemTuningSpaces::Find(VARIANT varIndex, long& ID, TuningSpaceContainer_t::iterator &its, CComBSTR& UniqueName, TuningSpaceNames_t::iterator &itn) {
	ATL_LOCK();
	_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
    HRESULT hr = S_OK;
    VARIANT varTmp;
    its = m_mapTuningSpaces.end();
    itn = m_mapTuningSpaceNames.end();
    PQTuningSpace pTuningSpace;

    VariantInit(&varTmp);

    // Try finding a tuning space by local system ID
    hr = VariantChangeType(&varTmp, &varIndex, 0, VT_I4);
    if (!FAILED(hr))
    {
        _ASSERT(varTmp.vt == VT_I4);
        ID = V_I4(&varTmp);
        its = m_mapTuningSpaces.find(ID);
    } else {

        // Try finding a tuning space by name
        hr = VariantChangeType(&varTmp, &varIndex, 0, VT_BSTR);
        if (FAILED(hr))
        {
            // we can only get here if both VariantChangeType calls failed
            return Error(IDS_E_TYPEMISMATCH, __uuidof(ITuningSpaceContainer), DISP_E_TYPEMISMATCH);
        }
        _ASSERT(varTmp.vt == VT_BSTR);
        UniqueName = V_BSTR(&varTmp);

        itn = m_mapTuningSpaceNames.find(UniqueName);
        if (itn != m_mapTuningSpaceNames.end()) {
            ID = (*itn).second;
            its = m_mapTuningSpaces.find(ID);
        }
    }

    if (its == m_mapTuningSpaces.end()) {
        return Error(IDS_E_NO_TS_MATCH, __uuidof(ITuningSpaceContainer), E_FAIL);
    }
    _ASSERT(((*its).second.vt == VT_UNKNOWN) || ((*its).second.vt == VT_DISPATCH));
    return NOERROR;
}


//////////////////////////////////////////////////////////////////////////////////////
// ITuningSpaceContainer
//////////////////////////////////////////////////////////////////////////////////////

STDMETHODIMP CSystemTuningSpaces::get_Item(/*[in]*/ VARIANT varIndex, /*[out, retval]*/ ITuningSpace **ppTuningSpace) {
	if (!ppTuningSpace) {
		return E_POINTER;
	}
	try {
	    ATL_LOCK();
		TuningSpaceContainer_t::iterator its = m_mapTuningSpaces.end();
		TuningSpaceNames_t::iterator itn = m_mapTuningSpaceNames.end();
		long id;
		CComBSTR un;
		HRESULT hr = Find(varIndex, id, its, un, itn);
		if (FAILED(hr) || its == m_mapTuningSpaces.end()) {
			return Error(IDS_E_NO_TS_MATCH, __uuidof(ITuningSpaceContainer), E_INVALIDARG);
		}
		_ASSERT(((*its).second.vt == VT_UNKNOWN) || ((*its).second.vt == VT_DISPATCH));
		PQTuningSpace pTS((*its).second.punkVal);
		if (!pTS) {
			return Error(IDS_E_NOINTERFACE, __uuidof(ITuningSpace), E_NOINTERFACE);
		}
		PQTuningSpace pTSNew;
		hr = pTS->Clone(&pTSNew);
		if (FAILED(hr)) {
			return hr;
		}
		*ppTuningSpace = pTSNew.Detach();
        return NOERROR;
	} catch(...) {
		return E_UNEXPECTED;
	}
}

STDMETHODIMP CSystemTuningSpaces::put_Item(VARIANT varIndex, ITuningSpace *pTS)
{
	if (!pTS) {
		return E_POINTER;
	}
    try {
        // wait for exclusive access
        CAutoMutex mutex(m_hMutex);
		_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());

        HRESULT hr = ChangeAccess(KEY_READ | KEY_WRITE);
        if (FAILED(hr)) {
            return hr;
        }
        long id;
        CComBSTR idxun;
        TuningSpaceContainer_t::iterator its;
        TuningSpaceNames_t::iterator itn;
        hr = Find(varIndex, id, its, idxun, itn);
        if (FAILED(hr) || its == m_mapTuningSpaces.end()) {
            return Error(IDS_E_NO_TS_MATCH, __uuidof(ITuningSpaceContainer), E_INVALIDARG);
        }
        _ASSERT(((*its).second.vt == VT_UNKNOWN) || ((*its).second.vt == VT_DISPATCH));
        CComBSTR un2(GetUniqueName(pTS));
        if (!un2.Length()) {
            // no uniquename prop set in ts
            return Error(IDS_E_NOUNIQUENAME, __uuidof(ITuningSpace), E_UNEXPECTED);
        }
        if (itn != m_mapTuningSpaceNames.end() && idxun != un2) {
            // unique name prop in ts doesn't match string specified in varindex
            return Error(IDS_E_NO_TS_MATCH, __uuidof(ITuningSpace), E_INVALIDARG);
        }
        return Add(un2, id, pTS, NULL);
    } CATCHCOM();
}

STDMETHODIMP CSystemTuningSpaces::Add(ITuningSpace *pTuningSpace, VARIANT *pvarIndex)
{
    try {
        // wait for exclusive access
        CAutoMutex mutex(m_hMutex);
		_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
        HRESULT hr = ChangeAccess(KEY_READ | KEY_WRITE);
        if (FAILED(hr)) {
            return Error(IDS_E_NOREGACCESS, __uuidof(ITuningSpaceContainer), hr);
        }

        if (!pTuningSpace) {
            return E_POINTER;
        }
        VARIANT vartmp;
        vartmp.vt = VT_I4;
        vartmp.ulVal = 0;
        if (pvarIndex && pvarIndex->vt != VT_I4) {
            hr = VariantChangeType(&vartmp, pvarIndex, 0, VT_I4);
            if (FAILED(hr)) {
                vartmp.vt = VT_I4;
                vartmp.ulVal = 0;
            }
        }
        CComBSTR un(GetUniqueName(pTuningSpace));
        if (!un.Length()) {
            return Error(IDS_E_NOUNIQUENAME, __uuidof(ITuningSpace), E_FAIL);
        }
        return Add(un, vartmp.ulVal, pTuningSpace, pvarIndex);
    } CATCHCOM();
}

STDMETHODIMP CSystemTuningSpaces::Remove(VARIANT varIndex)
{
    try {
        // wait for exclusive access
        CAutoMutex mutex(m_hMutex);
		_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());

        HRESULT hr = ChangeAccess(KEY_READ | KEY_WRITE);
        if (FAILED(hr)) {
            return hr;
        }

        TuningSpaceContainer_t::iterator its = m_mapTuningSpaces.end();
        TuningSpaceNames_t::iterator itn = m_mapTuningSpaceNames.end();

        long id;
        CComBSTR un;
        hr = Find(varIndex, id, its, un, itn);
        if (FAILED(hr)) {
            return Error(IDS_E_NO_TS_MATCH, __uuidof(ITuningSpaceContainer), E_INVALIDARG);
        }
        if (itn == m_mapTuningSpaceNames.end()) {
            ASSERT(its != m_mapTuningSpaces.end());  // otherwise find above should have returned failure
            hr = Find(its, un, itn);
            if (FAILED(hr) || itn == m_mapTuningSpaceNames.end()) {
                // found its but not itn, must have inconsistent cache
                return Error(IDS_E_NO_TS_MATCH, __uuidof(ITuningSpaceContainer), E_UNEXPECTED);
            }
        }
        
        m_mapTuningSpaces.erase(its);
        m_mapTuningSpaceNames.erase(itn);

        return DeleteID(id);
    } CATCHCOM();
}


STDMETHODIMP CSystemTuningSpaces::TuningSpacesForCLSID(BSTR bstrSpace, ITuningSpaces **ppTuningSpaces)
{
    try {
        return _TuningSpacesForCLSID(GUID2(bstrSpace), ppTuningSpaces);
    } catch (ComException &e) {
        return e;
    } catch (...) {
        return E_UNEXPECTED;
    }
}

STDMETHODIMP CSystemTuningSpaces::_TuningSpacesForCLSID(REFCLSID clsidSpace, ITuningSpaces **ppTuningSpaces)
{
	if (!ppTuningSpaces) {
		return E_POINTER;
	}
	try {
	    ATL_LOCK();
		_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
		CTuningSpaces* pTSCollection = new CTuningSpaces;
		for (TuningSpaceContainer_t::iterator i = m_mapTuningSpaces.begin(); i != m_mapTuningSpaces.end(); ++i) {
			CComVariant v((*i).second);
			if (v.vt != VT_UNKNOWN && v.vt != VT_DISPATCH) {
				return E_UNEXPECTED; //corrupt in-memory collection
			}
			PQPersist pTS(v.punkVal);
			if (!pTS) {
                delete pTSCollection;
				return E_UNEXPECTED;  // corrupt in-memory collection;
			}
			GUID2 g;
			HRESULT hr = pTS->GetClassID(&g);
			if (FAILED(hr)) {
                delete pTSCollection;
				return E_UNEXPECTED;
			}
			if (g == clsidSpace) {
                PQTuningSpace newts;
                hr = PQTuningSpace(pTS)->Clone(&newts);
                if (FAILED(hr)) {
                    delete pTSCollection;
                    return hr;
                }
                pTSCollection->m_mapTuningSpaces[(*i).first] = CComVariant(newts);
			}
		}
		*ppTuningSpaces = pTSCollection;
		(*ppTuningSpaces)->AddRef();
		return NOERROR;
	} catch(...) {
		return E_UNEXPECTED;
	}

}

STDMETHODIMP CSystemTuningSpaces::TuningSpacesForName(BSTR bstrName, ITuningSpaces **ppTuningSpaces)
{
	if (!ppTuningSpaces) {
		return E_POINTER;
	}
	try {
	    ATL_LOCK();
		_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
		PQRegExp pRE;
		HRESULT hr;
		if (!m_cookieRegExp) {
            // at the time this code was written the only regex library that was readily available
            // was the one in the vbscript engine.  therefore we create and access this through
            // com.  however, this is an apartment model object this we have to create it
            // on a background apartment thread so we can always marshall over and access no matter
            // what thread we're on.
            // there is now a good c++ regex in the http://toolbox and at some point we should probably 
            // check it for thread safety and convert.
			m_pRET = new CRegExThread();
			if (!m_pRET) {
				return E_OUTOFMEMORY;
			}
			if (!m_pRET->Create()) {
				return E_UNEXPECTED;
			}
			hr = m_pRET->CallWorker(CRegExThread::RETHREAD_CREATEREGEX);
			if (FAILED(hr)) {
				return hr;
			}
			m_cookieRegExp = m_pRET->GetCookie();
			if (!m_cookieRegExp) {
				return E_UNEXPECTED;
			}
		} 
		if (!m_pGIT) {
			hr = m_pGIT.CoCreateInstance(CLSID_StdGlobalInterfaceTable, 0, CLSCTX_INPROC_SERVER);
			if (FAILED(hr)) {
				return hr;
			}
		}
		hr = m_pGIT->GetInterfaceFromGlobal(m_cookieRegExp, __uuidof(IRegExp), reinterpret_cast<LPVOID *>(&pRE));
		if (FAILED(hr)) {
			return hr;
		}
		hr = pRE->put_Pattern(bstrName);
		if (FAILED(hr)) {
			return hr;
		}

		CTuningSpaces* pTSCollection = new CTuningSpaces;
		for (TuningSpaceContainer_t::iterator i = m_mapTuningSpaces.begin(); i != m_mapTuningSpaces.end(); ++i) {
			if ((*i).second.vt != VT_UNKNOWN && (*i).second.vt != VT_DISPATCH) {
				return E_UNEXPECTED; //corrupt in-memory collection
			}
			PQTuningSpace pTS((*i).second.punkVal);
			CComBSTR name;
			hr = pTS->get_FriendlyName(&name);
			if (FAILED(hr)) {
				return E_UNEXPECTED;
			}
            PQTuningSpace newTS;
			VARIANT_BOOL bMatch = VARIANT_FALSE;
			hr = pRE->Test(name, &bMatch);
			if (FAILED(hr) || bMatch != VARIANT_TRUE) {
				hr = pTS->get_UniqueName(&name);
				if (FAILED(hr)) {
					return E_UNEXPECTED;
				}
				hr = pRE->Test(name, &bMatch);
				if (FAILED(hr) || bMatch != VARIANT_TRUE) {
                    continue;
                }
            }
            hr = pTS->Clone(&newTS);
            if (FAILED(hr)) {
                return hr;
            }
            pTSCollection->m_mapTuningSpaces[(*i).first] = newTS;
		}

		*ppTuningSpaces = pTSCollection;
		(*ppTuningSpaces)->AddRef();
		return NOERROR;
	} catch(...) {
		return E_UNEXPECTED;
	}
}

STDMETHODIMP CSystemTuningSpaces::get_MaxCount(LONG *plVal)
{
	if (!plVal) {
		return E_POINTER;
	}
	try {
	    ATL_LOCK();
		_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
		*plVal = m_MaxCount;
		return NOERROR;
	} catch(...) {
		return E_POINTER;
	}

}

STDMETHODIMP CSystemTuningSpaces::put_MaxCount(LONG lVal)
{
	try {
        if (lVal < 0) {
            return E_INVALIDARG;
        }
	    CAutoMutex mutex(m_hMutex);
		_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
        HRESULT hr = ChangeAccess(KEY_READ | KEY_WRITE);
        if (FAILED(hr)) {
            return hr;
        }
		ULONG count = max(lVal, m_mapTuningSpaces.size());
		CComVariant v;
		v.vt = VT_UI4;
		v.lVal = count;
		PQPropertyBag pb(m_pTSBag);
		if (!pb) {
			return E_UNEXPECTED;
		}
		hr = pb->Write(MAX_COUNT_NAME, &v);
		if (FAILED(hr)) {
			return hr;
		}
		m_MaxCount = count;
		if (m_MaxCount != lVal) {
			return S_FALSE;
		}
		return NOERROR;
    } CATCHCOM();
}

STDMETHODIMP CSystemTuningSpaces::FindID(ITuningSpace *pTS, long* pID)
{
    try {
		if (!pID || !pTS) {
			return E_POINTER;
		}
	    ATL_LOCK();
		_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
        CComBSTR un(GetUniqueName(pTS));
        if (!un.Length()) {
            return Error(IDS_E_NOUNIQUENAME, __uuidof(ITuningSpace), E_UNEXPECTED);
        }
        *pID = GetID(un);
        if (!(*pID)) {
            return Error(IDS_E_NO_TS_MATCH, __uuidof(ITuningSpace), E_INVALIDARG);
        }
        return NOERROR;
    } catch (...) {
        return E_UNEXPECTED;
    }
}

HRESULT CSystemTuningSpaces::RegisterTuningSpaces(HINSTANCE hMod) {
	try {
		CAutoMutex mutex(m_hMutex);
		_ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
		CString cs;
		cs.LoadString(IDS_RGSLIST_TYPE);
		HRSRC hRes = ::FindResource(hMod, MAKEINTRESOURCE(IDR_CANONICAL_TUNINGSPACE_LIST), (LPCTSTR)cs);
		if (!hRes) {
			return HRESULT_FROM_WIN32(::GetLastError());
		}
		HANDLE hData = ::LoadResource(hMod, hRes);
		if (!hData) {
			return HRESULT_FROM_WIN32(::GetLastError());
		}
		DWORD *p = reinterpret_cast<DWORD *>(::LockResource(hData));
		if (!p) {
			return HRESULT_FROM_WIN32(::GetLastError());
		}
		cs.LoadString(IDS_TUNINGSPACE_FRAGMENT_TYPE);
		for (DWORD idx = 1; idx <= p[0]; ++idx) {
			hRes = ::FindResource(hMod, MAKEINTRESOURCE(p[idx]), (LPCTSTR)cs);
			if (!hRes) {
				return HRESULT_FROM_WIN32(::GetLastError());
			}
			LPCSTR psz = reinterpret_cast<LPCSTR>(::LoadResource(hMod, hRes));
			if (!psz) {
				return HRESULT_FROM_WIN32(::GetLastError());
			}
			USES_CONVERSION;
			int cch;
			CRegObject cro;  // init %mapping% macros here if desired
			PQPropertyBag rgsBag(new CRGSBag(A2CT(psz), cro, cch));
            if (!rgsBag) {
                return E_UNEXPECTED;
            }
			CString csName;
			csName.LoadString(IDS_TSKEYNAMEVAL);
			CComVariant tsval;
			HRESULT hr = rgsBag->Read(T2COLE(csName), &tsval, NULL);
			if (FAILED(hr)) {
				return E_FAIL;  // bad script, no unique name property
			}
			if (tsval.vt != VT_UNKNOWN) {
				return DISP_E_TYPEMISMATCH;
			}
			PQTuningSpace pTS(tsval.punkVal);
			if (!pTS) {
				return DISP_E_TYPEMISMATCH;
			}
			CComVariant Varidx;
			Varidx.vt = VT_UI4;
			Varidx.ulVal = 0;
			hr = Add(pTS, &Varidx);
			// ignore existing ts w/ same unique name and move one
			if (FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_DUP_NAME)) {  
				return hr;
			}
		}
	    return NOERROR;
    } CATCHCOM();
}

HRESULT CSystemTuningSpaces::UnregisterTuningSpaces() {
    try {
        CAutoMutex mutex(m_hMutex);
	    _ASSERT(m_mapTuningSpaces.size() == m_mapTuningSpaceNames.size());
	    // currently we delete all tuning spaces when we unreg
        // its possible that it would be better to just delete the canonical ones
        // that we created when we registered.  on the other hand, that reg space
        // would leak forever if tv support is really being uninstalled.  and, since
        // we're in the os we're unlikely to ever get unregistered anyway.
        HRESULT hr = OpenRootKeyAndBag(KEY_READ | KEY_WRITE);
        if (SUCCEEDED(hr)) {
            DWORD rc = m_RootKey.RecurseDeleteKey(_T(""));
            if (rc != ERROR_SUCCESS) {
                return E_FAIL;
            }
        }
        return NOERROR;
    } CATCHCOM();
}

HRESULT UnregisterTuningSpaces() {
    PQTuningSpaceContainer pst(CLSID_SystemTuningSpaces, NULL, CLSCTX_INPROC_SERVER);
    if (!pst) {
        return E_UNEXPECTED;
    }
    CSystemTuningSpaces *pc = static_cast<CSystemTuningSpaces *>(pst.p);
    return pc->UnregisterTuningSpaces();

}

HRESULT RegisterTuningSpaces(HINSTANCE hMod) {
    PQTuningSpaceContainer pst(CLSID_SystemTuningSpaces, NULL, CLSCTX_INPROC_SERVER);
    if (!pst) {
        return E_UNEXPECTED;
    }
    CSystemTuningSpaces *pc = static_cast<CSystemTuningSpaces *>(pst.p);
    return pc->RegisterTuningSpaces(hMod);
}

};
// end of file - tuningspacecontainer.cpp