#include "priv.h"


class CMyHlinkSrc : public IHlinkSource
{
    friend HRESULT CMyHlinkSrc_CreateInstance(REFCLSID rclsid, DWORD grfContext, REFIID riid, LPVOID* ppvOut);
public:
    // *** IUnknown methods ***
    virtual STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
    virtual STDMETHODIMP_(ULONG) AddRef(void) ;
    virtual STDMETHODIMP_(ULONG) Release(void);

    // *** IHlinkSource methods ***
    virtual STDMETHODIMP SetBrowseContext(
	 IHlinkBrowseContext *pihlbc);

    virtual STDMETHODIMP GetBrowseContext(
	 IHlinkBrowseContext **ppihlbc);

    virtual STDMETHODIMP Navigate(
	 DWORD grfHLNF,
	 LPCWSTR pwzJumpLocation);

    virtual STDMETHODIMP GetMoniker(
	 LPCWSTR pwzLocation,
	 DWORD dwAssign,
	 IMoniker **ppimkLocation);

    virtual STDMETHODIMP GetFriendlyName(
	 LPCWSTR pwzLocation,
	 LPWSTR *ppwzFriendlyName);

protected:
    CMyHlinkSrc();
    ~CMyHlinkSrc();

    UINT		_cRef;
    IUnknown*   	_punkInner;	// aggregated inner object
    IHlinkSource* 	 _phlsrc;	// cached IHlinkSource
    IHlinkBrowseContext* _phlbc;
};

CMyHlinkSrc::CMyHlinkSrc() : _cRef(1), _punkInner(NULL), _phlsrc(NULL), _phlbc(NULL)
{
    DllAddRef();
}

CMyHlinkSrc::~CMyHlinkSrc()
{
    DllRelease();
}

//
// This function returns an aggregated object
//
HRESULT CMyHlinkSrc_CreateInstance(REFCLSID rclsid, DWORD grfContext, REFIID riid, LPVOID* ppvOut)
{
    HRESULT hres = E_OUTOFMEMORY;
    *ppvOut = NULL;

    CMyHlinkSrc* phlsrcOuter = new CMyHlinkSrc();
    if (phlsrcOuter)
    {

	hres = CoCreateInstance(rclsid, phlsrcOuter, grfContext, IID_IUnknown,
			        (LPVOID*)&phlsrcOuter->_punkInner);
	if (SUCCEEDED(hres))
	{
	    // TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc_CreateInstenace CoCreateSucceeded");
	    // Cache IHlinkSource of the inner object (if any).
	    HRESULT hresT = phlsrcOuter->_punkInner->QueryInterface(
				IID_IHlinkSource, (LPVOID*)&phlsrcOuter->_phlsrc);
	    // TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc_CreateInstenace QI(IID_IHlinkSource) returned (%x)", hres);
	    if (SUCCEEDED(hresT)) {
		//
		// Decrement the reference count to avoid cycled reference.
		// See "The COM Programmer's Cookbook for detail.
		//
    		phlsrcOuter->Release();
	    }

	    hres = phlsrcOuter->QueryInterface(riid, ppvOut);
	}
	else
	{
	    TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc_CreateInstenace CoCI failed (%x)", hres);
	}

	phlsrcOuter->Release();
    }

    // TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc_CreateInstenace leaving");

    return hres;
}

HRESULT CMyHlinkSrc::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
    if (IsEqualIID(riid, IID_IUnknown))
    {
        *ppvObj = (IUnknown*)this;
        _cRef++;
        return S_OK;
    }
    else if (IsEqualIID(riid, IID_IHlinkSource))
    {
	//
	// If the inner object supports IHlinkSource, return it;
	// otherwise, return our own.
	//
	TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc::QueryInterface IID_IHlinkSource called");
        *ppvObj = _phlsrc ? _phlsrc : (IHlinkSource*)this;
        _cRef++;
        return S_OK;
    }
    else if (_punkInner)
    {
	//
	// Delegate QI down to the inner object. This technique is
	// called "Blind QueryInterfcae" in the COM Programmer's Cookbook.
	// This book says, we shouldn't use this technique unless we modify
	// any behavior of other interfaces. In this case, we don't modify
	// any behavior and it's safe to use this technique.
	//
	// TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc::QueryInterface delegating QI to inner object");
	return _punkInner->QueryInterface(riid, ppvObj);
    }

    *ppvObj = NULL;
    return E_NOINTERFACE;
}

ULONG CMyHlinkSrc::AddRef(void)
{
    // TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc::AddRef new _cRef is %d", _cRef+1);
    return ++_cRef;
}

ULONG CMyHlinkSrc::Release(void)
{
    if (--_cRef > 0) {
	// TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc::Release new _cRef is %d", _cRef);
	return _cRef;
    }

    TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc::Release deleting this object ----- (YES!)");

    if (_phlbc) {
	_phlbc->Release();
    }

    _cRef = 1;	// guard (to be recursively hit this code)
    if (_phlsrc) {
	AddRef();		// balance the ref. count
	_phlsrc->Release();	// release the cached interface
    }

    if (_punkInner) {
	_punkInner->Release();
    }

    ASSERT(_cRef == 1);
    delete this;
    return 0;
}

// *** IHlinkSource methods ***
HRESULT CMyHlinkSrc::SetBrowseContext(
     IHlinkBrowseContext *pihlbc)
{
    if (_phlbc) {
	_phlbc->Release();
    }

    _phlbc = pihlbc;
    if (_phlbc) {
	_phlbc->AddRef();
    }

    return S_OK;
}

HRESULT CMyHlinkSrc::GetBrowseContext(
     IHlinkBrowseContext **ppihlbc)
{
    *ppihlbc = _phlbc;

    if (_phlbc) {
	_phlbc->AddRef();
    }

    return S_OK;
}

HRESULT CMyHlinkSrc::Navigate(
     DWORD grfHLNF,
     LPCWSTR pwzJumpLocation)
{
    IOleDocumentView* pmsov = NULL;
    HRESULT hres = _punkInner->QueryInterface(IID_IOleDocumentView, (LPVOID*)&pmsov);
    if (SUCCEEDED(hres)) {
	hres = pmsov->UIActivate(TRUE);
	TraceMsg(DM_TRACE, "sdv TR CHS::Navigate pmsov->UIActivate() returned %x", hres);
	if (SUCCEEDED(hres)) {
	    // HlinkOnNavigate
	}
	pmsov->Release();
    } else {
	TraceMsg(DM_TRACE, "sdv TR CHS::Navigate _punkInner->QI(IID_Mso) failed");
    }

    return S_OK;
}

HRESULT CMyHlinkSrc::GetMoniker(
     LPCWSTR pwzLocation,
     DWORD dwAssign,
     IMoniker **ppimkLocation)
{
    return E_NOTIMPL;
}

HRESULT CMyHlinkSrc::GetFriendlyName(
     LPCWSTR pwzLocation,
     LPWSTR *ppwzFriendlyName)
{
    return E_NOTIMPL;
}

//
// Almost identical copy of OleCreate, which allows us to pass
// the punkOuter.
//
HRESULT CMyHlinkSrc_OleCreate(CLSID rclsid, REFIID riid, DWORD renderOpt,
		   FORMATETC* pFormatEtc, IOleClientSite* pclient,
		   IStorage* pstg, LPVOID* ppvOut)
{
    HRESULT hres;
    *ppvOut = NULL;	// assume error

    IUnknown* punk;
    hres = CMyHlinkSrc_CreateInstance(rclsid, CLSCTX_INPROC, IID_IUnknown, (LPVOID*)&punk);
    if (SUCCEEDED(hres))
    {
	// Artificial one-time loop, which allows us to easily
	// handle error cases by saying "if (FAILED(hres)) break;"
	do {
	    // Call IPersistStorage::InitNew
	    IPersistStorage* ppstg;
	    hres = punk->QueryInterface(IID_IPersistStorage, (LPVOID*)&ppstg);
	    if (FAILED(hres))
		break;
	    hres = ppstg->InitNew(pstg);
	    ppstg->Release();
	    if (FAILED(hres))
		break;

	    // Call IOleObject::SetClientSite
	    IOleObject* pole;
	    hres = punk->QueryInterface(IID_IOleObject, (LPVOID*)&pole);
	    if (FAILED(hres))
		break;
	    hres = pole->SetClientSite(pclient);
	    pole->Release();
	    if (FAILED(hres))
		break;

	    hres = punk->QueryInterface(riid, ppvOut);
	} while (0);

	punk->Release();
    }
    return hres;
}

//
// Almost identical copy of OleLoad, which allows us to pass
// the punkOuter.
//
HRESULT CMyHlinkSrc_OleLoad(IStorage* pstg, REFIID riid,
		            IOleClientSite* pclient, LPVOID* ppvOut)
{
    // TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc_OleLoad called");

    HRESULT hres;
    *ppvOut = NULL;	// assume error

    STATSTG statstg;
    hres = pstg->Stat(&statstg, STATFLAG_NONAME);
    if (SUCCEEDED(hres))
    {
	IUnknown* punk;
	hres = CMyHlinkSrc_CreateInstance(statstg.clsid, CLSCTX_INPROC, IID_IUnknown, (LPVOID*)&punk);
	if (SUCCEEDED(hres))
	{
	    // Artificial one-time loop, which allows us to easily
	    // handle error cases by saying "if (FAILED(hres)) break;"
	    do {
		// TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc_OladLoad calling IPS::Load");
		// Call IPersistStorage::Load
		IPersistStorage* ppstg;
		hres = punk->QueryInterface(IID_IPersistStorage, (LPVOID*)&ppstg);
		if (FAILED(hres))
		    break;
		hres = ppstg->Load(pstg);
		ppstg->Release();
		if (FAILED(hres))
		    break;

		// TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc_OladLoad calling IOO::SetClientSite");
		// Call IOleObject::SetClientSite
		IOleObject* pole;
		hres = punk->QueryInterface(IID_IOleObject, (LPVOID*)&pole);
		if (FAILED(hres))
		    break;
		hres = pole->SetClientSite(pclient);
		pole->Release();
		if (FAILED(hres))
		    break;

		// TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc_OladLoad calling IUnk::QI");
		hres = punk->QueryInterface(riid, ppvOut);
	    } while (0);

	    punk->Release();
	}
    }

    // TraceMsg(DM_TRACE, "sdv TR CMyHlinkSrc_OleLoad is leaving");

    return hres;
}



