#ifndef ISOMETRIC_H_
#define ISOMETRIC_H_

#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include <assert.h>
#include <math.h>
#include "graphicscontext.h"
#include <algorithm>
#include "map2d.h"

extern float LIGHTX;
extern float LIGHTY;
extern float LIGHTZ;

const int DEFAULT_TILEX = 32;
const int DEFAULT_TILEY = 32;
const int DEFAULT_TILEZ = 16;

// default tile size in texture space
const int DEFAULT_TILEU = 16;
const int DEFAULT_TILEV = 16;

/**
 * Draw a surface.
 *
 * z is the height, in tile units, of the top corner.
 *
 * dzleft, dzright and dzbot are the z-delta, in tile units, of the left,
 * right and bottom corners
 */
struct Cell
{
	int z;
	short dzleft;
	short dzright;
	short dzbot;
	Cell() { z = 0; dzleft= 0; dzright = 0; dzbot = 0; }


	bool isVerticalSplit()
	{
		return dzbot == 0;
	}

	// lift corner of a single tile
	void liftCorner(int delta, int side)
	{
		switch (side)
		{
		case 0:
			z += delta;
			dzleft -= delta;
			dzright -= delta;
			dzbot -= delta;
			break;
		case 1:
			dzright += delta;
			break;
		case 2:
			dzbot += delta;
			break;
		case 3:
			dzleft += delta;
			break;
		default:
			assert (false);
			break;
		}
	}

	void setCorner(int value, int side)
	{
		int delta = value - z;
		switch (side)
		{
		case 0:
			z += delta;
			dzleft -= delta;
			dzright -= delta;
			dzbot -= delta;
			break;
		case 1:
			dzright = delta;
			break;
		case 2:
			dzbot = delta;
			break;
		case 3:
			dzleft = delta;
			break;
		default:
			assert (false);
			break;
		}
	}

	// return the z of the highest corner.
	int getMaxHeight()
	{
		return std::max(z, std::max (z + dzbot, std::max (z + dzleft, z + dzright)));
	}

	// return the z of the lowest corner
	int getMinHeight()
	{
		return std::min(z, std::min (z + dzbot, std::min (z + dzleft, z + dzright)));
	}

	bool isFlat()
	{
		return (dzbot == 0 && dzleft == 0 && dzright == 0);
	}

};

/**
 * The return value is between -1 and 1.
 */
float surfaceLighting(float x1, float y1, float z1, float x2, float y2, float z2);
ALLEGRO_COLOR litColor (ALLEGRO_COLOR color, float light);

class IsoMap : public Map2D<Cell>
{
protected:

	// maximum height
	int sizez = 20;

	// dimensions of a single cell
	int tilex = DEFAULT_TILEX;
	int tiley = DEFAULT_TILEY;
	int tilez = DEFAULT_TILEZ;

	// dimensions of a texture tile
	int tileu = DEFAULT_TILEU;
	int tilev = DEFAULT_TILEV;

public:

	IsoMap() : Map2D<Cell>() {}
	IsoMap(int w, int h) : Map2D<Cell>(w, h) {}

	int getTilex() { return tilex; }
	int getTiley() { return tiley; }

	void setTileSize(int x, int y, int z)
	{
		tilex = x;
		tiley = y;
		tilez = z;
	}

	void setTileTextureSize(int u, int v)
	{
		tileu = u;
		tilev = v;
	}

	/** get dimension of map in isometric pixel units */
	int getDimIX() { return dim_my * tiley; }
	int getDimIY() { return dim_mx * tilex; }

	ALLEGRO_BITMAP *tiles = NULL;

	// assuming z = 0...
	void isoFromCanvas (double rx, double ry, double &x, double &y)
	{
		double rx0 = rx - getXorig();
		double ry0 = ry - getYorig();
		x = ry0 + rx0 / 2;
		y = ry0 - rx0 / 2;
	}

	int canvasFromMapX (int mx, int my) const
	{
		return getXorig() + mx * 32 + my * -32;
	}

	int canvasFromMapY (int mx, int my) const
	{
		return getYorig() + mx * 16 + my * 16;
	}

	int canvasFromMapX (float mx, float my) const
	{
		return (int)(getXorig() + mx * 32.0 + my * -32.0);
	}

	int canvasFromMapY (float mx, float my) const
	{
		return (int)(getYorig() + mx * 16.0 + my * 16.0);
	}

	int mapFromCanvasX (int x, int y) const
	{
		return ((x - getXorig()) / 2 + (y - getYorig())) / 32;
	}

	int mapFromCanvasY (int x, int y) const
	{
		return  (y - getYorig() - (x - getXorig()) / 2) / 32;
	}

	int getw() const { return (dim_mx + tilex + dim_my * tiley) * 2; }
	int geth() const { return (dim_mx * tilex + dim_my * tiley) / 2 + sizez * tilez; }

	/** distance from the cell at 0,0 to the edge of the virtual screen */
	int getXorig () const { return getw() / 2; }

	/** distance from the cell at 0,0 to the edge of the virtual screen */
	int getYorig () const { return sizez * tilez; }

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

	void drawSurface(const GraphicsContext &gc, int mx, int my, Cell &c);

	void setPointAtLeast(int mx, int my, int value)
	{
		if (!inBounds(mx, my)) return;

		if (get(mx, my).z >= value) return;
		setPoint (mx, my, value);
	}

	void setPoint(int mx, int my, int value)
	{
		if (!inBounds(mx, my)) return;

		get(mx    ,     my).setCorner(value, 0);

		if (mx >= 1)
		{
			get(mx - 1,     my).setCorner(value, 1);
		}

		if (my >= 1 && mx >= 1)
		{
			get(mx - 1, my - 1).setCorner(value, 2);
		}

		if (my >= 1)
		{
			get(mx    , my - 1).setCorner(value, 3);
		}
	}

	// recursive version of setPointAtLeast
	void setHillAtLeast(int mx, int my, int value)
	{
		if (!inBounds(mx, my)) return;

		if (get(mx, my).z >= value) return;
		setPointAtLeast (mx, my, value);

		if (value > 0)
		{
			setHillAtLeast (mx + 1, my, value - 1);
			setHillAtLeast (mx, my + 1, value - 1);
			setHillAtLeast (mx - 1, my, value - 1);
			setHillAtLeast (mx, my - 1, value - 1);
		}
	}


	// lift all four corners around a point
	void raisePoint(int mx, int my, int delta)
	{
		get(mx    ,     my).liftCorner(delta, 0);
		get(mx - 1,     my).liftCorner(delta, 1);
		get(mx - 1, my - 1).liftCorner(delta, 2);
		get(mx    , my - 1).liftCorner(delta, 3);
	}

	void drawLeftWall(const GraphicsContext &gc, int mx, int my, Cell &c)
	{
		int x[4];
		int y[4];
		int z[4];

		x[0] = tilex * (mx + 1);
		y[0] = tiley * (my + 1);
		z[0] = 0;

		x[1] = tilex * mx;
		y[1] = tiley * (my + 1);
		z[1] = 0;

		x[2] = tilex * mx;
		y[2] = tiley * (my + 1);
		z[2] = tilez * (c.z + c.dzleft);

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

		ALLEGRO_COLOR color = litColor (al_map_rgb (192, 192, 192),
					surfaceLighting (0, 1, 0, 0, 0, 1 ));

		drawIsoPoly(gc, 4, x, y, z, color);
	}

	void drawRightWall(const GraphicsContext &gc, int mx, int my, Cell &c)
	{
		int x[4];
		int y[4];
		int z[4];

		x[0] = tilex * (mx + 1);
		y[0] = tiley * my;
		z[0] = 0;

		x[1] = tilex * (mx + 1);
		y[1] = tiley * (my + 1);
		z[1] = 0;

		x[2] = tilex * (mx + 1);
		y[2] = tiley * (my + 1);
		z[2] = tilez * (c.z + c.dzbot);

		x[3] = tilex * (mx + 1);
		y[3] = tiley * my;
		z[3] = tilez * (c.z + c.dzright);

		ALLEGRO_COLOR color = litColor (al_map_rgb (192, 192, 192),
					surfaceLighting (0, 0, 1, -1, 0, 0) );

		drawIsoPoly(gc, 4, x, y, z, color);
	}

	void drawIsoPoly (const GraphicsContext &gc, int num, int x[], int y[], int z[], ALLEGRO_COLOR color)
	{
		const int BUF_SIZE = 20; // max 20 points
		assert (num <= BUF_SIZE);

		ALLEGRO_VERTEX coord[BUF_SIZE]; // hold actual objects
		ALLEGRO_VERTEX *pcoord[BUF_SIZE]; // hold array of pointers

		// initialize pointers to point to objects
		for (int i = 0; i < BUF_SIZE; ++i) { pcoord[i] = &coord[i]; }

		for (int i = 0; i < num; ++i)
		{
			canvasFromIso_f (x[i], y[i], z[i], coord[i].x, coord[i].y);
			coord[i].x += gc.xofst;
			coord[i].y += gc.yofst;
			coord[i].color = color;
		}

		al_set_target_bitmap (gc.buffer);
		al_draw_prim (coord, NULL, NULL, 0, num, ALLEGRO_PRIM_TRIANGLE_FAN);
	}

	double getSurfaceIsoz(double ix, double iy);

};

/**
	draw wire model
	to help testing

	the wireFrame fills the cube from iso (0,0,0) to iso (sx, sy, sz),
	with iso (0, 0, 0) positioned at screen (rx, ry)
*/
void drawWireFrame (int rx, int ry, int sx, int sy, int sz, ALLEGRO_COLOR color);

void drawIsoPoly (GraphicsContext *gc, int num, int x[], int y[], int z[], int color);

// assume z == 0
void screenToIso (int rx, int ry, float &x, float &y);

/* transform x, y and z from isometric to screen coordinates and put them in rx, ry */
void isoToScreen (float x, float y, float z, int &rx, int &ry);

/* same as isoToScreen but with floats */
void isoToScreen_f (float x, float y, float z, float &rx, float &ry);

void drawMap(const GraphicsContext &gc, IsoMap *map);

#endif /* ISOMETRIC_H_ */

