#include "threadimpl.h"

Pqueue _threadpq;	/* list of all procs */
int _threadnprocs;	/* count of procs */

static int newthreadid(void);
static int newprocid(void);

/*
 * Create and initialize a new Thread structure attached to a given proc.
 */
int
_newthread(Proc *p, void (*f)(void *arg), void *arg, uint stacksize, 
	char *name, int grp)
{
	int id;
	Thread *t;

	t = _threadmalloc(sizeof(Thread), 1);
	t->proc = p;
	t->nextproc = p;
	t->homeproc = p;

	t->grp = grp;
	t->id = id = newthreadid();
	if(name)
		t->name = strdup(name);
	_threaddebug(DBGSCHED, "create thread %d.%d name %s", p->id, id, name);

	/*
	 * Allocate and clear stack.
	 */
	if(stacksize < 1024)
		sysfatal("bad stacksize %d", stacksize);
	t->stk = _threadmalloc(stacksize, 0);
	t->stksize = stacksize;
	_threaddebugmemset(t->stk, 0xFE, stacksize);

	/*
	 * Set up t->context to call f(arg).
	 */
	_threadinitstack(t, f, arg);

	/*
	 * Add thread to proc.
	 */
	lock(&p->lock);
	_procaddthread(p, t);

	/*
	 * Mark thread as ready to run.
	 */
	t->state = Ready;
	_threadready(t);
	unlock(&p->lock);

	return id;
}

/* 
 * Free a Thread structure.
 */
void
_threadfree(Thread *t)
{
	free(t->stk);
	free(t->name);
	free(t);
}

/*
 * Create and initialize a new Proc structure with a single Thread
 * running inside it.  Add the Proc to the global process list.
 */
Proc*
_newproc(void)
{
	Proc *p;

	/*
	 * Allocate.
	 */
	p = _threadmalloc(sizeof *p, 1);
	p->id = newprocid();
	
	/*
	 * Add to list.  Record if we're now multiprocess.
	 */
	lock(&_threadpq.lock);
	if(_threadpq.head == nil)
		_threadpq.head = p;
	else
		*_threadpq.tail = p;
	_threadpq.tail = &p->next;
	if(_threadnprocs == 1)
		_threadmultiproc();
	_threadnprocs++;
	unlock(&_threadpq.lock);

	return p;
}

/*
 * Allocate a new thread running f(arg) on a stack of size stacksize.
 * Return the thread id.  The thread group inherits from the current thread.
 */
int
threadcreate(void (*f)(void*), void *arg, uint stacksize)
{
	return _newthread(_threadgetproc(), f, arg, stacksize, nil, threadgetgrp());
}

/* 
 * Allocate a new idle thread.  Only allowed in a single-proc program.
 */
int
threadcreateidle(void (*f)(void *arg), void *arg, uint stacksize)
{
	int id;

	assert(_threadpq.head->next == nil);	/* only 1 */

	id = threadcreate(f, arg, stacksize);
	_threaddebug(DBGSCHED, "idle is %d", id);
	_threadsetidle(id);
	return id;
}

/*
 * Threadcreate, but do it inside a fresh proc.
 */
int
proccreate(void (*f)(void*), void *arg, uint stacksize)
{
	int id;
	Proc *p, *np;

	p = _threadgetproc();
	np = _newproc();
	p->newproc = np;
	p->schedfn = _threadstartproc;
	id = _newthread(np, f, arg, stacksize, nil, p->thread->grp);
	_sched();	/* call into scheduler to create proc XXX */
	return id;
}

/*
 * Allocate a new thread id.
 */
static int
newthreadid(void)
{
	static Lock l;
	static int id;
	int i;

	lock(&l);
	i = ++id;
	unlock(&l);
	return i;
}

/*
 * Allocate a new proc id.
 */
static int
newprocid(void)
{
	static Lock l;
	static int id;
	int i;

	lock(&l);
	i = ++id;
	unlock(&l);
	return i;
}

/*
 * Add thread to proc's list.
 */
void
_procaddthread(Proc *p, Thread *t)
{
	p->nthreads++;
	if(p->threads.head == nil)
		p->threads.head = t;
	else{
		t->prevt = p->threads.tail;
		t->prevt->nextt = t;
	}
	p->threads.tail = t;
	t->next = (Thread*)~0;
}

/*
 * Remove thread from proc's list.
 */
void
_procdelthread(Proc *p, Thread *t)
{
	if(t->prevt)
		t->prevt->nextt = t->nextt;
	else
		p->threads.head = t->nextt;
	if(t->nextt)
		t->nextt->prevt = t->prevt;
	else
		p->threads.tail = t->prevt;
	p->nthreads--;
}
