/*
 * Copyright 2010-2016 OpenXcom Developers.
 *
 * This file is part of OpenXcom.
 *
 * OpenXcom is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * OpenXcom is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OpenXcom.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "CrossPlatform.h"
#include <exception>
#include <algorithm>
#include <sstream>
#include <string>
#include <stdint.h>
#include <time.h>
#include <signal.h>
#include <sys/stat.h>
#include "../dirent.h"
#include "Logger.h"
#include "Exception.h"
#include "Options.h"
#include "Unicode.h"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <shellapi.h>
#ifndef __NO_DBGHELP
#include <dbghelp.h>
#endif
#ifdef __MINGW32__
#include <cxxabi.h>
#endif
#define EXCEPTION_CODE_CXX 0xe06d7363
#ifndef __GNUC__
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "shlwapi.lib")
#ifndef __NO_DBGHELP
#pragma comment(lib, "dbghelp.lib")
#endif
#endif
#else
#include <iostream>
#include <fstream>
#include <locale>
#include <SDL_image.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
#include <pwd.h>
#include <execinfo.h>
#include <cxxabi.h>
#include <dlfcn.h>
#include "Unicode.h"
#endif
#include <SDL.h>
#include <SDL_syswm.h>
#ifdef __HAIKU__
#include <FindDirectory.h>
#include <StorageDefs.h>
#endif

namespace OpenXcom
{
namespace CrossPlatform
{
	std::string errorDlg;

/**
 * Determines the available Linux error dialogs.
 */
void getErrorDialog()
{
#ifndef _WIN32
	if (system(NULL))
	{
		if (getenv("KDE_SESSION_UID") && system("which kdialog > /dev/null 2>&1") == 0)
			errorDlg = "kdialog --error ";
		else if (system("which zenity > /dev/null 2>&1") == 0)
			errorDlg = "zenity --no-wrap --error --text=";
		else if (system("which kdialog > /dev/null 2>&1") == 0)
			errorDlg = "kdialog --error ";
		else if (system("which gdialog > /dev/null 2>&1") == 0)
			errorDlg = "gdialog --msgbox ";
		else if (system("which xdialog > /dev/null 2>&1") == 0)
			errorDlg = "xdialog --msgbox ";
	}
#endif
}

/**
 * Displays a message box with an error message.
 * @param error Error message.
 */
void showError(const std::string &error)
{
#ifdef _WIN32
	MessageBoxA(NULL, error.c_str(), "OpenXcom Error", MB_ICONERROR | MB_OK);
#else
	if (errorDlg.empty())
	{
		std::cerr << error << std::endl;
	}
	else
	{
		std::string nError = '"' + error + '"';
		Unicode::replace(nError, "\n", "\\n");
		std::string cmd = errorDlg + nError;
		if (system(cmd.c_str()) != 0)
			std::cerr << error << std::endl;
	}
#endif
	Log(LOG_FATAL) << error;
}

#ifndef _WIN32
/**
 * Gets the user's home folder according to the system.
 * @return Absolute path to home folder.
 */
static char const *getHome()
{
	char const *home = getenv("HOME");
	if (!home)
	{
		struct passwd *const pwd = getpwuid(getuid());
		home = pwd->pw_dir;
	}
	return home;
}
#endif

/**
 * Builds a list of predefined paths for the Data folder
 * according to the running system.
 * @return List of data paths.
 */
std::vector<std::string> findDataFolders()
{
	std::vector<std::string> list;
#ifdef __MORPHOS__
	list.push_back("PROGDIR:");
	return list;
#endif

#ifdef _WIN32
	char path[MAX_PATH];

	// Get Documents folder
	if (SHGetSpecialFolderPathA(NULL, path, CSIDL_PERSONAL, FALSE))
	{
		PathAppendA(path, "OpenXcom\\");
		list.push_back(path);
	}
#ifdef DATADIR
	snprintf(path, MAX_PATH, "%s\\", DATADIR);
	list.push_back(path);
#endif
	// Get binary directory
	if (GetModuleFileNameA(NULL, path, MAX_PATH) != 0)
	{
		PathRemoveFileSpecA(path);
		list.push_back(path);
	}

	// Get working directory
	if (GetCurrentDirectoryA(MAX_PATH, path) != 0)
	{
		list.push_back(path);
	}
#else
	char const *home = getHome();
#ifdef __HAIKU__
	char data_path[B_PATH_NAME_LENGTH];
	find_directory(B_SYSTEM_SETTINGS_DIRECTORY, 0, true, data_path, sizeof(data_path)-strlen("/OpenXcom/"));
	strcat(data_path,"/OpenXcom/");
	list.push_back(data_path);
#endif
	char path[MAXPATHLEN];

	// Get user-specific data folders
	char const *const xdg_data_home = getenv("XDG_DATA_HOME");
	if (xdg_data_home && *xdg_data_home)
 	{
		snprintf(path, MAXPATHLEN, "%s/openxcom/", xdg_data_home);
 	}
 	else
 	{
#ifdef __APPLE__
		snprintf(path, MAXPATHLEN, "%s/Library/Application Support/OpenXcom/", home);
#else
		snprintf(path, MAXPATHLEN, "%s/.local/share/openxcom/", home);
#endif
 	}
 	list.push_back(path);
#ifdef DATADIR
	snprintf(path, MAXPATHLEN, "%s/" DATADIR);
	list.push_back(path);
#endif
	// Get global data folders
	char const *const xdg_data_dirs = getenv("XDG_DATA_DIRS");
	if (xdg_data_dirs && *xdg_data_dirs)
	{
		char xdg_data_dirs_copy[strlen(xdg_data_dirs)+1];
		strcpy(xdg_data_dirs_copy, xdg_data_dirs);
		char *dir = strtok(xdg_data_dirs_copy, ":");
		while (dir != 0)
		{
			snprintf(path, MAXPATHLEN, "%s/openxcom/", dir);
			list.push_back(path);
			dir = strtok(0, ":");
		}
	}
	else
	{
#ifdef __APPLE__
		list.push_back("/Users/Shared/OpenXcom/");
#else
		list.push_back("/usr/local/share/openxcom/");
		list.push_back("/usr/share/openxcom/");
#endif
	}
#ifdef INSTALLDIR
	snprintf(path, MAXPATHLEN, "%s", INSTALLDIR);
	list.push_back(path);
#endif

#ifdef __linux
	{
		char buffer[PATH_MAX];
		const ssize_t count = readlink("/proc/self/exe", buffer, PATH_MAX);
		// Get absolute executable path
		if (count != 0) {
			const std::string exe_path = std::string(buffer, count);
			// Get folder path
			const size_t dir_pos = exe_path.find_last_of("/");
			if (dir_pos != std::string::npos) {
				std::string dir = exe_path.substr(0, dir_pos);
				list.push_back( dir.append("/") );
				list.push_back( dir.append("/../share/openxcom/") ); // Relative FHS
			}
		}
	}
#endif
	// Get working directory
	list.push_back("./");
#endif

	return list;
}

/**
 * Builds a list of predefined paths for the User folder
 * according to the running system.
 * @return List of data paths.
 */
std::vector<std::string> findUserFolders()
{
	std::vector<std::string> list;

#ifdef __MORPHOS__
	list.push_back("PROGDIR:");
	return list;
#endif

#ifdef _WIN32
	char path[MAX_PATH];

	// Get Documents folder
	if (SHGetSpecialFolderPathA(NULL, path, CSIDL_PERSONAL, FALSE))
	{
		PathAppendA(path, "OpenXcom\\");
		list.push_back(path);
	}

	// Get binary directory
	if (GetModuleFileNameA(NULL, path, MAX_PATH) != 0)
	{
		PathRemoveFileSpecA(path);
		PathAppendA(path, "user\\");
		list.push_back(path);
	}

	// Get working directory
	if (GetCurrentDirectoryA(MAX_PATH, path) != 0)
	{
		PathAppendA(path, "user\\");
		list.push_back(path);
	}
#else
#ifdef __HAIKU__
	char user_path[B_PATH_NAME_LENGTH];
	find_directory(B_USER_SETTINGS_DIRECTORY, 0, true, user_path, sizeof(user_path)-strlen("/OpenXcom/"));
	strcat(user_path,"/OpenXcom/");
	list.push_back(user_path);
#endif
	char const *home = getHome();
	char path[MAXPATHLEN];

	// Get user folders
	if (char const *const xdg_data_home = getenv("XDG_DATA_HOME"))
 	{
		snprintf(path, MAXPATHLEN, "%s/openxcom/", xdg_data_home);
 	}
 	else
 	{
#ifdef __APPLE__
		snprintf(path, MAXPATHLEN, "%s/Library/Application Support/OpenXcom/", home);
#else
		snprintf(path, MAXPATHLEN, "%s/.local/share/openxcom/", home);
#endif
 	}
	list.push_back(path);

	// Get old-style folder
	snprintf(path, MAXPATHLEN, "%s/.openxcom/", home);
	list.push_back(path);

	// Get working directory
	list.push_back("./user/");
#endif

	return list;
}

/**
 * Finds the Config folder according to the running system.
 * @return Config path.
 */
std::string findConfigFolder()
{
#ifdef __MORPHOS__
	return "PROGDIR:";
#endif

#if defined(_WIN32) || defined(__APPLE__)
	return "";
#elif defined (__HAIKU__)
	char settings_path[B_PATH_NAME_LENGTH];
	find_directory(B_USER_SETTINGS_DIRECTORY, 0, true, settings_path, sizeof(settings_path)-strlen("/OpenXcom/"));
	strcat(settings_path,"/OpenXcom/");
	return settings_path;
#else
	char const *home = getHome();
	char path[MAXPATHLEN];
	// Get config folders
	if (char const *const xdg_config_home = getenv("XDG_CONFIG_HOME"))
	{
		snprintf(path, MAXPATHLEN, "%s/openxcom/", xdg_config_home);
		return path;
	}
	else
	{
		snprintf(path, MAXPATHLEN, "%s/.config/openxcom/", home);
		return path;
	}
#endif
}

std::string searchDataFile(const std::string &filename)
{
	// Correct folder separator
	std::string name = filename;
#ifdef _WIN32
	std::replace(name.begin(), name.end(), '/', PATH_SEPARATOR);
#endif

	// Check current data path
	std::string path = Options::getDataFolder() + name;
	if (fileExists(path))
	{
		return path;
	}

	// Check every other path
	for (std::vector<std::string>::const_iterator i = Options::getDataList().begin(); i != Options::getDataList().end(); ++i)
	{
		path = *i + name;
		if (fileExists(path))
		{
			Options::setDataFolder(*i);
			return path;
		}
	}

	// Give up
	return filename;
}

std::string searchDataFolder(const std::string &foldername, int size)
{
	// Correct folder separator
	std::string name = foldername;
#ifdef _WIN32
	std::replace(name.begin(), name.end(), '/', PATH_SEPARATOR);
#endif

	// Check current data path
	std::string path = Options::getDataFolder() + name;
	if (folderMinSize(path, size))
	{
		return path;
	}

	// Check every other path
	for (std::vector<std::string>::const_iterator i = Options::getDataList().begin(); i != Options::getDataList().end(); ++i)
	{
		path = *i + name;
		if (folderMinSize(path, size))
		{
			Options::setDataFolder(*i);
			return path;
		}
	}

	// Give up
	return foldername;
}

/**
 * Creates a folder at the specified path.
 * @note Only creates the last folder on the path.
 * @param path Full path.
 * @return Folder created or not.
 */
bool createFolder(const std::string &path)
{
#ifdef _WIN32
	int result = CreateDirectoryA(path.c_str(), 0);
	if (result == 0)
		return false;
	else
		return true;
#else
	mode_t process_mask = umask(0);
	int result = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
	umask(process_mask);
	if (result == 0)
		return true;
	else
		return false;
#endif
}

/**
 * Adds an ending slash to a path if necessary.
 * @param path Folder path.
 * @return Terminated path.
 */
std::string endPath(const std::string &path)
{
	if (!path.empty() && path.at(path.size()-1) != PATH_SEPARATOR)
		return path + PATH_SEPARATOR;
	return path;
}

/**
 * Gets the name of all the files
 * contained in a certain folder.
 * @param path Full path to folder.
 * @param ext Extension of files ("" if it doesn't matter).
 * @return Ordered list of all the files.
 */
std::vector<std::string> getFolderContents(const std::string &path, const std::string &ext)
{
	std::vector<std::string> files;

	DIR *dp = opendir(path.c_str());
	if (dp == 0)
	{
	#ifdef __MORPHOS__
		return files;
	#else
		std::string errorMessage("Failed to open directory: " + path);
		throw Exception(errorMessage);
	#endif
	}

	struct dirent *dirp;
	while ((dirp = readdir(dp)) != 0)
	{
		std::string file = dirp->d_name;

		if (!file.empty() && file[0] == '.')
		{
			//skip ".", "..", ".git", ".svn", ".bashrc", ".ssh" etc.
			continue;
		}
		if (!compareExt(file, ext))
		{
			continue;
		}

		files.push_back(file);
	}
	closedir(dp);
	std::sort(files.begin(), files.end());
	return files;
}

/**
 * Gets the contents of a folder and checks
 * if they meet a required minimum size.
 * @param path Full path to folder.
 * @param size Size of the folder (number of contents).
 * @return False if the folder doesn't exist or doesn't meet the size.
 */
bool folderMinSize(const std::string &path, int size)
{
	if (!folderExists(path))
	{
		return false;
	}
	if (size == 0)
	{
		return true;
	}

	DIR *dp = opendir(path.c_str());
	if (dp == 0)
	{
	#ifdef __MORPHOS__
		return files;
	#else
		std::string errorMessage("Failed to open directory: " + path);
		throw Exception(errorMessage);
	#endif
	}

	int num = 0;
	struct dirent *dirp;
	while ((dirp = readdir(dp)) != 0)
	{
		std::string file = dirp->d_name;

		if (!file.empty() && file[0] == '.')
		{
			//skip ".", "..", ".git", ".svn", ".bashrc", ".ssh" etc.
			continue;
		}

		num++;
		if (num >= size)
		{
			break;
		}
	}
	closedir(dp);
	return (num >= size);
}

/**
 * Checks if a certain path exists and is a folder.
 * @param path Full path to folder.
 * @return Does it exist?
 */
bool folderExists(const std::string &path)
{
#ifdef _WIN32
	return (PathIsDirectoryA(path.c_str()) != FALSE);
#elif __MORPHOS__
	BPTR l = Lock( path.c_str(), SHARED_LOCK );
	if ( l != NULL )
	{
		UnLock( l );
		return 1;
	}
	return 0;
#else
	struct stat info;
	return (stat(path.c_str(), &info) == 0 && S_ISDIR(info.st_mode));
#endif
}

/**
 * Checks if a certain path exists and is a file.
 * @param path Full path to file.
 * @return Does it exist?
 */
bool fileExists(const std::string &path)
{
#ifdef _WIN32
	return (PathFileExistsA(path.c_str()) != FALSE);
#elif __MORPHOS__
	BPTR l = Lock( path.c_str(), SHARED_LOCK );
	if ( l != NULL )
	{
		UnLock( l );
		return 1;
	}
	return 0;
#else
	struct stat info;
	return (stat(path.c_str(), &info) == 0 && S_ISREG(info.st_mode));
#endif
}

/**
 * Removes a file from the specified path.
 * @param path Full path to file.
 * @return True if the operation succeeded, False otherwise.
 */
bool deleteFile(const std::string &path)
{
#ifdef _WIN32
	return (DeleteFileA(path.c_str()) != 0);
#else
	return (remove(path.c_str()) == 0);
#endif
}

/**
 * Returns only the filename from a specified path.
 * @param path Full path.
 * @return Filename component.
 */
std::string baseFilename(const std::string &path)
{
	size_t sep = path.find_last_of("/\\");
	std::string filename;
	if (sep == std::string::npos)
	{
		filename = path;
	}
	else if (sep == path.size() - 1)
	{
		return baseFilename(path.substr(0, path.size() - 1));
	}
	else
	{
		filename = path.substr(sep + 1);
	}
	return filename;
}

/**
 * Replaces invalid filesystem characters with _.
 * @param filename Original filename.
 * @return Filename without invalid characters.
 */
std::string sanitizeFilename(const std::string &filename)
{
	std::string newFilename = filename;
	for (std::string::iterator i = newFilename.begin(); i != newFilename.end(); ++i)
	{
		if ((*i) == '<' ||
			(*i) == '>' ||
			(*i) == ':' ||
			(*i) == '"' ||
			(*i) == '/' ||
			(*i) == '?' ||
			(*i) == '\\')
		{
			*i = '_';
		}
	}
	return newFilename;
}

/**
 * Removes the extension from a filename. Only the
 * last dot is considered.
 * @param filename Original filename.
 * @return Filename without the extension.
 */
std::string noExt(const std::string &filename)
{
	size_t dot = filename.find_last_of('.');
	if (dot == std::string::npos)
	{
		return filename;
	}
	return filename.substr(0, dot);
}

/**
 * Returns the extension from a filename. Only the
 * last dot is considered.
 * @param filename Original filename.
 * @return Extension component, includes dot.
 */
std::string getExt(const std::string &filename)
{
	size_t dot = filename.find_last_of('.');
	if (dot == std::string::npos)
	{
		return "";
	}
	return filename.substr(dot);
}

/**
 * Compares the extension in a filename (case-insensitive).
 * @param filename Filename to compare.
 * @param extension Extension to compare to.
 * @return If the extensions match.
 */
bool compareExt(const std::string &filename, const std::string &extension)
{
	if (extension.empty())
		return true;
	int j = filename.length() - extension.length();
	if (j <= 0)
		return false;
	if (filename[j - 1] != '.')
		return false;
	for (size_t i = 0; i < extension.length(); ++i)
	{
		if (::tolower(filename[j + i]) != ::tolower(extension[i]))
			return false;
	}
	return true;
}

/**
 * Gets the current locale of the system in language-COUNTRY format.
 * @return Locale string.
 */
std::string getLocale()
{
#ifdef _WIN32
	char language[9], country[9];

	GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, language, 9);
	GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, country, 9);

	std::ostringstream locale;
	locale << language << "-" << country;
	return locale.str();
#else
	std::locale l;
	try
	{
		l = std::locale("");
	}
	catch (const std::runtime_error &)
	{
		return "x-";
	}
	std::string name = l.name();
	size_t dash = name.find_first_of('_'), dot = name.find_first_of('.');
	if (dot != std::string::npos)
	{
		name = name.substr(0, dot - 1);
	}
	if (dash != std::string::npos)
	{
		std::string language = name.substr(0, dash - 1);
		std::string country = name.substr(dash - 1);
		std::ostringstream locale;
		locale << language << "-" << country;
		return locale.str();
	}
	else
	{
		return name + "-";
	}
#endif
}

/**
 * Checks if the system's default quit shortcut was pressed.
 * @param ev SDL event.
 * @return Is quitting necessary?
 */
bool isQuitShortcut(const SDL_Event &ev)
{
#ifdef _WIN32
	// Alt + F4
	return (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_F4 && ev.key.keysym.mod & KMOD_ALT);
#elif __APPLE__
	// Command + Q
	return (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_q && ev.key.keysym.mod & KMOD_LMETA);
#else
	//TODO add other OSs shortcuts.
    (void)ev;
	return false;
#endif
}

/**
 * Gets the last modified date of a file.
 * @param path Full path to file.
 * @return The timestamp in integral format.
 */
time_t getDateModified(const std::string &path)
{
/*#ifdef _WIN32
	WIN32_FILE_ATTRIBUTE_DATA info;
	if (GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &info))
	{
		FILETIME ft = info.ftLastWriteTime;
		LARGE_INTEGER li;
		li.HighPart = ft.dwHighDateTime;
		li.LowPart = ft.dwLowDateTime;
		return li.QuadPart;
	}
	else
	{
		return 0;
	}
#endif*/
	struct stat info;
	if (stat(path.c_str(), &info) == 0)
	{
		return info.st_mtime;
	}
	else
	{
		return 0;
	}
}

/**
 * Converts a date/time into a human-readable string
 * using the ISO 8601 standard.
 * @param time Value in timestamp format.
 * @return String pair with date and time.
 */
std::pair<std::string, std::string> timeToString(time_t time)
{
	char localDate[25], localTime[25];

/*#ifdef _WIN32
	LARGE_INTEGER li;
	li.QuadPart = time;
	FILETIME ft;
	ft.dwHighDateTime = li.HighPart;
	ft.dwLowDateTime = li.LowPart;
	SYSTEMTIME st;
	FileTimeToLocalFileTime(&ft, &ft);
	FileTimeToSystemTime(&ft, &st);

	GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, localDate, 25);
	GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, localTime, 25);
#endif*/

	struct tm *timeinfo = localtime(&(time));
	strftime(localDate, 25, "%Y-%m-%d", timeinfo);
	strftime(localTime, 25, "%H:%M", timeinfo);

	return std::make_pair(localDate, localTime);
}

/**
 * Moves a file from one path to another,
 * replacing any existing file.
 * @param src Source path.
 * @param dest Destination path.
 * @return True if the operation succeeded, False otherwise.
 */
bool moveFile(const std::string &src, const std::string &dest)
{
#ifdef _WIN32
	return (MoveFileExA(src.c_str(), dest.c_str(), MOVEFILE_REPLACE_EXISTING) != 0);
#else
	//return (rename(src.c_str(), dest.c_str()) == 0);
	std::ifstream srcStream;
	std::ofstream destStream;
	srcStream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	destStream.exceptions(std::ofstream::failbit | std::ofstream::badbit);
	try
	{
		srcStream.open(src.c_str(), std::ios::binary);
		destStream.open(dest.c_str(), std::ios::binary);
		destStream << srcStream.rdbuf();
		srcStream.close();
		destStream.close();
	}
	catch (const std::fstream::failure &)
	{
		return false;
	}
	return deleteFile(src);
#endif
}

/**
 * Notifies the user that maybe he should have a look.
 */
void flashWindow()
{
#ifdef _WIN32
	SDL_SysWMinfo wminfo;
	SDL_VERSION(&wminfo.version)
	if (SDL_GetWMInfo(&wminfo))
	{
		HWND hwnd = wminfo.window;
		FlashWindow(hwnd, true);
	}
#endif
}

/**
 * Gets the executable path in DOS-style (short) form.
 * For non-Windows systems, just use a dummy path.
 * @return Executable path.
 */
std::string getDosPath()
{
#ifdef _WIN32
	std::string path, bufstr;
	char buf[MAX_PATH];
	if (GetModuleFileNameA(0, buf, MAX_PATH) != 0)
	{
		bufstr = buf;
		size_t c1 = bufstr.find_first_of('\\');
		path += bufstr.substr(0, c1+1);
		size_t c2 = bufstr.find_first_of('\\', c1+1);
		while (c2 != std::string::npos)
		{
			std::string dirname = bufstr.substr(c1+1, c2-c1-1);
			if (dirname == "..")
			{
				path = path.substr(0, path.find_last_of('\\', path.length()-2));
			}
			else
			{
				if (dirname.length() > 8)
					dirname = dirname.substr(0, 6) + "~1";
				std::transform(dirname.begin(), dirname.end(), dirname.begin(), toupper);
				path += dirname;
			}
			c1 = c2;
			c2 = bufstr.find_first_of('\\', c1+1);
			if (c2 != std::string::npos)
				path += '\\';
		}
	}
	else
	{
		path = "C:\\GAMES\\OPENXCOM";
	}
	return path;
#else
	return "C:\\GAMES\\OPENXCOM";
#endif
}

/**
 * Sets the window titlebar icon.
 * For Windows, use the embedded resource icon.
 * For other systems, use a PNG icon.
 * @param winResource ID for Windows icon.
 * @param unixPath Path to PNG icon for Unix.
 */
#ifdef _WIN32
void setWindowIcon(int winResource, const std::string &)
{
	HINSTANCE handle = GetModuleHandle(NULL);
	HICON icon = LoadIcon(handle, MAKEINTRESOURCE(winResource));

	SDL_SysWMinfo wminfo;
	SDL_VERSION(&wminfo.version)
	if (SDL_GetWMInfo(&wminfo))
	{
		HWND hwnd = wminfo.window;
		SetClassLongPtr(hwnd, GCLP_HICON, (LONG_PTR)icon);
	}
}
#else
void setWindowIcon(int, const std::string &unixPath)
{
	std::string utf8 = Unicode::convPathToUtf8(unixPath);
	SDL_Surface *icon = IMG_Load(utf8.c_str());
	if (icon != 0)
	{
		SDL_WM_SetIcon(icon, NULL);
		SDL_FreeSurface(icon);
	}
}
#endif

/**
 * Logs the stack back trace leading up to this function call.
 * @param ctx Pointer to stack context (PCONTEXT on Windows), NULL to use current context.
 */
void stackTrace(void *ctx)
{
#ifdef _WIN32
#ifndef __NO_DBGHELP
	const int MAX_SYMBOL_LENGTH = 1024;
	CONTEXT context;
	if (ctx != 0)
	{
		context = *((PCONTEXT)ctx);
	}
	else
	{
#ifdef _M_IX86
		memset(&context, 0, sizeof(CONTEXT));
		context.ContextFlags = CONTEXT_CONTROL;
#ifdef __MINGW32__
		asm("Label:\n\t"
			"movl %%ebp,%0;\n\t"
			"movl %%esp,%1;\n\t"
			"movl $Label,%%eax;\n\t"
			"movl %%eax,%2;\n\t"
			: "=r" (context.Ebp), "=r" (context.Esp), "=r" (context.Eip)
			: //no input
			: "eax");
#else
		_asm {
		Label:
			mov[context.Ebp], ebp;
			mov[context.Esp], esp;
			mov eax, [Label];
			mov[context.Eip], eax;
		}
#endif
#else
		RtlCaptureContext(&context);
#endif
	}
	HANDLE thread = GetCurrentThread();
	HANDLE process = GetCurrentProcess();
	STACKFRAME64 frame;
	memset(&frame, 0, sizeof(STACKFRAME64));
	DWORD image;
#ifdef _M_IX86
	image = IMAGE_FILE_MACHINE_I386;
	frame.AddrPC.Offset = context.Eip;
	frame.AddrPC.Mode = AddrModeFlat;
	frame.AddrFrame.Offset = context.Ebp;
	frame.AddrFrame.Mode = AddrModeFlat;
	frame.AddrStack.Offset = context.Esp;
	frame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
	image = IMAGE_FILE_MACHINE_AMD64;
	frame.AddrPC.Offset = context.Rip;
	frame.AddrPC.Mode = AddrModeFlat;
	frame.AddrFrame.Offset = context.Rbp;
	frame.AddrFrame.Mode = AddrModeFlat;
	frame.AddrStack.Offset = context.Rsp;
	frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
	image = IMAGE_FILE_MACHINE_IA64;
	frame.AddrPC.Offset = context.StIIP;
	frame.AddrPC.Mode = AddrModeFlat;
	frame.AddrFrame.Offset = context.IntSp;
	frame.AddrFrame.Mode = AddrModeFlat;
	frame.AddrBStore.Offset = context.RsBSP;
	frame.AddrBStore.Mode = AddrModeFlat;
	frame.AddrStack.Offset = context.IntSp;
	frame.AddrStack.Mode = AddrModeFlat;
#else
	Log(LOG_FATAL) << "Unfortunately, no stack trace information is available";
	return;
#endif
	SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO) + (MAX_SYMBOL_LENGTH - 1) * sizeof(TCHAR));
	symbol->MaxNameLen = MAX_SYMBOL_LENGTH;
	symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
	IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
	line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
	DWORD displacement;
	SymInitialize(process, NULL, TRUE);
	while (StackWalk64(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
	{
		if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
		{
			std::string symname = symbol->Name;
#ifdef __MINGW32__
			symname = "_" + symname;
			int status = 0;
			size_t outSz = 0;
			char* demangled = abi::__cxa_demangle(symname.c_str(), 0, &outSz, &status);
			if (status == 0)
			{
				symname = demangled;
				if (outSz > 0)
					free(demangled);
			}
			else
			{
				symname = symbol->Name;
			}
#endif
			if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
			{
				std::string filename = line->FileName;
				size_t n = filename.find_last_of(PATH_SEPARATOR);
				if (n != std::string::npos)
				{
					filename = filename.substr(n + 1);
				}
				Log(LOG_FATAL) << "0x" << std::hex << symbol->Address << std::dec << " " << symname << " (" << filename << ":" << line->LineNumber << ")";
			}
			else
			{
				Log(LOG_FATAL) << "0x" << std::hex << symbol->Address << std::dec << " " << symname;
			}
		}
		else
		{
			Log(LOG_FATAL) << "??";
		}
	}
	DWORD err = GetLastError();
	if (err)
	{
		Log(LOG_FATAL) << "Unfortunately, no stack trace information is available";
	}
	SymCleanup(process);
#else
	Log(LOG_FATAL) << "Unfortunately, no stack trace information is available";
#endif
#elif __CYGWIN__
	Log(LOG_FATAL) << "Unfortunately, no stack trace information is available";
#else
	void *frames[32];
	char buf[1024];
	int  frame_count = backtrace(frames, 32);
	char *demangled = NULL;
	const char *mangled = NULL;
	int status;
	size_t sym_offset;

	for (int i = 0; i < frame_count; i++) {
		Dl_info dl_info;
		if (dladdr(frames[i], &dl_info )) {
			demangled = NULL;
			mangled = dl_info.dli_sname;
			if ( mangled != NULL) {
				sym_offset = (char *)frames[i] - (char *)dl_info.dli_saddr;
				demangled = abi::__cxa_demangle( dl_info.dli_sname, NULL, 0, &status);
				snprintf(buf, sizeof(buf), "%s(%s+0x%zx) [%p]",
						dl_info.dli_fname,
						status == 0 ? demangled : mangled,
						sym_offset, frames[i] );
			} else { // symbol not found
				sym_offset = (char *)frames[i] - (char *)dl_info.dli_fbase;
				snprintf(buf, sizeof(buf), "%s(+0x%zx) [%p]", dl_info.dli_fname, sym_offset, frames[i]);
			}
			free(demangled);
			Log(LOG_FATAL) << buf;
		} else { // object not found
			snprintf(buf, sizeof(buf), "? ? [%p]", frames[i]);
			Log(LOG_FATAL) << buf;
		}
	}
#endif
	ctx = (void*)ctx;
}

/**
 * Generates a timestamp of the current time.
 * @return String in D-M-Y_H-M-S format.
 */
std::string now()
{
	const int MAX_LEN = 25, MAX_RESULT = 80;
	char result[MAX_RESULT] = { 0 };
#ifdef _WIN32
	char date[MAX_LEN], time[MAX_LEN];
	if (GetDateFormatA(LOCALE_INVARIANT, 0, 0, "dd'-'MM'-'yyyy", date, MAX_LEN) == 0)
		return "00-00-0000";
	if (GetTimeFormatA(LOCALE_INVARIANT, TIME_FORCE24HOURFORMAT, 0, "HH'-'mm'-'ss", time, MAX_LEN) == 0)
		return "00-00-00";
	sprintf(result, "%s_%s", date, time);
#else
	char buffer[MAX_LEN];
	time_t rawtime;
	struct tm *timeinfo;
	time(&rawtime);
	timeinfo = localtime(&rawtime);
	strftime(buffer, MAX_LEN, "%d-%m-%Y_%H-%M-%S", timeinfo);
	sprintf(result, "%s", buffer);
#endif
	return result;
}

/**
 * Logs the details of this crash and shows an error.
 * @param ex Pointer to exception data (PEXCEPTION_POINTERS on Windows, signal int on Unix)
 * @param err Exception message, if any.
 */
void crashDump(void *ex, const std::string &err)
{
	std::ostringstream error;
#ifdef _MSC_VER
	PEXCEPTION_POINTERS exception = (PEXCEPTION_POINTERS)ex;
	std::exception *cppException = 0;
	switch (exception->ExceptionRecord->ExceptionCode)
	{
	case EXCEPTION_CODE_CXX:
		cppException = (std::exception *)exception->ExceptionRecord->ExceptionInformation[1];
		error << cppException->what();
		break;
	case EXCEPTION_ACCESS_VIOLATION:
		error << "Memory access violation. This usually indicates something missing in a mod.";
		break;
	default:
		error << "code 0x" << std::hex << exception->ExceptionRecord->ExceptionCode;
		break;
	}
	Log(LOG_FATAL) << "A fatal error has occurred: " << error.str();
	stackTrace(exception->ContextRecord);
	std::string dumpName = Options::getUserFolder();
	dumpName += now() + ".dmp";
	HANDLE dumpFile = CreateFileA(dumpName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
	exceptionInformation.ThreadId = GetCurrentThreadId();
	exceptionInformation.ExceptionPointers = exception;
	exceptionInformation.ClientPointers = FALSE;
	if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile, MiniDumpNormal, exception ? &exceptionInformation : NULL, NULL, NULL))
	{
		Log(LOG_FATAL) << "Crash dump generated at " << dumpName;
	}
	else
	{
		Log(LOG_FATAL) << "No crash dump generated: " << GetLastError();
	}
#else
	if (ex == 0)
	{
		error << err;
	}
	else
	{
		int signal = *((int*)ex);
		switch (signal)
		{
		case SIGSEGV:
			error << "Segmentation fault. This usually indicates something missing in a mod.";
			break;
		default:
			error << "signal " << signal;
			break;
		}
	}
	Log(LOG_FATAL) << "A fatal error has occurred: " << error.str();
	stackTrace(0);
#endif
	std::ostringstream msg;
	msg << "OpenXcom has crashed: " << error.str() << std::endl;
	msg << "More details here: " << Logger::logFile() << std::endl;
	msg << "If this error was unexpected, please report it to the developers.";
	showError(msg.str());
}

/**
 * Opens a file or web path in the system default browser.
 */
bool openExplorer(const std::string &url)
{
#ifdef _WIN32
	HINSTANCE ret = ShellExecuteW(NULL, L"open", Unicode::convMbToWc(url, CP_UTF8).c_str(), NULL, NULL, SW_SHOWNORMAL);
	// The return value is not a true HINSTANCE. If the function succeeds, it returns a value greater than 32.
	return (static_cast<int>(reinterpret_cast<uintptr_t>(ret)) > 32);
#elif __APPLE__
	std::string cmd = "open \"" + url + "\"";
	return (system(cmd.c_str()) == 0);
#else
	std::string cmd = "xdg-open \"" + url + "\"";
	return (system(cmd.c_str()) == 0);
#endif
}

/**
 * Gets the path to the executable file.
 * @return Path to the EXE file.
 */
std::string getExeFolder()
{
#ifdef _WIN32
	wchar_t dest[MAX_PATH + 1];
	if (GetModuleFileNameW(NULL, dest, MAX_PATH) != 0)
	{
		PathRemoveFileSpecW(dest);
		auto ret = Unicode::convWcToMb(dest) + "/";
		return ret;
	}
#endif
	return std::string();
}

}

}
