#include "ascript/String.h"
#include "ascript/Symbol.h"
#include "ascript/Value.h"
#include "ascript/OAL.h"

namespace AScript {

//-----------------------------------------------------------------------------
// String
//-----------------------------------------------------------------------------
const StringList StringList::Null;

static const unsigned short __ctypeTbl[] = {
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0030, 0x0020, 0x0000, 0x0000, 0x0020, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0030, 0x0000, 0x0000, 0x0000, 0x0400, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x000e, 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, 0x000e,
	0x0006, 0x0006, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0480, 0x1085, 0x1085, 0x1085, 0x1085, 0x1085, 0x1085, 0x1081,
	0x1081, 0x1081, 0x1081, 0x1081, 0x1081, 0x1081, 0x1081, 0x1081,
	0x1081, 0x1081, 0x1081, 0x1081, 0x1081, 0x1081, 0x1081, 0x1081,
	0x1081, 0x1081, 0x1081, 0x0080, 0x0080, 0x0080, 0x0080, 0x0480,
	0x0080, 0x0885, 0x0885, 0x0885, 0x0885, 0x0885, 0x0885, 0x0881,
	0x0881, 0x0881, 0x0881, 0x0881, 0x0881, 0x0881, 0x0881, 0x0881,
	0x0881, 0x0881, 0x0881, 0x0881, 0x0881, 0x0881, 0x0881, 0x0881,
	0x0881, 0x0881, 0x0881, 0x0080, 0x0080, 0x0080, 0x0080, 0x0000,
	0x0280, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0,
	0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0,
	0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0,
	0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0, 0x02c0,
	0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280,
	0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280,
	0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280,
	0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280, 0x0280,
	0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
	0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
	0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
	0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
	0x01c0, 0x01c0, 0x01c0, 0x01c0, 0x01c0, 0x01c0, 0x01c0, 0x01c0,
	0x01c0, 0x01c0, 0x01c0, 0x01c0, 0x01c0, 0x01c0, 0x01c0, 0x01c0,
	0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
	0x0180, 0x0180, 0x01c0, 0x01c0, 0x01c0, 0x0100, 0x0100, 0x0100,
};

unsigned short GetCType(char ch)
{
	return __ctypeTbl[static_cast<unsigned char>(ch)];
}

char GetEscaped(char ch)
{
	static const struct {
		char ch;
		char chConv;
	} tbl[] = {
		{ 'a',	'\a'	},
		{ 'b',	'\b'	},
		{ 'f',	'\f'	},
		{ 'n',	'\n'	},
		{ 'r',	'\r'	},
		{ 't',	'\t'	},
		{ 'v',	'\v'	},
		{ '\\',	'\\'	},
		{ '\'',	'\''	},
		{ '"',	'"'		},
	};
	for (int i = 0; i < NUMBEROF(tbl); i++) {
		if (tbl[i].ch == ch) return tbl[i].chConv;
	}
	return ch;
}

void EscapeString(String &strDst, const char *str)
{
	for (const char *p = str; *p != '\0'; p++) {
		char ch = *p;
		if (ch == '\n') {
			strDst += "\\n";
		} else if (ch == '\r') {
			strDst += "\\r";
		} else if (ch == '\t') {
			strDst += "\\t";
		} else if (ch == '"') {
			strDst += "\\\"";
		} else if (ch == '\\') {
			strDst += "\\\\";
		} else if (IsUTF8First(ch) || IsUTF8Follower(ch)) {
			strDst += ch;
		} else if (ch < 0x20 || ch >= 0x7f) {
			char tmp[16];
			::sprintf(tmp, "\\x%02x", static_cast<unsigned char>(ch));
			strDst += tmp;
		} else {
			strDst += ch;
		}
	}
}

String EscapeString(const char *str)
{
	String rtn;
	EscapeString(rtn, str);
	return rtn;
}

void EscapeHtml(String &strDst, const char *str, bool quoteFlag)
{
	for (const char *p = str; *p != '\0'; p++) {
		char ch = *p;
		if (ch == '&') {
			strDst += "&amp;";
		} else if (ch == '>') {
			strDst += "&gt;";
		} else if (ch == '<') {
			strDst += "&lt;";
		} else if (quoteFlag && ch == '"') {
			strDst += "&quot;";
		} else {
			strDst += ch;
		}
	}
}

String EscapeHtml(const char *str, bool quoteFlag)
{
	String rtn;
	EscapeHtml(rtn, str, quoteFlag);
	return rtn;
}

void UnescapeHtml(String &strDst, const char *str)
{
	enum {
		STAT_Normal, STAT_Escape,
	} stat = STAT_Normal;
	String field;
	for (const char *p = str; *p != '\0'; p++) {
		char ch = *p;
		if (stat == STAT_Normal) {
			if (ch == '&') {
				field.clear();
				stat = STAT_Escape;
			} else {
				strDst += ch;
			}
		} else if (stat == STAT_Escape) {
			if (ch == ';') {
				if (field == "amp") {
					strDst += '&';
				} else if (field == "gt") {
					strDst += '>';
				} else if (field == "lt") {
					strDst += '<';
				} else if (field == "quot") {
					strDst += '"';
				}
				stat = STAT_Normal;
			} else {
				field += ch;
			}
		}
	}
}

String UnescapeHtml(const char *str)
{
	String rtn;
	UnescapeHtml(rtn, str);
	return rtn;
}

String NumberToString(Number num)
{
	char buff[32];
	if (-2147483647. <= num && num < 2147483648. &&
					static_cast<Number>(static_cast<int>(num)) == num) {
		::sprintf(buff, "%d", static_cast<int>(num));
	} else if (0 <= num && num <= 4294967295. &&
			static_cast<Number>(static_cast<unsigned int>(num)) == num) {
		::sprintf(buff, "%u", static_cast<unsigned int>(num));
	} else {
		::sprintf(buff, GetNumberFormat(), num);
	}
	return String(buff);
}

const char *FindString(const char *str, const char *sub, bool ignoreCaseFlag)
{
	for ( ; *str != '\0'; str++) {
		const char *p1 = str, *p2 = sub;
		for ( ; *p2 != '\0'; p1++, p2++) {
			if (CompareChar(*p1, *p2, ignoreCaseFlag) != 0) break;
		}
		if (*p2 == '\0') return str;
	}
	return NULL;
}

String::const_iterator FindString(String::const_iterator str,
		String::const_iterator strEnd, const String &sub, bool ignoreCaseFlag)
{
	for ( ; str != strEnd; str++) {
		String::const_iterator p1 = str;
		String::const_iterator p2 = sub.begin();
		for ( ; p2 != sub.end(); p1++, p2++) {
			if (CompareChar(*p1, *p2, ignoreCaseFlag) != 0) break;
		}
		if (p2 == sub.end()) return str;
	}
	return strEnd;
}

bool StartsWith(const char *str, const char *prefix, size_t pos, bool ignoreCaseFlag)
{
	str = Forward(str, pos);
	const char *p1 = str, *p2 = prefix;
	for ( ; *p2 != '\0'; p1++, p2++) {
		if (CompareChar(*p1, *p2, ignoreCaseFlag) != 0) return false;
	}
	return true;
}

bool EndsWith(const char *str, const char *suffix, bool ignoreCaseFlag)
{
	size_t len = ::strlen(suffix);
	const char *p = str + ::strlen(str);
	if (str + len > p) return false;
	const char *p1 = p - len, *p2 = suffix;
	for ( ; *p2 != '\0'; p1++, p2++) {
		if (CompareChar(*p1, *p2, ignoreCaseFlag) != 0) return false;
	}
	return true;
}

bool EndsWith(const char *str, const char *suffix, size_t posEnd, bool ignoreCaseFlag)
{
	size_t len = ::strlen(suffix);
	const char *p = Forward(str, posEnd);
	if (str + len > p) return false;
	const char *p1 = p - len, *p2 = suffix;
	for ( ; *p2 != '\0'; p1++, p2++) {
		if (CompareChar(*p1, *p2, ignoreCaseFlag) != 0) return false;
	}
	return true;
}

String Capitalize(const char *str)
{
	String rtn;
	const char *p = str;
	if (*p != '\0') {
		rtn += ('a' <= *p && *p <= 'z')? *p - 'a' + 'A' : *p;
		p++;
	}
	for ( ; *p != '\0'; p++) {
		rtn += ('A' <= *p && *p <= 'Z')? *p - 'A' + 'a' : *p;
	}
	return rtn;
}

String Lower(const char *str)
{
	String rtn;
	for (const char *p = str; *p != '\0'; p++) {
		rtn += ('A' <= *p && *p <= 'Z')? *p - 'A' + 'a' : *p;
	}
	return rtn;
}

String Upper(const char *str)
{
	String rtn;
	for (const char *p = str; *p != '\0'; p++) {
		rtn += ('a' <= *p && *p <= 'z')? *p - 'a' + 'A' : *p;
	}
	return rtn;
}

String Strip(const char *str, bool stripLeftFlag, bool stripRightFlag)
{
	size_t len = ::strlen(str);
	if (len == 0) return String("");
	const char *p1 = str;
	const char *p2 = str + len - 1;
	if (stripLeftFlag) {
		for ( ; IsSpace(*p1); p1++) ;
	}
	if (stripRightFlag) {
		for ( ; p2 != p1 && IsSpace(*p2); p2--) ;
	}
	return String(p1, p2 - p1 + 1);
}

String Strip(const char *str, const SymbolSet &attrs)
{
	bool stripLeftFlag = true, stripRightFlag = true;
	if (attrs.IsSet(AScript_Symbol(both))) {
		// nothing to do
	} else if (attrs.IsSet(AScript_Symbol(left)) && !attrs.IsSet(AScript_Symbol(right))) {
		stripRightFlag = false;
	} else if (!attrs.IsSet(AScript_Symbol(left)) && attrs.IsSet(AScript_Symbol(right))) {
		stripLeftFlag = false;
	}
	return Strip(str, stripLeftFlag, stripRightFlag);
}

size_t Length(const char *str)
{
	size_t len = 0;
	for ( ; *str != '\0'; len++) {
		int ch = static_cast<unsigned char>(*str);
		str++;
		if (IsUTF8First(ch)) {
			while (IsUTF8Follower(*str)) str++;
		}
	}
	return len;
}

size_t CalcCharPos(const char *str, size_t idx)
{
	size_t len = 0;
	const char *strEnd = str + idx;
	for ( ; *str != '\0' && str < strEnd; len++) {
		int ch = static_cast<unsigned char>(*str);
		str++;
		if (IsUTF8First(ch)) {
			while (IsUTF8Follower(*str)) str++;
		}
	}
	return len;
}

String PickChar(const String &str, size_t idx)
{
	String::const_iterator p = str.begin();
	for ( ; p != str.end() && idx > 0; p = NextChar(str, p), idx--) ;
	String::const_iterator pEnd = NextChar(str, p);
	String rtn;
	for ( ; p != pEnd; p++) rtn.push_back(*p);
	return rtn;
}

String::const_iterator NextChar(const String &str, String::const_iterator p)
{
	if (p != str.end()) {
		int ch = static_cast<unsigned char>(*p);
		p++;
		if (IsUTF8First(ch)) {
			while (p != str.end() && IsUTF8Follower(*p)) p++;
		}
	}
	return p;
}

String::const_iterator NextUTF32(const String &str, String::const_iterator p, unsigned long &codeUTF32)
{
	codeUTF32 = 0x00000000;
	if (p == str.end()) {
		// nothing to do
	} else if ((*p & 0x80) == 0x00) {
		codeUTF32 = static_cast<unsigned char>(*p);
		p++;
	} else {
		int cntChars = 0;
		if ((*p & 0xe0) == 0xc0) {
			codeUTF32 = static_cast<unsigned char>(*p) & 0x1f;
			cntChars = 1;
		} else if ((*p & 0xf0) == 0xe0) {
			codeUTF32 = static_cast<unsigned char>(*p) & 0x0f;
			cntChars = 2;
		} else if ((*p & 0xf8) == 0xf0) {
			codeUTF32 = static_cast<unsigned char>(*p) & 0x07;
			cntChars = 3;
		} else if ((*p & 0xfc) == 0xf8) {
			codeUTF32 = static_cast<unsigned char>(*p) & 0x03;
			cntChars = 4;
		} else {
			codeUTF32 = static_cast<unsigned char>(*p) & 0x01;
			cntChars = 5;
		}
		p++;
		for (int i = 0; i < cntChars && (*p & 0xc0) == 0x80; i++, p++) {
			codeUTF32 = (codeUTF32 << 6) | (*p & 0x3f);
		}
	}
	return p;
}

void _Copy(String &rtn, const char *str, size_t len)
{
	for ( ; *str != '\0' && len > 0; len--) {
		int ch = static_cast<unsigned char>(*str);
		rtn += *str;
		str++;
		if (IsUTF8First(ch)) {
			while (IsUTF8Follower(*str)) {
				rtn += *str;
				str++;
			}
		}
	}
}

const char *Forward(const char *str, size_t len)
{
	for ( ; *str != '\0' && len > 0; len--) {
		int ch = static_cast<unsigned char>(*str);
		str++;
		if (IsUTF8First(ch)) {
			while (IsUTF8Follower(*str)) str++;
		}
	}
	return str;
}

String Center(const char *str, size_t len, const char *padding)
{
	size_t lenBody = Length(str);
	if (len <= lenBody) return String(str);
	String rtn;
	size_t lenRight = (len - lenBody) / 2;
	size_t lenLeft = len - lenBody - lenRight;
	while (lenLeft-- > 0) rtn += padding;
	rtn += str;
	while (lenRight-- > 0) rtn += padding;
	return rtn;
}

String LJust(const char *str, size_t len, const char *padding)
{
	size_t lenBody = Length(str);
	if (len <= lenBody) return String(str);
	String rtn;
	size_t lenRight = len - lenBody;
	rtn += str;
	while (lenRight-- > 0) rtn += padding;
	return rtn;
}

String RJust(const char *str, size_t len, const char *padding)
{
	size_t lenBody = Length(str);
	if (len <= lenBody) return String(str);
	String rtn;
	size_t lenLeft = len - lenBody;
	while (lenLeft-- > 0) rtn += padding;
	rtn += str;
	return rtn;
}

String Left(const char *str, size_t len)
{
	String rtn;
	_Copy(rtn, str, len);
	return rtn;
}

String Right(const char *str, size_t len)
{
	size_t lenSrc = Length(str);
	if (lenSrc > len) {
		str = Forward(str, lenSrc - len);
	}
	return String(str);
}

String Middle(const char *str, int start, int len)
{
	int lenSrc = static_cast<int>(Length(str));
	if (start < 0) {
		start += lenSrc;
		if (start < 0) start = 0;
	}
	if (start >= lenSrc || len == 0) {
		return String("");
	} else if (len > 0 && start + len < lenSrc) {
		str = Forward(str, start);
		String rtn;
		_Copy(rtn, str, len);
		return rtn;
	} else {
		str = Forward(str, start);
		return String(str);
	}
}

String Join(const ValueList &valList, const char *str)
{
	Signal sig;
	ValueList::const_iterator pValue = valList.begin();
	if (pValue == valList.end()) return "";
	String rtn = pValue->ToString(sig, false);
	pValue++;
	for ( ; pValue != valList.end(); pValue++) {
		rtn += str;
		rtn += pValue->ToString(sig, false);
	}
	return rtn;
}

Value FindString(Environment &env, Signal sig,
		const char *str, const char *sub, int start, const SymbolSet &attrs)
{
	bool ignoreCaseFlag = attrs.IsSet(AScript_Symbol(icase));
	do {
		int len = static_cast<int>(::strlen(str));
		if (start < 0) {
			start += len;
			if (start < 0) start = 0;
		}
		if (start > len) return Value::Null;
	} while (0);
	if (attrs.IsSet(AScript_Symbol(rev))) {
		const char *p = FindString(str + start, sub, ignoreCaseFlag);
		if (attrs.IsSet(AScript_Symbol(list))) {
			ValueList valListOrg;
			for ( ; p != NULL; p = FindString(p + 1, sub, ignoreCaseFlag)) {
				valListOrg.push_back(Value(static_cast<Number>(p - str)));
			}
			Value result;
			ValueList &valList = result.InitAsList(env);
			foreach_reverse (ValueList, pValue, valListOrg) {
				valList.push_back(*pValue);
			}
			return result;
		} else {
			const char *pLast = NULL;
			for ( ; p != NULL; p = FindString(p + 1, sub, ignoreCaseFlag)) {
				pLast = p;
			}
			return (pLast == NULL)? Value::Null :
							Value(static_cast<Number>(pLast - str));
		}
	} else {
		const char *p = FindString(str + start, sub, ignoreCaseFlag);
		if (attrs.IsSet(AScript_Symbol(list))) {
			Value result;
			ValueList &valList = result.InitAsList(env);
			for ( ; p != NULL; p = FindString(p + 1, sub, ignoreCaseFlag)) {
				valList.push_back(Value(static_cast<Number>(p - str)));
			}
			return result;
		} else {
			return (p == NULL)? Value::Null :
							Value(static_cast<Number>(p - str));
		}
	}
}

String Replace(const char *str, const char *sub, const char *replace,
										int nMaxReplace, const SymbolSet &attrs)
{
	bool ignoreCaseFlag = attrs.IsSet(AScript_Symbol(icase));
	String result;
	int lenSub = static_cast<int>(::strlen(sub));
	if (lenSub == 0) {
		if (nMaxReplace != 0) {
			result += replace;
			nMaxReplace--;
		}
		const char *p = str;
		for ( ; *p != '\0' && nMaxReplace != 0; p++, nMaxReplace--) {
			result += *p;
			result += replace;
		}
		result += p;
	} else {
		const char *pLeft = str;
		const char *pRight = FindString(pLeft, sub, ignoreCaseFlag);
		for ( ; pRight != NULL && nMaxReplace != 0;
					pRight = FindString(pLeft, sub, ignoreCaseFlag), nMaxReplace--) {
			result.append(pLeft, pRight - pLeft);
			result += replace;
			pLeft = pRight + lenSub;
		}
		result += pLeft;
	}
	return result;
}

void SplitPathList(const char *str, StringList &strList)
{
	enum {
		STAT_SkipSpace, STAT_Field,
	} stat = STAT_SkipSpace;
	char ch = '\0';
	String field;
	int cntSpace = 0;
	bool eatNextFlag = true;
	do {
		if (eatNextFlag) ch = *str++;
		eatNextFlag = true;
		if (stat == STAT_SkipSpace) {
			if (IsSpace(ch)) {
				// nothing to do
			} else {
				field.clear();
				cntSpace = 0;
				eatNextFlag = false;
				stat = STAT_Field;
			}
		} else if (stat == STAT_Field) {
			if (OAL::IsPathSeparator(ch) || ch == '\0') {
				strList.push_back(field);
				stat = STAT_SkipSpace;
			} else if (ch == ' ') {
				cntSpace++;
			} else {
				while (cntSpace-- > 0) field.push_back(' ');
				field.push_back(ch);
			}
		}
	} while (ch != '\0');
}

//-----------------------------------------------------------------------------
// StringList
//-----------------------------------------------------------------------------
StringList::StringList(const StringList &stringList)
{
	reserve(stringList.size());
	foreach_const (StringList, pStr, stringList) {
		push_back(*pStr);
	}
}

void StringList::operator=(const StringList &stringList)
{
	reserve(stringList.size());
	foreach_const (StringList, pStr, stringList) {
		push_back(*pStr);
	}
}

}
