/*
 * Thread scheduler.
 */
#include "threadimpl.h"

static Thread *runthread(Proc*);
static void schedexit(Proc*);

/*
 * Main scheduling loop.
 */
void
_threadscheduler(void *arg)
{
	Proc *p;
	Thread *t;

	p = arg;

	_threadlinkmain();
	_threadinitproc(p);

	for(;;){
		/* 
		 * Clean up zombie children.
		 */
		_threadwaitkids(p);

		/*
		 * Find next thread to run.
		 */
		_threaddebug(DBGSCHED, "runthread");
		t = runthread(p);
		if(t == nil)
			schedexit(p);
	
		/*
		 * If it's ready, run it (might instead be marked to die).
		 */
		lock(&p->lock);
		if(t->state == Ready){
			_threaddebug(DBGSCHED, "running %d.%d", p->id, t->id);
			t->state = Running;
			t->nextstate = Ready;
			p->thread = t;
			unlock(&p->lock);
			_swaplabel(&p->context, &t->context);
			lock(&p->lock);
			p->thread = nil;
		}

		/*
		 * If thread needs to die, kill it.
		 * t->proc == p may not be true if we're
		 * trying to jump into the exec proc (see exec-unix.c).
		 */
		if(t->moribund){
			_threaddebug(DBGSCHED, "moribund %d.%d", p->id, t->id);
			if(t->moribund != 1)
				print("moribund broke %p %d\n", &t->moribund, t->moribund);
			assert(t->moribund == 1);
			t->state = Dead;
			_procdelthread(p, t);
			unlock(&p->lock);
			_threadfree(t);
			t = nil;
			continue;
		}

		/*
		 * If the thread has asked to move to another proc,
		 * let it go (only to be used in *very* special situations).
		if(t->nextproc != p)
			_procdelthread(p, t);
		 */

		unlock(&p->lock);

		/*
		 * If the thread has asked to move to another proc,
		 * add it to the new proc.
		 */
		if(t->nextproc != p){
		//	lock(&t->nextproc->lock);
		//	_procaddthread(t->nextproc, t);
		//	unlock(&t->nextproc->lock);
			t->proc = t->nextproc;
		}

		/*
		 * If there is a request to run a function on the 
		 * scheduling stack, do so.
		 */
		if(p->schedfn){
			_threaddebug(DBGSCHED, "schedfn");
			p->schedfn(p);
			p->schedfn = nil;
			_threaddebug(DBGSCHED, "schedfn ended");
		}

		/*
		 * Move the thread along.
		 */
		t->state = t->nextstate;
		_threaddebug(DBGSCHED, "moveon %d.%d", t->proc->id, t->id);
		if(t->state == Ready)
			_threadready(t);
	}
}

/*
 * Called by thread to give up control of processor to scheduler.
 */
int
_sched(void)
{
	Proc *p;
	Thread *t;

	p = _threadgetproc();
	t = p->thread;
	assert(t != nil);
	_swaplabel(&t->context, &p->context);
	return p->nsched++;
}

/*
 * Called by thread to yield the processor to other threads.
 * Returns number of other threads run between call and return.
 */
int
yield(void)
{
	Proc *p;
	int nsched;

	p = _threadgetproc();
	nsched = p->nsched;
	return _sched() - nsched;
}

/*
 * Choose the next thread to run.
 */
static Thread*
runthread(Proc *p)
{
	Thread *t;
	Tqueue *q;

	/*
	 * No threads left?
	 */
	if(p->nthreads==0 || (p->nthreads==1 && p->idle))
		return nil;

	lock(&p->readylock);
	q = &p->ready;
	if(q->head == nil){
		/*
		 * Is this a single-process program with an idle thread?
		 */
		if(p->idle){
			/*
			 * The idle thread had better be ready!
			 */
			if(p->idle->state != Ready)
				sysfatal("all threads are asleep");

			/*
			 * Run the idle thread.
			 */
			unlock(&p->readylock);
			_threaddebug(DBGSCHED, "running idle thread", p->nthreads);
			return p->idle;
		}

		/*
		 * Wait until one of our threads is readied (by another proc!).
		 */
		q->asleep = 1;
		p->rend.l = &p->readylock;
		_procsleep(&p->rend);

		/*
		 * Maybe we were awakened to exit?
		 */
		if(_threadexitsallstatus){
			_threaddebug(DBGSCHED, "time to exit");
			_exits(_threadexitsallstatus);
		}
		assert(q->head != nil);
	}

	t = q->head;
	q->head = t->next;
	unlock(&p->readylock);

	return t;
}

/*
 * Add a newly-ready thread to its proc's run queue.
 */
void
_threadready(Thread *t)
{
	Tqueue *q;

	/*
	 * The idle thread does not go on the run queue.
	 */
	if(t == t->proc->idle){
		_threaddebug(DBGSCHED, "idle thread is ready");
		return;
	}

	assert(t->state == Ready);
	_threaddebug(DBGSCHED, "readying %d.%d", t->proc->id, t->id);

	/*
	 * Add thread to run queue.
	 */
	q = &t->proc->ready;
	lock(&t->proc->readylock);

	t->next = nil;
	if(q->head == nil)
		q->head = t;
	else
		q->tail->next = t;
	q->tail = t;

	/*
	 * Wake proc scheduler if it is sleeping.
	 */
	if(q->asleep){
		assert(q->asleep == 1);
		q->asleep = 0;
		_procwakeup(&t->proc->rend);
	}
	unlock(&t->proc->readylock);
}

/*
 * Mark the given thread as the idle thread.
 * Since the idle thread was just created, it is sitting
 * somewhere on the ready queue.
 */
void
_threadsetidle(int id)
{
	Tqueue *q;
	Thread *t, **l, *last;
	Proc *p;

	p = _threadgetproc();

	lock(&p->readylock);

	/*
	 * Find thread on ready queue.
	 */
	q = &p->ready;
	for(l=&q->head, last=nil; (t=*l) != nil; l=&t->next, last=t)
		if(t->id == id)
			break;
	assert(t != nil);

	/* 
	 * Remove it from ready queue.
	 */
	*l = t->next;
	if(t == q->head)
		q->head = t->next;
	if(t->next == nil)
		q->tail = last;

	/*
	 * Set as idle thread.
	 */
	p->idle = t;
	_threaddebug(DBGSCHED, "p->idle is %d\n", t->id);
	unlock(&p->readylock);
}

static void
schedexit(Proc *p)
{
	char ex[ERRMAX];
	int n;
	Proc **l;

	_threaddebug(DBGSCHED, "exiting proc %d", p->id);
	lock(&_threadpq.lock);
	for(l=&_threadpq.head; *l; l=&(*l)->next){
		if(*l == p){
			*l = p->next;
			if(*l == nil)
				_threadpq.tail = l;
			break;
		}
	}
	n = --_threadnprocs;
	unlock(&_threadpq.lock);

	strncpy(ex, p->exitstr, sizeof ex);
	ex[sizeof ex-1] = '\0';
	free(p);
	if(n == 0){
		_threaddebug(DBGSCHED, "procexit; no more procs");
		_threadexitallproc(ex);
	}else{
		_threaddebug(DBGSCHED, "procexit");
		_threadexitproc(ex);
	}
}

