#include <windows.h>
#include "ctimedta.h"

extern UINT cfTimeData, cfThreadId ;
extern long g_ObjectCount ;
extern CTimeData *g_pTimeDataObj ;

BOOL g_ThreadShutdownFlag ;

/*
constructor/destructor 
*/

CTimeData::CTimeData (BOOL bBothThreadingModels)
{

/*
Initialize our member variables
*/
	m_RefCount = 0 ; 
	m_pAdvSink = NULL ;
	InitializeCriticalSection (&m_csDAdvise) ;
/*
Increment the global object count
*/

	InterlockedIncrement (&g_ObjectCount) ;
/*
Create free threaded marshaler
*/
	m_bBothThreadingModels = bBothThreadingModels ;

	if (m_bBothThreadingModels)
	{
		MessageBox (NULL, "Creating Free", "", 0) ;

		CoCreateFreeThreadedMarshaler (
			this,
			&m_pFreeThreadedMarshaler) ;
	}
	else
	{
		m_pFreeThreadedMarshaler = NULL ;
	}

    return ;
}


CTimeData::~CTimeData(void)
{  
	if (m_pAdvSink)
	{
		m_pAdvSink->Release( ) ;
	}

	g_ThreadShutdownFlag = TRUE ;

	DeleteCriticalSection (&m_csDAdvise) ;

	InterlockedDecrement (&g_ObjectCount) ;
	g_pTimeDataObj = NULL ;
	CloseHandle (m_hThread) ;
/*
Release free threaded marshaler.
*/
	if (m_pFreeThreadedMarshaler)
	{
		MessageBox (NULL, "Releasing Free", "", 0) ;
		m_pFreeThreadedMarshaler->Release( ) ;
	}

    return;
}


/*
IUnknown methods
*/

HRESULT CTimeData::QueryInterface(REFIID riid, LPVOID FAR *ppv)
{
    if (riid == IID_IUnknown || riid == IID_IDataObject)
    {
        *ppv = (LPVOID)this;  
        AddRef();
        return S_OK ;
    }
	else if (m_bBothThreadingModels && riid == IID_IMarshal)
	{
		MessageBox (NULL, "Querying Free", "", 0) ;
		return m_pFreeThreadedMarshaler->QueryInterface (riid, ppv) ;
	}
    else
    {
    	*ppv = NULL;       
    	return  E_NOINTERFACE ;
    }
}


STDMETHODIMP_(ULONG) CTimeData::AddRef(void)
{
    return InterlockedIncrement (&m_RefCount) ;
}


STDMETHODIMP_(ULONG) CTimeData::Release(void)
{
	UINT retval = InterlockedDecrement (&m_RefCount) ;
	
	if (retval == 0)
    {
        delete this;
    }	

    return retval ;
}


/*
GetData Method.  Caller is asking if we can supply data in a given 
format and medium
*/

STDMETHODIMP CTimeData::GetData (LPFORMATETC lpfe, LPSTGMEDIUM lpstg) 
{

/*
We support a private clipboard format, identified by the UINT cfTimeData.
This is actually a structure of type SYSTEMTIME. 
Allocate a new global, copy our global system time into it, and fill in the 
elements of the STGMEDIUM structure that we were passed
*/

	if (lpfe->cfFormat == cfTimeData
		&& lpfe->tymed == TYMED_HGLOBAL
		&& lpfe->dwAspect == DVASPECT_CONTENT)
	{
		SYSTEMTIME *pSt ;
			
/*
If lindex == -1, then we are simply transferring a SYSTEMTIME structure.
Any other value means that we are doing a performance test.  In this
case, the value of lindex is the amount of memory to allocate, while
the calling app measures response time.
*/
		if (lpfe->lindex == -1)
		{
			pSt = (SYSTEMTIME *)GlobalAlloc(GMEM_FIXED, sizeof (SYSTEMTIME));
		}
		else
		{
			pSt = (SYSTEMTIME *)GlobalAlloc(GMEM_FIXED, lpfe->lindex);
		}

		if (!pSt)
		{
    		return E_OUTOFMEMORY ;
		}	                                       
		
		GetLocalTime (pSt) ; 
	    
	    lpstg->tymed = TYMED_HGLOBAL ;
    	lpstg->hGlobal = (HGLOBAL) pSt ;
	   	lpstg->pUnkForRelease = NULL ; ;
    	
    	return  S_OK ;

	}

/*
We support a private clipboard format, identified by the UINT cfThreadId.
The caller is asking for the ID of the thread on which we receive the call.
Allocate a new global, copy our thread ID into it, and fill in the 
elements of the STGMEDIUM structure that we were passed
*/

	if (lpfe->cfFormat == cfThreadId
		&& lpfe->tymed == TYMED_HGLOBAL
		&& lpfe->dwAspect == DVASPECT_CONTENT)
	{
		DWORD *pdw ;
		pdw = (DWORD *)GlobalAlloc(GMEM_FIXED, sizeof (DWORD));

		if (!pdw)
		{
    		return E_OUTOFMEMORY ;
		}	                                       
		
		*pdw = GetCurrentThreadId ( ) ;
		
	    lpstg->tymed = TYMED_HGLOBAL ;
    	lpstg->hGlobal = (HGLOBAL) pdw ;
	   	lpstg->pUnkForRelease = NULL ; 
    	
    	return  S_OK ;

	}


/*
User asked for a format that we couldn't supply.  Return error code.
*/
	return DATA_E_FORMATETC ;
}



STDMETHODIMP CTimeData::QueryGetData (LPFORMATETC lpfe) 
{

/*
We'll give you text, transferred through a global, with aspect
content.  If the user is asking for this, return success code.
*/

	if (lpfe->cfFormat == cfTimeData 
		&& lpfe->tymed == TYMED_HGLOBAL
		&& lpfe->dwAspect == DVASPECT_CONTENT)
	{
		return S_OK ;
	}
	else if (lpfe->cfFormat == cfThreadId
		&& lpfe->tymed == TYMED_HGLOBAL
		&& lpfe->dwAspect == DVASPECT_CONTENT)
	{
		return S_OK ;
	}


/*
But we won't give you anything else
*/

	return DATA_E_FORMATETC ;
    
}


/*
Advise loop methods.  Delegate to the advise holder created in
our class constructor.
*/

STDMETHODIMP CTimeData::DAdvise (FORMATETC FAR* lpfe, DWORD dw, LPADVISESINK lpas, 
	DWORD *lpdw) 
{
	HRESULT hr, retval = S_OK ;

	EnterCriticalSection (&m_csDAdvise) ;

	if (m_pAdvSink == NULL)
	{
/*
Make a copy of the advise sink pointer, which requires an AddRef.
*/
		m_pAdvSink = lpas ;
		m_pAdvSink->AddRef () ;

/*
Marshal the advise sink interface pointer into a stream.
*/

		IStream *pStream ;
		hr = CoMarshalInterThreadInterfaceInStream (
			IID_IAdviseSink, 
			m_pAdvSink, 
			&pStream) ;

		if (hr != S_OK)
		{
			char out [128] ;
			wsprintf (out, "Marshal failed %x", hr) ;
			MessageBox (NULL, out, "", 0) ;
		}
/*
Spin off a new thread to do timer notifications. Pass the
stream pointer to the new thread as its startup parameter.
*/
		g_ThreadShutdownFlag = FALSE ;

		m_hThread = CreateThread (NULL, 0, TimerProc, pStream, 0, &m_ThreadID) ;
	}
	else
	{
		retval = E_FAIL ;
	}
	
	LeaveCriticalSection (&m_csDAdvise) ;

	return 	hr ;
}

STDMETHODIMP CTimeData::DUnadvise  (DWORD dw)
{
	EnterCriticalSection (&m_csDAdvise) ;
	
	if (m_pAdvSink)
	{
		m_pAdvSink->Release() ;
		m_pAdvSink = NULL ;
		g_ThreadShutdownFlag = TRUE ;
	}
	
	LeaveCriticalSection (&m_csDAdvise) ;

	return S_OK ;
}

/*
Unimplemented members
*/

STDMETHODIMP CTimeData::EnumFormatEtc (DWORD dwDir, LPENUMFORMATETC FAR* lpEnum) 
{
	*lpEnum = NULL ;
	return E_NOTIMPL ;
}

STDMETHODIMP CTimeData::EnumDAdvise  (LPENUMSTATDATA *lpen)
{
	*lpen = NULL ;
	return E_NOTIMPL ;
}


STDMETHODIMP CTimeData::GetDataHere (LPFORMATETC lpfe, LPSTGMEDIUM lpst)
{
	return E_NOTIMPL ;
}

STDMETHODIMP CTimeData::GetCanonicalFormatEtc (LPFORMATETC lpout, LPFORMATETC lpin)
{
	return E_NOTIMPL ;
}

STDMETHODIMP CTimeData::SetData (LPFORMATETC lpfe, STGMEDIUM FAR * lpst, BOOL fRelease)
{
	return E_NOTIMPL ;
}

