#include "Game.h"

#include <SDL.h>
#include <stdlib.h>

#include "Console.h"
#include "Inventory.h"
#include "inv_gui.h"
#include "Level.h"
#include "LevelGen.h"
#include "Log.h"
#include "Menus.h"
#include "Particle.h"
#include "Player.h"
#include "Render.h"
#include "rovdefs.h"
#include "Save.h"
#include "System.h"
#include "Video.h"
#include "World.h"

static int mouseXpos, mouseYpos;

#define STATUSMSG_DISPLAY_TIME 180
static char statusMsg[64];
static time statusMsgTimer = 0;

static time levelTransitionTime = 0;

struct _GameState Game;

void Game_SaveAllIfNeeded(void)
{
	switch (Game.state)
	{
	case GAME_STATE_PLAY:
	case GAME_STATE_PAUSED:
		Save_SaveGame();
		break;
	case GAME_STATE_DEAD:
	case GAME_STATE_TITLE:
	default:
		break;
	}
}

void Game_Init(RPKFile *rpk)
{
	Game.state = GAME_STATE_TITLE;
	Game.inventoryOpen = false;
	Game.mapOpen = false;
	Game.levelTime = 0;
	Game.cheats = 0;
	
	World_Init(rpk);

	SDL_memset(Game.input.keys, 0, sizeof(Game.input.keys));
	Game.input.command.rotDelta = 0.0f;
	Game.input.command.impulseForward = 0.0f;
	Game.input.command.impulseLateral = 0.0f;
	Game.input.mode = INPUTMODE_KEYBOARDMOUSE;

	Game.activeMenu = &Menu_TitleScreen;
	Game.activeMenu->selected = Gui_FirstInteractable(Game.activeMenu);
	SDL_strlcpy(Game.serverAddr, "localhost", 63);

	Vid_SetMouseLocked(false);

	initParticles();
}

void Game_Cleanup(void)
{
	Game_SaveAllIfNeeded();
	World_Destroy();
}

void Game_Start(enum GameStartType type, int depth)
{
	SDL_memset(Game.globalReg, 0, 4 * sizeof(int));
	Game.depth = depth;
	Game.levelTime = 0;
	Game.state = GAME_STATE_PLAY;
	Game_SetMenu(NULL);
	World_OnNewGame(type, depth);
}

void Game_ChangeFloor(int floor, float x, float y)
{
	if (floor < 1 || floor > gNumLevels || floor == Game.depth)
		return;
	World_OnLevelChange(floor);
	if (Level.enterName[0] != '\0')
		Game.state = GAME_STATE_XLEVEL;
	Game.levelTime = 0;
	Game.depth = floor;
}

void Game_TogglePaused(void)
{
	switch (Game.state)
	{
	case GAME_STATE_PLAY:
		Game.state = GAME_STATE_PAUSED;
		Game_SetMenu(&Menu_Pause);
		break;
	case GAME_STATE_PAUSED:
		Game.state = GAME_STATE_PLAY;
		Game_SetMenu(NULL);
		break;
	case GAME_STATE_DEAD:
	case GAME_STATE_TITLE:
	default:
		break;
	}
}

void Game_QuitToTitle(void)
{
	World_QuitGame();
	Game_SaveAllIfNeeded();
	Game.state = GAME_STATE_TITLE;
	Game_SetMenu(&Menu_TitleScreen);
}

#define OPTIONS_FILE		"Options.txt"
#define OPTIONS_FORMAT		"width:%d\nheight:%d\nfullscreen:%d\nsens:%f\nvolume:%f\nsfxVolume:%f\nmusicVolume:%f\n"
#define OPTIONS_ITEMSINFILE 7
#define OPTIONS_BUFFERSIZE  1024
static char optionsBuffer[OPTIONS_BUFFERSIZE];

void Game_SaveOptions(void)
{
	SDL_RWops *io;
	int msglen;

	io = SDL_RWFromFile(OPTIONS_FILE, "w");
	if (io == NULL)
	{
		Msg_Error("Unable to write options file: %s", SDL_GetError());
		return;
	}

	msglen = SDL_snprintf(optionsBuffer, OPTIONS_BUFFERSIZE, OPTIONS_FORMAT,
		Game.options.width, Game.options.height, Game.options.fullscreen, Game.options.mouseSens,
		Game.options.masterVolume, Game.options.sfxVolume, Game.options.musicVolume);
	io->write(io, optionsBuffer, 1, msglen);
	io->close(io);
}

struct Options *Game_LoadOptions(void)
{
	SDL_RWops *io;
	int numRead;

	int width, height, fullscreen;
	float mouseSens, masterVolume, sfxVolume, musicVolume;

	/* Set defaults in case loading fails */
	Game.options.width = 1280;
	Game.options.height = 720;
	Game.options.fullscreen = false;
	Game.options.mouseSens = 0.2f;
	Game.options.masterVolume = 1.0f;
	Game.options.sfxVolume = 1.0f;
	Game.options.musicVolume = 0.5f;

	io = SDL_RWFromFile(OPTIONS_FILE, "r");
	if (io == NULL)
	{
		return &Game.options;
	}

	io->read(io, optionsBuffer, 1, OPTIONS_BUFFERSIZE);

	numRead = SDL_sscanf(optionsBuffer, OPTIONS_FORMAT, &width, &height, &fullscreen, &mouseSens, &masterVolume, &sfxVolume, &musicVolume);
	if (numRead != OPTIONS_ITEMSINFILE)
	{
		Msg_Warning("Options file appears to be invalid.");
	}
	else
	{
		Game.options.width = width;
		Game.options.height = height;
		Game.options.fullscreen = fullscreen;

		Game.options.mouseSens = mouseSens;

		Game.options.masterVolume = masterVolume;
		Game.options.sfxVolume = sfxVolume;
		Game.options.musicVolume = musicVolume;
	}

	io->close(io);

	return &Game.options;
}

void Game_ShowStatusMsg(const char *msg)
{
	size_t n = MATH_MIN(SDL_strlen(msg), 63);
	SDL_memcpy(statusMsg, msg, n);
	statusMsg[n] = '\0';
	statusMsgTimer = STATUSMSG_DISPLAY_TIME;
}

void Game_Tick(void)
{
	if (Game.state == GAME_STATE_XLEVEL)
	{
		levelTransitionTime++;
		if (levelTransitionTime > 128)
		{
			Game.state = GAME_STATE_PLAY;
			levelTransitionTime = 0;
		}
		return;
	}

	if (Game.state == GAME_STATE_TITLE)
		return;

	if (statusMsgTimer > 0)
		statusMsgTimer--;

	World_Tick();
	Game.levelTime++;
}

void Game_Update(float deltaTime)
{
	if (Game.state == GAME_STATE_PLAY && Game.input.mode != INPUTMODE_CONTROLLER)
	{
		Game.input.command.impulseForward = 0.0f;
		Game.input.command.impulseLateral = 0.0f;
		if (Game.input.keys[INPUT_KEY_FORWARD] && !Game.input.keys[INPUT_KEY_BACKWARD])
			Game.input.command.impulseForward = 1.0f;
		if (Game.input.keys[INPUT_KEY_BACKWARD] && !Game.input.keys[INPUT_KEY_FORWARD])
			Game.input.command.impulseForward = -1.0f;
		if (Game.input.keys[INPUT_KEY_STRAFELEFT] && !Game.input.keys[INPUT_KEY_STRAFERIGHT])
			Game.input.command.impulseLateral = 1.0f;
		if (Game.input.keys[INPUT_KEY_STRAFERIGHT] && !Game.input.keys[INPUT_KEY_STRAFELEFT])
			Game.input.command.impulseLateral = -1.0f;
		if (Game.input.keys[INPUT_KEY_TURNLEFT] && !Game.input.keys[INPUT_KEY_TURNRIGHT])
			Game.input.command.rotDelta = -1.0f;
		if (Game.input.keys[INPUT_KEY_TURNRIGHT] && !Game.input.keys[INPUT_KEY_TURNLEFT])
			Game.input.command.rotDelta = 1.0f;
	}

	if (Game.state != GAME_STATE_TITLE && Game.state != GAME_STATE_XLEVEL)
	{
		World_Update(deltaTime);
		Game.input.command.rotDelta = 0.0f;
	}
}

static void DrawMap(Bmp_t *screen)
{
	const int glyphW = 4;
	const int glyphH = 8;

	int xc = (screen->w) / 2 - TOFLOAT(World_Players[0].sprite->x) * glyphW;
	int yc = (screen->h) / 2 - TOFLOAT(World_Players[0].sprite->y) * glyphH;

	int x, y;

	// Bmp_FillRegion(screen, 0x40000000, 0, 0, screen->w, screen->h);
	Bmp_SetActiveFont(BMPFONT_TINY);

	for (y = 0; y < LEVEL_SIZE; y++)
	{
		for (x = 0; x < LEVEL_SIZE; x++)
		{
			int xx = xc + x * glyphW;
			int yy = yc + y * glyphH;
			if (Level.vismap[x + y * LEVEL_SIZE] > 0)
			{
				bmcol_t col = Level.vismap[x + y * LEVEL_SIZE] > 1 ? 0xffa0a0a0 : 0xff2a2a2a;
				char tile = Level.tilemap[x + y * LEVEL_SIZE];
				if (tile == 'S' || tile == 'A')
					col = 0xff40ffa0;
				Bmp_DrawGlyph(screen, tile, col, xx, yy);
			}
		}
	}
	Bmp_DrawGlyph(screen, '@', 0xffff0000, (screen->w - glyphW) / 2, (screen->h - glyphH) / 2);
}

void Game_SetMenu(struct GuiMenu *menu)
{
	Game.activeMenu = menu;
	Vid_SetMouseLocked(Game.state == GAME_STATE_PLAY);
	if (menu != NULL)
		Gui_SelectWidget(menu, Gui_FirstInteractable(menu));
}

void Game_Render(Bmp_t *screen, float delta)
{
	int i;

	if (Game.state == GAME_STATE_XLEVEL)
	{
		int tw, th;
		int xx, yy;

		Gfx_DrawFireBackground(screen);

		Bmp_TextSize(Level.enterName, &tw, &th, screen->w);
		xx = (screen->w - tw) / 2;
		yy = (screen->h - th) / 2;
		Bmp_DrawText(screen, Level.enterName, 0xff000000, xx - 1, yy - 1, screen->w);
		Bmp_DrawText(screen, Level.enterName, 0xff000000, xx - 1, yy + 1, screen->w);
		Bmp_DrawText(screen, Level.enterName, 0xff000000, xx + 1, yy - 1, screen->w);
		Bmp_DrawText(screen, Level.enterName, 0xff000000, xx + 1, yy + 1, screen->w);
		Bmp_DrawText(screen, Level.enterName, 0xff000000, xx, yy + 1, screen->w);
		Bmp_DrawText(screen, Level.enterName, 0xff000000, xx, yy - 1, screen->w);
		Bmp_DrawText(screen, Level.enterName, 0xff000000, xx + 1, yy, screen->w);
		Bmp_DrawText(screen, Level.enterName, 0xff000000, xx - 1, yy, screen->w);
		Bmp_DrawText(screen, Level.enterName, 0xffffffff, xx, yy, screen->w);

		Console_Draw(screen);
		return;
	}

	if (Game.state == GAME_STATE_TITLE)
	{
		Gfx_DrawFireBackground(screen);
		if (Game.activeMenu != NULL)
		{
			Game.activeMenu->drawProc(Game.activeMenu, screen);
		}

		Console_Draw(screen);
		return;
	}

	World_Render(screen, delta);

	if (World_Players[0].entity)
	{

		const Item_t *handItem = Inv_GetItem(&World_Players[0].inventory, World_Players[0].handSlot, NULL);
		if (handItem != NULL && Game.state != GAME_STATE_DEAD)
		{
			Gfx_DrawHeldItem(handItem, World_Players[0].viewBob, World_Players[0].itemCooldown);
		}

		Gfx_DrawHUDPanel(World_Players[0].entity->health, World_Players[0].entity->maxHealth, World_Players[0].entity->invulnerableTime, Game.depth);

		if (statusMsgTimer > 0)
		{
			int msgw, msgh;
			Bmp_TextSize(statusMsg, &msgw, &msgh, screen->w);
			int xx = (screen->w - msgw) / 2;
			int yy = screen->h - 64;
			Bmp_SetActiveFont(BMPFONT_MINISERIF);
			Bmp_DrawText(screen, statusMsg, 0xff000000, xx + 1, yy + 1, screen->w);
			Bmp_DrawText(screen, statusMsg, 0xff992222, xx, yy, screen->w);
		}

		if (Level.boss)
			Gfx_DrawBossHealthBar(screen, Level.boss->health, Level.boss->maxHealth);

		if (World_CanUseLadderDown(&World_Players[0]))
		{
			int msgw, msgh;
			const char *ladderMsg = "Press E to descend deeper into the dungeon";
			Bmp_TextSize(ladderMsg, &msgw, &msgh, screen->w);
			int xx = (screen->w - msgw) / 2;
			int yy = screen->h - 50;
			Bmp_SetActiveFont(BMPFONT_MINISERIF);
			Bmp_DrawText(screen, ladderMsg, 0xff000000, xx + 1, yy + 1, screen->w);
			Bmp_DrawText(screen, ladderMsg, 0xff25bdf9, xx, yy, screen->w);
		}
		else if (World_CanUseLadderUp(&World_Players[0]))
		{
			int msgw, msgh;
			const char *ladderMsg = "Press E to ascend nearer to the surface";
			Bmp_TextSize(ladderMsg, &msgw, &msgh, screen->w);
			int xx = (screen->w - msgw) / 2;
			int yy = screen->h - 50;
			Bmp_SetActiveFont(BMPFONT_MINISERIF);
			Bmp_DrawText(screen, ladderMsg, 0xff000000, xx + 1, yy + 1, screen->w);
			Bmp_DrawText(screen, ladderMsg, 0xff25bdf9, xx, yy, screen->w);
		}

		if (Game.inventoryOpen)
		{
			Inv_Render(screen, &World_Players[0].inventory, mouseXpos, mouseYpos);
		}

		if (Game.mapOpen)
		{
			DrawMap(screen);
		}
	}

	if (Game.activeMenu != NULL)
	{
		Game.activeMenu->drawProc(Game.activeMenu, screen);
	}

	Console_Draw(screen);
}

void Game_OnKey(enum InputType key, int pressed)
{
	Game.input.keys[key] = pressed;

	switch (key)
	{
	case INPUT_KEY_USE:
		if (Game.inventoryOpen && Game.input.mode == INPUTMODE_CONTROLLER)
		{
			Inv_ControllerClick(&World_Players[0].inventory);
			break;
		}
	case INPUT_KEY_ATTACK:
	case INPUT_KEY_DROP:
	case INPUT_KEY_HOTBARPREV:
	case INPUT_KEY_HOTBARNEXT:
	case INPUT_KEY_HOTBAR1:
	case INPUT_KEY_HOTBAR2:
	case INPUT_KEY_HOTBAR3:
	case INPUT_KEY_HOTBAR4:
	case INPUT_KEY_HOTBAR5:
		if (pressed && !Game.inventoryOpen && Game.activeMenu == NULL)
			World_PlayerButtonPress(0, key);
		break;
	case INPUT_KEY_INVENTORY:
		if (pressed)
		{
			Game.inventoryOpen = !Game.inventoryOpen;
			Vid_SetMouseLocked(!Game.inventoryOpen);
		}
		else
		{
			Inv_OnClose(&World_Players[0].inventory);
		}
		break;
	case INPUT_KEY_MAP:
		if (pressed) Game.mapOpen = !Game.mapOpen;
		break;
		break;

	case INPUT_KEY_PAUSE:
		if (!pressed)
			break;
		if (Game.inventoryOpen)
		{
			Game.inventoryOpen = false;
			Vid_SetMouseLocked(!Game.inventoryOpen);
			break;
		}
		Game_TogglePaused();
		break;
	case INPUT_KEY_CONSOLE:
		if (!pressed)
			break;
		Game.consoleOpen = !Game.consoleOpen;
		if (Game.consoleOpen)
			SDL_StartTextInput();
		else
			SDL_StopTextInput();
		break;
	case INPUT_KEY_MENUDOWN:
		if (!pressed || Game.activeMenu == NULL)
			break;
		Gui_SelectWidget(Game.activeMenu, Gui_NextInteractable(Game.activeMenu, Game.activeMenu->selected));
		break;
	case INPUT_KEY_MENUUP:
		if (!pressed || Game.activeMenu == NULL)
			break;
		Gui_SelectWidget(Game.activeMenu, Gui_PrevInteractable(Game.activeMenu, Game.activeMenu->selected));
		break;
	case INPUT_KEY_MENULEFT:
		if (pressed && Game.activeMenu != NULL)
			Gui_HandleLeftRight(Game.activeMenu, -1);
		break;
	case INPUT_KEY_MENURIGHT:
		if (pressed && Game.activeMenu != NULL)
			Gui_HandleLeftRight(Game.activeMenu, 1);
		break;
	case INPUT_KEY_MENUACCEPT:
		if (!pressed)
			break;
		if (Game.activeMenu)
		{
			Gui_ClickMenu(Game.activeMenu, Game.activeMenu->selected);
		}
		break;
	case INPUT_KEY_MENUCLOSE:
		if (!pressed)
			break;
		if (Game.activeMenu)
		{
			Gui_CloseMenu(Game.activeMenu);
		}
		break;
	}
}

bool Game_OnMouseClick(int x, int y)
{
	if (Game.inventoryOpen)
	{
		World_InventoryClick(x, y);
		return true;
	}
	else if (Game.activeMenu != NULL)
	{
		Gui_HandleClick(Game.activeMenu, x, y);
		Vid_SetMouseLocked(Game.state == GAME_STATE_PLAY);
		return true;
	}
	return false;
}

void Game_OnMouseMove(int x, int y, float delta)
{
	mouseXpos = x;
	mouseYpos = y;

	if (!Game.inventoryOpen && !Game.activeMenu)
	{
		Game.input.command.rotDelta = delta * Game.options.mouseSens;
	}

	if (Game.activeMenu)
	{
		int _x, _y;
		Uint32 button = SDL_GetMouseState(&_x, &_y);
		Gui_HandleMouseMove(Game.activeMenu, SDL_BUTTON(button) == SDL_BUTTON_LEFT, x, y);
	}
}

void Game_OnScroll(int direction)
{
	if (direction <= 0 && !Game.inventoryOpen)
	{
		World_Players[0].handSlot++;
		if (World_Players[0].handSlot > INV_HAND_SLOT + 4)
			World_Players[0].handSlot = INV_HAND_SLOT;
	}
	else if (direction > 0 && !Game.inventoryOpen)
	{
		World_Players[0].handSlot--;
		if (World_Players[0].handSlot < INV_HAND_SLOT)
			World_Players[0].handSlot = INV_HAND_SLOT + 4;
	}
}
