#include "isometric.h"
#include "color.h"

float LIGHTX = 0.2;
float LIGHTY = -0.8;
float LIGHTZ = -0.6;

// copied from allegro4
float dot_product_f (float x1, float y_1, float z1, float x2, float y2, float z2)
{
   return (x1 * x2) + (y_1 * y2) + (z1 * z2);
}

/* cross_productf:
 *  Floating point version of cross_product().
 */
// copied from allegro4
void cross_product_f(float x1, float y1, float z1, float x2, float y2, float z2, float *xout, float *yout, float *zout)
{
   assert(xout);
   assert(yout);
   assert(zout);

   *xout = (y1 * z2) - (z1 * y2);
   *yout = (z1 * x2) - (x1 * z2);
   *zout = (x1 * y2) - (y1 * x2);
}


/**
 * The return value is between -1 and 1.
 */
float surfaceLighting(float x1, float y1, float z1, float x2, float y2, float z2)
{
	float norm[3]; // normal of the plane defined by the two vectors
	cross_product_f (x1, y1, z1, x2, y2, z2, &norm[0], &norm[1], &norm[2]);
	return cos (dot_product_f (LIGHTX, LIGHTY, LIGHTZ, norm[0], norm[1], norm[2]));
}

ALLEGRO_COLOR litColor (ALLEGRO_COLOR color, float light)
{
	float light2 = light < 0.2 ? 0.2 : light;
	light2 = 0.5 + (light2 / 2);

	unsigned char rr, gg, bb;
	al_unmap_rgb(color, &rr, &gg, &bb);

	float r = light2 * rr;
	float g = light2 * gg;
	float b = light2 * bb;
	return al_map_rgb (r, g, b);
}

void drawWireFrame (int rx, int ry, int sx, int sy, int sz, ALLEGRO_COLOR color)
{
	int drx[7];
	int dry[7];

	isoToScreen (0, 0, sz, drx[0], dry[0]);
	isoToScreen (sx, 0, sz, drx[1], dry[1]);
	isoToScreen (0, sy, sz, drx[2], dry[2]);
	isoToScreen (sx, sy, sz, drx[3], dry[3]);
	isoToScreen (sx, 0, 0, drx[4], dry[4]);
	isoToScreen (0, sy, 0, drx[5], dry[5]);
	isoToScreen (sx, sy, 0, drx[6], dry[6]);

	al_draw_line (rx + drx[0], ry + dry[0], rx + drx[1], ry + dry[1], color, 1.0);
	al_draw_line (rx + drx[0], ry + dry[0], rx + drx[2], ry + dry[2], color, 1.0);
	al_draw_line (rx + drx[3], ry + dry[3], rx + drx[1], ry + dry[1], color, 1.0);
	al_draw_line (rx + drx[3], ry + dry[3], rx + drx[2], ry + dry[2], color, 1.0);
	al_draw_line (rx + drx[3], ry + dry[3], rx + drx[6], ry + dry[6], color, 1.0);
	al_draw_line (rx + drx[1], ry + dry[1], rx + drx[4], ry + dry[4], color, 1.0);
	al_draw_line (rx + drx[2], ry + dry[2], rx + drx[5], ry + dry[5], color, 1.0);
	al_draw_line (rx + drx[5], ry + dry[5], rx + drx[6], ry + dry[6], color, 1.0);
	al_draw_line (rx + drx[4], ry + dry[4], rx + drx[6], ry + dry[6], color, 1.0);
}

void isoToScreen (float x, float y, float z, int &rx, int &ry)
{
	rx = (int)(x - y);
	ry = (int)(x * 0.5 + y * 0.5 - z);
}

void isoToScreen_f (float x, float y, float z, float &rx, float &ry)
{
	rx = (x - y);
	ry = (x * 0.5 + y * 0.5 - z);
}

int isoToScreenX (float x, float y, float z)
{
 	return (int)(x - y);
}

int isoToScreenY (float x, float y, float z)
{
	return (int)(x * 0.5 + y * 0.5 - z);
}

int isoToScreenZ (float x, float y, float z)
{
	return (int)(x + y + z);
}

void screenToIso (int rx, int ry, float &x, float &y)
{
	x = ry + rx / 2;
	y = ry - rx / 2;
}

void drawMap(const GraphicsContext &gc, IsoMap *map)
{
	for (int mx = 0; mx < map->getDimMX(); ++mx)
		for (int my = 0; my < map->getDimMY(); ++my)
		{
			Cell &c = map->get(mx, my);

			map->drawSurface (gc, mx, my, c);
			map->drawLeftWall (gc, mx, my, c);
			map->drawRightWall (gc, mx, my, c);
		}

}

void IsoMap::drawSurface(const GraphicsContext &gc, int mx, int my, Cell &c)
{
	al_set_target_bitmap (gc.buffer);

	ALLEGRO_VERTEX coord[6]; // hold memory for coordinates

	memset(&coord, 0, sizeof(ALLEGRO_VERTEX) * 6);

	// ALLEGRO_COLOR baseColor = al_map_rgb (192, 255, 192);
	ALLEGRO_COLOR baseColor = al_map_rgb (255, 255, 255);

	// TODO hard coded tile indices for use in krampushack for now...
	int tilei = (c.z + c.dzbot > 2 || c.z > 2 || c.z + c.dzright > 2 || c.z + c.dzleft > 2) ? 2 : 1;

	int ubase = tileu * tilei;
	int vbase = 0;

	canvasFromIso_f (tilex * mx,
					tiley * my,
					tilez * c.z,
					coord[0].x, coord[0].y);
	coord[0].u = ubase;
	coord[0].v = vbase;

	canvasFromIso_f (	tilex * (mx + 1),
					tiley * my,
					tilez * (c.z + c.dzright),
					coord[1].x, coord[1].y);
	coord[1].u = ubase + tileu;
	coord[1].v = vbase;

	canvasFromIso_f (	tilex * mx,
					tiley * (my + 1),
					tilez * (c.z + c.dzleft),
					coord[5].x, coord[5].y);
	coord[5].u = ubase;
	coord[5].v = vbase + tilev;

	canvasFromIso_f (	tilex * (mx + 1),
					tiley * (my + 1),
					tilez * (c.z + c.dzbot),
					coord[3].x, coord[3].y);

	coord[3].u = ubase + tileu;
	coord[3].v = vbase + tilev;

	ALLEGRO_COLOR color1, color2;

/*
*
*
*    y   A
*   /   / \   x
*  +   /   \   \
*     C     B   +
*      \   /
*       \ /
*        D
*
*
*   Coordinate array
*
*   0 1 2   3 4 5
*   A B -   D - C
* - A B D   D A C   ->  vertical split
* - A B C   D B C   ->  horizontal split
*/
	if (c.isVerticalSplit())
	{
		memcpy (&coord[4], &coord[0], sizeof(ALLEGRO_VERTEX));
		memcpy (&coord[2], &coord[3], sizeof(ALLEGRO_VERTEX));

		/*
		 *
		 *
		 *    y   A
		 *   /   /|\   x
		 *  +   / | \   \
		 *     C  |  B   +
		 *      \ | /
		 *       \|/
		 *        D
		 *
		*/
		// lighting for A-B-D
		color1 = litColor (baseColor,
					surfaceLighting (-1, 0, -c.dzright, 0, 1, c.dzbot - c.dzright) );
		// lighting for A-D-C
		color2 = litColor (baseColor,
						surfaceLighting (0, -1, -c.dzleft, 1, 0, c.dzbot - c.dzleft) );
	}
	else
	{
		/*
		 *
		 *
		 *    y   A
		 *   /   / \   x
		 *  +   /   \   \
		 *     C-----B   +
		 *      \   /
		 *       \ /
		 *        D
		 *
		*/

		memcpy (&coord[4], &coord[1], sizeof(ALLEGRO_VERTEX));
		memcpy (&coord[2], &coord[5], sizeof(ALLEGRO_VERTEX));
		// lighting for A-B-C
		color1 = litColor (baseColor,
						surfaceLighting (1, 0, c.dzright, 0, 1, c.dzleft) );

		// lighting for C-B-D
		color2 = litColor (baseColor,
					surfaceLighting (0, -1, c.dzright - c.dzbot, -1, 0, c.dzleft - c.dzbot) );
	}

	for (int i = 0; i < 6; ++i)
	{
		coord[i].x += gc.xofst;
		coord[i].y += gc.yofst;
	}


	for (int i = 0; i < 3; ++i)
	{
		coord[i].color = color1;
	}


	for (int i = 3; i < 6; ++i)
	{
		coord[i].color = color2;
	}

	al_draw_prim(coord, NULL, tiles, 0, 6, ALLEGRO_PRIM_TRIANGLE_LIST);

	/*
	// debugging help for interpolation
	for (int xx = 0; xx < 8; xx ++)
	{
		for (int yy = 0; yy < 8; yy ++)
		{
			float jx = (mx * tilex) + (xx * tilex / 8);
			float jy = (my * tiley) + (yy * tiley / 8);
			float jz = getSurfaceIsoz(jx, jy);
			float rx, ry;
			canvasFromIso_f(jx, jy, jz, rx, ry);

			al_draw_filled_circle(rx + gc.xofst, ry + gc.yofst, 2.0, MAGENTA);
		}
	}
	*/

}

// get the iso z coordinate in pixels, at a given isometric x, y coordinate
double IsoMap::getSurfaceIsoz(double ix, double iy)
{
	// cell
	int mx = ix / tilex;
	int remx = ix - (mx * tilex);

	int my = iy / tiley;
	int remy = iy - (my * tiley);

	if (!inBounds(mx, my)) return -std::numeric_limits<double>::infinity();

	Cell &c = get(mx, my);

	double result = 0;
	// interpolate

	if (c.isVerticalSplit())
	{
		// NOTE: this comparison assumes tilex == tiley
		if (remx < remy)
		{
			// left half
			result = c.z * tilez;
			result += remx * (- c.dzleft + c.dzbot) * tilez / tilex;
			result += remy * c.dzleft * tilez / tiley;
		}
		else
		{
			// right half
			result = c.z * tilez;
			result += remx * c.dzright * tilez / tilex;
			result += remy * (- c.dzright + c.dzbot) * tilez / tiley;
		}
	}
	else
	{
		// NOTE: this comparison assumes tilex == tiley
		if (remx + remy < tilex)
		{
			// top half
			result = c.z * tilez;
			result += remx * c.dzright * tilez / tilex;
			result += remy * c.dzleft * tilez / tiley;
		}
		else
		{
			// bottom half
			result = (c.z + c.dzbot) * tilez;
			result += (tilex - remx) * (- c.dzbot + c.dzleft) * tilez / tilex;
			result += (tiley - remy) * (- c.dzbot + c.dzright) * tilez / tiley;
		}
	}

	return result;
}
