/* Written by Stone Horse.
 * Public domain.
 */
#include <iostream.h>
#include <errors.h>
#include <strings.h>
#include <ioctl.h>
#include <memory.h>
#include <unix.h>
#include <div.h>
#include <fbtools.h>
#include <graph.h>

#define DEVICE_TYPE_NONE 0
#define DEVICE_TYPE_FB 1

struct window {
	unsigned char *virscr;
	unsigned short scale;
	struct refresh_area {
		unsigned short x0;
		unsigned short y0;
		unsigned short x1;
		unsigned short y1;
	} area;
	long memsize;
	long line_length;
};

static unsigned short color = 0;
static int device_type;
static struct window *curwin;
static struct graph_screen scr = { 0 };

static unsigned int rgb888(unsigned short rgb)
{
	unsigned int r = rgb & 0xf800 >> 11;
	unsigned int g = rgb & 0x07e0 >> 5;
	unsigned int b = rgb & 0x001f;

	r *= 8;
	g *= 8;
	b *= 8;
	return (r << 16) | (g << 8) | b;
}

static void record_changes(int x0, int y0, int x1, int y1)
{
	/* Calculate changes for later update */
	(*curwin).area.x0 = x0 < (*curwin).area.x0 ? x0 : (*curwin).area.x0;
	(*curwin).area.y0 = y0 < (*curwin).area.y0 ? y0 : (*curwin).area.y0;
	(*curwin).area.x1 = (*curwin).area.x1 < x1 ? x1 : (*curwin).area.x1;
	(*curwin).area.y1 = (*curwin).area.y1 < y1 ? y1 : (*curwin).area.y1;
}
	
void setcolor(unsigned short value) { color = value; }
int getmaxx(void) { return scr.xres; }
int getmaxy(void) { return scr.yres; }

int initgraph(const char *device)
{
	if (device) {
		if (!memcmp(device, "/dev/fb", 7)) {
			if (fb_open(device, &scr) != 0)
				return -1;
			device_type = DEVICE_TYPE_FB;
		}
		return 0;
	}
	if (fb_open(NULL, &scr) != 0) {
		return -1;
	} else {
		device_type = DEVICE_TYPE_FB;
	}
	return 0;
}

struct window *initwindow(int scale)
{
	div_t ld;
	struct window *win;

	ld = div(scr.size, scale);
	if (scr.bits_per_pixel == 32)
		ld = div(ld.quot, 2);
	win = mmap(0, sizeof(*win) + ld.quot, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (win == (void *)-ENOMEM) {
		putz(2, __FILE__);
		putz(2, ": mmap: ");
		put_line(2, strerror(ENOMEM));
		return NULL;
	}
	(*win).virscr = (unsigned char *)win + sizeof(*win);
	(*win).memsize = sizeof(*win) + ld.quot;
	ld = div(scr.line_length, scale);
	if (scr.bits_per_pixel == 32)
		ld = div(ld.quot, 2);
	(*win).line_length = ld.quot;
	(*win).scale = scale;
	(*win).area.x0 = div(scr.line_length, scale).quot;
	(*win).area.y0 = div(getmaxy(), scale).quot;
	(*win).area.x1 = 0;
	(*win).area.y1 = 0;
	return win;
}

int closewindow(struct window *win)
{
	if (win == NULL) {
		putz(2, __FILE__);
		putz(2, ": Window does not exist\n");
		return -1;
	}
	munmap((*win).virscr, (*win).memsize);
	return 0;
}

int setcurrentwindow(struct window *win)
{
	if (win == NULL) {
		putz(2, __FILE__);
		put_line(2, ": Window does not exist\n");
		return -1;
	}
	curwin = win;
	return 0;
}

int closegraph(void)
{
	int ret;

	if (device_type == DEVICE_TYPE_FB) {
		ret = fb_close(&scr);
		if (ret < 0) {
			putz(2, __FILE__);
			putz(2, ": fb_close: ");
			put_line(2, strerror(-ret));
			return -1;
		}
		return 0;
	}
	return -1;
}

static void update_line(unsigned long vlin, unsigned long plin)
{
	int x;
	int px = (*curwin).area.x0 * (*curwin).scale;
	int mul;
	unsigned long vloc;
	unsigned long ploc;

	for (x = (*curwin).area.x0; x <= (*curwin).area.x1; x++) {
		vloc = x * sizeof(unsigned short) + vlin;
		for (mul = 0; mul < (*curwin).scale; mul++, px++) {
			if (scr.bits_per_pixel == 16) {
				ploc = px * sizeof(unsigned short) + plin;
				*(unsigned short *)(scr.physcr + ploc) = *(unsigned short *)((*curwin).virscr + vloc);
			} else if (scr.bits_per_pixel == 32) {
				ploc = px * sizeof(unsigned int) + plin;
				*(unsigned int *)(scr.physcr + ploc) = rgb888(*(unsigned short *)((*curwin).virscr + vloc));
			}
		}
	}
}

/* Compares the virtual screen to the physical screen and updates it where nessesary.
 * Nearest neighbour interpolation algorithum used for pixel scale.
 */
int doupdate(void)
{
	int py = (*curwin).area.y1 * (*curwin).scale;
	int y;
	int mul;
	unsigned long vlin;
	unsigned long plin;

	if (curwin == NULL)
		return -EINVAL;
	for (y = (*curwin).area.y0; y <= (*curwin).area.y1; y++) {
		vlin = y * (*curwin).line_length;
		for (mul = 0; mul < (*curwin).scale; mul++, py++) {
			plin = py * scr.line_length;
			update_line(vlin, plin);
		}
	}
	(*curwin).area.x0 = div(scr.line_length, (*curwin).scale).quot;
	(*curwin).area.y0 = div(getmaxy(), (*curwin).scale).quot;
	(*curwin).area.x1 = 0;
	(*curwin).area.y1 = 0;
	return 0;
}

/* Writes an image to the virtual screen */
int putimage(unsigned int height, unsigned int width, unsigned int starty, unsigned int startx, void *pixels)
{
	int y;
	int x;
	unsigned short *ptr = (unsigned short *)pixels;
	unsigned long line;

	if (curwin == NULL)
		return -EINVAL;
	record_changes(startx, starty, startx + width, starty + height);

	for (y = starty; y < starty + height && y < getmaxy(); y++) {
		line = y * (*curwin).line_length;
		for (x = startx; x < startx + width && x < scr.line_length; x++) {
			*(unsigned short *)((*curwin).virscr + x * sizeof(unsigned short) + line) = *ptr;
			ptr++;
		}
	}
	return 0;
}

/* Reads an image from the virtual screen */
int getimage(unsigned int height, unsigned int width, unsigned int starty, unsigned int startx, void *pixels)
{
	int y;
	int x;
	unsigned short *ptr = (unsigned short *)pixels;
	unsigned long line;

	if (curwin == NULL)
		return -EINVAL;
	for (y = starty; y < starty + height && y < getmaxy(); y++) {
		line = y * (*curwin).line_length;
		for (x = startx; x < startx + width && x < scr.line_length; x++) {
			*ptr = *(unsigned short *)((*curwin).virscr + x * sizeof(unsigned short) + line);
			ptr++;
		}
	}
	return 0;
}

int rectangle(int x0, int y0, int x1, int y1)
{
	int x = x0;
	int y = y0;
	unsigned long line;

	if (curwin == NULL)
		return -EINVAL;
	record_changes(x0, y0, x1, y1);

	for (y = y0; y < y1 && y < getmaxy(); y++) {
		line = y * (*curwin).line_length;
		for (x = x0; x < x1 && x < scr.line_length; x++)
			*(unsigned short *)((*curwin).virscr + x * sizeof(unsigned short) + line) = color;
	}
	return 0;
}

static void putpixel(int x, int y)
{
	unsigned long line = y * (*curwin).line_length;

	*(unsigned short *)((*curwin).virscr + x * sizeof(unsigned short) + line) = color;
}

int line(int x0, int y0, int x1, int y1)
{
	int dx = x1 - x0;
	int dy = y1 - y0;
	int p = 2 * dy - dx;
	int x = x0;
	int y = y0;

	if (curwin == NULL)
		return -EINVAL;
	record_changes(x0, y0, x1, y1);

	while (x < x1) {
		if (p >= 0) {
			putpixel(x, y);
			y = y + 1;
			p = p + 2 * dy - 2 * dx;
		} else {
			putpixel(x, y);
			p = p + 2 * dy;
		}
		x = x + 1;
	}
	return 0;
}

/* Draws a circle of the given radius at (x, y) */
int circle(int x, int y, int radius)
{
	int xx = -radius;
	int yy = 0;
	int err = 2 - 2 * radius;

	if (curwin == NULL)
		return -EINVAL;
	record_changes(x - radius, y - radius, x + radius, y + radius);

	do {
		putpixel(x - xx, y + yy); /* I quadrant */
		putpixel(x - yy, y - xx); /* II quadrant */
		putpixel(x + xx, y - yy); /* III quadrant */
		putpixel(x + yy, y + xx); /* IV quadrant */
		radius = err;

		if (radius <= yy)
			err += ++yy * 2 + 1;
		if (radius > xx || err > yy)
			err += ++xx * 2 + 1;
	} while (xx < 0);
	return 0;
}
