#include "oneshot.h"

/******************
 * HERE BE DRAGONS
 ******************/

#include "eventthread.h"
#include "debugwriter.h"
#include "bitmap.h"
#include "font.h"

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>

//OS-Specific code
#if defined _WIN32
	#define OS_W32
	#define WIN32_LEAN_AND_MEAN
	#define SECURITY_WIN32
	#include <windows.h>
	#include <mmsystem.h>
	#include <security.h>
	#include <shlobj.h>
	#include <SDL2/SDL_syswm.h>
#elif defined __APPLE__ || __linux__
	#include <stdlib.h>
	#include <unistd.h>
	#include <pwd.h>
	#include <dlfcn.h>

	#ifdef __APPLE__
		#define OS_OSX
	#else
		#define OS_LINUX

		class GtkWidget;

		typedef enum
		{
			GTK_MESSAGE_INFO,
			GTK_MESSAGE_WARNING,
			GTK_MESSAGE_QUESTION,
			GTK_MESSAGE_ERROR
		} GtkMessageType;

		typedef enum
		{
			GTK_BUTTONS_NONE,
			GTK_BUTTONS_OK,
			GTK_BUTTONS_CLOSE,
			GTK_BUTTONS_CANCEL,
			GTK_BUTTONS_YES_NO,
			GTK_BUTTONS_OK_CANCEL
		} GtkButtonsType;

		typedef enum
		{
			GTK_RESPONSE_NONE = -1,
			GTK_RESPONSE_REJECT = -2,
			GTK_RESPONSE_ACCEPT = -3,
			GTK_RESPONSE_DELETE_EVENT = -4,
			GTK_RESPONSE_OK = -5,
			GTK_RESPONSE_CANCEL = -6,
			GTK_RESPONSE_CLOSE = -7,
			GTK_RESPONSE_YES = -8,
			GTK_RESPONSE_NO = -9,
			GTK_RESPONSE_APPLY = -10,
			GTK_RESPONSE_HELP = -11
		} GtkResponseType;

		/**
		 * xdg_user_dir_lookup_with_fallback:
		 * @type: a string specifying the type of directory
		 * @fallback: value to use if the directory isn't specified by the user
		 * @returns: a newly allocated absolute pathname
		 *
		 * Looks up a XDG user directory of the specified type.
		 * Example of types are "DESKTOP" and "DOWNLOAD".
		 *
		 * In case the user hasn't specified any directory for the specified
		 * type the value returned is @fallback.
		 *
		 * The return value is newly allocated and must be freed with
		 * free(). The return value is never NULL if @fallback != NULL, unless
		 * out of memory.
		 **/
		static char *
		xdg_user_dir_lookup_with_fallback (const char *type, const char *fallback)
		{
		  FILE *file;
		  char *home_dir, *config_home, *config_file;
		  char buffer[512];
		  char *user_dir;
		  char *p, *d;
		  int len;
		  int relative;

		  home_dir = getenv ("HOME");

		  if (home_dir == NULL)
		    goto error;

		  config_home = getenv ("XDG_CONFIG_HOME");
		  if (config_home == NULL || config_home[0] == 0)
		    {
		      config_file = (char*) malloc (strlen (home_dir) + strlen ("/.config/user-dirs.dirs") + 1);
		      if (config_file == NULL)
		        goto error;

		      strcpy (config_file, home_dir);
		      strcat (config_file, "/.config/user-dirs.dirs");
		    }
		  else
		    {
		      config_file = (char*) malloc (strlen (config_home) + strlen ("/user-dirs.dirs") + 1);
		      if (config_file == NULL)
		        goto error;

		      strcpy (config_file, config_home);
		      strcat (config_file, "/user-dirs.dirs");
		    }

		  file = fopen (config_file, "r");
		  free (config_file);
		  if (file == NULL)
		    goto error;

		  user_dir = NULL;
		  while (fgets (buffer, sizeof (buffer), file))
		    {
		      /* Remove newline at end */
		      len = strlen (buffer);
		      if (len > 0 && buffer[len-1] == '\n')
			buffer[len-1] = 0;

		      p = buffer;
		      while (*p == ' ' || *p == '\t')
			p++;

		      if (strncmp (p, "XDG_", 4) != 0)
			continue;
		      p += 4;
		      if (strncmp (p, type, strlen (type)) != 0)
			continue;
		      p += strlen (type);
		      if (strncmp (p, "_DIR", 4) != 0)
			continue;
		      p += 4;

		      while (*p == ' ' || *p == '\t')
			p++;

		      if (*p != '=')
			continue;
		      p++;

		      while (*p == ' ' || *p == '\t')
			p++;

		      if (*p != '"')
			continue;
		      p++;

		      relative = 0;
		      if (strncmp (p, "$HOME/", 6) == 0)
			{
			  p += 6;
			  relative = 1;
			}
		      else if (*p != '/')
			continue;

		      if (relative)
			{
			  user_dir = (char*) malloc (strlen (home_dir) + 1 + strlen (p) + 1);
		          if (user_dir == NULL)
		            goto error2;

			  strcpy (user_dir, home_dir);
			  strcat (user_dir, "/");
			}
		      else
			{
			  user_dir = (char*) malloc (strlen (p) + 1);
		          if (user_dir == NULL)
		            goto error2;

			  *user_dir = 0;
			}

		      d = user_dir + strlen (user_dir);
		      while (*p && *p != '"')
			{
			  if ((*p == '\\') && (*(p+1) != 0))
			    p++;
			  *d++ = *p++;
			}
		      *d = 0;
		    }
		error2:
		  fclose (file);

		  if (user_dir)
		    return user_dir;

		 error:
		  if (fallback)
		    return strdup (fallback);
		  return NULL;
		}
	#endif
#else
    #error "Operating system not detected."
#endif

#define DEF_SCREEN_W 640
#define DEF_SCREEN_H 480

struct OneshotPrivate
{
	//Main SDL window
	SDL_Window *window;

	//String data
	std::string lang;
	std::string userName;
	std::string savePath;
	std::string docsPath;
	std::string gamePath;
	std::string journal;

	//Dialog text
	std::string txtYes;
	std::string txtNo;

	bool exiting;
	bool allowExit;

	//Alpha texture data for portions of window obscured by screen edges
	int winX, winY;
	SDL_mutex *winMutex;
	bool winPosChanged;
	std::vector<uint8_t> obscuredMap;
	bool obscuredCleared;

#if defined OS_LINUX
	//GTK+
	void *libgtk;
	void (*gtk_init)(int *argc, char ***argv);
	GtkWidget *(*gtk_message_dialog_new)(void *parent, int flags, GtkMessageType type, GtkButtonsType buttons, const char *message_format, ...);
	void (*gtk_window_set_title)(GtkWidget *window, const char *title);
	GtkResponseType (*gtk_dialog_run)(GtkWidget *dialog);
	void (*gtk_widget_destroy)(GtkWidget *widget);
	void (*gtk_main_quit)();
	void (*gtk_main)();
	unsigned int (*gdk_threads_add_idle)(int (*function)(void *data), void *data);
#endif

	OneshotPrivate()
		: window(0),
	      winMutex(SDL_CreateMutex())
#if defined OS_LINUX
		  ,libgtk(0)
#endif
	{
	}

	~OneshotPrivate()
	{
		SDL_DestroyMutex(winMutex);
#ifdef OS_LINUX
		if (libgtk)
			dlclose(libgtk);
#endif
	}
};

//OS-SPECIFIC FUNCTIONS
#if defined OS_LINUX
struct linux_DialogData
{
	//Input
	OneshotPrivate *p;
	int type;
	const char *body;
	const char *title;

	//Output
	bool result;
};

static int linux_dialog(void *rawData)
{
	linux_DialogData *data = reinterpret_cast<linux_DialogData*>(rawData);
	OneshotPrivate *p = data->p;

	//Determine correct flags
	GtkMessageType gtktype;
	GtkButtonsType gtkbuttons = GTK_BUTTONS_OK;
	switch (data->type)
	{
	case Oneshot::MSG_INFO:
		gtktype = GTK_MESSAGE_INFO;
		break;
	case Oneshot::MSG_YESNO:
		gtktype = GTK_MESSAGE_QUESTION;
		gtkbuttons = GTK_BUTTONS_YES_NO;
		break;
	case Oneshot::MSG_WARN:
		gtktype = GTK_MESSAGE_WARNING;
		break;
	case Oneshot::MSG_ERR:
		gtktype = GTK_MESSAGE_ERROR;
		break;
	default:
		p->gtk_main_quit();
		return 0;
	}

	//Display dialog and get result
	GtkWidget *dialog = p->gtk_message_dialog_new(0, 0, gtktype, gtkbuttons, data->body);
	p->gtk_window_set_title(dialog, data->title);
	int result = p->gtk_dialog_run(dialog);
	p->gtk_widget_destroy(dialog);

	//Interpret result and return
	data->result = (result == GTK_RESPONSE_OK || result == GTK_RESPONSE_YES);
	p->gtk_main_quit();
	return 0;
}
#elif defined OS_W32
/* Convert WCHAR pointer to std::string */
static std::string w32_fromWide(const WCHAR *ustr)
{
	std::string result;
	int size = WideCharToMultiByte(CP_UTF8, 0, ustr, -1, 0, 0, 0, 0);
	if (size > 0)
	{
		CHAR *str = new CHAR[size];
		if (WideCharToMultiByte(CP_UTF8, 0, ustr, -1, str, size, 0, 0) == size)
			result = str;
		delete [] str;
	}
	return result;
}
/* Convert WCHAR pointer from const char* */
static WCHAR *w32_toWide(const char *str)
{
	if (str)
	{
		int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, 0, 0);
		if (size > 0)
		{
			WCHAR *ustr = new WCHAR[size];
			if (MultiByteToWideChar(CP_UTF8, 0, str, -1, ustr, size) == size)
				return ustr;
			delete [] ustr;
		}
	}

	//Return empty string
	WCHAR *ustr = new WCHAR[1];
	*ustr = 0;
	return ustr;
}
#endif

LTexture::LTexture() {
	//Initialize
	mTexture = NULL;
	mWidth = 0;
	mHeight = 0;
}

LTexture::~LTexture() {
	//Deallocate
	free();
}

bool LTexture::loadFromFile(std::string path, SDL_Renderer *gRenderer) {
	//Get rid of preexisting texture
	free();

	//The final texture
	SDL_Texture *newTexture = NULL;

	//Load image at specified path
	SDL_Surface *loadedSurface = IMG_Load(path.c_str());
	if (loadedSurface == NULL) {
		printf("Unable to load image %s! SDL_image Error: %s\n", path.c_str(), IMG_GetError());
	} else {
		//Color key image
		SDL_SetColorKey(loadedSurface, SDL_TRUE, SDL_MapRGB(loadedSurface->format, 0, 0xFF, 0xFF));

		//Create texture from surface pixels
		newTexture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);
		if (newTexture == NULL) {
			printf("Unable to create texture from %s! SDL Error: %s\n", path.c_str(), SDL_GetError());
		} else {
			//Get image dimensions
			mWidth = loadedSurface->w;
			mHeight = loadedSurface->h;
		}

		//Get rid of old loaded surface
		SDL_FreeSurface(loadedSurface);
	}

	//Return success
	mTexture = newTexture;
	return mTexture != NULL;
}

#ifdef _SDL_TTF_H
bool LTexture::loadFromRenderedText(std::string textureText, SDL_Color textColor, SDL_Renderer *gRenderer, TTF_Font *gFont) {
	//Get rid of preexisting texture
	free();

	//Render text surface
	SDL_Surface *textSurface = TTF_RenderText_Solid(gFont, textureText.c_str(), textColor);
	if (textSurface != NULL) {
		//Create texture from surface pixels
		mTexture = SDL_CreateTextureFromSurface(gRenderer, textSurface);
		if (mTexture == NULL) {
			printf("Unable to create texture from rendered text! SDL Error: %s\n", SDL_GetError());
		} else {
			//Get image dimensions
			mWidth = textSurface->w;
			mHeight = textSurface->h;
		}

		//Get rid of old surface
		SDL_FreeSurface(textSurface);
	} else {
		printf("Unable to render text surface! SDL_ttf Error: %s\n", TTF_GetError());
	}


	//Return success
	return mTexture != NULL;
}
#endif

void LTexture::free() {
	//Free texture if it exists
	if (mTexture != NULL) {
		SDL_DestroyTexture(mTexture);
		mTexture = NULL;
		mWidth = 0;
		mHeight = 0;
	}
}

void LTexture::setColor(Uint8 red, Uint8 green, Uint8 blue) {
	//Modulate texture rgb
	SDL_SetTextureColorMod(mTexture, red, green, blue);
}

void LTexture::setBlendMode(SDL_BlendMode blending) {
	//Set blending function
	SDL_SetTextureBlendMode(mTexture, blending);
}

void LTexture::setAlpha(Uint8 alpha) {
	//Modulate texture alpha
	SDL_SetTextureAlphaMod(mTexture, alpha);
}

void LTexture::render(SDL_Renderer *gRenderer, int x, int y, SDL_Rect *clip, double angle, SDL_Point *center,
                      SDL_RendererFlip flip) {
	//Set rendering space and render to screen
	SDL_Rect renderQuad = {x, y, mWidth, mHeight };

	//Set clip rendering dimensions
	if (clip != NULL) {
		renderQuad.w = clip->w;
		renderQuad.h = clip->h;
	}

	//Render to screen
	SDL_RenderCopyEx(gRenderer, mTexture, clip, &renderQuad, angle, center, flip);
}

int LTexture::getWidth() {
	return mWidth;
}

int LTexture::getHeight() {
	return mHeight;
}

Oneshot::Oneshot(RGSSThreadData &threadData) :
    threadData(threadData)
{
	p = new OneshotPrivate();
	p->window = threadData.window;
	p->savePath = threadData.config.commonDataPath.substr(0, threadData.config.commonDataPath.size() - 1);
	p->obscuredMap.resize(640 * 480, 255);
	obscuredDirty = true;
	p->winX = 0;
	p->winY = 0;
	p->winPosChanged = false;
	p->allowExit = true;
	p->exiting = false;

	/********************
	 * USERNAME/DOCS PATH
	 ********************/
#if defined OS_W32
	//Get language code
	WCHAR wlang[9];
	GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, wlang, sizeof(wlang) / sizeof(WCHAR));
	p->lang = w32_fromWide(wlang) + "_";
	GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, wlang, sizeof(wlang) / sizeof(WCHAR));
	p->lang += w32_fromWide(wlang);

	//Get user's name
	ULONG size = 0;
	GetUserNameEx(NameDisplay, 0, &size);
	if (GetLastError() == ERROR_MORE_DATA)
	{
		//Get their full (display) name
		WCHAR *name = new WCHAR[size];
		GetUserNameEx(NameDisplay, name, &size);
		p->userName = w32_fromWide(name);
		delete [] name;
	}
	else
	{
		//Get their login name
		DWORD size2 = 0;
		GetUserName(0, &size2);
		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
			WCHAR *name = new WCHAR[size2];
			GetUserName(name, &size2);
			p->userName = w32_fromWide(name);
			delete [] name;
		}
	}

	//Get documents path
	WCHAR path[MAX_PATH];
	SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, path);
	p->docsPath = w32_fromWide(path);
	p->gamePath = p->docsPath+"\\My Games";
	p->journal = "_______.exe";
#else
	//Get language code
	const char *lc_all = getenv("LC_ALL");
	const char *lang = getenv("LANG");
	const char *code = (lc_all ? lc_all : lang);
	if (code)
	{
		//find first dot, copy language code
		int end = 0;
		for (; code[end] && code[end] != '.'; ++end) {}
		p->lang = std::string(code, end);
	}
	else
		p->lang = "en";

	//Get user's name
	#ifdef OS_OSX
		struct passwd *pwd = getpwuid(geteuid());
	#elif defined OS_LINUX
		struct passwd *pwd = getpwuid(getuid());
	#endif
	if (pwd)
	{
        if (pwd->pw_gecos && pwd->pw_gecos[0] && pwd->pw_gecos[0] != ',')
        {
			//Get the user's full name
			int comma = 0;
			for (; pwd->pw_gecos[comma] && pwd->pw_gecos[comma] != ','; ++comma) {}
			p->userName = std::string(pwd->pw_gecos, comma);
		}
		else
			p->userName = pwd->pw_name;
	}

	//Get documents path
	std::string path = std::string(getenv("HOME")) + std::string("/Documents"); //xdg_user_dir_lookup_with_fallback("Documents", getenv("HOME"));
	p->docsPath = path.c_str();
	p->gamePath = path.c_str();
	#ifdef OS_OSX
		p->journal = "_______.app/Contents/MacOS/_______";
	#elif defined OS_LINUX
		p->journal = "_______";
	#endif
#endif

	/**********
	 * MSGBOX
	 **********/
#ifdef OS_LINUX
#define LOAD_FUNC(name) *reinterpret_cast<void**>(&p->name) = dlsym(p->libgtk, #name)
	//Attempt to link to gtk (prefer gtk2 over gtk3 until I can figure that message box icon out)
	static const char *gtklibs[] =
	{
		"libgtk-x11-2.0.so",
		"libgtk-3.0.so",
	};

	for (size_t i = 0; i < ARRAY_SIZE(gtklibs); ++i)
	{
		if (!(p->libgtk = dlopen("libgtk-x11-2.0.so", RTLD_NOW)))
			p->libgtk = dlopen("libgtk-3.0.so", RTLD_NOW);
		if (p->libgtk)
		{
			//Load functions
			LOAD_FUNC(gtk_init);
			LOAD_FUNC(gtk_message_dialog_new);
			LOAD_FUNC(gtk_window_set_title);
			LOAD_FUNC(gtk_dialog_run);
			LOAD_FUNC(gtk_widget_destroy);
			LOAD_FUNC(gtk_main_quit);
			LOAD_FUNC(gtk_main);
			LOAD_FUNC(gdk_threads_add_idle);

			if (p->gtk_init
					&& p->gtk_message_dialog_new
					&& p->gtk_window_set_title
					&& p->gtk_dialog_run
					&& p->gtk_widget_destroy
					&& p->gtk_main_quit
					&& p->gtk_main
					&& p->gdk_threads_add_idle)
			{
				p->gtk_init(0, 0);
			}
			else
			{
				dlclose(p->libgtk);
				p->libgtk = 0;
			}
		}
		if (p->libgtk)
			break;
	}
#undef LOAD_FUNC
#endif
	/********
	 * MISC
	 ********/
#if defined OS_W32
	//Get windows version
	OSVERSIONINFOW version;
	ZeroMemory(&version, sizeof(version));
	version.dwOSVersionInfoSize = sizeof(version);
	GetVersionEx(&version);
#endif
}

Oneshot::~Oneshot()
{
	delete p;
}

void Oneshot::update()
{
	if (p->winPosChanged)
	{
		p->winPosChanged = false;

		//Map of unobscured pixels in this frame
		static std::vector<bool> obscuredFrame;
		obscuredFrame.resize(p->obscuredMap.size());
		std::fill(obscuredFrame.begin(), obscuredFrame.end(), true);

		SDL_Rect screenRect;
		SDL_LockMutex(p->winMutex);
		screenRect.x = p->winX;
		screenRect.y = p->winY;
		SDL_UnlockMutex(p->winMutex);
		screenRect.w = 640;
		screenRect.h = 480;

		//Update obscured map and texture for window portion offscreen
		for (int i = 0, max = SDL_GetNumVideoDisplays(); i < max; ++i)
		{
			SDL_Rect bounds;
			SDL_GetDisplayBounds(i, &bounds);

			//Get intersection of window and the screen
			SDL_Rect intersect;
			if (!SDL_IntersectRect(&screenRect, &bounds, &intersect))
				continue;
			intersect.x -= screenRect.x;
			intersect.y -= screenRect.y;

			//If it's entirely within the bounds of the screen, we don't need to check out
			//any other monitors
			if (intersect.x == 0 && intersect.y == 0 && intersect.w == 640 && intersect.h == 480)
				return;

			for (int y = intersect.y; y < intersect.y + intersect.h; ++y)
			{
				int start = y * 640 + intersect.x;
				std::fill(obscuredFrame.begin() + start, obscuredFrame.begin() + (start + intersect.w), false);
			}
		}

		//Update the obscured map, and return prematurely if we don't have any changes
		//to make to the texture
		bool needsUpdate = false;
		bool cleared = true;
		for (size_t i = 0; i < obscuredFrame.size(); ++i)
		{
			if (obscuredFrame[i])
			{
				p->obscuredMap[i] = 0;
				needsUpdate = true;
			}
			if (p->obscuredMap[i] == 255)
				cleared = false;
		}
		p->obscuredCleared = cleared;
		if (!needsUpdate)
			return;

		//Flag as dirty
		obscuredDirty = true;
	}
}

const std::string &Oneshot::lang() const
{
	return p->lang;
}

const std::string &Oneshot::userName() const
{
	return p->userName;
}

const std::string &Oneshot::savePath() const
{
	return p->savePath;
}

const std::string &Oneshot::docsPath() const
{
	return p->docsPath;
}

const std::string &Oneshot::gamePath() const
{
	return p->gamePath;
}

const std::string &Oneshot::journal() const
{
	return p->journal;
}

const std::vector<uint8_t> &Oneshot::obscuredMap() const
{
	return p->obscuredMap;
}

bool Oneshot::obscuredCleared() const
{
	return p->obscuredCleared;
}

bool Oneshot::exiting() const
{
	return p->exiting;
}

bool Oneshot::allowExit() const
{
	return p->allowExit;
}

void Oneshot::setYesNo(const char *yes, const char *no)
{
	p->txtYes = yes;
	p->txtNo = no;
}

void Oneshot::setExiting(bool exiting)
{
	if (p->exiting != exiting) {
		p->exiting = exiting;
		if (exiting) {
			threadData.exiting.set();
		} else {
			threadData.exiting.clear();
		}
	}
}

void Oneshot::setAllowExit(bool allowExit)
{
	if (p->allowExit != allowExit) {
		p->allowExit = allowExit;
		if (allowExit) {
			threadData.allowExit.set();
		} else {
			threadData.allowExit.clear();
		}
	}
}

bool Oneshot::msgbox(int type, const char *body, const char *title)
{
	if (!title)
		title = "";
#if 0
	//Get native window handle
	SDL_SysWMinfo wminfo;
	SDL_version version;
	SDL_VERSION(&version);
	wminfo.version = version;
	SDL_GetWindowWMInfo(p->window, &wminfo);
	HWND hwnd = wminfo.info.win.window;

	//Construct flags
	UINT flags = 0;
	switch (type)
	{
	case MSG_INFO:
		flags = MB_ICONINFORMATION;
		break;
	case MSG_YESNO:
		flags = MB_ICONQUESTION | MB_YESNO;
		break;
	case MSG_WARN:
		flags = MB_ICONWARNING;
		break;
	case MSG_ERR:
		flags = MB_ICONERROR;
		break;
	}

	//Create message box
	WCHAR *wbody = w32_toWide(body);
	WCHAR *wtitle = w32_toWide(title);
	int result = MessageBoxW(N, wbody, wtitle, flags);
	delete [] title;
	delete [] body;

	//Interpret result
	return (result == IDOK || result == IDYES);
#else
	#if defined OS_LINUX
		if (p->libgtk)
		{
			linux_DialogData data = {p, type, body, title, 0};
			p->gdk_threads_add_idle(linux_dialog, &data);
			p->gtk_main();
			return data.result;
		}
	#endif

	//SDL message box

	//Button data
	static const SDL_MessageBoxButtonData buttonOk = {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "OK"};
	static const SDL_MessageBoxButtonData buttonsOk[] = {buttonOk};
	SDL_MessageBoxButtonData buttonYes = {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, p->txtYes.c_str()};
	SDL_MessageBoxButtonData buttonNo = {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, p->txtNo.c_str()};
	SDL_MessageBoxButtonData buttonsYesNo[] = {buttonNo, buttonYes};

	//Messagebox data
	SDL_MessageBoxData data;
	data.window = NULL;//p->window;
	data.colorScheme = 0;
	data.title = title;
	data.message = body;
#ifdef OS_W32
	DWORD sound;
#endif

	//Set type
	switch (type)
	{
	case MSG_INFO:
	case MSG_YESNO:
	default:
		data.flags = SDL_MESSAGEBOX_INFORMATION;
#ifdef OS_W32
		sound = SND_ALIAS_SYSTEMQUESTION;
#endif
		break;
	case MSG_WARN:
		data.flags = SDL_MESSAGEBOX_WARNING;
#ifdef OS_W32
		sound = SND_ALIAS_SYSTEMEXCLAMATION;
 #endif
		break;
	case MSG_ERR:
		data.flags = SDL_MESSAGEBOX_WARNING;
#ifdef OS_W32
		sound = SND_ALIAS_SYSTEMASTERISK;
#endif
		break;
	}

	//Set buttons
	switch (type)
	{
	case MSG_INFO:
	case MSG_WARN:
	case MSG_ERR:
	default:
		data.numbuttons = 1;
		data.buttons = buttonsOk;
		break;
	case MSG_YESNO:
		data.numbuttons = 2;
		data.buttons = buttonsYesNo;
		break;
	}

	//Show messagebox
#ifdef OS_W32
	PlaySoundW((LPCWSTR)sound, NULL, SND_ALIAS_ID | SND_ASYNC);
#endif
	int button;
	SDL_ShowMessageBox(&data, &button);
	return button ? true : false;
#endif
}

std::string Oneshot::textinput(const char* prompt, int char_limit, const char* fontName) {
	// SDL_Color textColor = {0xFF, 0xFF, 0xFF, 0xFF}; //Set text color as black
	// SDL_Renderer *gRenderer = SDL_CreateRenderer(threadData.window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	// // SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
	// LTexture gPromptTextTexture;
	// LTexture gInputTextTexture;

	// //Open the font
	// TTF_Font *gFont = TTF_OpenFont("VL-Gothic-Regular.ttf", 18); // XXX Implement font changing
	// if (gFont == NULL) {
	// 	printf("Failed to load lazy font! SDL_ttf Error: %s\n", TTF_GetError());
	// 	// success = false;
	// } else {
	// 	//Render the prompt
	// 	if (!gPromptTextTexture.loadFromRenderedText(prompt, textColor, gRenderer, gFont)) {
	// 		printf("Failed to render prompt text!\n");
	// 		// success = false;
	// 	}
	// }

	// gInputTextTexture.loadFromRenderedText(threadData.inputText.c_str(), textColor, gRenderer, gFont);
	
	std::vector<std::string> *fontNames = new std::vector<std::string>();
	fontNames->push_back(fontName);
	fontNames->push_back("VL Gothic");
	Font *font = new Font(fontNames, 18);

	Bitmap *promptBmp = new Bitmap(DEF_SCREEN_W, DEF_SCREEN_H);
	promptBmp->setInitFont(font);
	promptBmp->drawText(0, 0, DEF_SCREEN_W, DEF_SCREEN_H, prompt, 1);

	Bitmap *inputBmp = new Bitmap(DEF_SCREEN_W, DEF_SCREEN_H);
	inputBmp->setInitFont(font);
	inputBmp->drawText(0, 0, DEF_SCREEN_W, DEF_SCREEN_H, "", 1);

	std::string inputTextPrev = std::string("");
	threadData.acceptingTextInput.set();
	threadData.inputTextLimit = char_limit;
	threadData.inputText.clear();
	SDL_StartTextInput();

	//Main loop
	while (threadData.acceptingTextInput) {
		if (inputTextPrev != threadData.inputText) {
			inputBmp->clear();
			inputBmp->drawText(DEF_SCREEN_W / 2, DEF_SCREEN_H / 2, DEF_SCREEN_W, DEF_SCREEN_H, threadData.inputText.c_str(), 1);
			inputTextPrev = threadData.inputText;
			// if (threadData.inputText.length() > 0) gInputTextTexture.loadFromRenderedText(threadData.inputText.c_str(), textColor, gRenderer, gFont);
			// else gInputTextTexture.loadFromRenderedText(" ", textColor, gRenderer, gFont);
		}

		// //Clear screen
		// // SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
		// SDL_RenderClear(gRenderer);

		// //Render text textures
		// gPromptTextTexture.render(gRenderer, (DEF_SCREEN_W - gPromptTextTexture.getWidth()) / 2,
		//                           (DEF_SCREEN_H / 2) - gPromptTextTexture.getHeight());
		// gInputTextTexture.render(gRenderer, (DEF_SCREEN_W - gInputTextTexture.getWidth()) / 2,
		//                          (DEF_SCREEN_H / 2));

		// //Update screen
		// SDL_RenderPresent(gRenderer);
	}

	//Disable text input
	SDL_StopTextInput();

	// //Free loaded images
	// gPromptTextTexture.free();
	// gInputTextTexture.free();

	// //Free global font
	// TTF_CloseFont(gFont);
	// gFont = NULL;

	// //Destroy renderer
	// SDL_DestroyRenderer(gRenderer);
	// gRenderer = NULL;
	// delete promptBmp;
	// delete inputBmp;

	return threadData.inputText;
}

void Oneshot::setWindowPos(int x, int y)
{
	SDL_LockMutex(p->winMutex);
	p->winX = x;
	p->winY = y;
	p->winPosChanged = true;
	SDL_UnlockMutex(p->winMutex);
}

void Oneshot::resetObscured()
{
	std::fill(p->obscuredMap.begin(), p->obscuredMap.end(), 255);
	obscuredDirty = true;
	p->obscuredCleared = false;
}
