#include <allegro.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "loadpng.h"

#undef DO_MAP

/* Weighted map entry */
typedef struct Map {
	int x, y;
	int a1, a2, a3, a4; /* ul, ur, ll, lr */
} Map;

#define W 640
#define H 480
#define SPACE 10

#define COUNT ((W + 80) / SPACE) * ((H + 80) / SPACE)
int gridx[COUNT];
int gridy[COUNT];

/* Sets up a grayscale palette. */
/* GRR... why can't I use eight-bit values in palettes!?! */
void init_palette(void)
{
	/* static int offset = 0;
	static PALETTE pal;
	int r, g, b;
	set_palette_range(pal, 0, 255, 0); */

	int c;
	RGB color;

	for (c = 0; c < 256; c++) {
		color.r = c / 4;
		color.g = c / 4;
		color.b = c / 4;
		set_color(c, &color);
	}
}	

/* getpixel variant that returns the pixel value the map entry points to. */
int get(BITMAP* bmp, Map* m)
{
	/*
	unsigned char** line = bmp->line + m->y;
	unsigned char* l1 = line[0] + m->x;
	unsigned char* l2 = m->y < H ? (line[1] + m->x) : NULL;
	int c = 0;
	if (m->a1 != 0) c += l1[0] * m->a1;
	if (m->a2 != 0) c += l1[1] * m->a2;
	if (m->a3 != 0) c += l2[0] * m->a3;
	if (m->a4 != 0) c += l2[1] * m->a4;
	return c / 256;
	*/
	unsigned** line = (unsigned**)bmp->line + m->y;
	unsigned* l1 = line[0] + m->x;
	unsigned* l2 = m->y < H ? (line[1] + m->x) : NULL;
	int r = 0, g = 0, b = 0;
	if (m->a1 != 0) {
		r += getr32(l1[0]) * m->a1;
		g += getg32(l1[0]) * m->a1;
		b += getb32(l1[0]) * m->a1;
	}
	if (m->a2 != 0) {
		r += getr32(l1[1]) * m->a2;
		g += getg32(l1[1]) * m->a2;
		b += getb32(l1[1]) * m->a2;
	}
	if (m->a3 != 0) {
		r += getr32(l2[0]) * m->a3;
		g += getg32(l2[0]) * m->a3;
		b += getb32(l2[0]) * m->a3;
	}
	if (m->a4 != 0) {
		r += getr32(l2[1]) * m->a4;
		g += getg32(l2[1]) * m->a4;
		b += getb32(l2[1]) * m->a4;
	}
	return makecol32(r / 256, g / 256, b / 256);
}

/* Sets up four contiguous map entries. */
void init_map_helper(float x, float y, Map* m)
{
	int fx = m->x = floor(x);
	int fy = m->y = floor(y);
	float cx = x - fx;
	float cy = y - fy;
	float icx = 1.0 - cx;
	float icy = 1.0 - cy;
	m->a1 = fx < 0 || fx > W - 1 || fy < 0 || fy > H - 1 ?
			0 : icx * icy * 256;
	m->a2 = fx < -1 || fx > W - 2 || fy < 0 || fy > H - 1 ?
			0 : cx * icy * 256;
	m->a3 = fx < 0 || fx > W - 1 || fy < -1 || fy > H - 2 ?
			0 : icx * cy * 256;
	m->a4 = fx < -1 || fx > W - 2 || fy < -1 || fy > H - 2 ?
			0 : cx * cy * 256;
}

/* Sets up the entire map. */
void init_map(Map* m)
{
	int x, y;
	float xx, yy;
	float a, xs, ys;
	for (y = 0; y < H; y++) {
		for (x = 0; x < W; x++, m++) {
			/*
			xs = sin((x - y - W / 2) * M_PI / 40.0) / 20.0 + 1.08;
			ys = cos((y - x - H / 2) * M_PI / 40.0) / 20.0 + 1.08;
			a = cos((x - sin((x + y) * M_PI / 40.0) * 10.0 - W /
					2) * M_PI / H * 4) * M_PI / 50.0;
			*/

			xs = cos(M_PI / 45 * sqrt((x - W / 2) * (x - W / 2) +
			(y - H / 2) * (y - H / 2))) * 0.05 + 1.05;
			ys = sin(M_PI / 50 * sqrt((x - W / 2) * (x - W / 2) +
			(y - H / 2) * (y - H / 2))) * 0.05 + 1.05;
			a = cos(M_PI / 30 * sqrt((x - W / 2) * (x - W / 2) +
					(y - H / 2) * (y - H / 2))) * M_PI / 20;

			/*
			xs = 1.01;
			ys = 0.99;
			a = M_PI / 40.;
			*/

			xx = W / 2 + (-cos(-a) * (W / 2 - x) +
					sin(-a) * (H / 2 - y)) / xs;
			yy = H / 2 + (-sin(-a) * (W / 2 - x) -
					cos(-a) * (H / 2 - y)) / ys;
			/* Source pixel: (xx, yy); Dest pixel: (x, y) */
			init_map_helper(xx, yy, m);
		}
	}
}

/* Frobs your widgets */
int main(int argc, char** argv)
{
	BITMAP* buffer;
	BITMAP* buffer2;
	clock_t timer;
	int frames = 0;
	BITMAP* buffer3;

#ifdef DO_MAP
	Map* map;
	map = malloc(sizeof(Map) * W * H);
	init_map(map);
#endif
	
	allegro_init();
	install_keyboard();
	install_mouse();
	install_timer();

	/* set_color_depth(8);
	set_gfx_mode(GFX_AUTODETECT, W, H, 0, 0);
	init_palette(); */
	set_color_depth(32);
	set_gfx_mode(GFX_AUTODETECT, W, H, 0, 0);
	
	if (argc == 2) {
		register_png_file_type();
		set_color_depth(32);
		buffer = load_bitmap(argv[1], NULL);
		/* set_color_depth(8); */
		buffer3 = create_bitmap(1024, 1024);
		stretch_blit(buffer, buffer3, 0, 0, buffer->w, buffer->h,
				0, 0, buffer3->w, buffer3->h);
		destroy_bitmap(buffer);
	}
	buffer = create_bitmap(W, H);
	clear_bitmap(buffer);

#ifdef DO_MAP
	buffer2 = create_bitmap(W, H);
	clear_bitmap(buffer2);
#endif
	
	text_mode(-1);		/* Disable text background */
	
	timer = clock();
	while (1) {
		/* unsigned char** bpp = buffer2->line;
		unsigned char** bpp_end = bpp + H;
		unsigned char* bp;
		unsigned char* bp_end; */
		unsigned** bpp = (unsigned**)buffer2->line;
		unsigned** bpp_end = bpp + H;
		unsigned* bp;
		unsigned* bp_end;
#ifdef DO_MAP
		Map* mp = (Map*)map;
#endif
		/* int x = W / 2 + (rand() & 63) - 31;
		int y = H / 2 + (rand() & 63) - 31; */
		int x, y;

		/* Add interesting stuff */
#if 1
		float ticks = frames * M_PI / 160;
		int i = 0;
		int w = (W + 80) / SPACE;
	        for (y = -40; y < H + 40; y += SPACE) {
	                for (x = -40; x < W + 40; x += SPACE) {
				int ix, iy;
				int x2, y2;

				/* ix = (int)(x + cos(ticks + y / 50.0) * 30);
				iy = (int)(y - sin(ticks + x / 50.0) * 30);
				circlefill(buffer, ix, iy, 3, 255);
				circlefill(buffer, ix, iy, 2, 0); */

				x2 = W / 2 + (-cos(1.2 * ticks) * (W / 2 - x) +
					sin(1.5 * ticks) * (H / 2 - y));
				y2 = H / 2 + (-sin(1.3 * ticks) * (W / 2 - x) -
					cos(-1.1 * ticks) * (H / 2 - y));
ix = x + cos(ticks + y2 / (H / 13.0)) * (5 * SPACE * sin(ticks + x2 / (W / 16.)));
iy = y - sin(ticks + x2 / (W / 13.0)) * (5 * SPACE * cos(ticks - y2 / (H / 16.)));
				gridx[i] = ix;
				gridy[i++] = iy;
			}
		}

#if 1
		{
		V3D_f vtx[4];
		V3D_f* vtx_i[4] = {&vtx[0], &vtx[1], &vtx[2], &vtx[3]};
		vtx[0].z = vtx[1].z = vtx[2].z = vtx[3].z = 1;
		vtx[0].c = vtx[1].c = vtx[2].c = vtx[3].c = 255;
		i = 0;
	        for (y = 0; y < (H + 80) / SPACE - 1; y++) {
			int ii = i;
			x = 0;
	                for (; i < ii + w - 1; i++, x++) {
				vtx[0].x = gridx[i];
				vtx[0].y = gridy[i];
				vtx[0].u = x * 1024. / ((W + 80) / SPACE);
				vtx[0].v = y * 1024. / ((H + 80) / SPACE);

				vtx[1].x = gridx[i + w];
				vtx[1].y = gridy[i + w];
				vtx[1].u = x * 1024. / ((W + 80) / SPACE);
				vtx[1].v = (y + 1) * 1024. / ((H + 80) / SPACE);

				vtx[2].x = gridx[i + w + 1];
				vtx[2].y = gridy[i + w + 1];
				vtx[2].u = (x + 1) * 1024. / ((W + 80) / SPACE);
				vtx[2].v = (y + 1) * 1024. / ((H + 80) / SPACE);

				vtx[3].x = gridx[i + 1];
				vtx[3].y = gridy[i + 1];
				vtx[3].u = (x + 1) * 1024. / ((W + 80) / SPACE);
				vtx[3].v = y * 1024. / ((H + 80) / SPACE);

				polygon3d_f(buffer, POLYTYPE_ATEX, buffer3,
				4, vtx_i);

#if 0
				line(buffer, vtx[0].x, vtx[0].y, vtx[1].x, vtx[1].y, 0);
				line(buffer, vtx[0].x, vtx[0].y, vtx[3].x, vtx[3].y, 0);
#endif
			}
			i = ii + w;
		}
		}
#endif
		#if 0
		i = 0;
	        for (y = -40; y < H + 40; y += SPACE) {
			int ii = i;
	                for (; i < ii + w; i++) {
				int x1, y1, x2, y2;

				x1 = gridx[i];
				y1 = gridy[i];

				if (i > ii) {
					x2 = gridx[i - 1];
					y2 = gridy[i - 1];
					line(buffer, x1, y1 - 1, x2, y2 - 1, 0);
					line(buffer, x1, y1 + 1, x2, y2 + 1, 0);
					line(buffer, x1 - 1, y1, x2 - 1, y2, 0);
					line(buffer, x1 + 1, y1, x2 + 1, y2, 0);
				}

				if (i >= w) {
					x2 = gridx[i - w];
					y2 = gridy[i - w];
					line(buffer, x1, y1 - 1, x2, y2 - 1, 0);
					line(buffer, x1, y1 + 1, x2, y2 + 1, 0);
					line(buffer, x1 - 1, y1, x2 - 1, y2, 0);
					line(buffer, x1 + 1, y1, x2 + 1, y2, 0);
				}
			}
		}
#endif
#if 0
		i = 0;
	        for (y = -40; y < H + 40; y += SPACE) {
			int ii = i;
	                for (; i < ii + w; i++) {
				int x1, y1, x2, y2;

				x1 = gridx[i];
				y1 = gridy[i];

				if (i > ii) {
					x2 = gridx[i - 1];
					y2 = gridy[i - 1];
					line(buffer, x1, y1, x2, y2, 255);
				}

				if (i >= w) {
					x2 = gridx[i - w];
					y2 = gridy[i - w];
					line(buffer, x1, y1, x2, y2, 255);
				}
			}
		}
#endif
#endif
#if 0
		for (x = 0; x < W; x++)
			putpixel(buffer, x, H / 2 + 32 * sin((frames + x) *
					M_PI / W * 2), 255);
		for (y = 0; y < H; y++)
			putpixel(buffer, W / 2 + 32 * cos((frames + y) *
					M_PI / H * 2), y, 255);
#endif
#if 0
		textprintf_centre(buffer, font, x + 1, y + 1, 0, "fps: %f",
				(float)frames * CLOCKS_PER_SEC /
				(clock() - timer));
		textprintf_centre(buffer, font, x, y, 255, "fps: %f",
				(float)frames * CLOCKS_PER_SEC /
				(clock() - timer));
#endif
#if 0
		circlefill(buffer, x, y, 4, 255);
#endif
#if 0
		circlefill(buffer, mouse_x, mouse_y, 5, 255);
#endif
		/* putpixel(buffer, W / 2, H / 2, 0); */
		
		/* Map old image to new */
#ifdef DO_MAP
		for (; bpp < bpp_end; bpp++) {
			bp = *bpp; bp_end = bp + W;
			for (; bp < bp_end; bp++, mp++)
				*bp = get(buffer, mp);
		}
		/* clear_bitmap(buffer); */
		blit(buffer2, buffer, 0, 0, 0, 0, W, H);	/* Feedback! */
		/* vsync(); */
		blit(buffer2, screen, 0, 0, 0, 0, W, H);	/* Display */
#else
		blit(buffer, screen, 0, 0, 0, 0, W, H);
		clear_bitmap(buffer);
#endif
		frames++;

		if (keypressed()) {
			int k = readkey();
			if (k >> 8 == KEY_ESC) break;
			if (k >> 8 == KEY_SPACE) {
				while (key[KEY_SPACE]);
				while (!key[KEY_SPACE]);
				while (key[KEY_SPACE]);
			}
			clear_keybuf();
		}
	}
	timer = clock() - timer;

 	set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
	printf("Average FPS: %f\n", (float)frames * CLOCKS_PER_SEC / timer);

	destroy_bitmap(buffer);
#ifdef DO_MAP
	destroy_bitmap(buffer2);
	free(map);
#endif
	return EXIT_SUCCESS;
}
END_OF_MAIN()


