/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_dtrans.hxx"

//------------------------------------------------------------------------
// includes
//------------------------------------------------------------------------
#include "APNDataObject.hxx"
#include <osl/diagnose.h>

#include <systools/win32/comtools.hxx>
#ifdef __MINGW32__
#define __uuidof(I) IID_##I
#endif

//------------------------------------------------------------------------
// defines
//------------------------------------------------------------------------

#define FREE_HGLOB_ON_RELEASE	TRUE
#define KEEP_HGLOB_ON_RELEASE	FALSE

//------------------------------------------------------------------------
// ctor
//------------------------------------------------------------------------

CAPNDataObject::CAPNDataObject( IDataObjectPtr rIDataObject ) :
	m_rIDataObjectOrg( rIDataObject ),
	m_hGlobal( NULL ),
	m_nRefCnt( 0 )
{	
    
	OSL_ENSURE( m_rIDataObjectOrg.get( ), "constructing CAPNDataObject with empty data object" );
	
	// we marshal the IDataObject interface pointer here so
	// that it can be unmarshaled multiple times when this 
	// class will be used from another apartment
	IStreamPtr pStm;
	HRESULT hr = CreateStreamOnHGlobal( 0, KEEP_HGLOB_ON_RELEASE, &pStm );
		    
    OSL_ENSURE( E_INVALIDARG != hr, "invalid args passed to CreateStreamOnHGlobal" );

    if ( SUCCEEDED( hr ) )
	{			
		HRESULT hr_marshal = CoMarshalInterface( 
            pStm.get(), 
			__uuidof(IDataObject), 
			static_cast<LPUNKNOWN>(m_rIDataObjectOrg.get()),
			MSHCTX_LOCAL,
			NULL,
			MSHLFLAGS_TABLEWEAK );
		
        OSL_ENSURE( CO_E_NOTINITIALIZED != hr_marshal, "COM is not initialized" );

        // marshalling may fail if COM is not initialized 
        // for the calling thread which is a program time
        // error or because of stream errors which are runtime
        // errors for instance E_OUTOFMEMORY etc.
                    
		hr = GetHGlobalFromStream(pStm.get(), &m_hGlobal );

        OSL_ENSURE( E_INVALIDARG != hr, "invalid stream passed to GetHGlobalFromStream" );

        // if the marshalling failed we free the 
        // global memory again and set m_hGlobal
        // to a defined value
        if (FAILED(hr_marshal))
        {
            OSL_ENSURE(sal_False, "marshalling failed");
            
            #if OSL_DEBUG_LEVEL > 0
            HGLOBAL hGlobal =
            #endif
                GlobalFree(m_hGlobal);            
            OSL_ENSURE(NULL == hGlobal, "GlobalFree failed");
            m_hGlobal = NULL;
        }
	}	        
}

CAPNDataObject::~CAPNDataObject( )
{    
	if (m_hGlobal)
	{
		IStreamPtr pStm;
		HRESULT  hr = CreateStreamOnHGlobal(m_hGlobal, FREE_HGLOB_ON_RELEASE, &pStm);

        OSL_ENSURE( E_INVALIDARG != hr, "invalid args passed to CreateStreamOnHGlobal" );

        if (SUCCEEDED(hr))
        {
		    hr = CoReleaseMarshalData(pStm.get());
            OSL_ENSURE(SUCCEEDED(hr), "CoReleaseMarshalData failed");
        }
	}    
}

//------------------------------------------------------------------------
// IUnknown->QueryInterface
//------------------------------------------------------------------------

STDMETHODIMP CAPNDataObject::QueryInterface( REFIID iid, LPVOID* ppvObject )
{	
	OSL_ASSERT( NULL != ppvObject );

	if ( NULL == ppvObject )
		return E_INVALIDARG;

	HRESULT hr = E_NOINTERFACE;
	*ppvObject = NULL;

	if ( ( __uuidof( IUnknown ) == iid ) || ( __uuidof( IDataObject ) == iid ) )
	{
		*ppvObject = static_cast< IUnknown* >( this );
		( (LPUNKNOWN)*ppvObject )->AddRef( );
		hr = S_OK;
	}

	return hr;	
}

//------------------------------------------------------------------------
// IUnknown->AddRef
//------------------------------------------------------------------------

STDMETHODIMP_(ULONG) CAPNDataObject::AddRef( )
{
	return static_cast< ULONG >( InterlockedIncrement( &m_nRefCnt ) );
}

//------------------------------------------------------------------------
// IUnknown->Release
//------------------------------------------------------------------------

STDMETHODIMP_(ULONG) CAPNDataObject::Release( )
{
	// we need a helper variable because it's not allowed to access 
	// a member variable after an object is destroyed
	ULONG nRefCnt = static_cast< ULONG >( InterlockedDecrement( &m_nRefCnt ) );

	if ( 0 == nRefCnt )
		delete this;

	return nRefCnt;
}

//------------------------------------------------------------------------
// IDataObject->GetData
//------------------------------------------------------------------------

STDMETHODIMP CAPNDataObject::GetData( LPFORMATETC pFormatetc, LPSTGMEDIUM pmedium )
{	
	HRESULT hr = m_rIDataObjectOrg->GetData( pFormatetc, pmedium );
    
	if (RPC_E_WRONG_THREAD == hr)
	{
		IDataObjectPtr pIDOTmp;
		hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);

		if (SUCCEEDED(hr))
			hr = pIDOTmp->GetData(pFormatetc, pmedium);				
	}
	return hr;
}

//------------------------------------------------------------------------
// IDataObject->EnumFormatEtc
//------------------------------------------------------------------------

STDMETHODIMP CAPNDataObject::EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc )
{
	HRESULT hr = m_rIDataObjectOrg->EnumFormatEtc(dwDirection, ppenumFormatetc);

	if (RPC_E_WRONG_THREAD == hr)
	{
		IDataObjectPtr pIDOTmp;
		hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);

		if (SUCCEEDED(hr))
			hr = pIDOTmp->EnumFormatEtc(dwDirection, ppenumFormatetc);
	}			
	return hr;
}

//------------------------------------------------------------------------
// IDataObject->QueryGetData
//------------------------------------------------------------------------

STDMETHODIMP CAPNDataObject::QueryGetData( LPFORMATETC pFormatetc )
{
	HRESULT hr = m_rIDataObjectOrg->QueryGetData( pFormatetc );

	if (RPC_E_WRONG_THREAD == hr)
	{	
		IDataObjectPtr pIDOTmp;
		hr = MarshalIDataObjectIntoCurrentApartment( &pIDOTmp );

		if (SUCCEEDED(hr))
			hr = pIDOTmp->QueryGetData(pFormatetc);				
	}
	return hr;
}

//------------------------------------------------------------------------
// IDataObject->GetDataHere
//------------------------------------------------------------------------

STDMETHODIMP CAPNDataObject::GetDataHere( LPFORMATETC pFormatetc, LPSTGMEDIUM pmedium )
{
	HRESULT hr = m_rIDataObjectOrg->GetDataHere(pFormatetc, pmedium);

	if (RPC_E_WRONG_THREAD == hr)
	{
		IDataObjectPtr pIDOTmp;
		hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);

		if (SUCCEEDED(hr))
			hr = pIDOTmp->GetDataHere(pFormatetc, pmedium);			
	}	
	return hr;
}

//------------------------------------------------------------------------
// IDataObject->GetCanonicalFormatEtc
//------------------------------------------------------------------------

STDMETHODIMP CAPNDataObject::GetCanonicalFormatEtc(LPFORMATETC pFormatectIn, LPFORMATETC pFormatetcOut)
{
	HRESULT hr = m_rIDataObjectOrg->GetCanonicalFormatEtc( pFormatectIn, pFormatetcOut );

	if (RPC_E_WRONG_THREAD == hr)
	{
		IDataObjectPtr pIDOTmp;
		hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);

		if (SUCCEEDED(hr))
			hr = pIDOTmp->GetCanonicalFormatEtc(pFormatectIn, pFormatetcOut);
	}	
	return hr;
}

//------------------------------------------------------------------------
// IDataObject->SetData
//------------------------------------------------------------------------

STDMETHODIMP CAPNDataObject::SetData( LPFORMATETC pFormatetc, LPSTGMEDIUM pmedium, BOOL fRelease )
{
	HRESULT hr = m_rIDataObjectOrg->SetData( pFormatetc, pmedium, fRelease );

	if (RPC_E_WRONG_THREAD == hr)
	{
		IDataObjectPtr pIDOTmp;
		hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);

		if (SUCCEEDED(hr))
			hr = pIDOTmp->SetData(pFormatetc, pmedium, fRelease);				
	}	
	return hr;
}

//------------------------------------------------------------------------
// IDataObject->DAdvise
//------------------------------------------------------------------------

STDMETHODIMP CAPNDataObject::DAdvise( LPFORMATETC pFormatetc, DWORD advf, LPADVISESINK pAdvSink, DWORD * pdwConnection )
{
	HRESULT hr = m_rIDataObjectOrg->DAdvise(pFormatetc, advf, pAdvSink, pdwConnection);

	if (RPC_E_WRONG_THREAD == hr)
	{
		IDataObjectPtr pIDOTmp;
		hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);

		if (SUCCEEDED(hr))
			hr = pIDOTmp->DAdvise(pFormatetc, advf, pAdvSink, pdwConnection);			
	}			
	return hr;
}

//------------------------------------------------------------------------
// IDataObject->DUnadvise
//------------------------------------------------------------------------

STDMETHODIMP CAPNDataObject::DUnadvise( DWORD dwConnection )
{
	HRESULT hr = m_rIDataObjectOrg->DUnadvise( dwConnection );

	if (RPC_E_WRONG_THREAD == hr)
	{
		IDataObjectPtr pIDOTmp;
		hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);

		if (SUCCEEDED(hr))
			hr = pIDOTmp->DUnadvise(dwConnection);			
	}	
	return hr;
}

//------------------------------------------------------------------------
// IDataObject->EnumDAdvise
//------------------------------------------------------------------------

STDMETHODIMP CAPNDataObject::EnumDAdvise( LPENUMSTATDATA * ppenumAdvise )
{
	HRESULT hr = m_rIDataObjectOrg->EnumDAdvise(ppenumAdvise);

	if (RPC_E_WRONG_THREAD == hr)
	{
		IDataObjectPtr pIDOTmp;
		hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp);

		if (SUCCEEDED(hr))
			hr = pIDOTmp->EnumDAdvise(ppenumAdvise);			
	}	
	return hr;
}

//------------------------------------------------------------------------
// for our convenience
//------------------------------------------------------------------------

CAPNDataObject::operator IDataObject*( )
{
	return static_cast< IDataObject* >( this );
}

//------------------------------------------------------------------------
// helper function
//------------------------------------------------------------------------

HRESULT CAPNDataObject::MarshalIDataObjectIntoCurrentApartment( IDataObject** ppIDataObj )
{
	OSL_ASSERT(NULL != ppIDataObj);

    *ppIDataObj = NULL;
    HRESULT hr = E_FAIL;
    
    if (m_hGlobal)
    {
	    IStreamPtr pStm;
	    hr = CreateStreamOnHGlobal(m_hGlobal, KEEP_HGLOB_ON_RELEASE, &pStm);
        
        OSL_ENSURE(E_INVALIDARG != hr, "CreateStreamOnHGlobal with invalid args called");

	    if (SUCCEEDED(hr))
	    {
		    hr = CoUnmarshalInterface(pStm.get(), __uuidof(IDataObject), (void**)ppIDataObj);		    
            OSL_ENSURE(CO_E_NOTINITIALIZED != hr, "COM is not initialized");
	    }
    }    
	return hr;
}
