#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"

#define	Image	IMAGE
#include <draw.h>
#include <memdraw.h>
#include <cursor.h>
#include "screen.h"

Memimage *gscreen;

static struct {
	Rendez;

	Rectangle	rect;
	Proc		*proc;

	uintptr		addr;
} fbscreen;

static struct {
	Rendez;

	Cursor;

	Proc		*proc;
	uintptr		addr;
} hwcursor;

void
cursoron(void)
{
	wakeup(&hwcursor);
}

void
cursoroff(void)
{
}

void
setcursor(Cursor *curs)
{
	hwcursor.Cursor = *curs;
}

void
flushmemscreen(Rectangle r)
{
	if(badrect(fbscreen.rect))
		fbscreen.rect = r;
	else
		combinerect(&fbscreen.rect, r);
	wakeup(&fbscreen);
}

void
screeninit(void)
{
	conf.monitor = 1;
}

Memdata*
attachscreen(Rectangle *r, ulong *chan, int* d, int *width, int *softscreen)
{
	if(gscreen == nil)
		return nil;

	*r = gscreen->r;
	*d = gscreen->depth;
	*chan = gscreen->chan;
	*width = gscreen->width;
	*softscreen = 1;

	gscreen->data->ref++;
	return gscreen->data;
}

void
getcolor(ulong, ulong *, ulong *, ulong *)
{
}

int
setcolor(ulong, ulong, ulong, ulong)
{
	return 0;
}

void
blankscreen(int)
{
}


static void
cursorproc(void *arg)
{
	uchar *set, *clr;
	u32int *reg;
	Point xy;
	int i;

	for(i = 0; i < NSEG; i++)
		if(up->seg[i] == nil && i != ESEG)
			break;
	if(i == NSEG)
		panic(up->text);

	up->seg[i] = arg;

	hwcursor.proc = up;
	if(waserror()){
		hwcursor.addr = 0;
		hwcursor.proc = nil;
		pexit("detached", 1);
	}

	reg = (u32int*)hwcursor.addr;
	for(;;){
		eqlock(&drawlock);
		xy = addpt(mousexy(), hwcursor.offset);
		qunlock(&drawlock);

		set = hwcursor.set;
		clr = hwcursor.clr;
		for(i=0; i<8; i++){
			reg[0x70/4 + i] = clr[i*4]<<24 | clr[i*4+1]<<16 | clr[i*4+2]<<8 | clr[i*4+3];
			reg[0x90/4 + i] = set[i*4]<<24 | set[i*4+1]<<16 | set[i*4+2]<<8 | set[i*4+3];
		}
		reg[0] = (xy.x<<16) | (xy.y&0xFFFF);

		sleep(&hwcursor, return0, nil);
	}
}

void
mousectl(Cmdbuf *cb)
{
	Segment *s;
	uintptr addr;

	if(strcmp(cb->f[0], "addr") == 0 && cb->nf == 2){
		s = nil;
		addr = strtoul(cb->f[1], 0, 0);
		if(addr != 0){
			if((s = seg(up, addr, 0)) == nil || (s->type&SG_RONLY) != 0
			|| (addr&3) != 0 || addr+0xB0 > s->top)
				error(Ebadarg);
			incref(s);
		}
		if(hwcursor.proc != nil){
			postnote(hwcursor.proc, 1, "die", NUser);
			while(hwcursor.proc != nil)
				sched();
		}
		if(addr != 0){
			hwcursor.addr = addr;
			kproc("cursor", cursorproc, s);
		}
		return;
	}

	if(strcmp(cb->f[0], "linear") == 0){
		mouseaccelerate(0);
		return;
	}

	if(strcmp(cb->f[0], "accelerated") == 0){
		mouseaccelerate(cb->nf == 1 ? 1 : atoi(cb->f[1]));
		return;
	}
}

static int
isflush(void *)
{
	return !badrect(fbscreen.rect);
}

static void
screenproc(void *arg)
{
	int sno, n, w;
	uchar *sp, *dp, *top;
	Rectangle r;

	for(sno = 0; sno < NSEG; sno++)
		if(up->seg[sno] == nil && sno != ESEG)
			break;
	if(sno == NSEG)
		panic(up->text);

	up->seg[sno] = arg;

	fbscreen.proc = up;
	if(waserror()){
		fbscreen.addr = 0;
		fbscreen.proc = nil;
		pexit("detached", 1);
	}

	for(;;){
		sleep(&fbscreen, isflush, nil);

		eqlock(&drawlock);
		r = fbscreen.rect;
		fbscreen.rect = ZR;
		if(badrect(r) || gscreen == nil || rectclip(&r, gscreen->r) == 0){
			qunlock(&drawlock);
			continue;
		}
		w = sizeof(ulong)*gscreen->width;
		n = bytesperline(r, gscreen->depth);
		sp = byteaddr(gscreen, r.min);
		dp = (uchar*)fbscreen.addr + (sp - &gscreen->data->bdata[gscreen->zero]);
		top = (uchar*)up->seg[sno]->top;
		if(dp+(Dy(r)-1)*w+n > top)
			r.max.y = (top-(uchar*)fbscreen.addr)/w;
		qunlock(&drawlock);

		while(r.min.y < r.max.y) {
			memmove(dp, sp, n);
			sp += w;
			dp += w;
			r.min.y++;
		}
	}
}

enum {
	CMaddr,
	CMsize,
	CMinit,
};

static Cmdtab fbctlmsg[] = {
	CMaddr,		"addr",		2,
	CMsize,		"size",		3,
	CMinit,		"init",		1,
};

long
fbctlwrite(Chan*, void *a, long n, vlong)
{
	Cmdbuf *cb;
	Cmdtab *ct;
	ulong x, y, z, chan;
	Segment *s;
	uintptr addr;
	char *p;

	cb = parsecmd(a, n);
	if(waserror()){
		free(cb);
		nexterror();
	}
	ct = lookupcmd(cb, fbctlmsg, nelem(fbctlmsg));
	switch(ct->index){
	case CMaddr:
		s = nil;
		addr = strtoul(cb->f[1], 0, 0);
		if(addr != 0){
			if((s = seg(up, addr, 0)) == nil || (s->type&SG_RONLY) != 0)
				error(Ebadarg);
			incref(s);
		}
		if(fbscreen.proc != nil){
			postnote(fbscreen.proc, 1, "die", NUser);
			while(fbscreen.proc != nil)
				sched();
		}
		if(addr != 0){
			fbscreen.addr = addr;
			kproc("screen", screenproc, s);
		}
		break;

	case CMsize:
		x = strtoul(cb->f[1], &p, 0);
		if(x == 0 || x > 10240)
			error(Ebadarg);
		if(*p)
			p++;
		y = strtoul(p, &p, 0);
		if(y == 0 || y > 10240)
			error(Ebadarg);
		if(*p)
			p++;
		z = strtoul(p, &p, 0);

		if((chan = strtochan(cb->f[2])) == 0)
			error("bad channel");
		if(chantodepth(chan) != z)
			error("depth, channel do not match");

		eqlock(&drawlock);
		deletescreenimage();
		if(memimageinit() < 0){
			qunlock(&drawlock);
			error("memimageinit failed");
		}
		if(gscreen != nil){
			freememimage(gscreen);
			gscreen = nil;
		}
		gscreen = allocmemimage(Rect(0,0,x,y), chan);
	Init:
		if(gscreen == nil){
			qunlock(&drawlock);
			error("no framebuffer");
		}
		resetscreenimage();
		qunlock(&drawlock);
		break;

	case CMinit:
		qlock(&drawlock);
		goto Init;
	}
	free(cb);
	poperror();
	return n;
}

long
fbctlread(Chan*, void *a, long n, vlong offset)
{
	char buf[256], chan[32], *p, *e;

	p = buf;
	e = p + sizeof(buf);
	qlock(&drawlock);
	if(gscreen != nil){
		p = seprint(p, e, "size %dx%dx%d %s\n",
			Dx(gscreen->r), Dy(gscreen->r), gscreen->depth,
			chantostr(chan, gscreen->chan));
	}
	qunlock(&drawlock);
	seprint(p, e, "addr %#p\n", fbscreen.addr);
	return readstr(offset, a, n, buf);
}
