// Queue.cpp : Implementation of CQueue
#include "stdafx.h"
#include "Queue1.h"
#include "Queue.h"

/////////////////////////////////////////////////////////////////////////////
// CQueue

CQueue::CQueue()
{
	m_Interface = 0;
	m_In = 0;
	m_Out = 0;
	m_Empty = false;
	m_Interrupt = false;
	m_InterruptGet = false;
	m_DoASignal = false;
}

HRESULT CQueue::FinalConstruct()
{
	//	Call the parent function
	HRESULT r = CComObjectRootEx<CComSingleThreadModel>::FinalConstruct();
	if (FAILED(r))
		return r;

	//	Initialize the critical sections
	InitializeCriticalSection(&m_Copying);

	//	Create the event to indicate the thread is done
	if ((m_Start = CreateEvent(0, false, false, 0)) == NULL)
		return E_FAIL;

	//	Create the event to indicate the thread is done
	if ((m_Done = CreateEvent(0, false, false, 0)) == NULL)
		return E_FAIL;

	//	Create the event to wake the sleeping threads
	if ((m_Signal = CreateEvent(0, false, false, 0)) == NULL)
		return E_FAIL;

	//	Create the event to tell when the thread is restarted
	//		(Prevents a race condition)
	if ((m_Signaled = CreateEvent(0, false, false, 0)) == NULL)
		return E_FAIL;

	return S_OK;
}

CQueue::~CQueue()
{
	//	Tell the loop to quit
	m_Interrupt = true;

	//	Wait for the loop to quit
	WaitForSingleObject(m_Done, 5000);

	//	Delete m_Copying
	DeleteCriticalSection(&m_Copying);

	//	Delete m_Start
	CloseHandle(&m_Start);

	//	Delete m_Done
	CloseHandle(&m_Done);

	//	Delete m_Signal
	CloseHandle(&m_Signal);

	//	Delete m_Signaled
	CloseHandle(&m_Signaled);
}

STDMETHODIMP CQueue::Get(UCHAR*aItem, UINT*aLength, UINT*aTimeout)
{
	HRESULT r;

	//	Can't interrupt yet
	m_InterruptGet = false;

	//	Wait for the data
	if (FAILED(r = Available(aLength, aTimeout)))
		return r;

	//	Move in the data
	UINT Length = *aLength;
	UINT End = m_Out + Length;
	if (End >= BufferSize)
		End = BufferSize;
	Length = End - m_Out;
	if (Length)
		memcpy(aItem, m_Buffer + m_Out, Length);
	aItem += Length;

	//	Reset the m_Out pointer
	EnterCriticalSection(&m_Copying);
	m_Out += Length;
	if (m_Out >= BufferSize)
		m_Out = 0;
	LeaveCriticalSection(&m_Copying);

	return S_OK;
}

STDMETHODIMP CQueue::Available(UINT*aLength, UINT*aTimeout)
{
	UINT Space;

	//	Range the length
	if ((*aLength) >= BufferSize)
		(*aLength = BufferSize);

	while (!m_InterruptGet)
	{
		//	Get the values as an atomic operation
		EnterCriticalSection(&m_Copying);

		//	The available memory
		Space = m_In - m_Out;
		if (m_Empty)
			Space = 0;

		//	Get the values as an atomic operation
		LeaveCriticalSection(&m_Copying);

		//	if they are flipped
		if (Space > BufferSize)
			Space += BufferSize;

		//	If a timeout, take what we get
		if (aTimeout && ((*aTimeout) == 0))
			break;

		//	Get the start time
		SYSTEMTIME Sys;
		FILETIME Start;
		FILETIME End;
		::GetSystemTime(&Sys);
		::SystemTimeToFileTime(&Sys, &Start);

		//	if not enough space, wait
		if (Space < (*aLength))
		{
			m_DoASignal = true;
			if (WaitForSingleObject(m_Signal, *aTimeout) != WAIT_TIMEOUT)
				SetEvent(m_Signaled);
		}
		else
			break;

		//	Get the time interval
		::GetSystemTime(&Sys);
		::SystemTimeToFileTime(&Sys, &End);
		UINT Interval = (UINT)((*(__int64*)&End - *(__int64*)&Start) / 10000);

		//	Adjust the time
		if (*aTimeout > Interval)
			*aTimeout -= Interval;
		else
			*aTimeout = 0;
	}

	if (Space < *aLength)
		(*aLength) = Space;

	return S_OK;
}

STDMETHODIMP CQueue::Clear()
{
	//	Empty as an atomic operation
	EnterCriticalSection(&m_Copying);

	//	Set to empty -- m_In will be set by its thread
	m_Empty = true;
	m_Out = 0;

	//	Empty as an atomic operation
	LeaveCriticalSection(&m_Copying);

	//	Clear the interface as well
	m_Interface->Clear();

	return S_OK;
}

STDMETHODIMP CQueue::Interrupt()
{
	//	Quit any loops
	m_InterruptGet = true;

	//	Get out of the using interface
	m_Interface->Interrupt();

	return S_OK;
}

STDMETHODIMP CQueue::Used(UINT*aSize)
{
	//	Get the values as an atomic operation
	EnterCriticalSection(&m_Copying);

	//	The available memory
	UINT Size = m_In - m_Out;
	if (m_Empty)
		Size = 0;

	//	Get the values as an atomic operation
	LeaveCriticalSection(&m_Copying);

	//	if they are flipped
	if (Size > BufferSize)
		Size += BufferSize;

	//	Add the values in the Interface.
	//		(Note:  There is a race condition here,
	//		but if people are added and removing information
	//		dynamically, this value cannot be exact)
	UINT Used;
	if (SUCCEEDED(m_Interface->Used(&Used)))
		Size += Used;

	//	Return the value
	if (aSize)
		*aSize = Size;

	return S_OK;
}

STDMETHODIMP CQueue::get_Interface(IGet **pVal)
{
	if (pVal)
	{
		*pVal = m_Interface;
		if (*pVal)
			(*pVal)->AddRef();
	}

	return S_OK;
}

STDMETHODIMP CQueue::put_Interface(IGet *newVal)
{
	HRESULT r;

	//	Add the reference
	if (newVal)
		newVal->AddRef();

	//	Quit any threads, etc.
	if (FAILED(r = Closeout()))
		return r;

	//	Set the interface
	if (newVal)
		m_Interface = newVal;
	else
		return S_FALSE;	//	This is not an error

	//	Start the loop
	if (FAILED(r = Restart()))
	{
		//	Reset us to not active
		Closeout();
		if (m_Interface)
			m_Interface->Release();
		m_Interface = 0;
		return r;
	}

	return S_OK;
}

STDMETHODIMP CQueue::Free(UINT*aSize)
{
	//	Get the values as an atomic operation
	EnterCriticalSection(&m_Copying);

	//	The available memory
	UINT Size = m_Out - m_In - 1;
	if (m_Empty)
		Size = BufferSize - 1;

	//	Get the values as an atomic operation
	LeaveCriticalSection(&m_Copying);

	//	if they are not flipped
	if (Size > BufferSize)
		Size += BufferSize;

	//	Return the value
	if (aSize)
		*aSize = Size;

	return S_OK;
}

HRESULT CQueue::Closeout()
{
	HRESULT r;

	//	If it is not active
	if (!m_Interface)
		return S_OK;

	//	Interrupt any operations
	if (FAILED(r = Interrupt()))
		return r;

	//	Mark as not active
	if (m_Interface)
		m_Interface->Release();
	m_Interface = 0;

	return S_OK;
}

HRESULT CQueue::Restart()
{
	//	If it is not ready
	if (!m_Interface)
		return E_FAIL;

	//	Start the thread
	DWORD Id;
	HANDLE Thread = CreateThread(0, 0, StartReadLoop, (void*)this, 0, &Id);
	if (!Thread)
		return E_FAIL;

	//	Set it to real time priority
	SetPriorityClass(Thread, REALTIME_PRIORITY_CLASS);

	//	Wait until the loop starts
	WaitForSingleObject(m_Start, 5000);

	//	It's going
	return S_OK;
}

DWORD WINAPI CQueue::StartReadLoop(LPVOID lpParameter)
{
	CQueue*Queue = (CQueue*)lpParameter;

	//	Do the loop
	if (Queue)
		return Queue->ReadLoop();

	return E_FAIL;
}

HRESULT CQueue::ReadLoop()
{
	HRESULT r = S_OK;

	//	Safety first
	if (!m_Interface)
		return E_FAIL;

	//	Not interrupted yet!
	m_Interrupt = false;

	//	Signal that we are started
	SetEvent(m_Start);

	//	The loop
	while (!m_Interrupt)
	{
		//	If cleared, reset m_In
		EnterCriticalSection(&m_Copying);
		if (m_Empty)
			m_In = 0;
		m_Empty = false;	//	The pointers now say that
		LeaveCriticalSection(&m_Copying);

		//	Get m_Out (Note: m_In is ours)
		UINT Out = m_Out;

		//	Get the available space
		UINT Space = Out - m_In - 1;
		if (Space > BufferSize)
			Space = BufferSize - m_In;

		//	If there is no space, wait, hoping for the best.
		if (Space == 0)
		{
			Sleep(20);
			continue;
		}

		//	Wait until there is something (loop every second)
		UINT Time;
		UINT Length;
		if (FAILED(m_Interface->Available(&(Length = 1), &(Time = 1000))))
			continue;

		//	Get the data
		if (FAILED(m_Interface->Get(m_Buffer + m_In, &Space, 0)))
		{
			//	We're in deep trouble
			m_Interrupt = true;
			r = E_FAIL;
			continue;
		}

		//	Adjust m_In (our end)
		EnterCriticalSection(&m_Copying);
		m_In += Space;
		if (m_In >= BufferSize)
			m_In = 0;
		LeaveCriticalSection(&m_Copying);

		//	Signal Available if needed
		if (m_DoASignal && Space)
		{
			SetEvent(m_Signal);
			WaitForSingleObject(m_Signaled, 250);
		}
	}

	//	End the loop
	if (m_Interface)
		m_Interface->Release();
	m_Interface = 0;

	//	Signal that we are done
	SetEvent(m_Done);

	return r;
}

