#ifndef _ConveyorThread_h_
#define _ConveyorThread_h_

#include <Core/Core.h>
#include <windows.h>

using namespace Upp;

template<class T> class ConveyorThread : protected Thread
{ typedef ConveyorThread CLASSNAME;
public:
	ConveyorThread(bool enabled = true)
	{
		finishEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		queueEvent  = CreateEvent(NULL, TRUE, FALSE, NULL);
		
		Run(THISBACK(RunQueue));
	}
	
	virtual ~ConveyorThread()
	{
		CloseHandle(finishEvent);
		CloseHandle(queueEvent);
	}
	
	void Request(Callback1<const T &>handler, const T &request)
	{
		queueMutex.Enter();
		queue.AddTail(RequestElement(request, handler));
		queueMutex.Leave();
		if (enabled)
			SetEvent(queueEvent);
	}
	
	void ClearAllRequests()
	{
		queueMutex.Enter();
		queue.Clear();
		queueMutex.Leave();
	}
	
	void Enable(bool enable = true)
	{
		if (enable && !enabled && !queue.IsEmpty())
			SetEvent(queueEvent);
		enabled = enable;
	}
	
	void SharedEnter() {sharedDataMutex.Enter();}
	void SharedLeave() {sharedDataMutex.Leave();}

	void RequestFinish() {SetEvent(finishEvent);}
	int  WaitFinished()  {return Wait();}
	bool IsFinished()    {return !IsOpen();}
	
private:
	void RunQueue()
	{
		const static int WAITINGEVENTS = 2;
		HANDLE waitingEvents[WAITINGEVENTS] = {finishEvent, queueEvent};
		const static DWORD SIGNALED_EVENT_FINISH = WAIT_OBJECT_0;
		const static DWORD SIGNALED_EVENT_QUEUE  = WAIT_OBJECT_0+1;
		
		DWORD signaledEvent;
		while ((signaledEvent = WaitForMultipleObjects(WAITINGEVENTS, waitingEvents, FALSE, INFINITE)) != SIGNALED_EVENT_FINISH)
		{
			queueMutex.Enter();
			bool requests = !queue.IsEmpty();
			queueMutex.Leave();
			
			while (requests)
			{
				queueMutex.Enter();
				const RequestElement headRequest = queue.Head(); //copy due to ::ClearRequests
				queue.DropHead();
				requests = !queue.IsEmpty();
				queueMutex.Leave();
	
				callback1(headRequest.handler, headRequest.t);
				
				if (requests && (WaitForSingleObject(finishEvent, 0) == WAIT_OBJECT_0))
					return;
			}
		}
	}
		
	bool   enabled;
	Mutex  queueMutex;
	Mutex  sharedDataMutex;
	HANDLE finishEvent;
	HANDLE queueEvent;
	
	struct RequestElement : Moveable<RequestElement>
	{
		const T  t;
		Callback1<const T &> handler;
		RequestElement(const T &_t, Callback1<const T &> _c) :t(_t) , handler(_c) {}
	};
	typedef BiVector<RequestElement> RequestQueue;
	RequestQueue queue;
};

#endif
