#include <err.h>
#include <sys/types.h>
#include <debug.h>
#include <string.h>
#include <kernel/Thread.h>
#include <kernel/AutoSpinlock.h>
#include <kernel/Heap.h>
#include <arch/ops.h>

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

namespace OS {
namespace Kernel {

Thread *Thread::sCurrent = NULL;

Thread::Thread(void *container, void (*startRoutine)(unsigned long), unsigned long arg, const char *name, int priority)
	: KObject(KObject::THREAD, name),
	  mQueueNode(this),
	  mThreadState(INITIAL),
	  mPriority(priority),
	  mStackBase(0),
	  mStackSize(0),
	  mStartRoutine(startRoutine),
	  mStartRoutineArg(arg),
	  mDispatcher(0),
	  mContainer(container)
{
	// initialize any arch part of the thread
	ArchInitialize();
}

Thread::~Thread()
{
	ASSERT(mThreadState == INITIAL || mThreadState == DEAD);

	// XXX free stack if allocated
	panic("thread destructor does not free stack\n");
}

status_t Thread::SetCustomStack(vaddr_t base, size_t len)
{
	if (mThreadState != INITIAL)
		return ERROR; // too late now

	mStackBase = base;
	mStackSize = len;

	return NO_ERROR;
}

status_t Thread::Run(bool reschedule)
{
	if (mThreadState != INITIAL)
		return ERR_ALREADY_STARTED; // it's already been started

	// if someone didn't already set up a custom stack, allocate one here
	if (mStackSize == 0) {
		panic("%s: no stack allocated\n", __PRETTY_FUNCTION__);
		return ERR_NO_MEMORY;
	}

	// initialize arch part of the stack
	ArchInitializeStack();

	// initialize the preemption count to 1
	mPreemptionDisable = 1;

	// publish it
	MakeGlobal();

	// stick this thread on the run queue
	Scheduler::Resume(this);

	// reschedule if asked
	if (reschedule)
		Scheduler::Reschedule();

	return NO_ERROR;
}

// trampoline code to set up a new thread in it's context
void Thread::ThreadStartRoutine(Thread *t)
{
	// release the scheduler's spinlock
	Scheduler::sRunQueueLock.Release();

	// enable preemption
	t->RestorePreemption();

	dprintf("*** Thread %d starting, starting routine %p with arg 0x%lx\n", t->Id(), t->mStartRoutine, t->mStartRoutineArg);

	// call the starting routine
	(*t->mStartRoutine)(t->mStartRoutineArg);

	// exit the thread
	ExitCurrent();
}

void Thread::ExitCurrent()
{
	Thread *current = GetCurrent();

	ThreadPreemptRef ref;

	current->mThreadState = DEAD;

	dprintf("*** Thread %d exiting, will not be scheduled any longer\n", current->Id());

	Scheduler::Reschedule();

	// Should not reach here
	panic("bottom of %s, should not be here\n", __PRETTY_FUNCTION__);
}

void Thread::Kill()
{
	if (IsCurrentThread()) {
		ExitCurrent();
	} else {
		ThreadPreemptRef ref;

		switch (mThreadState) {
			case INITIAL:
			case SUSPENDED:
			case DEAD:
				// we aren't in any run queues, just move to dead state and exit
				mThreadState = DEAD;
				break;
			case READY:
				Scheduler::RemoveThreadFromRunQueue(this);
				mThreadState = DEAD;
				break;
			case RUNNING:
				panic("%s: non current thread in RUNNING state\n", __PRETTY_FUNCTION__);
				break;
			case BLOCKED:
				panic("%s: unhandled case\n", __PRETTY_FUNCTION__);
				break;
		}
	}
}

int Thread::SetPriority(int priority)
{
	if (!Scheduler::IsValidPriority(priority))
		return ERR_NOT_VALID;
	
	if (IsCurrentThread()) {
		mPriority = priority;
	} else {
		ThreadPreemptRef ref;

		switch (mThreadState) {
			case INITIAL:
			case SUSPENDED:
			case BLOCKED:
				mPriority = priority;
				break;	
			default:
				;
			case READY:
				if (mPriority != priority) {
					Scheduler::RemoveThreadFromRunQueue(this);
					mPriority = priority;
					Scheduler::Resume(this);
				}
				break;
		}
	}

	return NO_ERROR;
}

#if 0
// block on this queue, releasing the passed in spinlock atomically
void Thread::BlockOn(List<Thread> &q, void *blockedData, Spinlock &releaseLock)
{
	ASSERT(Thread::GetCurrent()->mPreemptionDisable > 0);
	ASSERT(mThreadState != INITIAL || mThreadState != BLOCKED); // cannot be new or already blocked

	mBlockedData = blockedData;

	if (GetCurrent() == this) {
		q.AddToTail(&this->mQueueNode);
		
		mThreadState = BLOCKED;

		Scheduler::RescheduleAndRelease(releaseLock);
	} else {
		panic("blocking non current thread unimplemented\n");
	}
}
#endif

Thread *Thread::LookupThread(kobj_id id)
{
	KObject *obj = KObject::Lookup(id, KObject::THREAD);
	ASSERT(obj == NULL || obj->Type() == KObject::THREAD);
	if (obj)
		return static_cast<Thread *>(obj);

	return NULL;
}

void Thread::DisablePreemption()
{
//	TRACE_ENTRY_OBJ;

	ASSERT(IsCurrentThread());

	ASSERT(mPreemptionDisable < 16); // any more than this is surely a bug

	if(++mPreemptionDisable == 1) {
//		dprintf("%s: disabling interrupts\n", __PRETTY_FUNCTION__);
		arch_disable_ints();
	}
}

void Thread::RestorePreemption()
{
//	TRACE_ENTRY_OBJ;

	ASSERT(IsCurrentThread());

	ASSERT(mPreemptionDisable > 0);

	if(--mPreemptionDisable == 0) {
//		dprintf("%s: enabling interrupts\n", __PRETTY_FUNCTION__);
		arch_enable_ints();
	}
}

bool Thread::DispatchSignal()
{
	/* should be coming out of being blocked */
	ASSERT(mThreadState == BLOCKED);

	mDispatcher = 0;

	/* add ourselves back to the run queue */
	Scheduler::Resume(this);

	return true;
}

status_t Thread::DispatchWait(Dispatcher *d, Spinlock &lock)
{
	switch (mThreadState) {
		case READY:
			Scheduler::RemoveThreadFromRunQueue(this);
			/* fall through */
		case SUSPENDED:
		case RUNNING:
			ASSERT(!mDispatcher);
			mDispatcher = d;
			mThreadState = BLOCKED;
			Scheduler::RescheduleAndRelease(lock);
			break;
		case BLOCKED:
		case DEAD:
		case INITIAL:
			return ERR_NOT_READY;
	}

	return NO_ERROR;
}


#if 0
/* message stuff */

status_t Thread::_ReceiveMessage(Message &msg, uint flags)
{
	AutoSpinlock alock(mMsgLock);
	
//	TRACE_ENTRY_OBJ;

	// see if there is a writer available
	if (mWriterQueue.Count() > 0) {
		// pull directly from their buffer
		Thread *sender = mWriterQueue.PopHead();
		ASSERT(sender != NULL);

		// we can open the lock at this point
		alock.Release();

		const Message *theirMsg = static_cast<const Message *>(sender->mBlockedData);
		ASSERT(theirMsg != NULL);

		msg = *theirMsg;

		// set the sender and receiver
		msg.mSender = sender->Id();
		msg.mReceiver = Id();

		Scheduler::Resume(sender);

		if ((flags & kMsgNoresched) == 0) {
			Scheduler::Yield();
		}
	} else if ((flags & kMsgNoblock) == 0) {
		// if not, block and wait for someone to deliver a message to us
		mThreadState = MSGRECV;

		mReceiveMsg = &msg;

		Scheduler::Reschedule();

		// if we have woken up, it's because someone delivered a message
	} else {
		// no message ready, and not going to block waiting for it
		return ERR_NO_MSG;
	}

	return NO_ERROR;
}

status_t Thread::DeliverMessage(const Message &msg, uint flags)
{
	AutoSpinlock alock(mMsgLock);

//	TRACE_ENTRY_OBJ;

	// see if the receiver thread is waiting
	if (mThreadState == MSGRECV) {
		// deliver directly into their buffer

		// we can open the lock at this point
		alock.Release();

		ASSERT(mReceiveMsg != NULL);

		*mReceiveMsg = msg;

		// set the sender and receiver
		if ((flags & kMsgNosetsender) == 0)
			mReceiveMsg->mSender = Thread::GetCurrentId();
		mReceiveMsg->mReceiver = Id();

		Scheduler::Resume(this);

		if ((flags & kMsgNoresched) == 0) {
			Scheduler::Yield();
		}
	} else if ((flags & kMsgNoblock) == 0) {
		// if not, then block and wait for someone to take our message
		Thread::GetCurrent()->BlockOn(mWriterQueue, const_cast<Message *>(&msg), mMsgLock);
	} else {
		return ERR_NOT_READY; // was not ready to be delivered to
	}

	return NO_ERROR;
}
#endif

#include <arch/arm.h>

void Thread::Dump()
{
	dprintf("Thread %p: id %d state %d preempt %d dispatcher %p cpsr 0x%x\n",
			this, Id(), mThreadState, mPreemptionDisable, mDispatcher, read_cpsr());
}

}} // namespace OS::Kernel

