#include <u.h>
#include <libc.h>
#include <draw.h>

Display	*display;
Font	*font;
Image	*screen;
int	_drawdebug = 0;

static char deffontname[] = "*default*";
Screen	*_screen;

int		debuglockdisplay = 0;

static void _closedisplay(Display*, int);

/* note handler */
static void
drawshutdown(void)
{
	Display *d;

	d = display;
	if(d){
		display = nil;
		_closedisplay(d, 1);
	}
}

int
geninitdraw(char *devdir, void(*error)(Display*, char*), char *fontname, char *label, char *windir, int ref)
{
	int fd, n;
	Subfont *df;
	char buf[128];

	display = initdisplay(devdir, windir, error);
	if(display == nil)
		return -1;

	/*
	 * Set up default font
	 */
	df = getdefont(display);
	display->defaultsubfont = df;
	if(df == nil){
		fprint(2, "imageinit: can't open default subfont: %r\n");
    Error:
		closedisplay(display);
		display = nil;
		return -1;
	}
	if(fontname == nil){
		fd = open("/env/font", OREAD);
		if(fd >= 0){
			n = read(fd, buf, sizeof(buf));
			if(n>0 && n<sizeof buf-1){
				buf[n] = 0;
				fontname = buf;
			}
			close(fd);
		}
	}
	/*
	 * Build fonts with caches==depth of screen, for speed.
	 * If conversion were faster, we'd use 0 and save memory.
	 */
	if(fontname == nil){
		snprint(buf, sizeof buf, "%d %d\n0 %d\t%s\n", df->height, df->ascent,
			df->n-1, deffontname);
//BUG: Need something better for this	installsubfont("*default*", df);
		font = buildfont(display, buf, deffontname);
		if(font == nil){
			fprint(2, "imageinit: can't open default font: %r\n");
			goto Error;
		}
	}else{
		font = openfont(display, fontname);	/* BUG: grey fonts */
		if(font == nil){
			fprint(2, "imageinit: can't open font %s: %r\n", fontname);
			goto Error;
		}
	}
	display->defaultfont = font;

	/*
	 * Write label; ignore errors (we might not be running under rio)
	 */
	if(label){
		snprint(buf, sizeof buf, "%s/label", display->windir);
		fd = open(buf, OREAD);
		if(fd >= 0){
			read(fd, display->oldlabel, (sizeof display->oldlabel)-1);
			close(fd);
			fd = create(buf, OWRITE, 0666);
			if(fd >= 0){
				write(fd, label, strlen(label));
				close(fd);
			}
		}
	}

	snprint(buf, sizeof buf, "%s/winname", display->windir);
	if(gengetwindow(display, buf, &screen, &_screen, ref) < 0)
		goto Error;

	atexit(drawshutdown);

	return 1;
}

int
initdraw(void(*error)(Display*, char*), char *fontname , char *label)
{
	char *dev = "/dev";

	if(access("/dev/draw/new", AEXIST)<0 && bind("#i", "/dev", MAFTER)<0){
		fprint(2, "imageinit: can't bind /dev/draw: %r\n");
		return -1;
	}
	return geninitdraw(dev, error, fontname, label, dev, Refnone);
}

/*
 * Attach, or possibly reattach, to window.
 * If reattaching, maintain value of screen pointer.
 */
int
gengetwindow(Display *d, char *winname, Image **winp, Screen **scrp, int ref)
{
	int n, fd;
	char buf[64+1];
	Image *image;
	Rectangle r;

	fd = open(winname, OREAD);
	if(fd<0 || (n=read(fd, buf, sizeof buf-1))<=0){
		if((image=d->image) == nil){
			fprint(2, "gengetwindow: %r\n");
			*winp = nil;
			d->screenimage = nil;
			return -1;
		}
		strcpy(buf, "noborder");
	}else{
		close(fd);
		buf[n] = '\0';
		if(*winp != nil){
			_freeimage1(*winp);
			freeimage((*scrp)->image);
			freescreen(*scrp);
			*scrp = nil;
		}
		image = namedimage(d, buf);
		if(image == 0){
			fprint(2, "namedimage %s failed: %r\n", buf);
			*winp = nil;
			d->screenimage = nil;
			return -1;
		}
		assert(image->chan != 0);
	}

	d->screenimage = image;
	*scrp = allocscreen(image, d->white, 0);
	if(*scrp == nil){
		freeimage(d->screenimage);
		*winp = nil;
		d->screenimage = nil;
		return -1;
	}

	r = image->r;
	if(strncmp(buf, "noborder", 8) != 0)
		r = insetrect(image->r, Borderwidth);
	*winp = _allocwindow(*winp, *scrp, r, ref, DWhite);
	if(*winp == nil){
		freescreen(*scrp);
		*scrp = nil;
		freeimage(image);
		d->screenimage = nil;
		return -1;
	}
	d->screenimage = *winp;
	assert((*winp)->chan != 0);
	return 1;
}

int
getwindow(Display *d, int ref)
{
	char winname[128];

	snprint(winname, sizeof winname, "%s/winname", d->windir);
	return gengetwindow(d, winname, &screen, &_screen, ref);
}

#define	NINFO	12*12

Display*
initdisplay(char *dev, char *win, void(*error)(Display*, char*))
{
	char buf[128], info[NINFO+1], *t, isnew;
	int n, datafd, ctlfd, reffd;
	Display *disp;
	Dir *dir;
	Image *image;

	fmtinstall('P', Pfmt);
	fmtinstall('R', Rfmt);
	if(dev == 0)
		dev = "/dev";
	if(win == 0)
		win = "/dev";
	if(strlen(dev)>sizeof buf-25 || strlen(win)>sizeof buf-25){
		werrstr("initdisplay: directory name too long");
		return nil;
	}
	t = strdup(win);
	if(t == nil)
		return nil;

	snprint(buf, sizeof buf, "%s/draw/new", dev);
	ctlfd = open(buf, ORDWR|OCEXEC);
	if(ctlfd < 0){
		if(bind("#i", dev, MAFTER) < 0){
    Error1:
			free(t);
			werrstr("initdisplay: %s: %r", buf);
			return 0;
		}
		ctlfd = open(buf, ORDWR|OCEXEC);
	}
	if(ctlfd < 0)
		goto Error1;
	if((n=read(ctlfd, info, sizeof info)) < 12){
    Error2:
		close(ctlfd);
		goto Error1;
	}
	if(n==NINFO+1)
		n = NINFO;
	info[n] = '\0';
	isnew = 0;
	if(n < NINFO)	/* this will do for now, we need something better here */
		isnew = 1;
	snprint(buf, sizeof buf, "%s/draw/%d/data", dev, atoi(info+0*12));
	datafd = open(buf, ORDWR|OCEXEC);
	if(datafd < 0)
		goto Error2;
	snprint(buf, sizeof buf, "%s/draw/%d/refresh", dev, atoi(info+0*12));
	reffd = open(buf, OREAD|OCEXEC);
	if(reffd < 0){
    Error3:
		close(datafd);
		goto Error2;
	}
	disp = mallocz(sizeof(Display), 1);
	if(disp == 0){
    Error4:
		close(reffd);
		goto Error3;
	}
	image = nil;
	if(0){
    Error5:
		free(image);
		free(disp);
		goto Error4;
	}
	if(n >= NINFO){
		image = mallocz(sizeof(Image), 1);
		if(image == nil)
			goto Error5;
		image->display = disp;
		image->id = 0;
		image->chan = strtochan(info+2*12);
		image->depth = chantodepth(image->chan);
		image->repl = atoi(info+3*12);
		image->r.min.x = atoi(info+4*12);
		image->r.min.y = atoi(info+5*12);
		image->r.max.x = atoi(info+6*12);
		image->r.max.y = atoi(info+7*12);
		image->clipr.min.x = atoi(info+8*12);
		image->clipr.min.y = atoi(info+9*12);
		image->clipr.max.x = atoi(info+10*12);
		image->clipr.max.y = atoi(info+11*12);
	}

	disp->_isnewdisplay = isnew;
	disp->bufsize = iounit(datafd);
	if(disp->bufsize <= 0)
		disp->bufsize = 8000;
	if(disp->bufsize < 512){
		werrstr("iounit %d too small", disp->bufsize);
		goto Error5;
	}
	disp->buf = malloc(disp->bufsize+5);	/* +5 for flush message */
	if(disp->buf == nil)
		goto Error5;

	disp->image = image;
	disp->dirno = atoi(info+0*12);
	disp->fd = datafd;
	disp->ctlfd = ctlfd;
	disp->reffd = reffd;
	disp->bufp = disp->buf;
	disp->error = error;
	disp->windir = t;
	disp->devdir = strdup(dev);
	qlock(&disp->qlock);
	disp->white = allocimage(disp, Rect(0, 0, 1, 1), GREY1, 1, DWhite);
	disp->black = allocimage(disp, Rect(0, 0, 1, 1), GREY1, 1, DBlack);
	if(disp->white == nil || disp->black == nil){
		free(disp->devdir);
		free(disp->white);
		free(disp->black);
		goto Error5;
	}
	disp->opaque = disp->white;
	disp->transparent = disp->black;
	dir = dirfstat(ctlfd);
	if(dir!=nil && dir->type=='i'){
		disp->local = 1;
		disp->dataqid = dir->qid.path;
	}
	if(dir!=nil && dir->qid.vers==1)	/* other way to tell */
		disp->_isnewdisplay = 1;
	free(dir);

	return disp;
}

/*
 * Call with d unlocked.
 * Note that disp->defaultfont and defaultsubfont are not freed here.
 */
void
closedisplay(Display *disp)
{
	_closedisplay(disp, 0);
}

static void
_closedisplay(Display *disp, int isshutdown)
{
	int fd;
	char buf[128];

	if(disp == nil)
		return;
	if(disp == display)
		display = nil;
	if(disp->oldlabel[0]){
		snprint(buf, sizeof buf, "%s/label", disp->windir);
		fd = open(buf, OWRITE);
		if(fd >= 0){
			write(fd, disp->oldlabel, strlen(disp->oldlabel));
			close(fd);
		}
	}

	/*
	 * if we're shutting down, don't free all the resources.
	 * if other procs are getting shot down by notes too,
	 * one might get shot down while holding the malloc lock.
	 * just let the kernel clean things up when we exit.
	 */
	if(isshutdown)
		return;

	free(disp->devdir);
	free(disp->windir);
	freeimage(disp->white);
	freeimage(disp->black);
	close(disp->fd);
	close(disp->ctlfd);
	/* should cause refresh slave to shut down */
	close(disp->reffd);
	qunlock(&disp->qlock);
	free(disp);
}

void
lockdisplay(Display *disp)
{
	if(debuglockdisplay){
		/* avoid busy looping; it's rare we collide anyway */
		while(!canqlock(&disp->qlock)){
			fprint(1, "proc %d waiting for display lock...\n", getpid());
			sleep(1000);
		}
	}else
		qlock(&disp->qlock);
}

void
unlockdisplay(Display *disp)
{
	qunlock(&disp->qlock);
}

void
drawerror(Display *d, char *s)
{
	char err[ERRMAX];

	if(d && d->error)
		d->error(d, s);
	else{
		errstr(err, sizeof err);
		fprint(2, "draw: %s: %s\n", s, err);
		exits(s);
	}
}

static
int
doflush(Display *d)
{
	int n, nn;

	n = d->bufp-d->buf;
	if(n <= 0)
		return 1;

	if((nn=write(d->fd, d->buf, n)) != n){
		if(_drawdebug)
			fprint(2, "flushimage fail: d=%p: n=%d nn=%d %r\n", d, n, nn); /**/
		d->bufp = d->buf;	/* might as well; chance of continuing */
		return -1;
	}
	d->bufp = d->buf;
	return 1;
}

int
flushimage(Display *d, int visible)
{
	if(d == nil)
		return 0;
	if(visible){
		*d->bufp++ = 'v';	/* five bytes always reserved for this */
		if(d->_isnewdisplay){
			BPLONG(d->bufp, d->screenimage->id);
			d->bufp += 4;
		}
	}
	return doflush(d);
}

uchar*
bufimage(Display *d, int n)
{
	uchar *p;

	if(n<0 || n>d->bufsize){
		werrstr("bad count in bufimage");
		return 0;
	}
	if(d->bufp+n > d->buf+d->bufsize)
		if(doflush(d) < 0)
			return 0;
	p = d->bufp;
	d->bufp += n;
	return p;
}

