#include <SDL.h>
#include <string.h>

#include "Bitmap.h"
#include "Console.h" // TODO: Make the game do this instead
#include "Game.h"
#include "Log.h"
#include "Main.h"
#include "Menus.h"
#include "Render.h"
#include "Version.h"

/* Any controller axis input value less than this number will be interpreted as 0 */
/* Controller input events when you let go of a stick almost never fully reach 0, so this prevents the player from drifting. */
#define INPUT_AXIS_DEADZONE 0.12f
#define INPUT_TRIGGER_THRESHOLD 25000

static SDL_Window *window;
static SDL_Renderer *renderer;
static SDL_Texture *texture;
static SDL_Event ev;

static int width, height; // Width and height of the screen without scaling
static int scale;

extern Bmp_t screen; // Main.c

// Controller stuff
static SDL_GameController *controller = NULL;
static float rotAxis = 0.0f;
static bool triggerDown = false;

void Vid_Init(int w, int h, int s, int fullscreen)
{
	int fullscreenMode = 0;
	int i;

	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0)
	{
		Msg_Fatal("Failed to initialize SDL: %s", SDL_GetError());
		return;
	}

	width = w;
	height = h;
	scale = s;
	if (fullscreen > 0)
		fullscreenMode = fullscreen == 2 ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;

	Msg_Info("Vid_Init: %dx%d (%dx%d*%d)", width * scale, height * scale, w, h, scale);

	/* Set up the window, renderer and framebuffer */
	window = SDL_CreateWindow("Rovgend's Labyrinth v" RVG_VERSION_STRING,
		SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
		width * scale, height * scale, fullscreenMode);
	if (window == NULL)
	{
		Msg_Fatal("Failed to create window: %s", SDL_GetError());
		return;
	}

	renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);
	if (renderer == NULL)
	{
		Msg_Fatal("Failed to initialize renderer: %s", SDL_GetError());
		return;
	}

	texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, w, h);
	if (texture == NULL)
	{
		Msg_Fatal("Failed to create framebuffer texture: %s", SDL_GetError());
		return;
	}

	for (i = 0; i < SDL_NumJoysticks(); i++)
	{
		if (SDL_IsGameController(i))
		{
			controller = SDL_GameControllerOpen(i);
			Msg_Info("Found controller: %s (#%d)", SDL_GameControllerName(controller), i);
			break; /* Use the first controller we find. */
		}
		Msg_Info("Joystick %s", SDL_GameControllerNameForIndex(i));
	}

	SDL_ShowWindow(window);
}

void Vid_Shutdown(void)
{
   	SDL_DestroyTexture(texture);
	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);
	SDL_Quit();
}

static enum InputType TranslateKey(int scancode)
{
	switch (scancode)
	{
	case SDL_SCANCODE_W: return INPUT_KEY_FORWARD;
	case SDL_SCANCODE_S: return INPUT_KEY_BACKWARD;
	case SDL_SCANCODE_A: return INPUT_KEY_STRAFELEFT;
	case SDL_SCANCODE_D: return INPUT_KEY_STRAFERIGHT;
	case SDL_SCANCODE_LEFT: return (Game.activeMenu == NULL) ? INPUT_KEY_TURNLEFT : INPUT_KEY_MENULEFT;
	case SDL_SCANCODE_RIGHT: return (Game.activeMenu == NULL) ? INPUT_KEY_TURNRIGHT : INPUT_KEY_MENURIGHT;
	case SDL_SCANCODE_UP: return INPUT_KEY_MENUUP;
	case SDL_SCANCODE_DOWN: return INPUT_KEY_MENUDOWN;
	case SDL_SCANCODE_LCTRL:
	case SDL_SCANCODE_RCTRL:
		return INPUT_KEY_ATTACK;
	case SDL_SCANCODE_E: return INPUT_KEY_USE;
	case SDL_SCANCODE_Q: return INPUT_KEY_DROP;
	case SDL_SCANCODE_TAB: return INPUT_KEY_INVENTORY;
	case SDL_SCANCODE_F: return INPUT_KEY_MAP;
	case SDL_SCANCODE_GRAVE: return INPUT_KEY_CONSOLE;
	case SDL_SCANCODE_ESCAPE: return (Game.activeMenu == NULL) ? INPUT_KEY_PAUSE : INPUT_KEY_MENUCLOSE;
	case SDL_SCANCODE_RETURN: return INPUT_KEY_MENUACCEPT;
	}
	if (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_5)
		return INPUT_KEY_HOTBAR1 + scancode - SDL_SCANCODE_1;
	return INPUT_KEY_NULL;
}

static enum InputType TranslateButton(SDL_GameControllerButton button)
{
	switch (button)
	{
	case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
		return INPUT_KEY_HOTBARPREV;
	case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
		return INPUT_KEY_HOTBARNEXT;
	case SDL_CONTROLLER_BUTTON_A:
		return (Game.activeMenu == NULL) ? INPUT_KEY_USE : INPUT_KEY_MENUACCEPT;
	case SDL_CONTROLLER_BUTTON_B:
		return (Game.activeMenu == NULL) ? INPUT_KEY_DROP : INPUT_KEY_MENUCLOSE;
	case SDL_CONTROLLER_BUTTON_X:
		/* Don't continue moving when the inventory is opened */
		Game.input.command.impulseForward = 0.0f;
		Game.input.command.impulseLateral = 0.0f;
		return INPUT_KEY_INVENTORY;
	case SDL_CONTROLLER_BUTTON_Y:
		return INPUT_KEY_MAP;
	case SDL_CONTROLLER_BUTTON_START:
		return INPUT_KEY_PAUSE;
	case SDL_CONTROLLER_BUTTON_DPAD_UP:
		return INPUT_KEY_MENUUP;
	case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
		return INPUT_KEY_MENUDOWN;
	case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
		return INPUT_KEY_MENULEFT;
	case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
		return INPUT_KEY_MENURIGHT;
	default:
		return INPUT_KEY_NULL;
	}
}

void Vid_DoEvent(void)
{
	switch (ev.type)
	{
	case SDL_QUIT:
		Main_QuitGame();
		break;
	case SDL_KEYDOWN:
		Game.input.mode = INPUTMODE_KEYBOARDMOUSE;
		// if (ev.key.keysym.sym == SDLK_F11)
		//{
		//	Main_ToggleFullscreen();
		// }
		if (Game.consoleOpen)
		{
			Console_HandleKey(ev.key.keysym.sym);
			break;
		}
		Game_OnKey(TranslateKey(ev.key.keysym.scancode), 1);
		break;
	case SDL_KEYUP:
		Game.input.mode = INPUTMODE_KEYBOARDMOUSE;
		Game_OnKey(TranslateKey(ev.key.keysym.scancode), 0);
		break;
	case SDL_MOUSEBUTTONDOWN:
		Game.input.mode = INPUTMODE_KEYBOARDMOUSE;
		if (!Game_OnMouseClick(ev.button.x / scale, ev.button.y / scale))
			Game_OnKey(INPUT_KEY_ATTACK, 1);
		break;
	case SDL_MOUSEBUTTONUP:
		Game.input.mode = INPUTMODE_KEYBOARDMOUSE;
		Game_OnKey(INPUT_KEY_ATTACK, 0);
		break;
	case SDL_MOUSEMOTION:
		Game.input.mode = INPUTMODE_KEYBOARDMOUSE;
		Game_OnMouseMove(ev.motion.x / scale, ev.motion.y / scale, (float)ev.motion.xrel);
		if (SDL_GetRelativeMouseMode())
			SDL_WarpMouseInWindow(window, (width * scale) / 2, (height * scale) / 2);
		break;
	case SDL_TEXTINPUT:
		if (Game.consoleOpen)
		{
			Console_HandleTyping(ev.text.text);
			break;
		}
		break;
	case SDL_MOUSEWHEEL:
		if (ev.wheel.y > 0)
		{
			Game_OnScroll(1);
		}
		else if (ev.wheel.y < 0)
		{
			Game_OnScroll(0);
		}
		break;
	case SDL_CONTROLLERBUTTONDOWN:
		Game.input.mode = INPUTMODE_CONTROLLER;
		Game_OnKey(TranslateButton(ev.cbutton.button), 1);
		break;
	case SDL_CONTROLLERBUTTONUP:
		Game.input.mode = INPUTMODE_CONTROLLER;
		Game_OnKey(TranslateButton(ev.cbutton.button), 0);
		break;
	case SDL_CONTROLLERAXISMOTION:
	{
		Game.input.mode = INPUTMODE_CONTROLLER;
		float valueNorm = (float)ev.caxis.value / (float)INT16_MAX;
		if (Math_Abs(valueNorm) < INPUT_AXIS_DEADZONE)
			valueNorm = 0.0f;
		switch (ev.caxis.axis)
		{
		case SDL_CONTROLLER_AXIS_LEFTX:
			if (Game.inventoryOpen)
				Inv_HandleControllerAxis(0, valueNorm);
			else
				Game.input.command.impulseLateral = -valueNorm;
			break;
		case SDL_CONTROLLER_AXIS_LEFTY:
			if (Game.inventoryOpen)
				Inv_HandleControllerAxis(1, valueNorm);
			else
				Game.input.command.impulseForward = -valueNorm;
			break;
		case SDL_CONTROLLER_AXIS_RIGHTX:
			rotAxis = valueNorm * 8.0f;
			break;
		default:
			break;
		}
		break;
	}
	}
}

void Vid_HandleEvents(void)
{
	while (SDL_PollEvent(&ev))
		Vid_DoEvent();

	Sint16 triggerValue = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
	if (triggerDown)
	{
		if (triggerValue < INPUT_TRIGGER_THRESHOLD)
		{
			Game_OnKey(INPUT_KEY_ATTACK, false);
			triggerDown = false;
		}
	}
	else
	{
		if (triggerValue > INPUT_TRIGGER_THRESHOLD)
		{
			Game_OnKey(INPUT_KEY_ATTACK, true);
			triggerDown = true;
		}
	}
}

void Vid_DrawFrame(void)
{
	void *texpixels;
	int pitch, row;
	
	// This needs to happen after the game tick, so it ends up here.
	Game.input.command.rotDelta = rotAxis;

	SDL_LockTexture(texture, NULL, &texpixels, &pitch);
	for (row = 0; row < screen.h; row++)
		SDL_memcpy4((char*)texpixels + row * pitch, screen.pixels + row * screen.w, screen.w);
	SDL_UnlockTexture(texture);

	SDL_RenderClear(renderer);
	SDL_RenderCopy(renderer, texture, NULL, NULL);
	SDL_RenderPresent(renderer);

	SDL_UpdateWindowSurface(window);
}

int Vid_ScaleResolution(int w, int h)
{
	return 2;
}

void Vid_SetDisplayMode(int newWidth, int newHeight, int fullscreenMode)
{
	int currentFullscreenMode;

	Uint32 flags = SDL_GetWindowFlags(window);
	if (flags & SDL_WINDOW_FULLSCREEN)
		currentFullscreenMode = 2;
	else if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP)
		currentFullscreenMode = 1;
	else
		currentFullscreenMode = 0;

	if (fullscreenMode == 0)
	{
		SDL_SetWindowFullscreen(window, 0);
		SDL_SetWindowSize(window, newWidth, newHeight);
		SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
	}
	else
	{
		SDL_DisplayMode desired, displayMode;

		desired.w = newWidth;
		desired.h = newHeight;
		desired.refresh_rate = 0;
		desired.format = SDL_PIXELFORMAT_ARGB8888;
		desired.driverdata = 0;

		SDL_SetWindowFullscreen(window, fullscreenMode == 2 ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP);
		SDL_GetClosestDisplayMode(SDL_GetWindowDisplayIndex(window), &desired, &displayMode);
		SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(window), &displayMode);
	}

	scale = Vid_ScaleResolution(newWidth, newHeight);

	/* Re-create framebuffer if the resolution has changed */
	if ((newWidth / scale) != width || (newHeight / scale) != height)
	{
		width = newWidth / scale;
		height = newHeight / scale;

		Bmp_Resize(&screen, width, height);
		Gfx_Resize(width, height);
		RelayoutMenus(&screen);

		SDL_DestroyTexture(texture);
		texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
		if (texture == NULL)
		{
			Msg_Fatal("Failed to create framebuffer texture: %s", SDL_GetError());
		}
	}
}

void Vid_SetMouseLocked(int locked)
{
	SDL_SetRelativeMouseMode(locked ? SDL_TRUE : SDL_FALSE);
}
