#include <glerminal.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

extern "C"
{
#include <lualib.h>
#include <lauxlib.h>
}

#include <iostream>
#include <string>

#ifdef _WIN32
#define WINDOWS_LEAN_AND_MEAN
#include <Windows.h>
#define CHANGE_WORKING_DIRECTORY(path) !(SetCurrentDirectory(path))
#else
#include <unistd.h>
#define CHANGE_WORKING_DIRECTORY(path) chdir(path)
#endif

namespace
{
	int lglerminal_quit(lua_State* L)
	{
		glerminal_quit();

		return 0;
	}

	int lglerminal_flush(lua_State* L)
	{
		glerminal_flush();

		return 0;
	}

	int lglerminal_set(lua_State* L)
	{
		const int x = luaL_checkinteger(L, 1);
		const int y = luaL_checkinteger(L, 2);
		const int layer = luaL_checkinteger(L, 3) - 1;
		const unsigned short sprite = luaL_checkinteger(L, 4) - 1;
		glerminal_set(x, y, layer, sprite);

		return 0;
	}

	int lglerminal_get(lua_State* L)
	{
		const int x = luaL_checkinteger(L, 1);
		const int y = luaL_checkinteger(L, 2);
		const int layer = luaL_checkinteger(L, 3) - 1;
		lua_pushnumber(L, glerminal_get(x, y, layer) + 1);

		return 1;
	}

	int lglerminal_offset(lua_State* L)
	{
		const int x = luaL_checkinteger(L, 1);
		const int y = luaL_checkinteger(L, 2);
		const int layer = luaL_checkinteger(L, 3) - 1;
		const float ox = luaL_checknumber(L, 4);
		const float oy = luaL_checknumber(L, 5);
		glerminal_offset(x, y, layer, ox, oy);

		return 0;
	}

	int lglerminal_layer_color(lua_State* L)
	{
		const int x = luaL_checkinteger(L, 1);
		const int y = luaL_checkinteger(L, 2);
		const int layer = luaL_checkinteger(L, 3) - 1;
		const float r = luaL_checknumber(L, 4);
		const float g = luaL_checknumber(L, 5);
		const float b = luaL_checknumber(L, 6);
		const float a =	luaL_optnumber(L, 7, 1.0f);

		int ri = 255 * r;
		int gi = 255 * g;
		int bi = 255 * b;
		int ai = 255 * a;

		if (ri > 255) { ri = 255; }
		if (ri < 0)   { ri = 0;   }

		if (gi > 255) { gi = 255; }
		if (gi < 0)   { gi = 0;   }

		if (bi > 255) { bi = 255; }
		if (bi < 0)   { bi = 0;   }

		if (ai > 255) { ai = 255; }
		if (ai < 0)   { ai = 0;   }

		const unsigned int color = ri | (gi << 8) | (bi << 16) | (ai << 24);

		glerminal_color(x, y, layer, color);

		return 0;
	}

	int lglerminal_layer_scale(lua_State* L)
	{
		const int x = luaL_checkinteger(L, 1);
		const int y = luaL_checkinteger(L, 2);
		const int layer = luaL_checkinteger(L, 3) - 1;
		const float scale = luaL_checknumber(L, 4);
		glerminal_scale(x, y, layer, scale);

		return 0;
	}

	int lglerminal_load_sprites_file(lua_State* L)
	{
		const char* path = luaL_checkstring(L, 1);
		lua_pushboolean(L, glerminal_load_sprites_file(path));

		return 1;
	}

	int lglerminal_play_sound(lua_State* L)
	{
		const char* name = luaL_checkstring(L, 1);
		glerminal_sound(name);

		return 0;
	}

	const luaL_Reg lglerminal_methods[] =
	{
		{ "quit",    lglerminal_quit },
		{ "flush",   lglerminal_flush },
		{ "set",     lglerminal_set },
		{ "get",     lglerminal_get },
		{ "offset",  lglerminal_offset },
		{ "tint",    lglerminal_layer_color },
		{ "scale",   lglerminal_layer_scale },
		{ "sprites", lglerminal_load_sprites_file },
		{ "sound",   lglerminal_play_sound },
		{ nullptr,   nullptr }
	};

	int message_handler(lua_State* L)
	{
		luaL_traceback(L, L, lua_tostring(L, -1), 1);
		lua_remove(L, -2);

		glerminal_quit();

		return true;
	}

	lua_State* L;

	void init()
	{
		lua_pushcfunction(L, message_handler);
		const int handler = lua_gettop(L);
		lua_getglobal(L, "glerminal");
		lua_getfield(L, -1, "init");
		lua_remove(L, -2);
		if (lua_pcall(L, 0, 0, handler) != LUA_OK)
		{
			std::cout << lua_tostring(L, -1) << std::endl;
			lua_pop(L, 1);
		}
		lua_remove(L, handler);
	}

	void mainloop(double dt)
	{
		lua_pushcfunction(L, message_handler);
		const int handler = lua_gettop(L);
		lua_getglobal(L, "glerminal");
		lua_getfield(L, -1, "main");
		lua_remove(L, -2);
		lua_pushnumber(L, dt);
		if (lua_pcall(L, 1, 0, handler) != LUA_OK)
		{
			std::cout << lua_tostring(L, -1) << std::endl;
			lua_pop(L, 1);
		}
		lua_remove(L, handler);
	}

	void keypressed(int key)
	{
		const char* const name = glfwGetKeyName(key, 0);
		if (name)
		{
			lua_pushcfunction(L, message_handler);
			const int handler = lua_gettop(L);
			lua_getglobal(L, "glerminal");
			lua_getfield(L, -1, "keypress");
			lua_remove(L, -2);
			lua_pushstring(L, name);
			if (lua_pcall(L, 1, 0, handler) != LUA_OK)
			{
				std::cout << lua_tostring(L, -1) << std::endl;
				lua_pop(L, 1);
			}
			lua_remove(L, handler);
		}
	}

	void keyrelease(int key)
	{
		const char* const name = glfwGetKeyName(key, 0);
		if (name)
		{
			lua_pushcfunction(L, message_handler);
			const int handler = lua_gettop(L);
			lua_getglobal(L, "glerminal");
			lua_getfield(L, -1, "keyrelease");
			lua_remove(L, -2);
			lua_pushstring(L, name);
			if (lua_pcall(L, 1, 0, handler) != LUA_OK)
			{
				std::cout << lua_tostring(L, -1) << std::endl;
				lua_pop(L, 1);
			}
			lua_remove(L, handler);
		}
	}

	void mousemove(double x, double y)
	{
		lua_pushcfunction(L, message_handler);
		const int handler = lua_gettop(L);
		lua_getglobal(L, "glerminal");
		lua_getfield(L, -1, "mousemove");
		lua_remove(L, -2);
		lua_pushnumber(L, x);
		lua_pushnumber(L, y);
		if (lua_pcall(L, 2, 0, handler) != LUA_OK)
		{
			std::cout << lua_tostring(L, -1) << std::endl;
			lua_pop(L, 1);
		}
		lua_remove(L, handler);
	}

	void mousepress(int button, double x, double y)
	{
		const char* button_name = nullptr;
		switch (button)
		{
			case GLFW_MOUSE_BUTTON_LEFT:
				button_name = "left";
			break;

			case GLFW_MOUSE_BUTTON_RIGHT:
				button_name = "right";
			break;

			case GLFW_MOUSE_BUTTON_MIDDLE:
				button_name = "middle";
			break;
		}

		if (button_name)
		{
			lua_pushcfunction(L, message_handler);
			const int handler = lua_gettop(L);
			lua_getglobal(L, "glerminal");
			lua_getfield(L, -1, "mousepress");
			lua_remove(L, -2);
			lua_pushstring(L, button_name);
			lua_pushnumber(L, x);
			lua_pushnumber(L, y);
			if (lua_pcall(L, 3, 0, handler) != LUA_OK)
			{
				std::cout << lua_tostring(L, -1) << std::endl;
				lua_pop(L, 1);
			}
			lua_remove(L, handler);
		}
	}

	void mouserelease(int button, double x, double y)
	{
		const char* button_name = nullptr;
		switch (button)
		{
			case GLFW_MOUSE_BUTTON_LEFT:
				button_name = "left";
			break;

			case GLFW_MOUSE_BUTTON_RIGHT:
				button_name = "right";
			break;

			case GLFW_MOUSE_BUTTON_MIDDLE:
				button_name = "middle";
			break;
		}

		if (button_name)
		{
			lua_pushcfunction(L, message_handler);
			const int handler = lua_gettop(L);
			lua_getglobal(L, "glerminal");
			lua_getfield(L, -1, "mouserelease");
			lua_remove(L, -2);
			lua_pushstring(L, button_name);
			lua_pushnumber(L, x);
			lua_pushnumber(L, y);
			if (lua_pcall(L, 3, 0, handler) != LUA_OK)
			{
				std::cout << lua_tostring(L, -1) << std::endl;
				lua_pop(L, 1);
			}
			lua_remove(L, handler);
		}
	}
}

int main(int argc, char** argv)
{
	if (argc == 1)
	{
		std::cout << "Usage: " << argv[0] << " <project directory>" << std::endl;
		return 0;
	}

	if (CHANGE_WORKING_DIRECTORY(argv[1]))
	{
		std::cout << "Failed to find project directory" << std::endl;
		return 1;
	}

	L = luaL_newstate();
	luaL_openlibs(L);

	luaL_newlib(L, lglerminal_methods);
	lua_setglobal(L, "glerminal");

	lua_pushcfunction(L, message_handler);
	const int handler = lua_gettop(L);
	if (luaL_loadfile(L, "main.lua") != LUA_OK)
	{
		const char* str = lua_tostring(L, -1);
		std::cout << str << std::endl;
		lua_pop(L, 1);
	}
	else if (lua_pcall(L, 0, LUA_MULTRET, handler) != LUA_OK)
	{
		const char* str = lua_tostring(L, -1);
		std::cout << str << std::endl;
		lua_pop(L, 1);
	}
	else
	{
		glerminal_init_params params;
		params.init = init;
		params.main = mainloop;
		params.keypress = keypressed;
		params.keyrelease = keyrelease;
		params.moved = mousemove;
		params.mousepress = mousepress;
		params.mouserelease = mouserelease;
		glerminal_run(params);
	}

	lua_close(L);

	return 0;
}