#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
 * OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
 *
 * OpenRCT2 is the work of many authors, a full list can be found in contributors.md
 * For more information, visit https://github.com/OpenRCT2/OpenRCT2
 *
 * OpenRCT2 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.
 *
 * A full copy of the GNU General Public License can be found in licence.txt
 *****************************************************************************/
#pragma endregion

#include "LanguagePack.h"

extern "C" {

#include "../addresses.h"
#include "../config.h"
#include "../drawing/drawing.h"
#include "../object.h"
#include "../openrct2.h"
#include "../util/util.h"
#include "localisation.h"

enum {
	RCT2_LANGUAGE_ID_ENGLISH_UK,
	RCT2_LANGUAGE_ID_ENGLISH_US,
	RCT2_LANGUAGE_ID_FRENCH,
	RCT2_LANGUAGE_ID_GERMAN,
	RCT2_LANGUAGE_ID_SPANISH,
	RCT2_LANGUAGE_ID_ITALIAN,
	RCT2_LANGUAGE_ID_DUTCH,
	RCT2_LANGUAGE_ID_SWEDISH,
	RCT2_LANGUAGE_ID_8,
	RCT2_LANGUAGE_ID_KOREAN,
	RCT2_LANGUAGE_ID_CHINESE_SIMPLIFIED,
	RCT2_LANGUAGE_ID_CHINESE_TRADITIONAL,
	RCT2_LANGUAGE_ID_12,
	RCT2_LANGUAGE_ID_PORTUGUESE,
	RCT2_LANGUAGE_ID_END = 255
};

static TTFFontSetDescriptor TTFFontMSGothic = {{
	{ "msgothic.ttc",	"MS PGothic",	9,		1,		0,		15,		nullptr },
	{ "msgothic.ttc",	"MS PGothic",	12,		1,		0,		17,		nullptr },
	{ "msgothic.ttc",	"MS PGothic",	12,		1,		0,		17,		nullptr },
	{ "msgothic.ttc",	"MS PGothic",	13,		1,		0,		20,		nullptr },
}};

static TTFFontSetDescriptor TTFFontMingLiu = {{
	{ "msjh.ttc",		"JhengHei",	9,		-1,		-3,		6,		nullptr },
	{ "mingliu.ttc",	"MingLiU",	11,		1,		1,		12,		nullptr },
	{ "mingliu.ttc",	"MingLiU",	12,		1,		0,		12,		nullptr },
	{ "mingliu.ttc",	"MingLiU",	13,		1,		0,		20,		nullptr },
}};

static TTFFontSetDescriptor TTFFontSimSun = {{
	{ "msyh.ttc",		"YaHei",	9,		-1,		-3,		6,		nullptr },
	{ "simsun.ttc",		"SimSun",	11,		1,		-1,		14,		nullptr },
	{ "simsun.ttc",		"SimSun",	12,		1,		-2,		14,		nullptr },
	{ "simsun.ttc",		"SimSun",	13,		1,		0,		20,		nullptr },
}};

static TTFFontSetDescriptor TTFFontGulim = {{
	{ "gulim.ttc",		"Gulim",	11,		1,		0,		15,		nullptr },
	{ "gulim.ttc",		"Gulim",	12,		1,		0,		17,		nullptr },
	{ "gulim.ttc",		"Gulim",	12,		1,		0,		17,		nullptr },
	{ "gulim.ttc",		"Gulim",	13,		1,		0,		20,		nullptr },
}};

static TTFFontSetDescriptor TTFFontArial = {{
	{ "arial.ttf",		"Arial",	8,		0,		-1,		6,		nullptr },
	{ "arial.ttf",		"Arial",	10,		0,		-1,		12,		nullptr },
	{ "arial.ttf",		"Arial",	11,		0,		-1,		12,		nullptr },
	{ "arial.ttf",		"Arial",	12,		0,		-1,		20,		nullptr },
}};

const language_descriptor LanguagesDescriptors[LANGUAGE_COUNT] = {
	{ "",			 "",						 "",						FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_ENGLISH_UK				},	// LANGUAGE_UNDEFINED
	{ "en-GB",		"English (UK)",				"English (UK)",				FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_ENGLISH_UK				},	// LANGUAGE_ENGLISH_UK
	{ "en-US",		"English (US)",				"English (US)",				FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_ENGLISH_US				},	// LANGUAGE_ENGLISH_US
	{ "de-DE",		"German",					"Deutsch",					FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_GERMAN					},	// LANGUAGE_GERMAN
	{ "nl-NL",		"Dutch",					"Nederlands",				FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_DUTCH					},	// LANGUAGE_DUTCH
	{ "fr-FR",		"French",					"Fran\xC3\xA7" "ais",		FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_FRENCH					},	// LANGUAGE_FRENCH
	{ "hu-HU",		"Hungarian",				"Magyar",					FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_ENGLISH_UK				},	// LANGUAGE_HUNGARIAN
	{ "pl-PL",		"Polish",					"Polski",					&TTFFontArial,			RCT2_LANGUAGE_ID_ENGLISH_UK				},	// LANGUAGE_POLISH
	{ "es-ES",		"Spanish",					"Espa\xC3\xB1ol",			FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_SPANISH				},	// LANGUAGE_SPANISH
	{ "sv-SE",		"Swedish",					"Svenska",					FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_SWEDISH				},	// LANGUAGE_SWEDISH
	{ "it-IT",		"Italian",					"Italiano",					FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_ITALIAN				},	// LANGUAGE_ITALIAN
	{ "pt-BR",		"Portuguese (BR)",			"Portugu\xC3\xAAs (BR)",	FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_PORTUGUESE				},	// LANGUAGE_PORTUGUESE_BR
	{ "zh-TW",		"Chinese (Traditional)",	"Chinese (Traditional)",	&TTFFontMingLiu,		RCT2_LANGUAGE_ID_CHINESE_TRADITIONAL	},	// LANGUAGE_CHINESE_TRADITIONAL
	{ "zh-CN",		"Chinese (Simplified)",		"Chinese (Simplified)",		&TTFFontSimSun,			RCT2_LANGUAGE_ID_CHINESE_SIMPLIFIED		},	// LANGUAGE_CHINESE_SIMPLIFIED
	{ "fi-FI",		"Finnish",					"Suomi",					FONT_OPENRCT2_SPRITE,	RCT2_LANGUAGE_ID_ENGLISH_UK				},	// LANGUAGE_FINNISH
	{ "ko-KR",		"Korean",					"Korean",					&TTFFontGulim,			RCT2_LANGUAGE_ID_KOREAN					},	// LANGUAGE_KOREAN
	{ "ru-RU",		"Russian",					"Russian",					&TTFFontArial,			RCT2_LANGUAGE_ID_ENGLISH_UK				},	// LANGUAGE_RUSSIAN
	{ "cs-CZ",		"Czech",					"Czech",					&TTFFontArial,			RCT2_LANGUAGE_ID_ENGLISH_UK				},	// LANGUAGE_CZECH
	{ "ja-JP",		"Japanese",					"Japanese",					&TTFFontMSGothic,		RCT2_LANGUAGE_ID_ENGLISH_UK				},	// LANGUAGE_JAPANESE
};

int gCurrentLanguage = LANGUAGE_UNDEFINED;
bool gUseTrueTypeFont = false;

LanguagePack *_languageFallback = nullptr;
LanguagePack *_languageCurrent = nullptr;

const char **_languageOriginal = (const char**)0x009BF2D4;

const utf8 BlackUpArrowString[] =		{ (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x96, (utf8)0xB2, (utf8)0x00 };
const utf8 BlackDownArrowString[] =		{ (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x96, (utf8)0xBC, (utf8)0x00 };
const utf8 BlackLeftArrowString[] =		{ (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x97, (utf8)0x80, (utf8)0x00 };
const utf8 BlackRightArrowString[] =	{ (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x96, (utf8)0xB6, (utf8)0x00 };
const utf8 CheckBoxMarkString[] =		{ (utf8)0xE2, (utf8)0x9C, (utf8)0x93, (utf8)0x00 };

void utf8_remove_format_codes(utf8 *text, bool allowcolours)
{
	utf8 *dstCh = text;
	utf8 *ch = text;
	int codepoint;
	while ((codepoint = utf8_get_next(ch, (const utf8**)&ch)) != 0) {
		if (!utf8_is_format_code(codepoint) || (allowcolours && utf8_is_colour_code(codepoint))) {
			dstCh = utf8_write_codepoint(dstCh, codepoint);
		}
	}
	*dstCh = 0;
}

const char *language_get_string(rct_string_id id)
{
	const char *openrctString = nullptr;

	if (id == STR_NONE)
		return nullptr;

	if (_languageCurrent != nullptr)
		openrctString = _languageCurrent->GetString(id);
	if (openrctString == nullptr && _languageFallback != nullptr)
		openrctString = _languageFallback->GetString(id);

	if (id >= STR_OPENRCT2_BEGIN_STRING_ID) {
		return openrctString != nullptr ? openrctString : "(undefined string)";
	} else {
		const char *rct = _languageOriginal[id];
		const char *str = (openrctString == nullptr || strlen(openrctString) == 0 ? rct : openrctString);
		return str == nullptr ? "" : str;
	}
}

bool language_open(int id)
{
	static const char *languagePath = "%s/language/%s.txt";
	char filename[MAX_PATH];
	char dataPath[MAX_PATH];

	language_close_all();
	if (id == LANGUAGE_UNDEFINED)
		return false;

	platform_get_openrct_data_path(dataPath);
	if (id != LANGUAGE_ENGLISH_UK) {
		sprintf(filename, languagePath, dataPath, LanguagesDescriptors[LANGUAGE_ENGLISH_UK].locale);
		_languageFallback = LanguagePack::FromFile(LANGUAGE_ENGLISH_UK, filename);
	}

	sprintf(filename, languagePath, dataPath, LanguagesDescriptors[id].locale);
	_languageCurrent = LanguagePack::FromFile(id, filename);
	if (_languageCurrent != nullptr) {
		gCurrentLanguage = id;

		if (LanguagesDescriptors[id].font == FONT_OPENRCT2_SPRITE) {
			ttf_dispose();
			gUseTrueTypeFont = false;
			gCurrentTTFFontSet = nullptr;
		} else {
			if (!String::IsNullOrEmpty(gConfigFonts.file_name)) {
				static TTFFontSetDescriptor TTFFontCustom = {{
					{ gConfigFonts.file_name,	gConfigFonts.font_name,	gConfigFonts.size_tiny,		gConfigFonts.x_offset,	gConfigFonts.y_offset,	gConfigFonts.height_tiny,	nullptr },
					{ gConfigFonts.file_name,	gConfigFonts.font_name,	gConfigFonts.size_small,	gConfigFonts.x_offset,	gConfigFonts.y_offset,	gConfigFonts.height_small,	nullptr },
					{ gConfigFonts.file_name,	gConfigFonts.font_name,	gConfigFonts.size_medium,	gConfigFonts.x_offset,	gConfigFonts.y_offset,	gConfigFonts.height_medium,	nullptr },
					{ gConfigFonts.file_name,	gConfigFonts.font_name,	gConfigFonts.size_big,		gConfigFonts.x_offset,	gConfigFonts.y_offset,	gConfigFonts.height_big,	nullptr },
				}};
				ttf_dispose();
				gUseTrueTypeFont = true;
				gCurrentTTFFontSet = &TTFFontCustom;
				
				bool font_initialised = ttf_initialise();
				if(!font_initialised) {
					log_warning("Unable to initialise configured TrueType font -- falling back to Language default.");
				} else {
					// Objects and their localized strings need to be refreshed
					reset_loaded_objects();
					
					return 1;
				}
			}
			ttf_dispose();
			gUseTrueTypeFont = true;
			gCurrentTTFFontSet = LanguagesDescriptors[id].font;
			bool font_initialised = ttf_initialise();

			// Have we tried Arial yet?
			if (!font_initialised && gCurrentTTFFontSet != &TTFFontArial) {
				log_warning("Unable to initialise prefered TrueType font -- falling back to Arial.");
				gCurrentTTFFontSet = &TTFFontArial;
				font_initialised = ttf_initialise();
			}

			// Fall back to sprite font.
			if (!font_initialised) {
				log_warning("Falling back to sprite font.");
				gUseTrueTypeFont = false;
				gCurrentTTFFontSet = nullptr;
				return false;
			}
		}

		// Objects and their localized strings need to be refreshed
		reset_loaded_objects();

		return true;
	}

	return 0;
}

void language_close_all()
{
	SafeDelete(_languageFallback);
	SafeDelete(_languageCurrent);
	gCurrentLanguage = LANGUAGE_UNDEFINED;
}

#define STEX_BASE_STRING_ID			3447
#define NONSTEX_BASE_STRING_ID		3463
#define MAX_OBJECT_CACHED_STRINGS	2048

/* rct2: 0x0098DA16 */
uint16 ObjectTypeStringTableCount[] = { 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3 };

utf8 *_cachedObjectStrings[MAX_OBJECT_CACHED_STRINGS] = { nullptr };

void utf8_trim_string(utf8 *text)
{
	utf8 *src = text;
	utf8 *dst = text;
	utf8 *last = text;
	int codepoint;

	// Trim left
	while ((codepoint = utf8_get_next(src, (const utf8**)&src)) != 0) {
		if (codepoint != ' ') {
			dst = utf8_write_codepoint(dst, codepoint);
			last = dst;
			break;
		}
	}
	if (codepoint != 0) {
		// Trim right
		while ((codepoint = utf8_get_next(src, (const utf8**)&src)) != 0) {
			dst = utf8_write_codepoint(dst, codepoint);
			if (codepoint != ' ') {
				last = dst;
			}
		}
	}
	*last = 0;
}

static wchar_t convert_specific_language_character_to_unicode(int languageId, wchar_t codepoint)
{
	switch (languageId) {
	case RCT2_LANGUAGE_ID_KOREAN:
		return codepoint;
	case RCT2_LANGUAGE_ID_CHINESE_TRADITIONAL:
		return encoding_convert_big5_to_unicode(codepoint);
	case RCT2_LANGUAGE_ID_CHINESE_SIMPLIFIED:
		return encoding_convert_gb2312_to_unicode(codepoint);
	default:
		return codepoint;
	}
}

static utf8 *convert_multibyte_charset(const char *src, int languageId)
{
	int reservedLength = (strlen(src) * 4) + 1;
	utf8 *buffer = (utf8*)malloc(reservedLength);
	utf8 *dst = buffer;
	for (const uint8 *ch = (const uint8*)src; *ch != 0;) {
		if (*ch == 0xFF) {
			ch++;
			uint8 a = *ch++;
			uint8 b = *ch++;
			uint16 codepoint = (a << 8) | b;

			codepoint = convert_specific_language_character_to_unicode(languageId, codepoint);
			dst = utf8_write_codepoint(dst, codepoint);
		} else {
			dst = utf8_write_codepoint(dst, *ch++);
		}
	}
	*dst++ = 0;
	int actualLength = dst - buffer;
	buffer = (utf8*)realloc(buffer, actualLength);

	return buffer;
}

static bool rct2_language_is_multibyte_charset(int languageId)
{
	switch (languageId) {
	case RCT2_LANGUAGE_ID_KOREAN:
	case RCT2_LANGUAGE_ID_CHINESE_TRADITIONAL:
	case RCT2_LANGUAGE_ID_CHINESE_SIMPLIFIED:
		return true;
	default:
		return false;
	}
}

/* rct2: 0x006A9E24*/
rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/*ecx*/, int index/*ebx*/, int tableindex/*edx*/)
{
	uint8 languageId, chosenLanguageId;
	char *pString = nullptr;
	int result = 0;
	bool isBlank;

	while ((languageId = *(*pStringTable)++) != RCT2_LANGUAGE_ID_END) {
		isBlank = true;

		// Strings that are just ' ' are set as invalid langauges.
		// But if there is no real string then it will set the string as
		// the blank string
		for (char *ch = (char*)(*pStringTable); *ch != 0; ch++) {
			if (!isblank(*ch)) {
				isBlank = false;
				break;
			}
		}

		if (isBlank) languageId = 0xFE;

		// This is the ideal situation. Language found
		if (languageId == LanguagesDescriptors[gCurrentLanguage].rct2_original_id) {
			chosenLanguageId = languageId;
			pString = (char*)(*pStringTable);
			result |= 1;
		}

		// Just in case always load english into pString
		if (languageId == RCT2_LANGUAGE_ID_ENGLISH_UK && !(result & 1)) {
			chosenLanguageId = languageId;
			pString = (char*)(*pStringTable);
			result |= 2;
		}

		// Failing that fall back to whatever is first string
		if (!(result & 7)) {
			chosenLanguageId = languageId;
			pString = (char*)(*pStringTable);
			if (!isBlank) result |= 4;
		}

		// Skip over the actual string entry to get to the next entry
		while (*(*pStringTable)++ != 0);
	}
	// Fall back in case language does not get set.
	if (pString == NULL)
	{
		pString = (char*)(*pStringTable);
	}

	char name[9];
	if (RCT2_GLOBAL(0x009ADAFC, uint8) == 0) {
		memcpy(name, object_entry_groups[type].entries[index].name, 8);
	} else {
		memcpy(name, gTempObjectLoadName, 8);
	}
	name[8] = 0;

	rct_string_id stringId = _languageCurrent->GetObjectOverrideStringId(name, tableindex);
	if (stringId != STR_NONE) {
		return stringId;
	}

	// If not scenario text
	if (RCT2_GLOBAL(0x009ADAFC, uint8) == 0) {
		int stringid = NONSTEX_BASE_STRING_ID;
		for (int i = 0; i < type; i++) {
			int nrobjects = object_entry_group_counts[i];
			int nrstringtables = ObjectTypeStringTableCount[i];
			stringid += nrobjects * nrstringtables;
		}
		stringid += index * ObjectTypeStringTableCount[type];
		// Used by the object list to allocate name in plugin.dat
		RCT2_GLOBAL(RCT2_ADDRESS_CURR_OBJECT_BASE_STRING_ID, uint32) = stringid;
		stringid += tableindex;

		// cache UTF-8 string
		int cacheStringOffset = stringid - STEX_BASE_STRING_ID;
		utf8 **cacheString = &_cachedObjectStrings[cacheStringOffset];
		if (*cacheString != nullptr) {
			free(*cacheString);
		}
		if (rct2_language_is_multibyte_charset(chosenLanguageId)) {
			*cacheString = convert_multibyte_charset(pString, chosenLanguageId);
		} else {
			*cacheString = win1252_to_utf8_alloc(pString);
		}
		utf8_trim_string(*cacheString);

		//put pointer in stringtable
		_languageCurrent->SetString(stringid, *cacheString);
		// Until all string related functions are finished copy
		// to old array as well.
		_languageOriginal[stringid] = *cacheString;
		return stringid;
	} else {
		int stringid = STEX_BASE_STRING_ID + tableindex;

		// cache UTF-8 string
		int cacheStringOffset = stringid - STEX_BASE_STRING_ID;
		utf8 **cacheString = &_cachedObjectStrings[cacheStringOffset];
		if (*cacheString != nullptr) {
			free(*cacheString);
		}
		if (rct2_language_is_multibyte_charset(chosenLanguageId)) {
			*cacheString = convert_multibyte_charset(pString, chosenLanguageId);
		} else {
			*cacheString = win1252_to_utf8_alloc(pString);
		}
		utf8_trim_string(*cacheString);

		//put pointer in stringtable
		_languageCurrent->SetString(stringid, *cacheString);
		// Until all string related functions are finished copy
		// to old array as well.
		_languageOriginal[stringid] = *cacheString;
		return stringid;
	}
}

bool language_get_localised_scenario_strings(const utf8 *scenarioFilename, rct_string_id *outStringIds)
{
	outStringIds[0] = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename, 0);
	outStringIds[1] = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename, 1);
	outStringIds[2] = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename, 2);
	return
		outStringIds[0] != STR_NONE ||
		outStringIds[1] != STR_NONE ||
		outStringIds[2] != STR_NONE;
}

}
