// for finding memory leaks in debug mode with Visual Studio 
#if defined _DEBUG && defined _MSC_VER
#include <crtdbg.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <math.h>
#include <sys/stat.h>
#ifdef _WIN32
#define WIN32_MEAN_AND_LEAN
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <SDL2/SDL.h>
#include "config.h"
#include "palette.h"
#include "gui.h"

#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))

#ifdef _MSC_VER
#pragma warning(disable: 4204)
#pragma warning(disable: 4221)
#endif

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

// globals
volatile bool programRunning, redrawScreen, allowSigterm = true;
uint32_t *frameBuffer;
keyb_t keyb;
mouse_t mouse;

static bool setupVideo(void);
static void readMouseXY(void);
static void handleInput(void);

#ifdef __APPLE__
static void osxSetDirToProgramDirFromArgs(char **argv);
#endif

int main(int argc, char *argv[])
{
	// for finding memory leaks in debug mode with Visual Studio
#if defined _DEBUG && defined _MSC_VER
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	(void)argc;
	(void)argv;

#ifdef __APPLE__
	osxSetDirToProgramDirFromArgs(argv);
#endif

	if (!setupVideo())
	{
		SDL_Quit();
		return 0;
	}

	setupGUI();
	redrawScreen = true;

	programRunning = true;
	while (programRunning)
	{
		readMouseXY();
		handleInput();

		if (redrawScreen)
		{
			redrawScreen = false;

			// redraw screen
			SDL_UpdateTexture(texture, NULL, frameBuffer, SCREEN_W * sizeof (int32_t));
			SDL_RenderClear(renderer);
			SDL_RenderCopy(renderer, texture, NULL, NULL);
			SDL_RenderPresent(renderer);
		}

		SDL_Delay((1000 / 60) + 1); // ~60Hz
	}

	if (loadedFile != NULL) free(loadedFile);
	free(frameBuffer);
	SDL_Quit();

	return 0;
}

static void readMouseXY(void)
{
	int32_t windowX, windowY, mx, my;

	SDL_GetGlobalMouseState(&mx, &my);
	SDL_GetWindowPosition(window, &windowX, &windowY);

	mx -= windowX;
	my -= windowY;

	mouse.x = (int32_t)(mx * mouse.fMouseXMul);
	mouse.y = (int32_t)(my * mouse.fMouseYMul);
}

static void showAskToSaveDialog(void)
{
	int32_t whichButtonPressed;

	const SDL_MessageBoxButtonData buttons[2] =
	{
		{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, "No"  },
		{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "Yes" },
	};

	const SDL_MessageBoxData messageBoxData =
	{
		SDL_MESSAGEBOX_WARNING,
		window,
		"Warning",
		"Colors are unsaved. Save before quitting?",
		SDL_arraysize(buttons),
		buttons,
		NULL
	};

	if (SDL_ShowMessageBox(&messageBoxData, &whichButtonPressed) < 0)
	{
		allowSigterm = true;
		programRunning = false;
		return;
	}

	if (whichButtonPressed == 1)
		savePalette(0);

	programRunning = false;
}

void showErrorMsgBox(const char *fmt, ...)
{
	char strBuf[1024];
	va_list args;

	// format the text string
	va_start(args, fmt);
	vsnprintf(strBuf, sizeof (strBuf), fmt, args);
	va_end(args);

	// window can be NULL here, no problem...
	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", strBuf, NULL);
}

static void handleInput(void)
{
	SDL_Event inputEvent;

	while (SDL_PollEvent(&inputEvent))
	{
		if (inputEvent.type == SDL_QUIT && allowSigterm)
		{
			if (!configIsSaved)
			{
				allowSigterm = false;
				showAskToSaveDialog();
			}
			else
			{
				programRunning = false;
			}
		}
		else if (inputEvent.type == SDL_KEYDOWN)
		{
			keyDownHandler(inputEvent.key.keysym.sym);
		}
		else if (inputEvent.type == SDL_MOUSEBUTTONUP)
		{
			if (inputEvent.button.button == SDL_BUTTON_LEFT)
				mouseButtonUpHandler();
		}
		else if (inputEvent.type == SDL_MOUSEBUTTONDOWN)
		{
			if (inputEvent.button.button == SDL_BUTTON_LEFT)
				mouseButtonDownHandler();
		}
	}

	handleSlidersHeldDown();
	handleRainbowHeldDown();
	handleRainbowUpDownButtons();
}

// macOS/OS X specific routines
#ifdef __APPLE__
static void osxSetDirToProgramDirFromArgs(char **argv)
{
	char *tmpPath;
	int32_t i, tmpPathLen;

	/* OS X/macOS: hackish way of setting the current working directory to the place where we double clicked
	** on the icon (for protracker.ini loading) */

	// if we launched from the terminal, argv[0][0] would be '.'
	if (argv[0] != NULL && argv[0][0] == '/') // don't do the hack if we launched from the terminal
	{
		tmpPath = strdup(argv[0]);
		if (tmpPath != NULL)
		{
			// cut off program filename
			tmpPathLen = strlen(tmpPath);
			for (i = tmpPathLen-1; i >= 0; i--)
			{
				if (tmpPath[i] == '/')
				{
					tmpPath[i] = '\0';
					break;
				}
			}

			chdir(tmpPath); // path to binary
			chdir("../../../"); // we should now be in the directory where the config can be.

			free(tmpPath);
		}
	}
}
#endif

static bool setupVideo(void)
{
	int32_t renderW, renderH;

	/* SDL 2.0.9 for Windows has a serious bug where you need to initialize the joystick subsystem
	** (even if you don't use it) or else weird things happen like random stutters, keyboard (rarely) being
	** reinitialized in Windows and what not.
	** Ref.: https://bugzilla.libsdl.org/show_bug.cgi?id=4391
	*/
#if defined _WIN32 && SDL_PATCHLEVEL == 9
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) != 0)
#else
	if (SDL_Init(SDL_INIT_VIDEO) != 0)
#endif
	{
		showErrorMsgBox("Couldn't initialize SDL: %s", SDL_GetError());
		return false;
	}

	window = SDL_CreateWindow("Palette editor for ProTracker 2 clone",
		SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_W * 2, SCREEN_H * 2, SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI);

	if (window == NULL)
	{
		showErrorMsgBox("Couldn't create an SDL2 window: %s", SDL_GetError());
		return false;
	}

	renderer = SDL_CreateRenderer(window, -1, 0);
	if (renderer == NULL)
	{
		showErrorMsgBox("Couldn't create an SDL2 renderer: %s", SDL_GetError());
		return false;
	}

	SDL_RenderSetLogicalSize(renderer, SCREEN_W, SCREEN_H);

#if SDL_PATCHLEVEL >= 5
	SDL_RenderSetIntegerScale(renderer, SDL_TRUE);
#endif

	SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);

	SDL_SetHint("SDL_RENDER_SCALE_QUALITY", "nearest");

	texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_W, SCREEN_H);
	if (texture == NULL)
	{
		showErrorMsgBox("Couldn't create an SDL2 texture: %s", SDL_GetError());
		return false;
	}

	SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE);

	// frame buffer used by SDL (for texture)
	frameBuffer = (uint32_t *)malloc(SCREEN_W * SCREEN_H * sizeof (int32_t));
	if (frameBuffer == NULL)
	{
		showErrorMsgBox("Out of memory!");
		return false;
	}

	SDL_GetWindowSize(window, &renderW, &renderH);
	if (renderW > 0) mouse.fMouseXMul = (float)SCREEN_W / renderW;
	if (renderH > 0) mouse.fMouseYMul = (float)SCREEN_H / renderH;

	return true;
}
