/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "common/endian.h"
#include "common/macresman.h"
#include "common/punycode.h"
#include "kyra/engine/util.h"

namespace Kyra {

int Util::decodeString1(const char *src, char *dst) {
	static const uint8 decodeTable1[] = {
		0x20, 0x65, 0x74, 0x61, 0x69, 0x6E, 0x6F, 0x73, 0x72, 0x6C, 0x68,
		0x63, 0x64, 0x75, 0x70, 0x6D
	};

	static const uint8 decodeTable2[] = {
		0x74, 0x61, 0x73, 0x69, 0x6F, 0x20, 0x77, 0x62, 0x20, 0x72, 0x6E,
		0x73, 0x64, 0x61, 0x6C, 0x6D, 0x68, 0x20, 0x69, 0x65, 0x6F, 0x72,
		0x61, 0x73, 0x6E, 0x72, 0x74, 0x6C, 0x63, 0x20, 0x73, 0x79, 0x6E,
		0x73, 0x74, 0x63, 0x6C, 0x6F, 0x65, 0x72, 0x20, 0x64, 0x74, 0x67,
		0x65, 0x73, 0x69, 0x6F, 0x6E, 0x72, 0x20, 0x75, 0x66, 0x6D, 0x73,
		0x77, 0x20, 0x74, 0x65, 0x70, 0x2E, 0x69, 0x63, 0x61, 0x65, 0x20,
		0x6F, 0x69, 0x61, 0x64, 0x75, 0x72, 0x20, 0x6C, 0x61, 0x65, 0x69,
		0x79, 0x6F, 0x64, 0x65, 0x69, 0x61, 0x20, 0x6F, 0x74, 0x72, 0x75,
		0x65, 0x74, 0x6F, 0x61, 0x6B, 0x68, 0x6C, 0x72, 0x20, 0x65, 0x69,
		0x75, 0x2C, 0x2E, 0x6F, 0x61, 0x6E, 0x73, 0x72, 0x63, 0x74, 0x6C,
		0x61, 0x69, 0x6C, 0x65, 0x6F, 0x69, 0x72, 0x61, 0x74, 0x70, 0x65,
		0x61, 0x6F, 0x69, 0x70, 0x20, 0x62, 0x6D
	};

	int size = 0;
	uint cChar = 0;
	while ((cChar = *src++) != 0) {
		if (cChar & 0x80) {
			cChar &= 0x7F;
			int index = (cChar & 0x78) >> 3;
			*dst++ = decodeTable1[index];
			++size;
			assert(cChar < sizeof(decodeTable2));
			cChar = decodeTable2[cChar];
		}

		*dst++ = cChar;
		++size;
	}

	*dst++ = 0;
	return size;
}

void Util::decodeString2(const char *src, char *dst) {
	if (!src || !dst)
		return;

	char out = 0;
	while ((out = *src) != 0) {
		if (*src == 0x1B) {
			++src;
			out = *src + 0x7F;
		}
		*dst++ = out;
		++src;
	}

	*dst = 0;
}

void Util::convertString_KYRAtoGUI(char *str, int bufferSize, Common::CodePage srcCP) {
	Common::U32String in(str, srcCP);
	memset(str, 0, bufferSize);
	Common::strlcpy(str, in.encode(Common::kUtf8).c_str(), bufferSize - 1);
}
void Util::convertString_GUItoKYRA(char *str, int bufferSize, Common::CodePage dstCP) {
	Common::U32String in(str, Common::kUtf8);
	memset(str, 0, bufferSize);
	Common::strlcpy(str, in.encode(dstCP).c_str(), bufferSize);
}

Common::String Util::convertString_GUItoKYRA(Common::String &str, Common::CodePage dstCP) {
	return Common::U32String(str.c_str(), Common::kUtf8).encode(dstCP);
}

Common::String Util::convertISOToUTF8(Common::String &str) {
	return Common::U32String(str.c_str(), Common::kISO8859_1).encode(Common::kUtf8);
}

void Util::convertISOToDOS(char &c) {
	if (c)
		c = Common::U32String(Common::String::format("%c", c), Common::kISO8859_1).encode(Common::kDos850).firstChar();
}

uint16 Util::convertDOSToJohab(char c, uint8 *mergeFlags) {
	uint16 dest = 0;
	if (!((c >= (uint8)'A' && c <= (uint8)'Z') || (c >= (uint8)'a' && c <= (uint8)'z')))
		return 0;

	uint16 res = 0;
	for (int16 lml = 0, lmu = ARRAYSIZE(_johabConvTable) - 1; lml <= lmu; ) {
		res = MAX<int>(0, (lml + lmu)) >> 1;
		if (_johabConvTable[res].key > c)
			lmu = res - 1;
		else
			lml = res + 1;
		if (_johabConvTable[res].key == c)
			break;
	}

	if (_johabConvTable[res].key == c) {
		dest = _johabConvTable[res].johabChar | 0x8000;
		if (mergeFlags)
			*mergeFlags = _johabConvTable[res].flags;
	}

	return dest;
}


struct JohabMergeTableEntry {
	const char *s;
	uint8 val;
};

const JohabMergeTableEntry _johabMergeTable[35] = {
	{ "R",  0x03 }, { "T",  0x16 },	{ "a",  0x11 }, { "c",  0x19 }, { "d",  0x17 },
	{ "e",  0x08 }, { "f",  0x09 },	{ "fa", 0x0B },	{ "fg", 0x10 },	{ "fq", 0x0C },
	{ "fr", 0x0A },	{ "ft", 0x0D },	{ "fv", 0x0F },	{ "fx", 0x0E },	{ "g",  0x1D },
	{ "hk", 0x20 },	{ "hl", 0xA0 },	{ "ho", 0x40 },	{ "ml", 0x20 },	{ "nj", 0x20 },
	{ "nl", 0x60 },	{ "np", 0x60 },	{ "q",  0x13 },	{ "qt", 0x14 },	{ "r",  0x02 },
	{ "rt", 0x04 }, { "s",  0x05 },	{ "sg", 0x07 },	{ "sw", 0x06 },	{ "t",  0x15 },
	{ "u",  0x00 },	{ "v",  0x1C },	{ "w",  0x18 },	{ "x",  0x1B }, { "z",  0x1A }
};

uint16 johabMergeGetOffs(const char *srch) {
	uint16 res = 0;
	int find = 1;

	for (int16 lml = 0, lmu = ARRAYSIZE(_johabMergeTable) - 1; lml <= lmu && find; ) {
		res = MAX<int>(0, (lml + lmu)) >> 1;
		find = strcmp(srch, _johabMergeTable[res].s);
		if (find < 0)
			lmu = res - 1;
		else
			lml = res + 1;
	}

	return find ? 0 : _johabMergeTable[res].val;
}

uint8 _hanMergeState = 0;
uint16 _2byteBackup = 0;
char _asciiPrev = '\0';

void Util::mergeUpdateJohabChars(uint16 &destJohabChar0, uint16 &destJohabChar1, char asciiInput, bool reset) {
	if (reset) {
		_hanMergeState = 0;
		_asciiPrev = '\0';
		_2byteBackup = 0;
	}

	if (!asciiInput)
		return;

	for (int loops = 1; loops; --loops) {
		uint8 flags = 0;
		uint8 offs = 0;
		destJohabChar0 &= 0x7fff;
		destJohabChar1 = convertDOSToJohab(asciiInput, &flags) & 0x7fff;
		char in[3];
		in[0] = in[1] = in[2] = '\0';

		switch (_hanMergeState) {
		case 0x01:
			if (flags & 6) {
				_2byteBackup = destJohabChar0;
				destJohabChar0 = (destJohabChar0 & 0xFC1F) | (convertDOSToJohab(asciiInput) & 0x3E0);
				destJohabChar1 = 0;
				_hanMergeState = flags;
			} else {
				++loops;
			}
			break;

		case 0x02:
			if (flags & 0x10) {
				in[0] = asciiInput;
				in[1] = '\0';
				offs = johabMergeGetOffs(in);
			}
			if (offs) {
				_2byteBackup = destJohabChar0;
				destJohabChar0 = (destJohabChar0 & 0xFFE0) | offs;
				destJohabChar1 = 0;
				_hanMergeState = flags;
			} else {
				++loops;
			}
			break;

		case 0x04:
			if (flags & 0x12) {
				in[0] = (flags & 2) ? _asciiPrev : asciiInput;
				in[1] = (flags & 2) ? asciiInput : '\0';
				offs = johabMergeGetOffs(in);
			}
			if (offs) {
				_2byteBackup = destJohabChar0;
				destJohabChar0 = (flags & 2) ? (destJohabChar0 + offs) : ((destJohabChar0 & 0xFFE0) | offs);
				destJohabChar1 = 0;
				_hanMergeState = flags;
			} else {
				++loops;
			}
			break;

		case 0x11:
			if (flags & 6) {
				destJohabChar1 = (convertDOSToJohab(_asciiPrev) & 0xFC1F) | (convertDOSToJohab(asciiInput, &flags) & 0x3E0);
				destJohabChar0 = _2byteBackup;
				_hanMergeState = flags;
			} else if (flags & 0x10) {
				in[0] = _asciiPrev;
				in[1] = asciiInput;
				in[2] = '\0';
				offs = johabMergeGetOffs(in);
			}
			if (offs) {
				_2byteBackup = destJohabChar0;
				destJohabChar0 = (destJohabChar0 & 0xFFE0) | offs;
				destJohabChar1 = 0;
				_hanMergeState = flags;
			} else if (!(flags & 6)) {
				++loops;
			}
			break;

		default:
			_hanMergeState = flags & 7;
			_2byteBackup = destJohabChar0;
			destJohabChar0 = 0;
			break;
		}

		_asciiPrev = asciiInput;

		if (loops == 2) {
			destJohabChar0 = 0;
			_hanMergeState = 0;
		}
	}

	if (destJohabChar0)
		destJohabChar0 |= 0x8000;
	if (destJohabChar1)
		destJohabChar1 |= 0x8000;
}

Common::String Util::decodeString1(const Common::String &src) {
	char *tmp = new char[src.size() * 2 + 2];
	Util::decodeString1(src.c_str(), tmp);
	Common::String reslt(tmp);
	delete[] tmp;
	return reslt;
}

Common::String Util::decodeString2(const Common::String &src) {
	char *tmp = new char[src.size() * 2 + 2];
	Util::decodeString2(src.c_str(), tmp);
	Common::String reslt(tmp);
	delete[] tmp;
	return reslt;
}

Common::Path Util::findMacResourceFile(const char *baseName, const char *suffix) {
	// The original executable has a TM char as its last character (character
	// 0xAA from Mac code page). Depending on the emulator or platform used to
	// copy the file it might have been reencoded to something else. So I look
	// for multiple versions, also for punycode encoded files and also for the
	// case where the user might have just removed the last character by
	// renaming the file.

	const Common::CodePage tryCodePages[] = {
		Common::kMacRoman,
		Common::kISO8859_1
	};

	Common::MacResManager resource;
	Common::String tryName(baseName);
	Common::Path fileName;

	for (int i = 0; i < 2; ++i) {
		for (int ii = 0; ii < ARRAYSIZE(tryCodePages); ++ii) {
			Common::U32String fn(tryName + suffix, tryCodePages[ii]);
			fileName = Common::Path(fn.encode(Common::kUtf8));
			if (resource.exists(fileName))
				return fileName;
			fileName = Common::Path(Common::punycode_encodefilename(fn));
			if (resource.exists(fileName))
				return fileName;
		}
		tryName += "\xAA";
	}

	fileName.clear();
	return fileName;
}

const Util::DOS2JOHABEntry Util::_johabConvTable[52] = {
	{ 'A', 0x2041, 0x11 }, { 'B', 0x0741, 0x04 }, { 'C', 0x4041, 0x11 }, { 'D', 0x3441, 0x11 },
	{ 'E', 0x1841, 0x01 }, { 'F', 0x1c41, 0x11 }, { 'G', 0x5041, 0x11 }, { 'H', 0x05a1, 0x04 },
	{ 'I', 0x04a1, 0x02 }, { 'J', 0x04e1, 0x02 }, { 'K', 0x0461, 0x02 }, { 'L', 0x07a1, 0x02 },
	{ 'M', 0x0761, 0x04 }, { 'N', 0x0681, 0x04 }, { 'O', 0x04c1, 0x02 }, { 'P', 0x0581, 0x02 },
	{ 'Q', 0x2841, 0x01 }, { 'R', 0x0c41, 0x11 }, { 'S', 0x1041, 0x11 }, { 'T', 0x3041, 0x11 },
	{ 'U', 0x0561, 0x02 }, { 'V', 0x4c41, 0x01 }, { 'W', 0x3c41, 0x01 }, { 'X', 0x4841, 0x11 },
	{ 'Y', 0x0661, 0x04 }, { 'Z', 0x4441, 0x11 }, { 'a', 0x2041, 0x11 }, { 'b', 0x0741, 0x04 },
	{ 'c', 0x4041, 0x11 }, { 'd', 0x3441, 0x11 }, { 'e', 0x1441, 0x11 }, { 'f', 0x1c41, 0x11 },
	{ 'g', 0x5041, 0x11 }, { 'h', 0x05a1, 0x04 }, { 'i', 0x04a1, 0x02 }, { 'j', 0x04e1, 0x02 },
	{ 'k', 0x0461, 0x02 }, { 'l', 0x07a1, 0x02 }, { 'm', 0x0761, 0x04 }, { 'n', 0x0681, 0x04 },
	{ 'o', 0x0481, 0x02 }, { 'p', 0x0541, 0x02 }, { 'q', 0x2441, 0x11 }, { 'r', 0x0841, 0x11 },
	{ 's', 0x1041, 0x11 }, { 't', 0x2c41, 0x11 }, { 'u', 0x0561, 0x02 }, { 'v', 0x4c41, 0x11 },
	{ 'w', 0x3841, 0x11 }, { 'x', 0x4841, 0x11 }, { 'y', 0x0661, 0x04 }, { 'z', 0x4441, 0x11 }
};

} // End of namespace Kyra
