#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>

#ifndef null
#define null				0
#endif

#define MakeID(a,b,c,d)		(((a)<<24) | ((b)<<16) | ((c)<<8) | (d))
#define GameTitle			"Tile Map"
#define TileFileName		"data\\tiles.png"
#define GameID				10954
#define GameVersionA		0
#define GameVersionB		0
#define GameVersionC		0
#define GameVersionD		1
#define GridWidth			25
#define GridHeight			15
#define TileWidth			16
#define TileHeight			16
#define TileShiftX			4 // 16 = 2^4
#define TileShiftY			4
#define TileAcross			40
#define PlayerWidth			20
#define PlayerHeight		40
#define ThresholdLeft		5 << TileShiftX;
#define ThresholdRight		(GridWidth - 6) << TileShiftY;

enum Keys
{
	KeyEscape,
	KeyLeft,
	KeyRight,
	KeyCount
};

enum CellLayers
{
	LayerDrawStart,
	LayerBackground1 = LayerDrawStart,
	LayerBackground2,
	LayerSolid1,
	LayerSolid2,
	LayerSolid3,
	LayerForeground,
	LayerDrawEnd = LayerForeground,

	LayerItems,
	LayerMobs,
	LayerSpecial1,
	LayerSpecial2,
	LayerUndefined1,
	LayerUndefined2,
	TotalLayers
};

typedef struct Cell
{
	unsigned int value[TotalLayers];
} Cell;

typedef struct Camera
{
	int x;
	int y;

	// threshold
	int tx_l;
	int tx_r;
	//int tx_u;
	//int tx_d;
} Camera;

typedef struct Map
{
	Cell* cells;
	int width;
	int height;
	int floor_count;
	int floor_start;
	int player_start_x;
	int player_start_y;
	int camera_start_x;
	int camera_start_y;
	bool is_dirty;
} Map;

typedef struct Player
{
	int x;
	int y;
	int w;
	int h;
	//int hp;
} Player;

int game_init();
void game_shutdown();
void game_loop();
void game_draw();
int game_logic();
void cell_draw(const Cell &cell, int x, int y);
void map_destroy(Map* map);
void map_draw(const Map* map, const Camera* camera);
Map* map_create_empty(unsigned int floor, unsigned int width, unsigned int height);
Map* map_load(const char* filename);
int map_save(const Map* map, const char* filename);
void camera_move(Camera* camera, const Map* map, const Player *player);
void player_draw(const Player *player, const Camera* camera);
void player_move(Player* player, int min_x, int min_y, int max_x, int max_y, int dx, int dy);

ALLEGRO_EVENT_QUEUE* app_event_queue = null;
ALLEGRO_DISPLAY* app_display = null;
ALLEGRO_BITMAP* app_buffer = null;
ALLEGRO_BITMAP* map_tiles = null;
ALLEGRO_TIMER* app_timer = null;
Camera camera = { 0, 0 };
Map* game_map = null;
Player game_player = { 0, 0, 0, 0 };

int main(int argc, char** argv)
{
	int rv = 0;

	rv = game_init();

	if (rv >= 0)
	{
		game_loop();
	}

	game_shutdown();

	return rv;
}

int game_init()
{
	if (!al_init())
	{
		return -1;
	}

	srand(time(null));

	ALLEGRO_PATH* basePath = al_get_standard_path(ALLEGRO_RESOURCES_PATH);
	if (basePath)
	{
		al_make_path_canonical(basePath);
		al_change_directory(al_path_cstr(basePath, ALLEGRO_NATIVE_PATH_SEP));
		al_destroy_path(basePath);
	}

	if (!al_install_keyboard())
	{
		return -1;
	}

	if (!al_install_mouse())
	{
		return -1;
	}

	if (!al_init_image_addon())
	{
		return -1;
	}

	if (!al_init_primitives_addon())
	{
		return -1;
	}

	al_set_new_display_option(ALLEGRO_SINGLE_BUFFER, 1, ALLEGRO_REQUIRE);
	al_set_new_display_flags(ALLEGRO_WINDOWED | ALLEGRO_RESIZABLE);
	app_display = al_create_display(1024, 768);
	if (!app_display)
	{
		return -1;
	}

	al_clear_to_color(al_map_rgb(255, 255, 255));
	al_set_window_title(app_display, GameTitle);


	app_buffer = al_create_bitmap(GridWidth << TileShiftX, GridHeight << TileShiftY);
	if (!app_buffer)
	{
		return -1;
	}

	map_tiles = al_load_bitmap(TileFileName);
	if (!map_tiles)
	{
		return -1;
	}

	app_timer = al_create_timer(1.0 / 30.0);
	if (!app_timer)
	{
		return -1;
	}

	app_event_queue = al_create_event_queue();
	if (!app_event_queue)
	{
		return -1;
	}

	game_map = map_load("map.dat");
	if (!game_map)
	{
		game_map = map_create_empty(4, 40, GridHeight);
		if (!game_map)
		{
			return -1;
		}

		game_map->player_start_x = 120;
		game_map->player_start_y = ((game_map->height - 1) * TileHeight) - PlayerHeight;
		
		game_map->cells[0].value[LayerBackground1] = 1;

		for (int i = 0; i < 40; ++i)
		{
			game_map->cells[i + ((GridHeight - 1) * game_map->width)].value[LayerSolid1] = 41;
		}

		for (int i = 0; i < 40; ++i)
		{
			if ((rand() % 100) < 15)
			{
				game_map->cells[i + ((GridHeight - 2) * game_map->width)].value[LayerForeground] = 80 + (rand() % 3);
			}
		}
		game_map->cells[0].value[LayerSolid1] = 41;
		game_map->cells[game_map->width - 1].value[LayerSolid1] = 41;
		map_save(game_map, "map.dat");
	}
	camera.x = game_map->camera_start_x;
	camera.y = game_map->camera_start_y;
	camera.tx_l = ThresholdLeft;
	camera.tx_r = ThresholdRight;
	game_player.x = game_map->player_start_x;
	game_player.y = game_map->player_start_y;
	game_player.w = PlayerWidth;
	game_player.h = PlayerHeight;

	al_register_event_source(app_event_queue, al_get_display_event_source(app_display));
	al_register_event_source(app_event_queue, al_get_timer_event_source(app_timer));
	al_register_event_source(app_event_queue, al_get_keyboard_event_source());
	al_register_event_source(app_event_queue, al_get_mouse_event_source());

	al_start_timer(app_timer);

	return 0;
}

void game_shutdown()
{
	if (map_tiles)
	{
		al_destroy_bitmap(map_tiles);
		map_tiles = null;
	}

	if (game_map)
	{
		map_destroy(game_map);
		game_map = null;
	}

	if (app_buffer)
	{
		al_destroy_bitmap(app_buffer);
		app_buffer = null;
	}

	if (app_event_queue)
	{
		al_destroy_event_queue(app_event_queue);
		app_event_queue = null;
	}

	if (app_timer)
	{
		al_stop_timer(app_timer);
		al_destroy_timer(app_timer);
		app_timer = null;
	}

	if (app_display)
	{
		al_destroy_display(app_display);
		app_display = null;
	}
}

void game_loop()
{
	ALLEGRO_EVENT event;
	bool redraw = true;
	bool dologic = true;
	bool shutdown = false;

	while (!shutdown)
	{
		while (!al_is_event_queue_empty(app_event_queue))
		{
			al_get_next_event(app_event_queue, &event);

			switch (event.type)
			{
			case ALLEGRO_EVENT_TIMER:
			{
				dologic = true;
			} break;

			case ALLEGRO_EVENT_DISPLAY_RESIZE:
			{
				al_acknowledge_resize(app_display);
				redraw = true;
			} break;

			case ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY:
			{
			} break;

			case ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY:
			{
			} break;

			case ALLEGRO_EVENT_DISPLAY_CLOSE:
			{
				shutdown = true;
			} break;

			default: break;
			}
		}

		if (al_is_event_queue_empty(app_event_queue))
		{
			if (dologic)
			{
				if (game_logic() != 0)
				{
					shutdown = true;
				}
				redraw = true;
				dologic = false;
			}

			if (redraw)
			{
				game_draw();
				redraw = false;
			}
		}
	}
}

int game_logic()
{
	static ALLEGRO_KEYBOARD_STATE kbdstate;
	static bool keys[KeyCount] = { false, false, false };

	al_get_keyboard_state(&kbdstate);

	keys[KeyEscape] = al_key_down(&kbdstate, ALLEGRO_KEY_ESCAPE);
	keys[KeyLeft] = (al_key_down(&kbdstate, ALLEGRO_KEY_LEFT) || al_key_down(&kbdstate, ALLEGRO_KEY_PAD_4));
	keys[KeyRight] = (al_key_down(&kbdstate, ALLEGRO_KEY_RIGHT) || al_key_down(&kbdstate, ALLEGRO_KEY_PAD_6));

	if (al_key_down(&kbdstate, ALLEGRO_KEY_LCTRL) || al_key_down(&kbdstate, ALLEGRO_KEY_RCTRL))
	{
		if (al_key_down(&kbdstate, ALLEGRO_KEY_X))
		{
			// emergency shut down
			return -1;
		}
	}

	if (keys[KeyLeft])
	{
		player_move(&game_player, 
					0, 
					0, 
					(game_map->width << TileShiftX) - game_player.w,
					(game_map->height<< TileShiftY) - game_player.h, -4, 0);
		camera_move(&camera, game_map, &game_player);
		keys[KeyLeft] = false;
	}

	if (keys[KeyRight])
	{
		player_move(&game_player,
			0,
			0,
			(game_map->width << TileShiftX) - game_player.w,
			(game_map->height << TileShiftY) - game_player.h, 4, 0);
		camera_move(&camera, game_map, &game_player);
		keys[KeyRight] = false;
	}

	if (keys[KeyEscape])
	{
		return 1;
	}

	return 0;
}

void game_draw()
{
	al_set_target_bitmap(app_buffer);

	al_clear_to_color(al_map_rgb(255, 255, 255));

	map_draw(game_map, &camera);
	player_draw(&game_player, &camera);

	al_set_target_bitmap(al_get_backbuffer(app_display));

	al_draw_scaled_bitmap(app_buffer, 0, 0, al_get_bitmap_width(app_buffer), al_get_bitmap_height(app_buffer), 0, 0, al_get_display_width(app_display), al_get_display_height(app_display), 0);

	al_flip_display();
}

void cell_draw(const Cell &cell, int x, int y)
{
	static int px = 0, py = 0, tile = 0;

	for (int l = LayerDrawStart; l <= LayerDrawEnd; ++l)
	{
		tile = cell.value[l];
		px = (tile % TileAcross) << TileShiftX;
		py = (tile / TileAcross) << TileShiftY;

		if (tile == 41)
		{
			tile = 41;
		}

		al_draw_bitmap_region(	map_tiles,
								px,
								py,
								TileWidth,
								TileHeight,
								x,
								y,
								0);
	}
}

void map_draw(const Map *map, const Camera *camera)
{
	static int x = 0, y = 0, sx = 0, sy = 0;

	x = camera->x >> TileShiftX;
	y = camera->y >> TileShiftY;
	sx = camera->x - (x << TileShiftX);
	sy = camera->y - (y << TileShiftY);

	for (int j = 0; j <= GridHeight; ++j)
	{
		for (int i = 0; i <= GridWidth; ++i)
		{
			if ((x + i) < map->width) // no need to draw extra tile of at end
			{
				cell_draw(map->cells[x + i + ((y + j) * map->width)], (i << TileShiftX) - sx, (j << TileShiftY) - sy);
			}
		}
	}
}

void map_destroy(Map* map)
{
	if (map)
	{
		if (map->cells)
		{
			delete[] map->cells;
		}
		delete map;
	}
}

Map* map_create_empty(unsigned int floor, unsigned int width, unsigned int height)
{
	Map* map = new Map;

	if (map)
	{
		int error = 0;

		map->floor_count = floor;
		map->floor_start = 0;
		map->width = width;
		map->height = height;
		map->player_start_x = 0;
		map->player_start_y = 0;
		map->camera_start_x = 0;
		map->camera_start_y = 0;
		map->is_dirty = false;
		map->cells = new Cell[map->floor_count * map->width * map->height];

		if (map->cells)
		{
			for (int f = 0; f < map->floor_count; f++)
			{
				for (int j = 0; j < map->height; j++)
				{
					for (int i = 0; i < map->width; i++)
					{
						map->cells[i + (j * map->width) + (f * map->width * map->height)].value[LayerBackground1] = 1;
						for (int c = 1; c < TotalLayers; ++c)
						{
							map->cells[i + (j * map->width) + (f * map->width * map->height)].value[c] = 0;
						}
					}
				}
			}
		}
		else
		{
			error = -1;
		}

		if (error < 0)
		{
			if (map->cells)
			{
				delete[] map->cells;
			}
			delete map;
			map = null;
		}
	}

	return map;
}

Map* map_load(const char* filename)
{
	Map* map = null;
	ALLEGRO_FILE* pfile = al_fopen(filename, "rb");

	if (pfile)
	{
		int error = 0;
		map = new Map;

		if (map)
		{
			ALLEGRO_FILE* pfile = al_fopen(filename, "rb");

			map->floor_count = 0;
			map->floor_start = 0;
			map->width = 0;
			map->height = 0;
			map->player_start_x = 0;
			map->player_start_y = 0;
			map->camera_start_x = 0;
			map->camera_start_y = 0;
			map->is_dirty = false;
			map->cells = null;

			int gameID = al_fread32be(pfile);
			int mapVersion = al_fread32be(pfile);
			int gameVersion = MakeID(GameVersionA, GameVersionB, GameVersionC, GameVersionD);

			if (!(gameID == GameID && mapVersion == gameVersion))
			{
				error = -2;
			}
			else
			{
				map->floor_count = al_fread32be(pfile);
				map->width = al_fread32be(pfile);
				map->height = al_fread32be(pfile);
				map->floor_start = al_fread32be(pfile);
				map->player_start_x = al_fread32be(pfile);
				map->player_start_y = al_fread32be(pfile);
				map->camera_start_x = al_fread32be(pfile);
				map->camera_start_y = al_fread32be(pfile);

				map->cells = new Cell[map->floor_count * map->width * map->height];

				if (map->cells)
				{
					for (int f = 0; f < map->floor_count; f++)
					{
						for (int j = 0; j < map->height; j++)
						{
							for (int i = 0; i < map->width; i++)
							{
								for (int c = 0; c < TotalLayers; ++c)
								{
									int value = al_fgetc(pfile);

									if (value == EOF)
									{
										error = -1;
										f = map->floor_count;
										j = map->height;
										i = map->width;
										c = TotalLayers;
									}
									else
									{
										map->cells[i + (j * map->width) + (f * map->width * map->height)].value[c] = (unsigned int)value;
									}
								}
							}
						}
					}
				}
				else
				{
					error = -1;
				}
			}

			al_fclose(pfile);
		}
		else
		{
			error = -1;
		}

		if (error < 0)
		{
			if (map)
			{
				if (map->cells)
				{
					delete[] map->cells;
				}
				delete map;
				map = null;
			}
		}
	}

	return map;
}

int map_save(const Map *map, const char* filename)
{
	int error = 0;
	ALLEGRO_FILE* pfile = al_fopen(filename, "wb");

	if (pfile)
	{
		al_fwrite32be(pfile, GameID);
		al_fwrite32be(pfile, MakeID(GameVersionA, GameVersionB, GameVersionC, GameVersionD));
		al_fwrite32be(pfile, map->floor_count);
		al_fwrite32be(pfile, map->width);
		al_fwrite32be(pfile, map->height);
		al_fwrite32be(pfile, map->floor_start);
		al_fwrite32be(pfile, map->player_start_x);
		al_fwrite32be(pfile, map->player_start_y);
		al_fwrite32be(pfile, map->camera_start_x);
		al_fwrite32be(pfile, map->camera_start_y);

		for (int f = 0; f < map->floor_count; ++f)
		{
			for (int j = 0; j < map->height; ++j)
			{
				for (int i = 0; i < map->width; ++i)
				{
					for (int c = 0; c < TotalLayers; ++c)
					{
						al_fputc(pfile, map->cells[i + (j * map->width) + (f * map->width * map->height)].value[c]);
					}
				}
			}
		}

		al_fclose(pfile);
	}
	else
	{
		error = -1;
	}

	return error;
}

void camera_move(Camera* camera, const Map* map, const Player* player)
{
	if ((player->x - camera->x) < camera->tx_l)
	{
		camera->x -= camera->tx_l - (player->x - camera->x);
		if (camera->x < 0)
		{
			camera->x = 0;
		}
	}

	if ((player->x - camera->x) >= camera->tx_r)
	{
		camera->x += (player->x - camera->x) - camera->tx_r;
		if (camera->x >= (map->width - GridWidth) << TileShiftX)
		{
			camera->x = (map->width - GridWidth) << TileShiftX;
		}
	}
}

void player_draw(const Player *player, const Camera* camera)
{
	static ALLEGRO_COLOR red = al_map_rgb(255, 0, 0);
	static ALLEGRO_COLOR dred = al_map_rgb(192, 0, 0);

	al_draw_filled_rectangle((player->x - camera->x), (player->y - camera->y), (player->x - camera->x) + player->w, (player->y - camera->y) + player->h, dred);
	al_draw_filled_rectangle((player->x - camera->x) + 1, (player->y - camera->y) + 1, (player->x - camera->x) + player->w - 1, (player->y - camera->y) + player->h - 1, red);
}

void player_move(Player* player, int min_x, int min_y, int max_x, int max_y, int dx, int dy)
{
	if (dx < 0 && (player->x + dx) < min_x)
	{
		dx = min_x - player->x;
	}
	
	if (dx > 0 && (player->x + dx) > max_x)
	{
		dx = max_x - player->x;
	}

	player->x += dx;
}


