#include	<9pm/u.h>
#include	<9pm/libc.h>
#include	<9pm/keyboard.h>
#include	<9pm/authsrv.h>
#include	"dat.h"
#include	"fns.h"

void	(*consdebug)(void) = nil;
void	(*screenputs)(char*, int) = nil;

char*	sysname;
Queue*	kbdq;			/* unprocessed console input */
Queue*	lineq;			/* processed console input */
Queue*	kprintoq;		/* console output, for /dev/kprint */
Lock	kprintinuse;		/* test and set whether /dev/kprint is open */

static struct
{
	QLock	lk;

	int	raw;		/* true if we shouldn't process input */
	int	ctl;		/* number of opens to the control file */
	int	x;		/* index into line */
	char	line[1024];	/* current input line */

	int	count;
	int	ctlpoff;

	/* a place to save up characters at interrupt time before dumping them in the queue */
	Lock	lockputc;
	char	istage[512];
	char	*iw;
	char	*ir;
	char	*ie;
} kbd;
/*
	.iw	= kbd.istage,
	.ir	= kbd.istage,
	.ie	= kbd.istage + sizeof(kbd.istage),
};
*/

vlong	fasthz;

static void	seedrand(void);
static int	readtime(ulong, char*, int);
static int	readbintime(char*, int);
static int	writetime(char*, int);
static int	writebintime(char*, int);

/*
 *   Print a string on the console.  Convert \n to \r\n for serial
 *   line consoles.  Locking of the queues is left up to the screen
 *   or uart code.  Multi-line messages to serial consoles may get
 *   interspersed with other messages.
 */
static void
putstrn0(char *str, int n, int usewrite)
{
	/*
	 *  if someone is reading /dev/kprint and the message
	 *  is from the kernel (as opposed to from conswrite),
	 *  put the message there.
	 *  if not and there's an attached bit mapped display,
	 *  put the message there.
	 */
	if(kprintoq != nil && !qisclosed(kprintoq)){
		if(usewrite)
			qwrite(kprintoq, str, n);
		else
			qiwrite(kprintoq, str, n);
	}else if(screenputs != nil)
		screenputs(str, n);
}

void
putstrn(char *str, int n)
{
	putstrn0(str, n, 0);
}

int
snprint(char *s, int n, char *fmt, ...)
{
	va_list arg;

	va_start(arg, fmt);
	n = doprint(s, s+n, fmt, arg) - s;
	va_end(arg);
	return n;
}

int
sprint(char *s, char *fmt, ...)
{
	int n;
	va_list arg;

	va_start(arg, fmt);
	n = doprint(s, s+PRINTSIZE, fmt, arg) - s;
	va_end(arg);
	return n;
}

char*
seprint(char *buf, char *e, char *fmt, ...)
{
	char *out;
	va_list arg;

	va_start(arg, fmt);
	out = doprint(buf, e, fmt, arg);
	va_end(arg);
	return out;
}

int noprint;

int
print(char *fmt, ...)
{
	int n;
	va_list arg;
	char buf[PRINTSIZE];

	if(noprint)
		return -1;

	va_start(arg, fmt);
	n = doprint(buf, buf+sizeof(buf), fmt, arg) - buf;
	va_end(arg);
	putstrn(buf, n);

	return n;
}

void
panic(char *fmt, ...)
{
	int n;
	va_list arg;
	char buf[PRINTSIZE];
	static int panicking;

	kprintoq = nil;	/* don't try to write to /dev/kprint */

	if(panicking)
		for(;;);
	panicking = 1;

	strcpy(buf, "panic: ");
	va_start(arg, fmt);
	n = doprint(buf+strlen(buf), buf+sizeof(buf)-1, fmt, arg) - buf;
	va_end(arg);
	buf[n] = '\n';
	putstrn(buf, n+1);
	abort();
}

void
_assert(char *s, char *file, int line)
{
	char buf[256];

	if(file)
		snprint(buf, sizeof buf, "assertion failed: %s:%d: %s\n", file, line, s);
	else
		snprint(buf, sizeof buf, "assertion failed: %s\n", s);
	dwrite(buf);
	abort();
}

int
pprint(char *fmt, ...)
{
	int n;
	Chan *c;
	va_list arg;
	char buf[2*PRINTSIZE];

	if(up == nil || up->fgrp == nil)
		return 0;

	c = up->fgrp->fd[2];
	if(c==0 || (c->mode!=OWRITE && c->mode!=ORDWR))
		return 0;
	n = sprint(buf, "%s %lud: ", up->text, up->pid);
	va_start(arg, fmt);
	n = doprint(buf+n, buf+sizeof(buf), fmt, arg) - buf;
	va_end(arg);

	if(waserror())
		return 0;
	devtab[c->type]->write(c, buf, n, c->offset);
	poperror();

	lock(&c->lk);
	c->offset += n;
	unlock(&c->lk);

	return n;
}

static void
echoscreen(char *buf, int n)
{
	char *e, *p;
	char ebuf[128];
	int x;

	p = ebuf;
	e = ebuf + sizeof(ebuf) - 4;
	while(n-- > 0){
		if(p >= e){
			screenputs(ebuf, p - ebuf);
			p = ebuf;
		}
		x = *buf++;
		if(x == 0x15){
			*p++ = '^';
			*p++ = 'U';
			*p++ = '\n';
		} else
			*p++ = x;
	}
	if(p != ebuf)
		screenputs(ebuf, p - ebuf);
}

static void
echo(char *buf, int n)
{
	if(kbdq != nil)
		qproduce(kbdq, buf, n);
	if(kbd.raw)
		return;
	if(screenputs != nil)
		echoscreen(buf, n);
}

static void
_kbdputc(int ch)
{
	int n;
	char buf[3];
	Rune r;

	r = ch;
	n = runetochar(buf, &r);
	if(n != 0)
		echo(buf, n);
}

/* _kbdputc, but with compose translation */
void
kbdputc(int c)
{
	int i;
	static int collecting, nk;
	static Rune kc[5];

	if(c == Kalt){
		collecting = 1;
		nk = 0;
		return;
	}
	
	if(!collecting){
		_kbdputc(c);
		return;
	}
	
	kc[nk++] = c;
	c = latin1(kc, nk);
	if(c < -1)	/* need more keystrokes */
		return;
	if(c != -1)	/* valid sequence */
		_kbdputc(c);
	else
		for(i=0; i<nk; i++)
			_kbdputc(kc[i]);
	nk = 0;
	collecting = 0;
}

enum{
	Qdir,
	Qbintime,
	Qcons,
	Qcputime,
	Qdrivers,
	Qkprint,
	Qhostdomain,
	Qhostowner,
	Qnull,
	Qosversion,
	Qpgrpid,
	Qpid,
	Qppid,
	Qrandom,
	Qsysname,
	Qtime,
	Quser,
	Qzero,
};

enum
{
	VLNUMSIZE=	22,
};

static Dirtab consdir[]={
	".",	{Qdir, 0, QTDIR},	0,		DMDIR|0555,
	"bintime",	{Qbintime},	24,		0444,
	"cons",		{Qcons},	0,		0440,
	"cputime",	{Qcputime},	6*NUMSIZE,	0444,
	"drivers",	{Qdrivers},	0,		0444,
	"hostdomain",	{Qhostdomain},	DOMLEN,		0664,
	"hostowner",	{Qhostowner},	0,	0664,
	"kprint",		{Qkprint, 0, QTEXCL},	0,	DMEXCL|0440,
	"null",		{Qnull},	0,		0666,
	"osversion",	{Qosversion},	0,		0444,
	"pgrpid",	{Qpgrpid},	NUMSIZE,	0444,
	"pid",		{Qpid},		NUMSIZE,	0444,
	"ppid",		{Qppid},	NUMSIZE,	0444,
	"random",	{Qrandom},	0,		0444,
	"sysname",	{Qsysname},	0,		0444,
	"time",		{Qtime},	NUMSIZE+3*VLNUMSIZE,	0444,
	"user",		{Quser},	0,	0444,
	"zero",		{Qzero},	0,		0444,
};

int
readnum(ulong off, char *buf, ulong n, ulong val, int size)
{
	char tmp[64];

	snprint(tmp, sizeof(tmp), "%*.0lud", size-1, val);
	tmp[size-1] = ' ';
	if(off >= size)
		return 0;
	if(off+n > size)
		n = size-off;
	memmove(buf, tmp+off, n);
	return n;
}

int
readstr(ulong off, char *buf, ulong n, char *str)
{
	int size;

	size = strlen(str);
	if(off >= size)
		return 0;
	if(off+n > size)
		n = size-off;
	memmove(buf, str+off, n);
	return n;
}

static void
consreset(void)
{
	lineq = qopen(2*1024, 0, 0, 0);
	if(lineq == nil)
		panic("printinit");
	qnoblock(lineq, 1);
}

static Chan*
consattach(char *spec)
{
	return devattach('c', spec);
}

static Walkqid*
conswalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name,nname, consdir, nelem(consdir), devgen);
}

static int
consstat(Chan *c, uchar *dp, int n)
{
	return devstat(c, dp, n, consdir, nelem(consdir), devgen);
}

static Chan*
consopen(Chan *c, int omode)
{
	c->aux = nil;
	c = devopen(c, omode, consdir, nelem(consdir), devgen);
	switch((ulong)c->qid.path){
	case Qkprint:
		if(!canlock(&kprintinuse)){
			c->flag &= ~COPEN;
			error(Einuse);
		}
		if(kprintoq == nil){
			kprintoq = qopen(8*1024, -1, 0, 0);
			if(kprintoq == nil){
				c->flag &= ~COPEN;
				error(Enomem);
			}
			qnoblock(kprintoq, 1);
		}else
			qreopen(kprintoq);
		c->iounit = qiomaxatomic;
		break;
	}
	return c;
}

static void
consclose(Chan *c)
{
	switch((ulong)c->qid.path){
	/* close of kprint allows other opens */
	case Qkprint:
		if(c->flag & COPEN){
			unlock(&kprintinuse);
			qhangup(kprintoq, nil);
		}
		break;
	}
}

static long
consread(Chan *c, void *buf, long n, vlong off)
{
	char *b;
	char tmp[128];		/* must be >= 6*NUMSIZE */
	char *cbuf = buf;
	int i, k, l;
	vlong offset = off;

	if(n <= 0)
		return n;
	switch((ulong)c->qid.path){
	case Qdir:
		return devdirread(c, buf, n, consdir, nelem(consdir), devgen);

	case Qcputime:
		k = offset;
		if(k >= 6*NUMSIZE)
			return 0;
		if(k+n > 6*NUMSIZE)
			n = 6*NUMSIZE - k;
		/* easiest to format in a separate buffer and copy out */
		for(i=0; i<6 && NUMSIZE*i<k+n; i++){
			l = up->time[i];
			if(i == TReal)
				l = machp0->ticks - l;
			l = TK2MS(l);
			readnum(0, tmp+NUMSIZE*i, NUMSIZE, l, NUMSIZE);
		}
		memmove(buf, tmp+k, n);
		return n;

	case Qkprint:
		return qread(kprintoq, buf, n);

	case Qpgrpid:
		return readnum((ulong)offset, buf, n, up->pgrp->pgrpid, NUMSIZE);

	case Qpid:
		return readnum((ulong)offset, buf, n, up->pid, NUMSIZE);

	case Qppid:
		return readnum((ulong)offset, buf, n, 0/*up->parentpid*/, NUMSIZE);

	case Qtime:
		return readtime((ulong)offset, buf, n);

	case Qbintime:
		return readbintime(buf, n);

	case Qhostowner:
		return readstr((ulong)offset, buf, n, eve);

	case Qhostdomain:
		return readstr((ulong)offset, buf, n, hostdomain);

	case Quser:
		return readstr((ulong)offset, buf, n, eve);

	case Qnull:
		return 0;

	case Qsysname:
		if(sysname == nil)
			return 0;
		return readstr((ulong)offset, buf, n, sysname);

	case Qrandom:
		return randomread(buf, n);

	case Qdrivers:
		b = malloc(READSTR);
		if(b == nil)
			error(Enomem);
		n = 0;
		for(i = 0; devtab[i] != nil; i++)
			n += snprint(b+n, READSTR-n, "#%C %s\n", devtab[i]->dc,  devtab[i]->name);
		if(waserror()){
			free(b);
			nexterror();
		}
		n = readstr((ulong)offset, buf, n, b);
		free(b);
		poperror();
		return n;

	case Qzero:
		memset(buf, 0, n);
		return n;

	case Qosversion:
		snprint(tmp, sizeof tmp, "2000");
		n = readstr((ulong)offset, buf, n, tmp);
		return n;

	default:
		print("consread 0x%llux\n", c->qid.path);
		error(Egreg);
	}
	return -1;		/* never reached */
}

static long
conswrite(Chan *c, void *va, long n, vlong off)
{
	char buf[256];
	long l, bp;
	char *a = va;

	USED(off);

	switch((ulong)c->qid.path){
	case Qcons:
		/*
		 * Can't page fault in putstrn, so copy the data locally.
		 */
		l = n;
		while(l > 0){
			bp = l;
			if(bp > sizeof buf - 1)
				bp = sizeof buf - 1;
			memmove(buf, a, bp);
			buf[bp] = 0;
			putstrn0(buf, bp, 1);
			a += bp;
			l -= bp;
		}
		break;

	case Qhostowner:
		return hostownerwrite(a, n);

	case Qhostdomain:
		return hostdomainwrite(a, n);

	case Qnull:
		break;

	default:
		error(Egreg);
	}
	return n;
}

Dev devcons = {
	'c',
	"cons",

	consreset,
	consattach,
	conswalk,
	consstat,
	consopen,
	devcreate,
	consclose,
	consread,
	devbread,
	conswrite,
	devbwrite,
	devremove,
	devwstat,
};

static uvlong uvorder = 0x0001020304050607;

static uchar*
le2vlong(vlong *to, uchar *f)
{
	uchar *t, *o;
	int i;

	t = (uchar*)to;
	o = (uchar*)&uvorder;
	for(i = 0; i < sizeof(vlong); i++)
		t[o[i]] = f[i];
	return f+sizeof(vlong);
}

static uchar*
vlong2le(uchar *t, vlong from)
{
	uchar *f, *o;
	int i;

	f = (uchar*)&from;
	o = (uchar*)&uvorder;
	for(i = 0; i < sizeof(vlong); i++)
		t[i] = f[o[i]];
	return t+sizeof(vlong);
}

static long order = 0x00010203;

static uchar*
le2long(long *to, uchar *f)
{
	uchar *t, *o;
	int i;

	t = (uchar*)to;
	o = (uchar*)&order;
	for(i = 0; i < sizeof(long); i++)
		t[o[i]] = f[i];
	return f+sizeof(long);
}

static uchar*
long2le(uchar *t, long from)
{
	uchar *f, *o;
	int i;

	f = (uchar*)&from;
	o = (uchar*)&order;
	for(i = 0; i < sizeof(long); i++)
		t[i] = f[o[i]];
	return t+sizeof(long);
}

char *Ebadtimectl = "bad time control";

/*
 *  like the old #c/time but with added info.  Return
 *
 *	secs	nanosecs	fastticks	fasthz
 */
static int
readtime(ulong off, char *buf, int n)
{
	vlong xnsec;
	long sec;
	char str[7*NUMSIZE];

	xnsec = nsec();
	sec = xnsec/(uvlong)1000000000;
	snprint(str, sizeof(str), "%*.0lud %*.0llud %*.0llud %*.0llud ",
		NUMSIZE-1, sec,
		VLNUMSIZE-1, nsec,
		VLNUMSIZE-1, (uvlong)0,
		VLNUMSIZE-1, (uvlong)0);
	return readstr(off, buf, n, str);
}

/*
 *  read binary time info.  all numbers are little endian.
 *  ticks and nsec are syncronized.
 */
static int
readbintime(char *buf, int n)
{
	int i;
	vlong xnsec;
	uvlong *b = (uvlong*)buf;

	i = 0;
	xnsec = nsec();
	if(n >= 3*sizeof(uvlong)){
		b[2] = 0;
		i += sizeof(uvlong);
	}
	if(n >= 2*sizeof(uvlong)){
		b[1] = 0;
		i += sizeof(uvlong);
	}
	if(n >= 8){
		vlong2le((uchar*)b, xnsec);
		i += sizeof(vlong);
	}
	return i;
}
