#include <u.h>
#include <libc.h>
#include <libg.h>
#include <gnot.h>

/*
 * bitblt operates a 'word' at a time.
 * WBITS is the number of bits in a word
 * LWBITS=log2(WBITS),
 * W2L is the number of words in a long
 * WMASK has bits set for the low order word of a long
 * WType is a pointer to a word
 */
#ifndef WBITS
#define WBITS	32
#define LWBITS	5
#define	W2L	1
#define WMASK	~0UL
typedef ulong	*WType;
#endif

#define DEBUG 

#ifdef TEST
/*
 * globals used for testing
 */
int	FORCEFORW;
int	FORCEBAKW;
GBitmap	*curdm, *cursm;
Point	curpt;
Rectangle curr;
Fcode	curf;
void	*mem;
#endif

static void
gbitexplode(ulong sw, ulong *buf, int sdep, int x)
{
	int j, o, q, n, nw, inc, qinc;
	ulong s, dw, pix;

	inc = 1 << sdep;
	pix = (1 << inc) - 1;
	nw = 1 << x;
	n = 32 >> x;
	qinc = (nw << sdep) - inc;
	for(o = 32 - n; o >= 0; o -= n){
		dw = 0;
		s = sw >> o;
		q = 0;
		for(j = 0; j < n; j += inc){
			dw |= (s & (pix << j)) << q;
			q += qinc;
		}
		for(j = 0; j < x; j++)
			dw |= dw << (inc << j);
		*buf++ = dw;
	}
}

/*
void
main(void)
{
	ulong buf[128];

	gbitexplode(0x7777, buf, 0, 3);
	exits(0);
}
*/

void
gbitblt(GBitmap *dm, Point pt, GBitmap *sm, Rectangle r, Fcode fcode)
{
	int	width;		/* width in bits of dst */
	int	wwidth;		/* floor width in words */
	int	height;		/* height in pixels minus 1 */
	int	sdep;		/* src ldepth */
	int 	ddep;		/* dst ldepth */
	int	deltadep;	/* diff between ldepths */
	int	sspan;		/* words between scanlines in src */
	int	dspan;		/* words between scanlines in dst */
	int	soff;		/* bit offset of src start point */
	int	sdest;		/* bit offset of src start point that matches doff when expanded */
	int	doff;		/* bit offset of dst start point */
	int	delta;		/* amount to shift src by */
	int	sign;		/* of delta */
	ulong	*saddr;
	ulong	*daddr;
	ulong	*s;
	ulong	*d;
	ulong	mask;
	ulong	tmp;		/* temp storage source word */
	ulong	sw;		/* source word constructed */
	ulong	dw;		/* dest word fetched */
	ulong	lmask;		/* affected pixels in leftmost dst word */
	ulong	rmask;		/* affected pixels in rightmost dst word */
	int	i;
	int	j;
	ulong	buf[32];	/* for expanding a source */
	ulong	*p;		/* pointer into buf */
	int	spare;		/* number of words already converted */


#ifdef TEST
	curdm = dm;
	cursm = sm;
	curpt = pt;
	curr = r;
	curf = fcode;
#endif

	gbitbltclip(&dm);

	width = r.max.x - r.min.x;
	if(width <= 0)
		return;
	height = r.max.y - r.min.y - 1;
	if(height < 0)
		return;

	ddep = dm->ldepth;
	pt.x <<= ddep;
	width <<= ddep;

	sdep = sm->ldepth;
	r.min.x <<= sdep;
	r.max.x <<= sdep;

	dspan = dm->width * W2L;
	sspan = sm->width * W2L;

	daddr = (ulong*)((WType)dm->base
			+ dm->zero*W2L + pt.y*dspan
			+ (pt.x >> LWBITS));
	saddr = (ulong*)((WType)sm->base
			+ sm->zero*W2L + r.min.y*sspan
			+ (r.min.x >> LWBITS));

	doff = pt.x & (WBITS - 1);
	lmask = WMASK >> doff;
	rmask = (WMASK << (WBITS - ((doff+width) & (WBITS-1))))&WMASK;
	if(!rmask)
		rmask = WMASK;
	soff = r.min.x & (WBITS-1);
	wwidth = ((pt.x+width-1)>>LWBITS) - (pt.x>>LWBITS);

	if(sm == dm){
#ifdef TEST
		if(!FORCEBAKW &&
		   (FORCEFORW || sm != dm || saddr > daddr ||
		    (saddr == daddr && soff > doff)))
			;
		else{
			daddr += height * dspan;
			saddr += height * sspan;
			sspan -= 2 * W2L * sm->width;
			dspan -= 2 * W2L * dm->width;
		}
#else
		if(r.min.y < pt.y){	/* bottom to top */
			daddr += height * dspan;
			saddr += height * sspan;
			sspan -= 2 * W2L * sm->width;
			dspan -= 2 * W2L * dm->width;
		}else if(r.min.y == pt.y && r.min.x < pt.x)
			abort()/*goto right*/;
#endif
	}
	if(wwidth == 0)		/* collapse masks for narrow cases */
		lmask &= rmask;
	fcode &= F;

	deltadep = ddep - sdep;
	sdest = doff >> deltadep;
	delta = soff - sdest;
	sign = 0;
	if(delta < 0){
		sign = 1;
		delta = -delta;
	}

	p = 0;
	for(j = 0; j <= height; j++){
		d = daddr;
		s = saddr;
		mask = lmask;
		tmp = 0;
		if(!sign)
			tmp = *s++;
		spare = 0;
		for(i = wwidth; i >= 0; i--){
			if(spare)
				sw = *p++;
			else{
				if(sign){
					sw = tmp << (WBITS-delta);
					tmp = *s++;
					sw |= tmp >> delta;
				}else{
					sw = tmp << delta;
					tmp = *s++;
					if(delta)
						sw |= tmp >> (WBITS-delta);
				}
				spare = 1 << deltadep;
				if(deltadep >= 1){
					gbitexplode(sw, buf, sdep, deltadep);
					p = buf;
					sw = *p++;
				}
			}

			dw = *d;
			switch(fcode){		/* ltor bit aligned */
			case Zero:	*d = dw & ~mask;		break;
			case DnorS:	*d = dw ^ ((~sw | dw) & mask);	break;
			case DandnotS:	*d = dw ^ ((sw & dw) & mask);	break;
			case notS:	*d = dw ^ ((~sw ^ dw) & mask);	break;
			case notDandS:	*d = dw ^ ((sw | dw) & mask);	break;
			case notD:	*d = dw ^ mask;			break;
			case DxorS:	*d = dw ^ (sw & mask);		break;
			case DnandS:	*d = dw ^ ((sw | ~dw) & mask);	break;
			case DandS:	*d = dw ^ ((~sw & dw) & mask);	break;
			case DxnorS:	*d = dw ^ (~sw & mask);		break;
			case D:						break;
			case DornotS:	*d = dw | (~sw & mask);		break;
			case S:		*d = dw ^ ((sw ^ dw) & mask);	break;
			case notDorS:	*d = dw ^ (~(sw & dw) & mask);	break;
			case DorS:	*d = dw | (sw & mask);		break;
			case F:		*d = dw | mask;			break;
			}
			d++;

			mask = WMASK;
			if(i == 1)
				mask = rmask;
			spare--;
		}
		saddr += sspan;
		daddr += dspan;
	}
}

#ifdef TEST
void	prprog(void);
GBitmap *bb1, *bb2;
ulong	*src, *dst, *xdst, *xans;
int	swds, dwds;
long	ticks;
int	timeit;

long
func(int f, long s, int sld, long d, int dld)
{
	long a;
	int sh, i, db, sb;

	db = 1 << dld;
	sb = 1 << sld;
	sh = db - sb;
	if(sh > 0) {
		a = s;
		for(i = sb; i<db; i += sb){
			a <<= sb;
			s |= a;
		}
	} else if(sh < 0)
		s >>= -sh;

	switch(f){
	case Zero:	d = 0;			break;
	case DnorS:	d = ~(d|s);		break;
	case DandnotS:	d = d & ~s;		break;
	case notS:	d = ~s;			break;
	case notDandS:	d = ~d & s;		break;
	case notD:	d = ~d;			break;
	case DxorS:	d = d ^ s;		break;
	case DnandS:	d = ~(d&s);		break;
	case DandS:	d = d & s;		break;
	case DxnorS:	d = ~(d^s);		break;
	case S:		d = s;			break;
	case DornotS:	d = d | ~s;		break;
	case D:		d = d;			break;
	case notDorS:	d = ~d | s;		break;
	case DorS:	d = d | s;		break;
	case F:		d = ~0;			break;
	}

	d &= ((1<<db)-1);
	return d;
}

void
run(int fr, int to, int w, int op)
{
	int i, j, f, t, fy, ty;
	extern long *_clock;

	fr += bb2->r.min.x;
	to += bb1->r.min.x;
	fy = bb2->r.min.y + 1;
	ty = bb1->r.min.y + 1;
	if(timeit) {
		memcpy(dst, xdst, dwds * sizeof(long));
		ticks -= *_clock;
		gbitblt(bb1, Pt(to,ty), bb2, Rect(fr,fy,fr+w,fy+2), op);
		ticks += *_clock;
		return;
	}
	f = fr;
	t = to;
	memcpy(dst, xdst, dwds * sizeof(long));
	for(i=0; i<w; i++) {
		gbitblt(bb1, Pt(t,ty), bb2, Rect(f,fy,f+1,fy+1), op);
		gbitblt(bb1, Pt(t,ty+1), bb2, Rect(f,fy+1,f+1,fy+2), op);
		f++;
		t++;
	}
	memcpy(xans, dst, dwds * sizeof(long));

	memcpy(dst, xdst, dwds * sizeof(long));
	gbitblt(bb1, Pt(to,ty), bb2, Rect(fr,fy,fr+w,fy+2), op);

	if(memcmp(xans, dst, dwds * sizeof(long))) {
		/*
		 * print src and dst row offset, width in bits, and forw/back
		 * then print for each of the four rows: the source (s),
		 * the dest (d), the good value of the answer (g),
		 * and the actual bad value of the answer (b)
		 */
		print("fr=%d to=%d w=%d fb=%d%d\n",
			fr, to, w, FORCEFORW, FORCEBAKW);
		print("dst bitmap b %#lux, z %d, w %d, ld %d, r [%d,%d][%d,%d]\n",
			bb1->base, bb1->zero, bb1->width, bb1->ldepth,
			bb1->r.min.x, bb1->r.min.y, bb1->r.max.x, bb1->r.max.y);
		print("src bitmap b %#lux, z %d, w %d, ld %d, r [%d,%d][%d,%d]\n",
			bb2->base, bb2->zero, bb2->width, bb2->ldepth,
			bb2->r.min.x, bb2->r.min.y, bb2->r.max.x, bb2->r.max.y);
		for(j=0; 7*j < dwds; j++) {
			print("\ns");
			for(i=0; i<7 && 7*j+i < dwds; i++)
				print(" %.8lux", src[7*j + i]);
			print("\nd");
			for(i=0; i<7 && 7*j+i < dwds; i++)
				print(" %.8lux", xdst[7*j + i]);
			print("\ng");
			for(i=0; i<7 && 7*j+i < dwds; i++)
				print(" %.8lux", xans[7*j + i]);
			print("\nb");
			for(i=0; i<7 && 7*j+i < dwds; i++)
				print(" %.8lux", dst[7*j + i]);
			print("\n");
		}
		prprog();
	}
}

void
prprog(void)
{
	exits(0);
}

int
main(int argc, char *argv[])
{
	int f, t, w, i, sld, dld, op, iters, simple;
	ulong s, d, spix, dpix, apix, fpix, m, *ps, *pd;
	Point sorg, dorg;
	GBitmap *bs, *bd;
	long seed;
	char *ct;

	sld = 0;
	dld = 0;
	timeit = 0;
	iters = 200;
	simple = 0;
	ARGBEGIN {
	case 'i':
		iters = atoi(ARGF());
		break;
	case 's':
		simple = 1;
		break;
	case 't':
		timeit = 1;
		ct = ARGF();
		if(ct)
			iters = atoi(ct);
		break;
	} ARGEND
	if(argc > 0)
		sld = atoi(argv[0]);
	if(argc > 1)
		dld = atoi(argv[1]);
	if(!timeit && !simple) {
		seed = time(0);
		print("seed %lux\n", seed); srand(seed);	/**/
	}

	print("sld %d dld %d\n", sld, dld);
	op = 1;

	/* bitmaps for 1-bit tests */
	bd = gballoc(Rect(0,0,32,1), dld);
	bs = gballoc(Rect(0,0,32,1), sld);
	for(i=0; i<bs->width; i++)
		bs->base[i] = lrand();

	/* bitmaps for rect tests */
	if(simple) {
		dorg = Pt(0,0);
		sorg = Pt(0,0);
	} else {
		dorg = Pt(nrand(63)-31,nrand(63)-31);
		sorg = Pt(nrand(63)-31,nrand(63)-31);
	}
	bb1 = gballoc(Rpt(dorg,add(dorg,Pt(200,4))), dld);
	bb2 = gballoc(Rpt(sorg,add(sorg,Pt(200,4))), sld);
	dwds = bb1->width * Dy(bb1->r);
	swds = bb2->width * Dy(bb2->r);
	dst = bb1->base;
	src = bb2->base;
	xdst = malloc(dwds * sizeof(long));
	xans =  malloc(dwds * sizeof(long));
	for(i=0; i<swds; i++)
		src[i] = lrand();
	for(i=0; i<dwds; i++)
		xdst[i] = lrand();

loop:
	print("Op %d\n", op);
	if(!timeit) {
		print("one pixel\n");
		ps = bs->base;
		pd = bd->base;
		FORCEFORW = 1;
		FORCEBAKW = 0;
		for(i=0; i<1000; i++, FORCEFORW = !FORCEFORW, FORCEBAKW = !FORCEBAKW) {
			f = nrand(32 >> sld);
			t = nrand(32 >> dld);
			s = lrand();
			d = lrand();
			ps[0] = s;
			pd[0] = d;
#ifdef T386
			spix = (byterev(s) >> (32 - ((f+1)<<sld))) & ((1 << (1<<sld)) - 1);
			dpix = (byterev(d) >> (32 - ((t+1)<<dld))) & ((1 << (1<<dld)) - 1);
#else
			spix = (s >> (32 - ((f+1)<<sld))) & ((1 << (1<<sld)) - 1);
			dpix = (d >> (32 - ((t+1)<<dld))) & ((1 << (1<<dld)) - 1);
#endif
#ifdef T386
			apix = byterev(func(op, spix, sld, dpix, dld) << (32 - ((t+1)<<dld)));
#else
			apix = func(op, spix, sld, dpix, dld) << (32 - ((t+1)<<dld));
#endif
			gbitblt(bd, Pt(t,0), bs, Rect(f,0,f+1,1), op);
			if(ps[0] != s) {
				print("bb src %.8lux %.8lux %d %d\n", ps[0], s, f, t);
				exits("error");
			}
			m = ((1 << (1<<dld)) - 1) << (32 - ((t+1)<<dld));
#ifdef T386
			m = byterev(m);
#endif
			if((pd[0] & ~m) != (d & ~m)) {
					print("bb dst1 %.8lux %.8lux\n",
						s, d);
					print("bb      %.8lux %.8lux %d %d\n",
						ps[0], pd[0], f, t);
					prprog();
					exits("error");
			}
			if((pd[0] & m) != apix) {
				spix <<= 32 - ((f+1)<<sld);
				dpix <<= 32 - ((t+1)<<dld);
#ifdef T386
				spix = byterev(spix);
				dpix = byterev(dpix);
#endif
				print("bb dst2 %.8lux %.8lux\n",
					s, d);
				print("bb      %.8lux %.8lux %d %d\n",
					ps[0], pd[0], f, t);
				print("bb      %.8lux %.8lux %.8lux %.8lux\n",
					spix, dpix, apix, pd[0] & m);
				prprog();
				exits("error");
			}
		}
	}

	print("for\n");
	FORCEFORW = 1;
	FORCEBAKW = 0;

	for(i=0; i<iters; i++) {
		f = nrand(64);
		t = nrand(64);
		w = nrand(130);
		run(f, t, w, op);
	}

	if(sld == dld) {
		print("bak\n");
		FORCEFORW = 0;
		FORCEBAKW = 1;
	
		for(i=0; i<iters; i++) {
			f = nrand(64);
			t = nrand(64);
			w = nrand(130);
			run(f, t, w, op);
		}
	}

	if(op < F) {
		op++;
		goto loop;
	}
	if(timeit)
		print("time: %d ticks\n", ticks);
	exits(0);
}


#endif
