#include <err.h>
#include <assert.h>
#include <new.h>
#include <bits.h>
#include <arch/ops.h>
#include <kernel/Scheduler.h>
#include <kernel/Thread.h>
#include <kernel/Timer.h>
#include <kernel/AutoSpinlock.h>
#include <os/Thread.h>
#include <platform/debug.h>

#define TRACE_SCHED 0

#if TRACE_SCHED
#define STRACE0(fmt) dprintf("SCHED %s: " fmt, __FUNCTION__)
#define STRACE(fmt, x...) dprintf("SCHED %s: " fmt, __FUNCTION__, x)
#else
#define STRACE0(fmt)
#define STRACE(fmt, x...)
#endif

// give the arch code a chance to declare inline versions of the Thread::Arch* routines
#include <arch/ArchThreadInlines.h>

namespace OS {
namespace Kernel {

// scheduler tunables
const time_t kQuantumTickFreq = 20;
const int kDefaultQuantum = 5;

// the run queue
List<Thread> Scheduler::sRunQueue[kNumPriorities];
unsigned int Scheduler::sRunQueueBitmap = 0;
Spinlock Scheduler::sRunQueueLock;

// the idle thread
static OS::Thread *sIdleThread = NULL;

// the quantum timer, to preempt the current thread
Timer Scheduler::sQuantumTimer;

// have we been started?
bool Scheduler::sStarted = false;

// an initial thread, to give the system some state before the idle thread takes over
static Thread sInitialThread(NULL, NULL, 0, "initial thread", 1);

int IdleThread()
{
	dprintf("idle thread starting\n");
	for(;;)
		arch_idle();

	return 0;
}

handlerReturn Scheduler::QuantumTimerCallback(Timer *, void *, time_t)
{
	Thread *t = Thread::GetCurrent();

	t->mRemainingQuantum--;
	if (t->mRemainingQuantum <= 0) {
		STRACE("thread %p's quantum expired\n", t);
		return INT_RESCHEDULE;
	} else {
		return INT_NO_RESCHEDULE;
	}
}

status_t Scheduler::Init()
{
	dprintf("%s: init\n", __PRETTY_FUNCTION__);

	// set the "initial" thread
	Thread::sCurrent = &sInitialThread;

	// create an idle thread
	sIdleThread = new OS::GenericThread(&IdleThread, "idle thread");
	sIdleThread->SetPriority(kIdlePriority);
	sIdleThread->Start(false);

	dprintf("%s: done\n", __PRETTY_FUNCTION__);

	return NO_ERROR;
}

void Scheduler::Start()
{
	dprintf("%s: starting scheduler\n", __PRETTY_FUNCTION__);
	sStarted = true;

	// set the quantum timer to free run, preempting the current thread
	sQuantumTimer.SetPeriodic(&QuantumTimerCallback, NULL, kQuantumTickFreq);
	
	Reschedule();
}

void Scheduler::Reschedule()
{
	STRACE("curr %d\n", Thread::GetCurrentId());

	// grab the run queue's spinlock
	AutoSpinlock alock(sRunQueueLock);

	_Reschedule();
}

void Scheduler::RescheduleAndRelease(Spinlock &lock)
{
	STRACE("curr %d\n", Thread::GetCurrentId());

	// grab the run queue's lock
	AutoSpinlock alock(sRunQueueLock);

	lock.Release();

	_Reschedule();
}

void Scheduler::_Reschedule()
{
	Thread *from = Thread::GetCurrent();
	Thread *to;

	STRACE("curr %d\n", Thread::GetCurrentId());

	ASSERT(sRunQueueLock.LockHeld());

//	dprintf("%s\n", __PRETTY_FUNCTION__);

	// look in the run queue bitmap to find the first available one
	ASSERT(sRunQueueBitmap != 0); // there should always be a thread in the run queue
	int firstPrio = (kNumPriorities - 1) - clz(sRunQueueBitmap);

#if 0 && TRACE_SCHED
	dprintf("firstPrio %d, bitmap 0x%x\n", firstPrio, sRunQueueBitmap);
	sRunQueue[firstPrio].Dump();
#endif

	to = PopRunQueueHead(firstPrio);
	ASSERT(to != NULL);

	// set the new thread's state
	Thread::sCurrent = to;
	to->mThreadState = Thread::RUNNING;

	if (to == from)
		return; // we picked ourselves, so just continue

	// reset the quantum on this thread
	if (to->mRemainingQuantum <= 0)
		to->mRemainingQuantum = kDefaultQuantum;

	STRACE("switching from %d to %d\n", from->Id(), to->Id());

	to->ArchContextSwitchTo(from);
}

void Scheduler::Yield()
{
	Thread *current = Thread::GetCurrent();

	STRACE("curr %d\n", Thread::GetCurrentId());

	ASSERT(current->mThreadState == Thread::RUNNING);

	// grab the run queue's spinlock
	AutoSpinlock alock(sRunQueueLock);

	// use up one of our quantums
	current->mRemainingQuantum--;

	// stick ourself back on the run queue and reschedule
	current->mThreadState = Thread::READY;
	AddToRunQueueTail(current, current->mPriority);

	_Reschedule();
}

void Scheduler::Preempt()
{
	Thread *current = Thread::GetCurrent();

	STRACE("curr %d\n", Thread::GetCurrentId());

	ASSERT(current->mThreadState == Thread::RUNNING);

	// grab the run queue's spinlock
	AutoSpinlock alock(sRunQueueLock);

	// stick ourself back on the run queue and reschedule
	current->mThreadState = Thread::READY;

	// if our remaining quantum is zero or negative, we've used up our time and
	// should go at the end of our priority queue's list
	if (current->mRemainingQuantum <= 0)
		AddToRunQueueTail(current, current->mPriority);
	else
		AddToRunQueueHead(current, current->mPriority);

	_Reschedule();
}

void Scheduler::Resume(Thread *t)
{
	ASSERT(t->mThreadState != Thread::RUNNING && t->mThreadState != Thread::READY);

	STRACE("t %d\n", t->Id());

	// grab the run queue's spinlock
	AutoSpinlock alock(sRunQueueLock);

	// stick the thread into the run queue
	t->mThreadState = Thread::READY;
	AddToRunQueueHead(t, t->mPriority);
}

void Scheduler::RemoveThreadFromRunQueue(Thread *t)
{
	ASSERT(t->mThreadState == Thread::READY);

	// grab the run queue's spinlock
	AutoSpinlock alock(sRunQueueLock);

	t->mQueueNode.Remove();
}

//
// run queue manipulation routines
//
void Scheduler::AddToRunQueueHead(Thread *t, int priority)
{
	ASSERT(sRunQueueLock.LockHeld());
	ASSERT(priority >= 0 && priority < kNumPriorities);

	sRunQueue[priority].AddToHead(&t->mQueueNode);
	sRunQueueBitmap |= (1 << priority);
}

void Scheduler::AddToRunQueueTail(Thread *t, int priority)
{
	ASSERT(sRunQueueLock.LockHeld());
	ASSERT(priority >= 0 && priority < kNumPriorities);

	sRunQueue[priority].AddToTail(&t->mQueueNode);
	sRunQueueBitmap |= (1 << priority);
}

Thread *Scheduler::PopRunQueueHead(int priority)
{
	ASSERT(sRunQueueLock.LockHeld());
	ASSERT(priority >= 0 && priority < kNumPriorities);

	Thread *t = sRunQueue[priority].PopHead();

	if (sRunQueue[priority].Count() == 0) {
		// clear the available bit in the bitmap
		sRunQueueBitmap &= ~(1 << priority);
	}

	return t;
}

}} // namespace OS::Kernel
