/*
	ZTaskStream.hpp
	Author: James Russell <jcrussell@762studios.com>

	Purpose: Manages the execution of 'Tasks' by a number of threads determined at runtime.  Attempts to parallelize
			 execution as well as possible with the provided information.

	Changelog
	2011/04/10 - creation (jcrussell)
*/

#ifndef _ZTASKSTREAM_H
#define _ZTASKSTREAM_H

#include <ZUtil/ZThread.hpp>
#include <stdlib.h> //size_t

//Forward Declarations
class ZTaskStream;

typedef size_t ZTaskPriority;			//Task Priority
typedef size_t ZTaskThreadId;			//Task Thread Id

//Definition for immediate task execution priority
#define ZTASK_PRIORITY_IMMEDIATE (0)

//Default allocator buffer size for the task thread
#define ZTT_DEFAULT_BUFFER_SIZE 256

//Default allocator buffer size for the task stream
#define ZTS_DEFAULT_BUFFER_SIZE 1024

/*
ZTask execute return values.
*/
enum ZTaskReturnStatus
{
	ZTRS_SUCCESS,	// Task has succeeded
	ZTRS_FAILURE,	// Task has failed

	ZTRS_ENUM_SIZE
};

/*
ZTask thread Status enumeration.
*/
enum ZTaskThreadStatus 
{
	ZTTS_UNINITIALIZED,		// Task thread is uninitialized
	ZTTS_WAITING,			// Task thread is awaiting tasks
	ZTTS_EXECUTING,			// Task thread is executing tasks

	ZTTS_ENUM_SIZE
};

/*
Tasks are the basic unit of execution for a ZTaskStream.  They are added to the ZTaskStream and
assigned to a thread for execution.  They are then executed as TaskThreads
become available.
*/
class ZTask
{
friend class ZTaskThread;
friend class ZTaskStream;
public:
	ZTaskStream* TaskStream;	// The task stream that is executing this task
	ZTaskPriority Priority;		// Priority, assigned at construction, decreased each time it is delayed to zero, which is immediate priority
	bool bCancelTask;			// Cancel Boolean, if set to true, will prevent the task from being executed

	/*
	Parameterized c'tor (with default arguments).

	@param _priority - The amount of time (in ms) the task can be delayed is priority.  This means the closer to zero
		the priority is set, the faster it will be executed.
	*/
	ZTask(ZTaskPriority _priority = ZTASK_PRIORITY_IMMEDIATE) 
		: TaskStream(NULL), Priority(_priority), bCancelTask(false), Func(NULL), TaskArgument(NULL) { }

	/*
	Parameterized c'tor.

	@param _func - function to execute
	@param _taskArg - argument to the task
	@param _priority - The amount of time (in ms) the task can be delayed is priority.  This means the closer to zero
		the priority is set, the faster it will be executed.
	*/
	ZTask(ZTaskReturnStatus (*_func)(ZTaskStream*, ZTask*, void*), void *_taskArg, ZTaskPriority _priority = ZTASK_PRIORITY_IMMEDIATE) 
		: TaskStream(NULL), Priority(_priority), bCancelTask(false), Func(_func),TaskArgument(_taskArg)  { }

	/*
	Destructor.
	*/
	virtual ~ZTask() { }

	/*
	virtual public ZTask::Execute

	Execute method.  Defined by the task implementation.

	@param _arg - argument to the task (NULL by default)
	@return (ZTaskReturnStatus) - task return value
	*/
	virtual ZTaskReturnStatus Execute(void *_arg) 
		{ URFP(_arg); if (Func != NULL) return Func(TaskStream, this, TaskArgument); else return ZTRS_FAILURE; }

	/*
	virtual public ZTask::OnTaskFailed

	Handler function for task failure.  No-op by default.
	
	@return (void)
	*/
	virtual void OnTaskFailed(void *_taskArgument) 
		{ URFP(_taskArgument); }

protected:
	ZTaskReturnStatus (*Func)(ZTaskStream*, ZTask*, void*);	// function for the ZTask to execute if not implemented as a subtask
	void* TaskArgument;										// Argument passed to this task
};

/*
Future.  Used to asynchronously execute a function that will return a value when completed.
*/
template <typename R, typename A>
class ZFuture
{
friend class ZTaskStream;
protected:
	// Future Task
	class ZFutureTask : public ZTask
	{
	public:
		R (*Func)(A);			// The Function
		A Arg;					// The argument
		R Ret;					// The return value
		bool Completed;			// Completed flag
		ZEvent CompletedEvent;	// Completed Event

		// c'tor
		ZFutureTask(R (*_func)(A), A _arg)
			: ZTask(), Func(_func), Arg(_arg), Ret(), Completed(false), CompletedEvent() { }

		// d'tor
		~ZFutureTask() { }

		// subclass override
		virtual ZTaskReturnStatus Execute(void *_arg)
		{
			URFP(_arg);

			CompletedEvent.Reset();
			Ret = Func(Arg);
			Completed = true;
			CompletedEvent.Notify();

			return ZTRS_SUCCESS;
		}
	};

	ZPtr<ZFutureTask> FutureTask;	// The function execute task

public:
	/*
	Parameterized c'tor.

	@param _func - the function to execute, must return a value of type R
	@param _arg - argument to the function
	*/
	ZFuture(R (_func)(A), A _arg) : FutureTask(znew ZFutureTask(_func, _arg)) { }

	/*
	public ZFuture<R, A>::IsComplete
	
	Returns whether or not the future has completed.
	
	@return (bool) - true if the task has completed
	*/
	bool IsComplete() { return FutureTask->Completed; }

	/*
	public ZFuture<R, A>::GetValue
	
	Gets the return value from the future.
	
	@return (R) - return from the task
	*/
	R GetValue() { if (!FutureTask->Completed) FutureTask->CompletedEvent.Wait(); return FutureTask->Ret; }

	/*
	public ZFuture<R, A>::GetValueRef
	
	Gets the return value from the future as a reference.
	
	@return (R&) - reference to return from the task
	*/
	R& GetValueRef() { if (!FutureTask->Completed) FutureTask->CompletedEvent.Wait(); return FutureTask->Ret; }
};

/*
Threads which execute tasks in the ZTaskStream.  The number of operating Task Threads is
assigned at construction of a ZTaskStream and can be changed.
*/
class ZTaskThread : public ZThread
{
friend class ZTaskStream;
public:
	/*
	Parameterized c'tor.

	@param _stream - the stream giving us tasks
	@param _id - the id of this task
	*/
	ZTaskThread(ZTaskStream *_stream, ZTaskThreadId _id);

	// d'tor
	virtual ~ZTaskThread();

	/*
	public ZTaskThread::PushTask()

	Adds a task to the task thread.

	@param _task - the task to add to this task thread (argument already set)
	@return (void)
	*/
	void PushTask(ZPtr<ZTask> _task);

protected:
	// Subclass implementation of thread run, which executes tasks
	ZThreadReturn run(uint64_t _dt);

private:
	ZMutex TaskLock;			// Concurrency lock
	ZSemaphore TaskSema;		// Event that is set when new tasks are available
	ZTaskStream* Stream;		// The task stream providing us with tasks
	ZTaskThreadId Id;			// Task thread ID, used to identify this task thread
	ZTaskThreadStatus ThrStatus;	// Status of this task thread

	ZTaskPriority CurrentTaskPriority;	//The priority of the currently executing task
	ZArray< ZPtr<ZTask> > Tasks;		//Set of tasks to execute
};

/*
The task stream.  Assigns tasks to task threads that execute when desired.  Can also 
handle 'background' tasks.
*/
class ZTaskStream
{
public:
	/*
	Default c'tor.

	Creates a single task thread.  The amount of task threads running concurrently can be changed 
	using SetTaskThreadCount.
	*/
	ZTaskStream();

	// d'tor
	~ZTaskStream();

	/*
	public ZTaskStream::ExecuteTasks

	Begins execution on the currently loaded set of tasks and any further tasks generated 
	during the execution of those tasks.  Returns after all tasks that will be executed this
	frame are done executing.

	Tasks that were not completed have had their priority values reduced, meaning that if 
	they are again added to the ZTaskStream for execution repeatedly until executed, they 
	are guaranteed to eventually execute within their priority time frame.
	
	@param _frameTime - (given in ms) is the desired amount of time that execution of all tasks
						should take; if any time is remaining after execution of ALL priority zero 
						tasks, a number of extra priority > 0 tasks are executed as can be fit into 
						the extra time.  Because of this definition, frameTime is an estimate and 
						not a guarantee - execution of priority 0 tasks can take more than frameTime.
	@return (void)
	*/
	void ExecuteTasks(int _frameTime);
	
	/*
	public ZTaskStream::GetTaskThreadCount

	Gets the current number of worker threads.

	@return (size_t) - number of task stream worker threads
	*/
	size_t GetTaskThreadCount();

	/*
	public ZTaskStream::HaltFrame

	Notifies the ZTaskStream to stop assigning tasks tasks and return from the ExecuteTasks
	call that started the process.  This does not stop the execution of tasks that have already 
	been assigned.

	@return (void)
	*/
	void HaltFrame();

	/*
	public ZTaskStream::Pause

	Notifies the ZTaskStream to pause task execution for all tasks.

	@param _pause - true to pause the stream, false to unpause
	@return (void)
	*/
	void PauseFrame(bool _pause);

	/*
	public ZTaskStream::PushFuture
	
	Adds a ZFuture to the task stream, which will execute the future at the soonest available 
	opportunity.

	The ZSmartPointer version will hold a reference to the future until after execution.  If 
	the raw pointer version is used, the user is required to keep the pointer valid until after
	execution.

	@param _future - the future to execute
	@return (void)
	*/
	template <typename R, typename A>
	void PushFuture(ZPtr< ZFuture<R, A> > _future) {
		TaskThreads[AssignTask(_future->FutureTask.Pointer())]->PushTask(_future->FutureTask);
	}

	template <typename R, typename A>
	void PushFuture(ZFuture<R, A>* _future) {
		// TODO
	}

	/*
	public ZTaskStream::PushTask

	Adds a task to the task list.  The task is added to the task queue in the currently executing
	frame phase, or if a phase is not yet executing, it will be added to the next frame phase to
	execute.

	The ZSmartPointer version will hold a reference to the task until after execution.  If the 
	raw pointer version is used, the user is required to keep the pointer valid until after
	execution.

	@param _task - the task to add
	@return (void) - the id of the task
	*/
	void PushTask(ZPtr<ZTask> _task);
	void PushTask(ZTask* _task);
	
	/*
	public ZTaskStream::PushTasks

	Adds a list of tasks. Identical to PushTask() but with less locking overhead. The list of
	tasks is not emptied (i.e _tasks.Clear() is not called).

	The ZSmartPointer version will hold a reference to the tasks until after execution.  If the 
	raw pointer version is used, the user is required to keep the pointer valid until after
	execution.

	@param _tasks - the tasks to add
	@param _count - the number of tasks
	@return (void)
	*/
	void PushTasks(ZArray< ZPtr<ZTask> >& _tasks);
	void PushTasks(ZTask* _tasks, size_t _count);

	/*
	public ZTaskStream::SetTaskThreadCount
	
	Sets the number of task threads that will be running.  Should be greater than zero, or the 
	call has no effect.
	
	@param _count - the number of task threads to run simultaneously

	@return (void)
	*/
	void SetTaskThreadCount(size_t _count);

protected:
	ZMutex TaskStreamLock;				// Lock for controlling access to the ZTaskStream objects
	ZSemaphore TaskStreamSema;			// Semaphore for signaling events
	ZEvent FrameCompletedEvent;			// Event for notifying the execute caller thread that a frame is completed
	ZEvent PausedEvent;					// Event for pausing the Task Stream
	ZArray<ZTaskThread*> TaskThreads;	// Task threads that run the set of tasks
	ZArray< ZPtr<ZTask> > Tasks;		// The task list

	// Pause flag, which indicates that task execution should be paused
	// This is only used for debugging purposes, 'PausedEvent' has the same state
	// wrapped into an OS object.
	bool bPaused;

	//Halt flag, which indicates that task execution should halt
	bool bShouldHalt;

	//Assigns a task to one of the task threads
	ZTaskThreadId AssignTask( ZTask *_task);

	//Kills a given task thread
	void KillTaskThread(size_t _idx);

};

#endif
