/**
 * TextExt.Script.txt
 * @author zocka
 * @link https://github.com/PRGfx/mQscripts/blob/master/lib/TextExt.Script.txt
 * @link https://github.com/PRGfx/mQscripts/wiki/TextExt
 */

declare Text _TextExt_lowercase;
declare Text _TextExt_uppercase;
declare Text[Integer] _TextExt_asciitable;
declare Boolean _TextExt_initiated;

Text TextExt_CharAt(Text string, Integer offset)
{
	if (offset <= TextLib::Length(string)) {
		return TextLib::SubString(string, offset, 1);
	}
	return "";
}

Void _TextExt_asciitable_init()
{
	_TextExt_asciitable = [32=>" ",33=>"!",34=>"\"",35=>"#",36=>"$",37=>"%",38=>"&",39=>"'",40=>"(",41=>")",42=>"*",43=>"+",44=>",",45=>"-",46=>".",47=>"/",48=>"0",49=>"1",50=>"2",51=>"3",52=>"4",53=>"5",54=>"6",55=>"7",56=>"8",57=>"9",58=>":",59=>";",60=>"<",61=>"=",62=>">",63=>"?",64=>"@",65=>"A",66=>"B",67=>"C",68=>"D",69=>"E",70=>"F",71=>"G",72=>"H",73=>"I",74=>"J",75=>"K",76=>"L",77=>"M",78=>"N",79=>"O",80=>"P",81=>"Q",82=>"R",83=>"S",84=>"T",85=>"U",86=>"V",87=>"W",88=>"X",89=>"Y",90=>"Z",91=>"[",92=>"\\",93=>"]",94=>"^",95=>"_",96=>"`",97=>"a",98=>"b",99=>"c",100=>"d",101=>"e",102=>"f",103=>"g",104=>"h",105=>"i",106=>"j",107=>"k",108=>"l",109=>"m",110=>"n",111=>"o",112=>"p",113=>"q",114=>"r",115=>"s",116=>"t",117=>"u",118=>"v",119=>"w",120=>"x",121=>"y",122=>"z",123=>"{",124=>"|",125=>"}",126=>"~"];
}

Integer TextExt_CharCodeAt(Text string, Integer offset)
{
	_TextExt_asciitable_init();
	declare Text char = TextExt_CharAt(string, offset);
	if (_TextExt_asciitable.exists(char))
		return _TextExt_asciitable.keyof(char);
	return 0;
}

Text TextExt_FromCharCode(Integer code)
{
	_TextExt_asciitable_init();
	if (_TextExt_asciitable.existskey(code))
		return _TextExt_asciitable[code];
	return "";
}

Text[] TextExt_Chars(Text string, Boolean whitespaces)
{
	declare Text[] result;
	declare Text char;
	for (i, 0, TextLib::Length(string)-1) {
		char = TextExt_CharAt(string, i);
		if (char != " " || whitespaces)
			result.add(char);
	}
	return result;
}
Text[] TextExt_Chars(Text string)
{
	return TextExt_Chars(string, False);
}

Text _TextExt_charCase(Text char, Text mode)
{
	if (!_TextExt_initiated) {
		_TextExt_lowercase = "abcdefghijklmnopqrstuvwxyzäöü";
		_TextExt_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜ";
		_TextExt_initiated = True;
	}
	declare Text[] _lower = TextExt_Chars(_TextExt_lowercase);
	declare Text[] _upper = TextExt_Chars(_TextExt_uppercase);
	if (mode == "u" && _lower.exists(char)) return _upper[_lower.keyof(char)];
	if (mode == "l" && _upper.exists(char)) return _lower[_upper.keyof(char)];
	return char;
}

Text TextExt_TrimL(Text string)
{
	declare Integer i = 0;
	while (TextExt_CharAt(string, i) == " "
		|| TextExt_CharAt(string, i) == "\n"
		|| TextExt_CharAt(string, i) == "\t") {
		i += 1;
	}
	return TextLib::SubString(string, i, TextLib::Length(string)-i);
}
Text TextExt_TrimR(Text string)
{
	declare Integer i = TextLib::Length(string) - 1;
	while (TextExt_CharAt(string, i) == " "
		|| TextExt_CharAt(string, i) == "\n"
		|| TextExt_CharAt(string, i) == "\t") {
		i -= 1;
	}
	return TextLib::SubString(string, 0, i + 1);
}
Text TextExt_Trim(Text string)
{
	declare Text result = string;
	result = TextExt_TrimL(result);
	result = TextExt_TrimR(result);
	return result;
}

Boolean TextExt_Empty(Text string)
{
	return TextExt_Trim(string) == "";
}

Text TextExt_Join(Text glue, Text[] strings)
{
	declare Text result = "";
	for (i, 0, strings.count-2) {
		result ^= strings[i] ^ glue;
	}
	result ^=  strings[strings.count-1];
	return result;
}
Text[] TextExt_Split(Text delimiter, Text string, Integer length)
{
	declare Text[] splitted = TextLib::Split(delimiter, string);
	declare Text[] result;
	if (length >= 0) {
		for (i, 0, length-2) {
			if (splitted.existskey(i)) {
				result.add(splitted[i]);
				declare Temp = splitted.removekey(i);
			}
		}
		result.add(TextExt_Join(delimiter, splitted));
	} else {
		result = splitted;
	}
	return result;
}
Text[] TextExt_Split(Text delimiter, Text string)
{
	return TextExt_Split(delimiter, string, -1);
}

Text TextExt_Repeat(Text string, Integer count, Text separator)
{
	declare Text result = "";
	for (i, 0, count-1) {
		result ^= string;
		if (i < count - 1) {
			result ^= separator;
		}
	}
	return result;
}
Text TextExt_Repeat(Text string, Integer count)
{
	return TextExt_Repeat(string, count, "");
}

Text[] TextExt_Words(Text string, Text separator)
{
	declare Text[] splitted = TextLib::Split(separator, string);
	declare Text[] result;
	foreach (word in splitted) {
		if (!TextExt_Empty(word))
			result.add(word);
	}
	return result;
}
Text[] TextExt_Words(Text string)
{
	return TextExt_Words(string, " ");
}

Text TextExt_Uppercase(Text string)
{
	return TextLib::ToUpperCase(string);
}
Text TextExt_Lowercase(Text string)
{
	return TextLib::ToLowerCase(string);
}
Text TextExt_Capitalize(Text string)
{
	declare Text[] result = TextExt_Chars(string, True);
	result[0] = TextLib::ToUpperCase(result[0]);
	return TextExt_Join("", result);
}
Text TextExt_Titleize(Text string)
{
	declare Text[] splitted = TextLib::Split(" ", string);
	declare Text[] result;
	foreach (word in splitted) {
		result.add(TextExt_Capitalize(word));
	}
	return TextExt_Join(" ", result);
}

Text TextExt_Reverse(Text string)
{
	declare Text result = "";
	declare Integer i = TextLib::Length(string)-1;
	while (i >= 0) {
		result ^= TextExt_CharAt(string, i);
		i -= 1;
	}
	return result;
}

Integer TextExt_StrPos(Text heystack, Text needle)
{
	declare Integer d = TextLib::Length(needle);
	declare Integer n = TextLib::Length(heystack);
	if (d > n) return -1;
	for (i, 0, n - d) {
		if (TextLib::SubString(heystack, i, d) == needle)
			return i;
	}
	return -1;
}
Boolean TextExt_Contains(Text heystack, Text needle)
{
	return (TextExt_StrPos(heystack, needle) >= 0);
}
Boolean TextExt_StartsWith(Text string, Text start)
{
	return TextLib::SubString(string, 0, TextLib::Length(start)) == start;
}
Boolean TextExt_EndsWith(Text string, Text end)
{
	return TextLib::SubString(string, TextLib::Length(string) - TextLib::Length(end), TextLib::Length(end)) == end;
}
Text TextExt_Replace(Text search, Text replace, Text subject, Integer count)
{
	declare Integer toRun = count;
	declare Integer slen = TextLib::Length(search);
	declare Text result = subject;
	declare Integer start;
	while (toRun >= 0 || (count < 0 && TextExt_StrPos(result, search) > 0)) {
		toRun -= 1;
		start = TextExt_StrPos(result, search);
		result = TextLib::SubString(result, 0, start) ^ replace ^ TextLib::SubString(result, start + slen, TextLib::Length(result));
	}
	return result;
}
Text TextExt_Replace(Text search, Text replace, Text subject)
{
	return TextExt_Replace(search, replace, subject, -1);
}
Text TextExt_Replace(Text[] search, Text[] replace, Text subject)
{
	declare Text result = subject;
	assert(search.count == replace.count);
	foreach (k=>v in search) {
		result = TextExt_Replace(v, replace[k], result);
	}
	return result;
}
Text TextExt_Replace(Text[] search, Text replace, Text subject)
{
	declare Text[] repl;
	for (i, 1, search.count) {
		repl.add(replace);
	}
	return TextExt_Replace(search, repl, subject);
}

Real TextExt_Levenshtein(Text word1, Text word2)
{
	declare n = TextLib::Length(word1);
	declare m = TextLib::Length(word2);
	declare Real[Integer][Integer] matrix;
	for (i, 0, n) {
		matrix[i] = Real[Integer];
		matrix[i][0] = MathLib::ToReal(i);
		for (j, 1, m) {
			if (i == 0)
				matrix[i][j] = MathLib::ToReal(j);
			else
				matrix[i][j] = 0.;
		}
	}
	declare Real min;
	declare Integer isEqual;
	declare Int3 cost = <1, 1, 1>; /* del, ins, rep */
	for (i, 1, n) {
		for (j, 1, m) {
			if (TextExt_Lowercase(TextLib::SubString(word1, j-1, 1)) == TextExt_Lowercase(TextLib::SubString(word2, i-1, 1)))
				isEqual = 0;
			else
				isEqual = 1;
			min = matrix[i-1][j] + cost[0];
			if (matrix[i][j-1] + cost[1] < min)
				min = matrix[i][j-1] + cost[1];
			if (matrix[i-1][j-1] + cost[2] < min)
				min = matrix[i-1][j-1] + cost[2];
			if (matrix[i-1][j-1] + isEqual < min)
				min = matrix[i-1][j-1] + isEqual;
			matrix[i][j] = min;
		}
	}
	return matrix[n][m];
}

Text[] TextExt_Levenshtein(Text input, Text[] p)
{
	declare Integer key1 = 0;
	declare Text[] possibilities = p;
	declare Text[] ordered;
	if (possibilities.count > 1) {
		declare Integer key2;
		declare Real[] levenshteinValues;
		foreach (word in possibilities) {
			levenshteinValues.add(TextExt_Levenshtein(input, word));
		}
		declare Real min1;
		declare Real min2;
		while (levenshteinValues.count > 0) {
			min1 = levenshteinValues[0];
			key1 = 0;
			min2 = 0.;
			key2 = 0;
			foreach (i=>v in levenshteinValues) {
				if (v <= min1) {
					key2 = key1;
					key1 = i;
					min1 = v;
				}
			}
			ordered.add(possibilities[key1]);
			declare Temp = possibilities.removekey(key1);
			declare Temp2 = levenshteinValues.removekey(key1);
			if (possibilities.count > 0) {
				ordered.add(possibilities[key2]);
				Temp = possibilities.removekey(key2);
				Temp2 = levenshteinValues.removekey(key2);
			}
		}
	}
	return ordered;
}


Text TextExt_StripFormat(Text string, Boolean sc, Boolean sf, Boolean sl)
{
	declare Text result;
	declare Integer length = TextLib::Length(string);
	if (length < 2) return string;
	declare Text[] ft = ["w", "n", "o", "i", "s"];
	declare Text[] lt = ["l", "h", "p"];
	declare Text[] ct = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];
	declare Text char;
	declare Text ochar;
	declare Text state = "0";
	declare Text color = "";
	declare Text linktag = "";
	for (i, 0, length-1) {
		ochar = TextExt_CharAt(string, i);
		char = TextExt_Lowercase(ochar);
		switch (state) {
			case "0": {
				if (char == "$")
					state = "tag";
				else
					result ^= ochar;
			}
			case "tag": {
				if (ft.exists(char) && sf) {
					state = "0";
				} else if (ct.exists(char) && sc) {
					color = ochar;
					state = "c1";
				} else if (lt.exists(char) && sl) {
					linktag = char;
					state = "link";
				} else {
					result ^= "$" ^ ochar;
					state = "0";
				}
			}
			case "c1": {
				if (ct.exists(char)) {
					color ^= char;
					state = "c2";
				} else {
					result ^= ochar;
					state = "0";
				}
			}
			case "c2": {
				if (ct.exists(char)) {
					color ^= char;
				} else {
					result ^= ochar;
				}
				state = "0";
			}
			case "link": {
				if (char == "[") {
					state = "linkurl";
				} else if (char == " ") {
					result ^= ochar;
					state = "0";
				} else {
					result ^= ochar;
				}
			}
			case "linkurl": {
				if (char == "]") {
					state = "0";
				}
			}
		}
	}
	return result;
}
Text TextExt_StripFormat(Text string)
{
	return TextExt_StripFormat(string, False, True, False);
}
Text TextExt_StripColors(Text string)
{
	return TextExt_StripFormat(string, True, False, False);
}
Text TextExt_StripLinks(Text string)
{
	return TextExt_StripFormat(string, False, False, True);
}
Text TextExt_StripTags(Text string)
{
	return TextExt_StripFormat(string, True, True, True);
}
