#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/ns.h>
#include <9pm/thread.h>
#include <9pm/threadimpl.h>

struct Pqueue _threadpq;

int
procrfork(void (*f)(void *), void *arg, uint stacksize, int rforkflag)
{
	Tproc *p, *np;

	p = _threadgetproc();
	np = _newproc(f, arg, stacksize, nil, p->curthread->grp, rforkflag);
	_threaddebug(DBGPROC, "newproc, switch stacks");
	p->arg = np;
	np->sysproc.err = np->sysproc.errbuf0;
	np->sysproc.fgrp = p->sysproc.fgrp;
	np->sysproc.pgrp = p->sysproc.pgrp;
	np->sysproc.egrp = p->sysproc.egrp;
	incref(&np->sysproc.egrp->ref);
	incref(&np->sysproc.fgrp->ref);
	incref(&np->sysproc.pgrp->ref);
	np->sysproc.slash = p->sysproc.slash;
	np->sysproc.dot = p->sysproc.dot;
	incref(&np->sysproc.slash->ref);
	incref(&np->sysproc.dot->ref);
	return _threadswtch(p->curthread->env, p->oldenv, DOPROC);	/* id of new proc */
}

int
proccreate(void (*f)(void*), void *arg, uint stacksize)
{
	return procrfork(f, arg, stacksize, 0);
}

static int
nextID(void)
{
	static Lock l;
	static id;
	int i;

	lock(&l);
	i = ++id;
	unlock(&l);
	return i;
}
	
/*
 * Create and initialize a new Thread structure attached to a given proc.
 */
static Thread*
newthread(Tproc *p, void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp)
{
	Thread *t;

	if(stacksize < 32)
		sysfatal("bad stacksize %d", stacksize);
	t = _threadmalloc(sizeof(Thread), 1);
	t->stksize = stacksize;
	t->stk = _threadmalloc(stacksize, 0);
	memset(t->stk, 0xFE, stacksize);
	_threadinitstack(t, f, arg);
	t->grp = grp;
	if(name)
		t->cmdname = strdup(name);
	t->id = nextID();
	t->next = (Thread*)~0;
	t->proc = p;
	lock(&p->threadlk);
	p->nthreads++;
	if(p->threads.head == nil)
		p->threads.head = t;
	else
		p->threads.tail->nextt = t;
	p->threads.tail = t;
	unlock(&p->threadlk);
	return t;
}

/* 
 * Create a new thread and schedule it to run.
 * The thread grp is inherited from the currently running thread.
 */
int
threadcreate(void (*f)(void *arg), void *arg, uint stacksize)
{
	Thread *t;
	Tproc *p;

	p = _threadgetproc();
	if((t = newthread(p, f, arg, stacksize, nil, threadgetgrp())) == nil)
		return -1;
	t->state = Runnable;
	_threaddebug(DBGPROC, "scheduling new thread %d.%d", p->pid, t->id);
	_threadputq(&p->runnable, t);
	return t->id;
}

/*
 * Create and initialize a new Tproc structure with a single Thread
 * running inside it.  Add the Tproc to the global process list.
 */
Tproc*
_newproc(void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp, int rforkflag)
{
	Tproc *p;
	Thread *t;

	p = _threadmalloc(sizeof *p, 1);
	p->pid = -1;
	p->rforkflag = rforkflag;
	p->sysproc.err = p->sysproc.errbuf0;
	t = newthread(p, f, arg, stacksize, name, grp);
	t->state = Running;
	p->curthread = t;
	assert(p->curthread != nil);

	lock(&_threadpq.lk);
	if(_threadpq.head == nil)
		_threadpq.head = p;
	else
		_threadpq.tail->next = p;
	_threadpq.tail = p;
	unlock(&_threadpq.lk);
	_osnewproc(p);
	return p;
}

void
_freeproc(Tproc *p)
{
	Thread *t, *nextt;

	_osfreeproc(p);
	for (t = p->threads.head; t; t = nextt) {
		if (t->cmdname)
			free(t->cmdname);
		threadassert(t->stk != nil);
		free(t->stk);
		nextt = t->nextt;
		free(t);
	}
	if(p->sysproc.pgrp)
		closepgrp(p->sysproc.pgrp);
	if(p->sysproc.fgrp)
		closefgrp(p->sysproc.fgrp);
	free(p->sysproc.wd);
	free(p);
}

void
_freethread(Thread *t)
{
	Tproc *p;
	Thread *r, *pr;
	int BUG_SHARED_ACCESSES_HERE;

	p = t->proc;
	threadassert(_threadgetproc() == p);
	pr = nil;
	for (r = p->threads.head; r; r = r->nextt) {
		if (r == t)
			break;
		pr = r;
	}
	threadassert(r != nil);
	if (pr)
		pr->nextt = r->nextt;
	else
		p->threads.head = r->nextt;
	if (p->threads.tail == r)
		p->threads.tail = pr;
	if (t->cmdname)
		free(t->cmdname);
	threadassert(t->stk != nil);
	free(t->stk);
	free(t);
}

