#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
/*
 * we included thread.h in order to include 9p.h,
 * but we don't use threads, so exits is ok.
 */
#undef exits

#include "a.h"

XFont *xfont;
int nxfont;

void
usage(void)
{
	fprint(2, "usage: fontsrv [-m mtpt]\n");
	fprint(2, "or fontsrv -p path\n");
	exits("usage");
}

static
void
packinfo(Fontchar *fc, uchar *p, int n)
{
	int j;

	for(j=0;  j<=n;  j++){
		p[0] = fc->x;
		p[1] = fc->x>>8;
		p[2] = fc->top;
		p[3] = fc->bottom;
		p[4] = fc->left;
		p[5] = fc->width;
		fc++;
		p += 6;
	}
}

enum
{
	Qroot = 0,
	Qfontdir,
	Qsizedir,
	Qfontfile,
	Qsubfontfile,
};

#define QTYPE(p) ((p) & 0xF)
#define QFONT(p) (((p) >> 4) & 0xFFFF)
#define QSIZE(p) (((p) >> 20) & 0xFF)
#define QANTIALIAS(p) (((p) >> 28) & 0x1)
#define QRANGE(p) (((p) >> 29) & 0xFFFFFF)
static int sizes[] = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 28 };

static vlong
qpath(int type, int font, int size, int antialias, int range)
{
	return type | (font << 4) | (size << 20) | (antialias << 28) | ((vlong)range << 29);
}

static void
dostat(vlong path, Qid *qid, Dir *dir)
{
	char *name;
	Qid q;
	ulong mode;
	vlong length;
	XFont *f;
	char buf[100];

	q.type = 0;
	q.vers = 0;
	q.path = path;
	mode = 0444;
	length = 0;
	name = "???";

	switch(QTYPE(path)) {
	default:
		sysfatal("dostat %#llux", path);

	case Qroot:
		q.type = QTDIR;
		name = "/";
		break;

	case Qfontdir:
		q.type = QTDIR;
		f = &xfont[QFONT(path)];
		name = f->name;
		break;

	case Qsizedir:
		q.type = QTDIR;
		snprint(buf, sizeof buf, "%lld%s", QSIZE(path), QANTIALIAS(path) ? "a" : "");
		name = buf;
		break;

	case Qfontfile:
		f = &xfont[QFONT(path)];
		load(f);
		length = 11+1+11+1+f->nfile*(8+1+8+1+11+1);
		name = "font";
		break;

	case Qsubfontfile:
		snprint(buf, sizeof buf, "x%06x.bit", (int)QRANGE(path)*SubfontSize);
		name = buf;
		break;
	}

	if(qid)
		*qid = q;
	if(dir) {
		memset(dir, 0, sizeof *dir);
		dir->name = estrdup9p(name);
		dir->muid = estrdup9p("");
		dir->uid = estrdup9p("font");
		dir->gid = estrdup9p("font");
		dir->qid = q;
		if(q.type == QTDIR)
			mode |= DMDIR | 0111;
		dir->mode = mode;
		dir->length = length;
	}
}

static char*
xwalk1(Fid *fid, char *name, Qid *qid)
{
	int i, dotdot;
	vlong path;
	char *p;
	int a, n;
	XFont *f;

	path = fid->qid.path;
	dotdot = strcmp(name, "..") == 0;
	switch(QTYPE(path)) {
	default:
	NotFound:
		return "file not found";

	case Qroot:
		if(dotdot)
			break;
		for(i=0; i<nxfont; i++) {
			if(strcmp(xfont[i].name, name) == 0) {
				path = qpath(Qfontdir, i, 0, 0, 0);
				goto Found;
			}
		}
		goto NotFound;

	case Qfontdir:
		if(dotdot) {
			path = Qroot;
			break;
		}
		n = strtol(name, &p, 10);
		if(n == 0)
			goto NotFound;
		a = 0;
		if(*p == 'a') {
			a = 1;
			p++;
		}
		if(*p != 0)
			goto NotFound;
		path += Qsizedir - Qfontdir + qpath(0, 0, n, a, 0);
		break;

	case Qsizedir:
		if(dotdot) {
			path = qpath(Qfontdir, QFONT(path), 0, 0, 0);
			break;
		}
		if(strcmp(name, "font") == 0) {
			path += Qfontfile - Qsizedir;
			break;
		}
		f = &xfont[QFONT(path)];
		load(f);
		p = name;
		if(*p != 'x')
			goto NotFound;
		p++;
		n = strtoul(p, &p, 16);
		if(p < name+7 || p > name+7 && name[1] == '0' || n%SubfontSize != 0 || n/SubfontSize >= MaxSubfont || strcmp(p, ".bit") != 0 || !f->range[n/SubfontSize])
			goto NotFound;
		path += Qsubfontfile - Qsizedir + qpath(0, 0, 0, 0, n/SubfontSize);
		break;
	}
Found:
	dostat(path, qid, nil);
	fid->qid = *qid;
	return nil;
}

static int
rootgen(int i, Dir *d, void *v)
{
	if(i >= nxfont)
		return -1;
	dostat(qpath(Qfontdir, i, 0, 0, 0), nil, d);
	return 0;
}

static int
fontgen(int i, Dir *d, void *v)
{
	vlong path;
	Fid *f;

	f = v;
	path = f->qid.path;
	if(i >= 2*nelem(sizes))
		return -1;
	dostat(qpath(Qsizedir, QFONT(path), sizes[i/2], i&1, 0), nil, d);
	return 0;
}

static int
sizegen(int i, Dir *d, void *v)
{
	vlong path;
	Fid *fid;
	XFont *f;

	fid = v;
	path = fid->qid.path;
	if(i == 0) {
		path += Qfontfile - Qsizedir;
		goto Done;
	}
	i--;
	f = &xfont[QFONT(path)];
	load(f);
	if(i < f->nfile) {
		path += Qsubfontfile - Qsizedir;
		path += qpath(0, 0, 0, 0, f->file[i]);
		goto Done;
	}
	return -1;

Done:
	dostat(path, nil, d);
	return 0;
}

static void
xattach(Req *r)
{
	dostat(0, &r->ofcall.qid, nil);
	r->fid->qid = r->ofcall.qid;
	respond(r, nil);
}

static void
xopen(Req *r)
{
	if(r->ifcall.mode != OREAD) {
		respond(r, "permission denied");
		return;
	}
	r->ofcall.qid = r->fid->qid;
	respond(r, nil);
}

void
responderrstr(Req *r)
{
	char err[ERRMAX];

	rerrstr(err, sizeof err);
	respond(r, err);
}

static void
xread(Req *r)
{
	int i, size, height, ascent;
	vlong path;
	Fmt fmt;
	XFont *f;
	char *data;
	char *buf;
	Memsubfont *sf;
	Memimage *m;

	path = r->fid->qid.path;
	switch(QTYPE(path)) {
	case Qroot:
		dirread9p(r, rootgen, nil);
		break;
	case Qfontdir:
		dirread9p(r, fontgen, r->fid);
		break;
	case Qsizedir:
		dirread9p(r, sizegen, r->fid);
		break;
	case Qfontfile:
		fmtstrinit(&fmt);
		f = &xfont[QFONT(path)];
		load(f);
		if(f->unit == 0 && f->loadheight == nil) {
			readstr(r, "font missing\n");
			break;
		}
		height = 0;
		ascent = 0;
		if(f->unit > 0) {
			height = f->height * (int)QSIZE(path)/f->unit + 0.99999999;
			ascent = height - (int)(-f->originy * (int)QSIZE(path)/f->unit + 0.99999999);
		}
		if(f->loadheight != nil)
			f->loadheight(f, QSIZE(path), &height, &ascent);
		fmtprint(&fmt, "%11d %11d\n", height, ascent);
		if(f->fonttext == nil) {
			for(i=0; i<f->nfile; i++)
				fmtprint(&fmt, "0x%06x 0x%06x x%06x.bit\n", f->file[i]*SubfontSize, ((f->file[i]+1)*SubfontSize) - 1, f->file[i]*SubfontSize);
			f->fonttext = fmtstrflush(&fmt);
			f->nfonttext = strlen(f->fonttext);
		} else {
			buf = fmtstrflush(&fmt);
			strncpy(f->fonttext, buf, strlen(buf));  // Do not copy the null byte.
			free(buf);
		}
		readbuf(r, f->fonttext, f->nfonttext);
		break;
	case Qsubfontfile:
		f = &xfont[QFONT(path)];
		load(f);
		if(r->fid->aux == nil) {
			r->fid->aux = mksubfont(f, f->name, QRANGE(path)*SubfontSize, ((QRANGE(path)+1)*SubfontSize)-1, QSIZE(path), QANTIALIAS(path));
			if(r->fid->aux == nil) {
				responderrstr(r);
				return;
			}
		}
		sf = r->fid->aux;
		m = sf->bits;
		if(r->ifcall.offset < 5*12) {
			char *chan;
			if(QANTIALIAS(path))
				chan = "k8";
			else
				chan = "k1";
			data = smprint("%11s %11d %11d %11d %11d ", chan, m->r.min.x, m->r.min.y, m->r.max.x, m->r.max.y);
			readstr(r, data);
			free(data);
			break;
		}
		r->ifcall.offset -= 5*12;
		size = bytesperline(m->r, chantodepth(m->chan)) * Dy(m->r);
		if(r->ifcall.offset < size) {
			readbuf(r, byteaddr(m, m->r.min), size);
			break;
		}
		r->ifcall.offset -= size;
		data = emalloc9p(3*12+6*(sf->n+1));
		sprint(data, "%11d %11d %11d ", sf->n, sf->height, sf->ascent);
		packinfo(sf->info, (uchar*)data+3*12, sf->n);
		readbuf(r, data, 3*12+6*(sf->n+1));
		free(data);
		break;
	}
	respond(r, nil);
}

static void
xdestroyfid(Fid *fid)
{
	Memsubfont *sf;

	sf = fid->aux;
	if(sf == nil)
		return;

	freememimage(sf->bits);
	free(sf->info);
	free(sf);
	fid->aux = nil;
}

static void
xstat(Req *r)
{
	dostat(r->fid->qid.path, nil, &r->d);
	respond(r, nil);
}

Srv xsrv;

int
proccreate(void (*f)(void*), void *a, unsigned i)
{
	abort();
}

int pflag;

static long dirpackage(uchar*, long, Dir**);

void
dump(char *path)
{
	char *elem, *p, *path0, *err;
	uchar buf[4096];
	Fid fid;
	Qid qid;
	Dir *d;
	Req r;
	int off, i, n;

	// root
	memset(&fid, 0, sizeof fid);
	dostat(0, &fid.qid, nil);
	qid = fid.qid;

	path0 = path;
	while(path != nil) {
		p = strchr(path, '/');
		if(p != nil)
			*p = '\0';
		elem = path;
		if(strcmp(elem, "") != 0 && strcmp(elem, ".") != 0) {
			err = xwalk1(&fid, elem, &qid);
			if(err != nil) {
				fprint(2, "%s: %s\n", path0, err);
				exits(err);
			}
		}
		if(p)
			*p++ = '/';
		path = p;
	}

	memset(&r, 0, sizeof r);
	xsrv.fake = 1;

	// read and display
	off = 0;
	for(;;) {
		r.srv = &xsrv;
		r.fid = &fid;
		r.ifcall.type = Tread;
		r.ifcall.count = sizeof buf;
		r.ifcall.offset = off;
		r.ofcall.data = (char*)buf;
		r.ofcall.count = 0;
		xread(&r);
		if(r.ofcall.type != Rread) {
			fprint(2, "reading %s: %s\n", path0, r.ofcall.ename);
			exits(r.ofcall.ename);
		}
		n = r.ofcall.count;
		if(n == 0)
			break;
		if(off == 0 && pflag > 1) {
			print("\001");
		}
		off += n;
		if(qid.type & QTDIR) {
			n = dirpackage(buf, n, &d);
			for(i=0; i<n; i++)
				print("%s%s\n", d[i].name, (d[i].mode&DMDIR) ? "/" : "");
			free(d);
		} else
			write(1, buf, n);
	}
}

int
fontcmp(const void *va, const void *vb)
{
	XFont *a, *b;

	a = (XFont*)va;
	b = (XFont*)vb;
	return strcmp(a->name, b->name);
}

void
main(int argc, char **argv)
{
	char *mtpt, *srvname;

	mtpt = nil;
	srvname = "font";

	ARGBEGIN{
	case 'D':
		chatty9p++;
		break;
	case 'F':
		chattyfuse++;
		break;
	case 'm':
		mtpt = EARGF(usage());
		break;
	case 's':
		srvname = EARGF(usage());
		break;
	case 'p':
		pflag++;
		break;
	default:
		usage();
	}ARGEND

	xsrv.attach = xattach;
	xsrv.open = xopen;
	xsrv.read = xread;
	xsrv.stat = xstat;
	xsrv.walk1 = xwalk1;
	xsrv.destroyfid = xdestroyfid;

	fmtinstall('R', Rfmt);
	fmtinstall('P', Pfmt);
	memimageinit();
	loadfonts();
	qsort(xfont, nxfont, sizeof xfont[0], fontcmp);

	if(pflag) {
		if(argc != 1 || chatty9p || chattyfuse)
			usage();
		dump(argv[0]);
		exits(0);
	}

	if(pflag || argc != 0)
		usage();

	/*
	 * Check twice -- if there is an exited instance
	 * mounted there, the first access will fail but unmount it.
	 */
	if(mtpt && access(mtpt, AEXIST) < 0 && access(mtpt, AEXIST) < 0)
		sysfatal("mountpoint %s does not exist", mtpt);

	xsrv.foreground = 1;
	threadpostmountsrv(&xsrv, srvname, mtpt, 0);
}

/*
	/sys/src/libc/9sys/dirread.c
*/
static
long
dirpackage(uchar *buf, long ts, Dir **d)
{
	char *s;
	long ss, i, n, nn, m;

	*d = nil;
	if(ts <= 0)
		return 0;

	/*
	 * first find number of all stats, check they look like stats, & size all associated strings
	 */
	ss = 0;
	n = 0;
	for(i = 0; i < ts; i += m){
		m = BIT16SZ + GBIT16(&buf[i]);
		if(statcheck(&buf[i], m) < 0)
			break;
		ss += m;
		n++;
	}

	if(i != ts)
		return -1;

	*d = malloc(n * sizeof(Dir) + ss);
	if(*d == nil)
		return -1;

	/*
	 * then convert all buffers
	 */
	s = (char*)*d + n * sizeof(Dir);
	nn = 0;
	for(i = 0; i < ts; i += m){
		m = BIT16SZ + GBIT16((uchar*)&buf[i]);
		if(nn >= n || convM2D(&buf[i], m, *d + nn, s) != m){
			free(*d);
			*d = nil;
			return -1;
		}
		nn++;
		s += m;
	}

	return nn;
}
