#include "CoWork.h"

namespace MyCoWork
{

#ifdef _MULTITHREADED

#define LLOG(x)   //  LOG(x)
#define LDUMP(x)  //  DUMP(x)

CoWork::Pool& CoWork::pool()
{
	static Pool pool;
	return pool;
}

void CoWork::Pool::AddThread(int count) 
{
	lock.Enter();
	for(int i = 0; i < count; i++)
	{
		Thread * pt = new Thread();
		unsigned key = GetPtrHashValue(pt);
		threads.Add(key, pt).Run(THISBACK1(ThreadRun, key));
	}
	lock.Leave();
}

void CoWork::Pool::KillThread(int count, bool waitone)
{
	MJob job;
	job.work = NULL;
	bool finishall = false;

	lock.Enter();
	if(count < 0)
	{
		finishall = true;
		jobs.Clear();
		count = threads.GetCount();
	}
	count = min(count, threads.GetCount()); //clip
		
	for(int i = 0; i < count; i++)
	{
		jobs.Add(job);
		waitforjob.Release(); //threads will consume their Thread instances
	}
	lock.Leave();
	
	//wait only when finishing
	//scan the threads to find quited ones, if none available, handle over
	if(waitone || finishall)
	{
		Sleep(1); //give chance for some to have finished, the waiting at least
		lock.Enter();
		int i = 0;
		while(count > 0)
		{
			if(threads.GetKey(i) != 0)
			{
				//a used thread, check next
				if(++i >= threads.GetCount())
				{
					i=0;
					lock.Leave();
					Sleep(1); //give chance for some to have finished
					lock.Enter();
				}
				continue;
			}
			
			//a quited thread
			Thread * pt = threads.Detach(i);
			--count;
			if(i >= threads.GetCount()) i=0;

			lock.Leave();
			
			//finish a Thread context
			pt->Wait();
			delete pt;
			pt = NULL;
			
			lock.Enter();
		}
		lock.Leave();
	}
}

CoWork::Pool::Pool(int threadnr)
{
	LLOG("CoWork INIT with threadnr " << threadnr);
	if(threadnr < 0)
		threadnr = CPU_Cores() + 2;
	AddThread(threadnr);
}

CoWork::Pool::~Pool()
{
	LLOG("Quit");
	KillThread(-1); //all
	LLOG("Quit ended");
}

bool CoWork::Pool::DoJob()
{
	MJob job = jobs.Pop();
	if(job.work == NULL) {
		RLOG("Quit thread initiate");
		return true;
	}
	lock.Leave();
	job.cb();
	lock.Enter();
	if(--job.work->todo <= 0) {
		LLOG("Releasing waitforfinish of (CoWork " << FormatIntHex(job.work) << ")");
		job.work->waitforfinish.Release(); //multiple call, but semaphore cant become negative anyway
	}
	LLOG("Finished, remaining todo " << job.work->todo << " (CoWork " << FormatIntHex(job.work) << ")");
	return false;
}

void CoWork::Pool::ThreadRun(unsigned tno)
{
	LLOG("CoWork thread #" << tno << " started");
	lock.Enter();
	for(;;) {
		while(jobs.GetCount() <= 0) {
			waiting_threads++;
			lock.Leave();
			LLOG("#" << tno << " Waiting for job");
			waitforjob.Wait();
			LLOG("#" << tno << " Waiting ended");
			lock.Enter();
		}
		LLOG("#" << tno << " Job acquired");
		if(DoJob())
			break;
		LLOG("#" << tno << " Job finished");
	}
	int id = threads.Find(tno);
	Thread & t = threads[id];
	
	threads.SetKey(id, 0); //mark as quited
	lock.Leave();
	RLOG("!!! CoWork thread #" << tno << " finished");
}

void CoWork::Do(Callback cb) {
	Pool& p = _pool;
	p.lock.Enter();
	if(p.jobs.GetCount() > 128) {
		LLOG("Stack full: running in the main thread");
		p.lock.Leave();
		cb();
		return;
	}
	MJob job;
	job.cb = cb;
	job.work = this;
	p.jobs.Add(job);
	todo++;
	LLOG("Adding job; todo: " << todo << " (CoWork " << FormatIntHex(this) << ")");
	if(p.waiting_threads>0) {
		LLOG("Releasing thread waiting for job: " << p.waiting_threads);
		p.waiting_threads--;
		p.waitforjob.Release();
	}
	p.lock.Leave();
}

void CoWork::Finish() {
#ifdef _MULTITHREADED
	Pool &p = _pool;
	p.lock.Enter();
	while(todo>0) {
		LLOG("Finish: todo: " << todo << " (CoWork " << FormatIntHex(this) << ")");
		if(p.jobs.GetCount()>0)
			p.DoJob();
		else {
			p.lock.Leave();
			LLOG("WaitForFinish (CoWork " << FormatIntHex(this) << ")");
			waitforfinish.Wait();
			p.lock.Enter();
		}
	}
	p.lock.Leave();
	LLOG("CoWork finished");
#endif
}

CoWork::CoWork(int threadnr)
	: _pool((threadnr<0)?(pool()):(	_opool.Attach(new Pool(threadnr)),_opool.operator*()))
{
	LLOG("*** CoWork constructed " << FormatHex(this));
	todo = 0;
}

CoWork::~CoWork()
{
	Finish();
	LLOG("~~~ CoWork destructed");
}

#endif

} //namespace
