#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "dat.h"
#include "fns.h"

static char *luser;
extern int pflag;

enum
{
	Qdir,
	Qtrace,
	Qargs,
	Qctl,
	Qfd,
	Qfpregs,
	Qkregs,
	Qmem,
	Qnote,
	Qnoteid,
	Qnotepg,
	Qns,
	Qproc,
	Qregs,
	Qsegment,
	Qstatus,
	Qtext,
	Qwait,
	Qprofile,
	Qsyscall,
	NQid,
};

typedef struct Aux Aux;
typedef struct Dirtab Dirtab;
struct Dirtab {
	char name[28];
	Qid qid;
	vlong length;
	long perm;
};
struct Aux {
	Process *p;
	int fd;
	Dirtab *d;
};

Dirtab procdir[] =
{
	"args",		{Qargs},	0,			0660,
	"ctl",		{Qctl},		0,			0600,
	"fd",		{Qfd},		0,			0444,
	"fpregs",	{Qfpregs},	0,			0400,
	"kregs",	{Qkregs},	18 * 4,			0400,
	"mem",		{Qmem},		0,			0400,
	"note",		{Qnote},	0,			0000,
	"noteid",	{Qnoteid},	0,			0664,
	"notepg",	{Qnotepg},	0,			0000,
	"ns",		{Qns},		0,			0444,
	"proc",		{Qproc},	0,			0400,
	"regs",		{Qregs},	18 * 4,			0400,
	"segment",	{Qsegment},	0,			0444,
	"status",	{Qstatus},	176,			0444,
	"text",		{Qtext},	0,			0400,
	"wait",		{Qwait},	0,			0400,
	"profile",	{Qprofile},	0,			0400,
	"syscall",	{Qsyscall},	0,			0400,	
	"",		{0},		0,			0,
};

static char *
readin(int pid, char *file)
{
	char *name, *buf;
	int fd, rc;
	
	name = smprint("/proc/%d/%s", pid, file);
	fd = open(name, OREAD);
	if(fd < 0){
		free(name);
		return nil;
	}
	buf = malloc(1024);
	rc = read(fd, buf, 1023);
	if(rc < 0){
		free(buf);
		free(name);
		close(fd);
		return nil;
	}
	buf[rc] = 0;
	free(name);
	close(fd);
	return buf;
}

static int
calcmem(Process *p)
{
	int i, r;
	
	r = 0;
	for(i = 0; i < SEGNUM; i++) {
		if(i == SEGSTACK)
			continue;
		if(p->S[i] == nil)
			continue;
		r += p->S[i]->size;
	}
	r = (r + 1023) / 1024;
	return r;
}

static int
copymem(Process *p, char *buf, u32int addr, int len)
{
	int i, n, r;

	r = len;
	while(len > 0) {
		for(i = 0; i < SEGNUM; i++) {
			if(p->S[i] == nil)
				continue;
			if(p->S[i]->start <= addr && p->S[i]->start + p->S[i]->size > addr)
				break;
		}
		if(i == SEGNUM) {
			werrstr("bad arg in syscall");
			return -1;
		}
		n = p->S[i]->start + p->S[i]->size - addr;
		if(n > len)
			n = len;
		memcpy(buf, (char*)p->S[i]->data + addr - p->S[i]->start, n);
		len -= n;
		buf += n;
	}
	return r;
}

static char *
segments(Process *p)
{
	char *r, *s;
	static char *names[] = {
		[SEGTEXT] "Text",
		[SEGSTACK] "Stack",
		[SEGDATA] "Data",
		[SEGBSS] "Bss",
	};
	int i;
	
	r = emalloc(1024);
	s = r;
	for(i = 0; i < SEGNUM; i++) {
		if(p->S[i] == nil)
			continue;
		s += sprint(s, "%-7s%c  %.8ux %.8ux %4ld\n", names[i], i == SEGTEXT ? 'R' : ' ', p->S[i]->start, p->S[i]->start + p->S[i]->size, p->S[i]->dref->ref);
	}
	return r;
}

static void
procattach(Req *req)
{
	req->fid->qid = (Qid) {0, 0, 0x80};
	req->fid->aux = emallocz(sizeof(Aux));
	((Aux *) req->fid->aux)->fd = -1;
	req->ofcall.qid = req->fid->qid;
	respond(req, nil);
}

static char *
procwalk(Fid *fid, char *name, Qid *qid)
{
	int pid;
	char buf[20];
	Dirtab *d;
	Aux *a;
	
	a = fid->aux;
	if(fid->qid.path == 0) {
		pid = atoi(name);
		sprint(buf, "%d", pid);
		if(strcmp(buf, name) != 0 || (a->p = findproc(pid)) == nil)
			return "file does not exist";
		*qid = (Qid) {pid * NQid, 0, 0x80};
		fid->qid = *qid;
		return nil;
	}
	if((fid->qid.path % NQid) == 0) {
		for(d = procdir; d->name[0] != 0; d++)
			if(strcmp(d->name, name) == 0)
				break;
		if(d->name[0] == 0)
			return "file does not exist";
		*qid = d->qid;
		qid->path += fid->qid.path;
		fid->qid = *qid;
		a->d = d;
		return nil;
	}
	return "the front fell off";
}

static char *
procclone(Fid *old, Fid *new)
{
	new->aux = emallocz(sizeof(Aux));
	memcpy(new->aux, old->aux, sizeof(Aux));
	return nil;
}

static void
procopen(Req *req)
{
	Aux *a;
	
	a = req->fid->aux;
	switch((int)(req->fid->qid.path % NQid)) {
	case Qtext:
		a->fd = open((char*)(a->p->path + 1), OREAD);
		break;
	default:
		respond(req, nil);
		return;
	}
	if(a->fd < 0)
		responderror(req);
	else
		respond(req, nil);
}

static void
procdestroyfid(Fid *fid)
{
	Aux *a;
	
	a = fid->aux;
	free(a);
}

static int
procgen(int n, Dir *d, void *)
{
	int i;
	Process *p;
	
	p = &plist;
	for(i = 0;; i++) {
		p = p->next;
		if(p == &plist)
			return -1;
		if(i == n)
			break;
	}
	d->uid = estrdup9p(luser);
	d->gid = estrdup9p(luser);
	d->muid = estrdup9p(luser);
	d->name = smprint("%d", p->pid);
	d->mode = DMDIR | 0555;
	d->qid = (Qid) {p->pid * NQid, 0, 0x80};
	return 0;
}

static int
procsubgen(int n, Dir *d, void *)
{
	Dirtab *di;
	
	if(n >= nelem(procdir) - 1)
		return -1;
	
	di = procdir + n;
	d->uid = estrdup9p(luser);
	d->gid = estrdup9p(luser);
	d->muid = estrdup9p(luser);
	d->name = estrdup9p(di->name);
	d->mode = di->perm;
	d->length = di->length;
	d->qid = di->qid;
	return 0;
}

static void
procread(Req *req)
{
	Aux *a;
	Process *p;
	char *buf;
	int rc;

	a = req->fid->aux;
	if(a == nil) {
		respond(req, "the front fell off");
		return;
	}
	if(req->fid->qid.path == 0) {
		dirread9p(req, procgen, nil);
		respond(req, nil);
		return;
	}
	p = a->p;
	switch((int)(req->fid->qid.path % NQid)) {
	case Qdir:
		dirread9p(req, procsubgen, nil);
		respond(req, nil);
		break;
	case Qstatus:
		buf = readin(p->pid, "status");
		if(buf == nil)
			responderror(req);
		else {
			memset(buf, ' ', 27);
			memcpy(buf, p->name, strlen(p->name));
			sprint(buf + 149, "%d", calcmem(p));
			buf[strlen(buf)] = ' ';
			readstr(req, buf);
			free(buf);
			respond(req, nil);
		}
		break;
	case Qsegment:
		buf = segments(p);
		readstr(req, buf);
		free(buf);
		respond(req, nil);
		break;
	case Qtext:
		rc = pread(a->fd, req->ofcall.data, req->ifcall.count, req->ifcall.offset);
		if(rc >= 0) {
			req->ofcall.count = rc;
			respond(req, nil);
		} else
			responderror(req);
		break;
	case Qmem:
		rc = copymem(p, req->ofcall.data, req->ifcall.offset, req->ifcall.count);
		if(rc >= 0) {
			req->ofcall.count = rc;
			respond(req, nil);
		} else
			responderror(req);
		break;
	case Qregs:
		buf = emallocz(18 * 4);
		memcpy(buf, p->R, 15 * 4);
		memcpy(buf + 16 * 4, &p->CPSR, 4);
		memcpy(buf + 17 * 4, p->R + 15, 4);
		readbuf(req, buf, 18 * 4);
		free(buf);
		respond(req, nil);
		break;
	default:
		respond(req, "the front fell off");
	}
}

static void
writeto(Req *req, char *fmt, ...)
{
	int fd, rc;
	va_list va;
	char *file;
	
	va_start(va, fmt);
	file = vsmprint(fmt, va);
	va_end(va);
	fd = open(file, OWRITE);
	free(file);
	if(fd < 0) {
		responderror(req);
		return;
	}
	rc = write(fd, req->ifcall.data, req->ifcall.count);
	req->ofcall.count = rc;
	if(rc < req->ifcall.count)
		responderror(req);
	else
		respond(req, nil);
	close(fd);
}

static void
procwrite(Req *req)
{
	switch((int)(req->fid->qid.path % NQid)) {
	case Qnote:
		writeto(req, "/proc/%lld/note", req->fid->qid.path / NQid);
		break;
	default:
		respond(req, "the front fell off");
	}
}

static void
procstat(Req *req)
{
	Aux *a;
	Dir *d;
	
	d = &req->d;
	a = req->fid->aux;
	if(a == nil) {
		respond(req, "the front fell off");
		return;
	}
	d->qid = req->fid->qid;
	if(a->d != nil) {
		d->mode = a->d->perm;
		d->length = a->d->length;
		d->name = strdup(a->d->name);
	} else {
		d->mode = 0555 | DMDIR;
		if(d->qid.path != 0)
			d->name = smprint("%lld", d->qid.path / NQid);
	}
	d->uid = strdup(luser);
	d->gid = strdup(luser);
	d->muid = strdup(luser);
	respond(req, nil);
}

static Srv procsrv = {
	.attach = procattach,
	.walk1 = procwalk,
	.clone = procclone,
	.destroyfid = procdestroyfid,
	.open = procopen,
	.read = procread,
	.stat = procstat,
};

void
initfs(char *name, char *mtpt)
{
	luser = getuser();
	remove("/srv/armproc");
	postmountsrv(&procsrv, name, mtpt, MREPL);
}
