

/* Graphics routines for Maelstrom!  (By Sam Lantinga) */

#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include "framebuf.h"
#include "keyboard.h"

#ifdef linux
#define copy1(dst, srcptr) \
		dst = *srcptr
#define copy2(dst, srcptr) \
   		*((short *)&dst) = *((short *)srcptr)
#define copy4(dst, srcptr) \
   		*((long *)&dst) = *((long *)srcptr)
#else
#define copy1(dst, srcptr) \
		dst = *srcptr
#define copy2(dst, srcptr) \
   		memcpy(&dst, srcptr, 2)
#define copy4(dst, srcptr) \
   		memcpy(&dst, srcptr, 4)
#endif

/* Opcodes for the compiled sprite: */
#define OP_SKIP		0x00
#define OP_CP_CHAR	0x10
#define OP_CP_WORD	0x20
#define OP_CP_LONG	0x40
#define OP_NEXT_LINE	0x80
#define OP_TERMINATE	0xF0
#define MAX_NOPS	1024		/* Max ops in a clipped sprite */
#define MAX_NPIXELS	4096		/* Max pixels in a clipped sprite */


/* This class allocates a 640x480 frame-buffer and provides high-level
   routines to blit images into it.  Well, it also provides access to
   other drawing routines.
*/

void
FrameBuf:: ReColor(unsigned char *data, int len) {
	/* Translate 8-bits/pixel data */
	while ( len-- ) {
		*data = Pixel_colors[*data];
		++data;
	}
}

unsigned char
FrameBuf:: Map_Color(unsigned short red, 
			unsigned short green, unsigned short blue) {
	/* Allocate custom colors... */
	/* Most of this code is adapted from 'xv' -- Thanks! :) */
	int rd, gd, bd;			/* The color shades we have */
	int mdist, close, d;		/* Distance and closest pixel */
	int i, c;

	/* Initialize for this pass */
	mdist = 1000000; close=0;

	/* Cycle through the colors we have */
	for ( c=0; c<NUM_COLORS; ++c ) {
		rd = ((red >> 8) - (Color_Map[c].red >> 8));
		gd = ((green >> 8) - (Color_Map[c].green >> 8));
		bd = ((blue >> 8) - (Color_Map[c].blue >> 8));
		d = rd*rd + gd*gd + bd*bd;

		if ( d == 0 )  // Perfect match!
			return(Pixel_colors[c]);

		// Work slowly closer to a matching color..
		if ( d < mdist ) {
			mdist = d;
			close = c;
		}
	}
	return(Pixel_colors[close]);
}

void
FrameBuf:: Clear(void) {
	memset((void *)shared_mem, Black, shared_len);
	Refresh();
}

void
FrameBuf:: DrawBGPoint(unsigned int x, unsigned int y, 
					unsigned char color) {
	shared_mem[(y*WIDTH)+x] = backbuf[(y*WIDTH)+x] = color;
}

void
FrameBuf:: DrawPoint(unsigned int x, unsigned int y,
					unsigned char color) {
	shared_mem[(y*WIDTH)+x] = color;
}

void
FrameBuf:: DrawLine(unsigned int X1, unsigned int Y1, 
		unsigned int X2, unsigned int Y2, unsigned char color) {
	int x, y;
	int lo, hi;
	double slope, b;

	if ( Y1 == Y2 )  {  /* Horizontal line */
		lo = (X1 < X2 ? X1 : X2);
		hi = (X1 > X2 ? X1 : X2);
		y = Y1;
		for ( x=lo; x<hi; ++x )
			shared_mem[(y*WIDTH)+x] = color;
	} else if ( X1 == X2 ) {  /* Vertical line */
		x = X1;
		lo = (Y1 < Y2 ? Y1 : Y2);
		hi = (Y1 > Y2 ? Y1 : Y2);
		for ( y=lo; y<hi; ++y ) 
			shared_mem[(y*WIDTH)+x] = color;
	} else {
		/* Equation:  y = mx + b */
		slope = ((double)((int)(Y2 - Y1)) / 
					(double)((int)(X2 - X1)));
		b = (double)(Y1 - slope*(double)X1);
		if ( ((slope < 0) ? slope > -1 : slope < 1) ) {
			lo = (X1 < X2 ? X1 : X2);
			hi = (X1 > X2 ? X1 : X2);
			for ( x=lo; x<hi; ++x ) {
				y = (int)((slope*(double)x) + b);
				shared_mem[(y*WIDTH)+x] = color;
			}
		} else {
			lo = (Y1 < Y2 ? Y1 : Y2);
			hi = (Y1 > Y2 ? Y1 : Y2);
			for ( y=lo; y<hi; ++y ) {
				x = (int)(((double)y - b)/slope);
				shared_mem[(y*WIDTH)+x] = color;
			}
		}
	}
}

void
FrameBuf:: DrawRectangle(int X, int Y, int width, int height, 
					unsigned char color) {
	int x, maxx, y, maxy;

	maxx = X+width;
	maxy = Y+height;
	for ( x=X; x<=maxx; ++x )
		shared_mem[(Y*WIDTH)+x] = color;
	for ( x=X; x<=maxx; ++x )
		shared_mem[(maxy*WIDTH)+x] = color;
	for ( y=Y; y<=maxy; ++y )
		shared_mem[(y*WIDTH)+X] = color;
	for ( y=Y; y<=maxy; ++y )
		shared_mem[(y*WIDTH)+maxx] = color;
}

void
FrameBuf:: FillRectangle(int X, int Y, int width, int height, 
					unsigned char color) {
	int x, maxx, y, maxy;

	maxx = X+width;
	maxy = Y+height;
	for ( y=Y; y<=maxy; ++y ) {
		for ( x=X; x<=maxx; ++x ) {
			shared_mem[(y*WIDTH)+x] = color;
		}
	}
}

unsigned char *
FrameBuf:: Grab_Area(int x, int y, int width, int height) {
	int            row;
	unsigned char *area = new unsigned char[width*height];

	++width; ++height;
	for ( row=0; row<height; ++row ) {
		memcpy(&area[row*width], &shared_mem[(row+y)*WIDTH+x],
								width);
	}
	return(area);
}

void
FrameBuf:: Set_Area(int x, int y, int width, int height,
						unsigned char *area) {
	int row;

	++width; ++height;
	for ( row=0; row<height; ++row ) {
		memcpy(&shared_mem[(row+y)*WIDTH+x], &area[row*width],
								width);
	}
}
 
void
FrameBuf:: Set_BlitClip(int left, int top, int right, int bottom) {
	clip_left = left;
	clip_top = top;
	clip_right = right;
	clip_bottom = bottom;
}

void
FrameBuf:: Blit_BitMap(int x, int y, int width, int height,
			unsigned char *bdata, unsigned char color) {
	int row, col, offset;

	/* Inefficient, for now. :) */
	for ( row=y; row<(y+height); ++row ) {
		for ( col=x; col<(x+width); ++col ) {
			offset = (((row-y)*width)+(col-x));
			if ((bdata[offset/8]>>(7-(offset%8))) & 0x01) {
				offset = ((row*WIDTH)+col);
				shared_mem[offset] = color;
			}
		}
	}
	RefreshArea(x, y, width, height);
}

void
FrameBuf:: UnBlit_BitMap(int x, int y, int width, int height,
						unsigned char *bdata) {
	int row, col, offset;

	/* Inefficient, for now. :) */
	for ( row=y; row<(y+height); ++row ) {
		for ( col=x; col<(x+width); ++col ) {
			offset = (((row-y)*width)+(col-x));
			if ((bdata[offset/8]>>(7-(offset%8))) & 0x01) {
				offset = ((row*WIDTH)+col);
				shared_mem[offset] = Black;
			}
		}
	}
	RefreshArea(x, y, width, height);
}

void
FrameBuf:: Blit_Title(int x, int y, int width, int height,
						unsigned char *data) {
	int row, col, offset;

	/* Inefficient, for now. :) */
	for ( row=y; row<(y+height); ++row ) {
		for ( col=x; col<(x+width); ++col, ++data ) {
			offset = ((row*WIDTH)+col);
			shared_mem[offset] = *data;
		}
	}
	RefreshArea(x, y, width, height);
	Flush(1);
}

void
FrameBuf:: Blit_Sprite(int x, int y, int width, int height,
			unsigned char *sdata, unsigned char *mdata) {
	int row, col, offset;

	/* Inefficient, for now. :) */
	for ( row=y; row<(y+height); ++row ) {
		for ( col=x; col<(x+width); ++col, ++sdata ) {
			offset = (((row-y)*width)+(col-x));
			if ((mdata[offset/8]>>(7-(offset%8))) & 0x01) {
				offset = ((row*WIDTH)+col);
				shared_mem[offset] = *sdata;
			}
		}
	}
	RefreshArea(x, y, width, height);
}

void
FrameBuf:: UnBlit_Sprite(int x, int y, int width, int height,
					unsigned char *mdata) {
	int row, col, offset;

	/* Inefficient, for now. :) */
	for ( row=y; row<(y+height); ++row ) {
		for ( col=x; col<(x+width); ++col ) {
			offset = (((row-y)*width)+(col-x));
			if ((mdata[offset/8]>>(7-(offset%8))) & 0x01) {
				offset = ((row*WIDTH)+col);
				shared_mem[offset] = Black;
			}
		}
	}
	RefreshArea(x, y, width, height);
}

void
FrameBuf:: ClipBlit_Sprite(int x, int y, int width, int height,
			unsigned char *sdata, unsigned char *mdata) {
	int row, col;
	int doffset, soffset;
	int xoff, maxx, maxy;

	/* Inefficient, for now. :) */
	maxx = (x+width);
	if ( maxx > clip_right )
		maxx = clip_right;
	xoff = 0;
	if ( x < clip_left )
		xoff = clip_left-x;
	maxy = (y+height);
	if ( maxy > clip_bottom )
		maxy = clip_bottom;
	if ( y < clip_top ) {
		sdata += (clip_top-y)*width;
		mdata += (clip_top-y)*width;
		y = clip_top;
	}
	for ( row=y; row<maxy; ++row ) {
		for ( col=(x+xoff); col<maxx; ++col ) {
			doffset = (col-x);
			if ( mdata[doffset] ) {
				soffset = ((row*WIDTH)+col);
				shared_mem[soffset] = sdata[doffset];
			}
		}
		sdata += width;
		mdata += width;
	}
	RefreshArea(x, y, width, height);
}

void
FrameBuf:: UnClipBlit_Sprite(int x, int y, int width, int height,
						unsigned char *mdata) {
	int row, col;
	int doffset, soffset;
	int xoff, maxx, maxy;

	/* Inefficient, for now. :) */
	maxx = (x+width);
	if ( maxx > clip_right )
		maxx = clip_right;
	xoff = 0;
	if ( x < clip_left )
		xoff = clip_left-x;
	maxy = (y+height);
	if ( maxy > clip_bottom )
		maxy = clip_bottom;
	if ( y < clip_top ) {
		mdata += (clip_top-y)*width;
		y = clip_top;
	}
	for ( row=y; row<maxy; ++row ) {
		for ( col=(x+xoff); col<maxx; ++col ) {
			doffset = (col-x);
			if ( mdata[doffset] ) {
				soffset = ((row*WIDTH)+col);
				shared_mem[soffset] = backbuf[soffset];
			}
		}
		mdata += width;
	}
	RefreshArea(x, y, width, height);
}

CSprite *
FrameBuf:: Compile_Sprite(int width, int height, 
			unsigned char *sdata, unsigned char *mdata) {
	int drun=0, srun=0;
	int row, col, offset;

	CSprite *sprite = new CSprite;
	unsigned char *ops = new unsigned char[width*(height+1)];
	int ops_i=0;
	unsigned char *pixels = new unsigned char[width*height];
	int pixels_i=0;

	for ( row=0; row<height; ++row ) {
		for ( col=0; col<width; ++col ) {
			offset = ((row*width)+col);
			if ((mdata[offset/8]>>(7-(offset%8))) & 0x01) {
				if ( srun ) {
					ops[ops_i++] = OP_SKIP;
					ops[ops_i++] = (unsigned char)srun;
					srun = 0;
				}
				++drun;
			} else {
				switch (drun) {
					case 0:	break;
					case 1:	ops[ops_i++]=OP_CP_CHAR;
						copy1(pixels[pixels_i], sdata);
						++pixels_i;
						++sdata;
						break;
					case 2:	ops[ops_i++]=OP_CP_WORD;
						copy2(pixels[pixels_i], sdata);
						pixels_i += 2;
						sdata    += 2;
						break;
					case 3:	ops[ops_i++]=OP_CP_CHAR;
						copy1(pixels[pixels_i], sdata);
						++pixels_i;
						++sdata;
						ops[ops_i++]=OP_CP_WORD;
						copy2(pixels[pixels_i], sdata);
						pixels_i += 2;
						sdata    += 2;
						break;
				}
				drun=0;
				++srun;
				++sdata;
			}
			if ( drun == 4 ) {
				ops[ops_i++] = OP_CP_LONG;
				copy4(pixels[pixels_i], sdata);
				pixels_i += 4;
				sdata += 4;
				drun = 0;
			}
			if ( srun == 255 ) {
				ops[ops_i++] = OP_SKIP;
				ops[ops_i++] = 255;
				srun = 0;
			}
		}
		switch (drun) {
			case 0:	break;
			case 1:	ops[ops_i++]=OP_CP_CHAR;
				copy1(pixels[pixels_i], sdata);
				++pixels_i;
				++sdata;
				break;
			case 2:	ops[ops_i++]=OP_CP_WORD;
				copy2(pixels[pixels_i], sdata);
				pixels_i += 2;
				sdata    += 2;
				break;
			case 3:	ops[ops_i++]=OP_CP_CHAR;
				copy1(pixels[pixels_i], sdata);
				++pixels_i;
				++sdata;
				ops[ops_i++]=OP_CP_WORD;
				copy2(pixels[pixels_i], sdata);
				pixels_i += 2;
				sdata    += 2;
				break;
		}
		drun=0;
		srun=0;
		ops[ops_i++] = OP_NEXT_LINE;
	}
	if ( srun ) {
		ops[ops_i++] = OP_SKIP;
		ops[ops_i++] = (unsigned char)srun;
		srun = 0;
	}
	ops[ops_i++] = OP_TERMINATE;

	sprite->numops = ops_i;
	sprite->ops = new unsigned char[ops_i];
	memcpy(sprite->ops, ops, ops_i);
	delete[] ops;
	sprite->numpixels = pixels_i;
	sprite->pixels = new unsigned char[pixels_i];
	memcpy(sprite->pixels, pixels, pixels_i);
	delete[] pixels;
	sprite->width = width;
	sprite->height = height;
	return(sprite);
}

void
FrameBuf:: Blit_CSprite(int x, int y, CSprite *sprite) {
	int ops_i=0, pixels_i=0;
	int offset, Y=y;
	int clipped=0;
	unsigned char *pixels;

	/* Do we need to worry about clipping? */
	if ( (x < clip_left) || ((x+sprite->width) > clip_right) ||
	     (y < clip_top) || ((y+sprite->height) > clip_bottom) ) {
		sprite = ClipSprite(x, y, sprite);
		clipped = 1;
	}

	pixels = sprite->pixels;
	offset = ((Y*WIDTH)+x);
	for ( ; ; ) {
		switch (sprite->ops[ops_i++]) {
			case OP_SKIP:
				offset += sprite->ops[ops_i++];
				break;
			case OP_CP_CHAR:
				copy1(shared_mem[offset], &pixels[pixels_i]);
				offset++;
				pixels_i++;
				break;
			case OP_CP_WORD:
				copy2(shared_mem[offset], &pixels[pixels_i]);
				offset += 2;
				pixels_i += 2;
				break;
			case OP_CP_LONG:
				copy4(shared_mem[offset], &pixels[pixels_i]);
				offset += 4;
				pixels_i += 4;
				break;
			case OP_NEXT_LINE:
				offset = ((++Y*WIDTH)+x);
				break;
			case OP_TERMINATE:
				goto finished;
			default:
				fprintf(stderr, 
			"Xwindow: Unknown sprite opcode! (0x%.2x)\n",
						sprite->ops[ops_i-1]);
				exit(255);
		}
	}
finished:
	RefreshArea(x, y, sprite->width, sprite->height);
	if ( clipped ) {
		delete[] sprite->ops;
		delete[] sprite->pixels;
		delete   sprite;
	}
	return;
}

void
FrameBuf:: UnBlit_CSprite(int x, int y, CSprite *sprite) {
	int ops_i=0;
	int offset, Y=y;
	int clipped=0;
	unsigned char *pixels;

	/* Do we need to worry about clipping? */
	if ( (x < clip_left) || ((x+sprite->width) > clip_right) ||
	     (y < clip_top) || ((y+sprite->height) > clip_bottom) ) {
		sprite = ClipSprite(x, y, sprite);
		clipped = 1;
	}

	pixels = sprite->pixels;
	offset = ((Y*WIDTH)+x);
	for ( ; ; ) {
		switch (sprite->ops[ops_i++]) {
			case OP_SKIP:
				offset += sprite->ops[ops_i++];
				break;
			case OP_CP_CHAR:
				copy1(shared_mem[offset], &backbuf[offset]);
				offset++;
				break;
			case OP_CP_WORD:
				copy2(shared_mem[offset], &backbuf[offset]);
				offset += 2;
				break;
			case OP_CP_LONG:
				copy4(shared_mem[offset], &backbuf[offset]);
				offset += 4;
				break;
			case OP_NEXT_LINE:
				offset = ((++Y*WIDTH)+x);
				break;
			case OP_TERMINATE:
				goto finished;
			default:
				fprintf(stderr, 
			"Xwindow: Unknown sprite opcode! (0x%.2x)\n",
						sprite->ops[ops_i-1]);
				exit(255);
		}
	}
finished:
	RefreshArea(x, y, sprite->width, sprite->height);
	if ( clipped ) {
		delete[] sprite->ops;
		delete[] sprite->pixels;
		delete   sprite;
	}
	return;
}

void
FrameBuf:: Free_CSprite(CSprite *sprite) {
	delete[] sprite->ops;
	delete[] sprite->pixels;
	delete   sprite;
}

/* Recompile a CSprite, taking clipping into account */
CSprite *
FrameBuf:: ClipSprite(int x, int y, CSprite *sprite) {
	CSprite *newsprite = new CSprite;
	int ops_i=0, numops=0;
	char newops[MAX_NOPS];
	int pixels_i=0, numpixels=0;
	char newpixels[MAX_NPIXELS];
	int n, col;

	/* Check top clipping */
	for ( ; y < clip_top; ++y ) {
		while ( sprite->ops[ops_i] != OP_NEXT_LINE ) {
			switch (sprite->ops[ops_i++]) {
				case OP_CP_CHAR:
					++pixels_i;
					break;
				case OP_CP_WORD:
					pixels_i += 2;
					break;
				case OP_CP_LONG:
					pixels_i += 4;
					break;
				case OP_TERMINATE:
					goto endsprite;
			}
		}
		newops[numops++] = OP_NEXT_LINE;
		++ops_i;
	}
	for ( ; y <= clip_bottom; ++y ) {
		if ( x < clip_left ) {
			newops[numops++] = OP_SKIP;
			newops[numops++] = (clip_left-x);
		}
		for ( col=x; col<clip_left; ++ops_i ) {
			switch (sprite->ops[ops_i]) {
				case OP_SKIP:
					col += sprite->ops[++ops_i];
					if ( col > clip_left ) {
						newops[numops++] = 
							OP_SKIP;
						newops[numops++] =
							col-clip_left;
					}
					break;
				case OP_CP_CHAR:
					++pixels_i;
					++col;
					break;
				case OP_CP_WORD:
					col += 2;
					pixels_i += 2;
					switch (col-clip_left) {
						case 0:
		newops[numops++] = OP_CP_CHAR;
		newpixels[numpixels++]= sprite->pixels[pixels_i-1];
							break;
						case 1:
		newops[numops++] = OP_CP_WORD;
		newpixels[numpixels++]= sprite->pixels[pixels_i-2];
		newpixels[numpixels++]= sprite->pixels[pixels_i-1];
							break;
					}
					break;
				case OP_CP_LONG:
					col += 4;
					pixels_i += 4;
					switch (col-clip_left) {
						case 0:
		newops[numops++] = OP_CP_CHAR;
		newpixels[numpixels++]= sprite->pixels[pixels_i-1];
							break;
						case 1:
		newops[numops++] = OP_CP_WORD;
		newpixels[numpixels++]= sprite->pixels[pixels_i-2];
		newpixels[numpixels++]= sprite->pixels[pixels_i-1];
							break;
						case 2:
		newops[numops++] = OP_CP_CHAR;
		newpixels[numpixels++]= sprite->pixels[pixels_i-1];
		newops[numops++] = OP_CP_WORD;
		newpixels[numpixels++]= sprite->pixels[pixels_i-2];
		newpixels[numpixels++]= sprite->pixels[pixels_i-1];
							break;
						case 3:
		newops[numops++] = OP_CP_LONG;
		newpixels[numpixels++]= sprite->pixels[pixels_i-4];
		newpixels[numpixels++]= sprite->pixels[pixels_i-3];
		newpixels[numpixels++]= sprite->pixels[pixels_i-2];
		newpixels[numpixels++]= sprite->pixels[pixels_i-1];
							break;
					}
					break;
				case OP_NEXT_LINE:
					goto nextline;
				case OP_TERMINATE:
					goto endsprite;
			}
		}
		for ( ; col <= clip_right; ++ops_i ) {
			switch (sprite->ops[ops_i]) {
				case OP_SKIP:
		newops[numops++] = OP_SKIP;
		newops[numops++] = sprite->ops[++ops_i];
					col += sprite->ops[ops_i];
					break;
				case OP_CP_CHAR:
					++col;
		newops[numops++] = OP_CP_CHAR;
		newpixels[numpixels++] = sprite->pixels[pixels_i++];
					break;
				case OP_CP_WORD:
					col += 2;
					pixels_i += 2;
					if ( col-clip_right > 1 )
						break;
					switch (col-clip_right) {
						case 2:
		newops[numops++] = OP_CP_CHAR;
		newpixels[numpixels++]= sprite->pixels[pixels_i-2];
							break;
						default:
		newops[numops++] = OP_CP_WORD;
		newpixels[numpixels++]= sprite->pixels[pixels_i-2];
		newpixels[numpixels++]= sprite->pixels[pixels_i-1];
							break;
					}
					break;
				case OP_CP_LONG:
					col += 4;
					pixels_i += 4;
					if ( col-clip_right > 4 )
						break;
					switch (col-clip_right) {
						case 4:
		newops[numops++] = OP_CP_CHAR;
		newpixels[numpixels++]= sprite->pixels[pixels_i-4];
							break;
						case 3:
		newops[numops++] = OP_CP_WORD;
		newpixels[numpixels++]= sprite->pixels[pixels_i-4];
		newpixels[numpixels++]= sprite->pixels[pixels_i-3];
							break;
						case 2:
		newops[numops++] = OP_CP_CHAR;
		newpixels[numpixels++]= sprite->pixels[pixels_i-4];
		newops[numops++] = OP_CP_WORD;
		newpixels[numpixels++]= sprite->pixels[pixels_i-3];
		newpixels[numpixels++]= sprite->pixels[pixels_i-2];
							break;
						default:
		newops[numops++] = OP_CP_LONG;
		newpixels[numpixels++]= sprite->pixels[pixels_i-4];
		newpixels[numpixels++]= sprite->pixels[pixels_i-3];
		newpixels[numpixels++]= sprite->pixels[pixels_i-2];
		newpixels[numpixels++]= sprite->pixels[pixels_i-1];
							break;
					}
					break;
				case OP_NEXT_LINE:
					goto nextline;
				case OP_TERMINATE:
					goto endsprite;
			}
		}
	nextline:
		while ( sprite->ops[ops_i] != OP_NEXT_LINE ) {
			switch (sprite->ops[ops_i++]) {
				case OP_CP_CHAR:
					++pixels_i;
					break;
				case OP_CP_WORD:
					pixels_i += 2;
					break;
				case OP_CP_LONG:
					pixels_i += 4;
					break;
				case OP_TERMINATE:
					goto endsprite;
			}
		}
		newops[numops++] = OP_NEXT_LINE;
		++ops_i;
	}
endsprite:
	newsprite->width = sprite->width;
	newsprite->height = sprite->height;
	newops[numops++] = OP_TERMINATE;
	newsprite->numops = numops;
	newsprite->ops = new unsigned char[numops];
	memcpy(newsprite->ops, newops, numops);
	newsprite->pixels = new unsigned char[numpixels];
	memcpy(newsprite->pixels, newpixels, numpixels);
	return(newsprite);
}
