#include <err.h>
#include <sys/types.h>
#include <kernel/Dispatcher.h>
#include <kernel/AutoSpinlock.h>
#include <kernel/Thread.h>
#include <kernel/Scheduler.h>

namespace OS {
namespace Kernel {

Dispatcher::Dispatcher()
	: mSignalled(false)
{
}

Dispatcher::Dispatcher(bool initialSignal)
	: mSignalled(initialSignal)
{
}

Dispatcher::~Dispatcher()
{
	/* flush all the waiters */
	Signal();
}

status_t Dispatcher::Wait(Thread *t, bool unsignalAfter, time_t timeout)
{
	if (timeout != INFINITE_TIME)
		panic("%s: non-infinite timeout %u not supported\n", __PRETTY_FUNCTION__, (unsigned int)timeout);
	
	/* we have to disable preemption and acquire our spinlock manually here
	 * because of the special semantics of Scheduler::RescheduleAndRelease() */
	ThreadPreemptRef ref;
	mLock.Acquire();

	if (mSignalled) {
		/* let precisely one object pass through the dispatcher */
		if (unsignalAfter)
			mSignalled = false;
		mLock.Release();
		return NO_ERROR;
	} else {
		/* add on our queue */
		mQueue.AddToTail(&t->mQueueNode);

		/* let the dispatch object do any pending work when it gets added to our list */
		status_t err = t->DispatchWait(this, mLock);
		if (err < NO_ERROR) {
			// something bad happened, try to clean up
			t->mQueueNode.Remove();
			mLock.Release();
			return err;
		}

		return err;
	}
}

void Dispatcher::Pulse(bool signalIfEmpty)
{
	if (!mSignalled) {
		bool resched = false;

		AutoSpinlock alock(mLock);

		if (signalIfEmpty && mQueue.Count() == 0)
			mSignalled = true;

		Thread *t = mQueue.PopHead();
		if (t) {
			resched = t->DispatchSignal();
		}

		alock.Release();

		if (resched)
			Scheduler::Preempt();
	}
}

void Dispatcher::Signal()
{
	if (!mSignalled) {
		bool resched = false;

		AutoSpinlock alock(mLock);

		Thread *t;
	 	while ((t = mQueue.PopHead())) {	
			if (t->DispatchSignal())
				resched = true;
		}

		mSignalled = true;
		alock.Release();

		if (resched)
			Scheduler::Preempt();
	}
}
	
void Dispatcher::Unsignal()
{
	mSignalled = false;
}

}} // namespace OS::Kernel

