#include "Symbol.h"
#include "String.h"
#include "Value.h"
#include "Formatter.h"

namespace AScript {

//-----------------------------------------------------------------------------
// FormatterBase
//-----------------------------------------------------------------------------
bool FormatterBase::DoFormat(Signal sig, const TCHAR *format, const ValueList &valList)
{
	bool eatNextFlag;
	const TCHAR *formatp = format;
	Flags flags;
	ValueList::const_iterator pValue = valList.begin();
	enum {
		STAT_Start,
		STAT_FlagsPre, STAT_Flags, STAT_FlagsAfterWhite,
		STAT_PrecisionPre, STAT_Precision,
		STAT_Padding,
	} stat = STAT_Start;
	TCHAR buff[128];
	for (;;) {
		TCHAR ch = *formatp;
		eatNextFlag = true;
		if (ch == _T('\0')) {
			break;
		} else if (stat == STAT_Start) {
			if (ch == _T('%')) {
				stat = STAT_FlagsPre;
			} else if (ch == _T('\n')) {
				for (const TCHAR *p = _lineSep; *p != _T('\0'); p++) {
					PutChar(*p);
				}
			} else {
				PutChar(ch);
			}
		} else if (stat == STAT_FlagsPre) {
			if (ch == _T('%')) {
				PutChar(ch);
				stat = STAT_Start;
			} else {
				if (pValue == valList.end()) {
					SetError_NotEnoughArguments(sig);
					break;
				}
				// initialize flags
				flags.leftAlignFlag = false;
				flags.sharpFlag = false;
				flags.fieldMinWidth = 0;
				flags.precision = -1;	// minus value means unspecified state
				flags.plusMode = PLUSMODE_None;
				flags.charPadding = _T(' ');
				eatNextFlag = false;
				stat = STAT_Flags;
			}
		} else if (stat == STAT_Flags) {
			if (ch == _T('#')) {
				flags.sharpFlag = true;
			} else if (ch == _T('0')) {
				if (!flags.leftAlignFlag) {
					flags.charPadding = _T('0');
				}
			} else if (ch == _T('-')) {
				flags.leftAlignFlag = true;
				flags.charPadding = _T(' ');
			} else if (ch == _T(' ')) {
				if (flags.plusMode == PLUSMODE_None) {
					flags.plusMode = PLUSMODE_Space;
				}
				stat = STAT_FlagsAfterWhite;
			} else if (ch == _T('+')) {
				flags.plusMode = PLUSMODE_Plus;
			} else if (ch == _T('*')) {
				if (!pValue->IsNumber()) {
					SetError_NumberIsExpected(sig,
									_T("*, specifier of variable field length"));
					break;
				}
				flags.fieldMinWidth = static_cast<int>(pValue->GetNumber());
				if (flags.fieldMinWidth < 0) {
					flags.leftAlignFlag = true;
					flags.fieldMinWidth = -flags.fieldMinWidth;
				}
				pValue++;
				if (pValue == valList.end()) {
					SetError_NotEnoughArguments(sig);
					break;
				}
			} else if (_T('0') < ch && ch <= _T('9')) {
				eatNextFlag = false;
				stat = STAT_Padding;
			} else if (ch == _T('.')) {
				flags.precision = 0;
				stat = STAT_PrecisionPre;
			} else if (ch == _T('l')) {
				// just ignore it
			} else if (ch == _T('d') || ch == _T('i')) {
				if (pValue->IsInvalid()) {
					PutInvalid(flags);
					pValue++;
					stat = STAT_Start;
				} else if (pValue->IsNumber()) {
					PutString(flags, Conv_d(flags,
						static_cast<int>(pValue->GetNumber()), buff, sizeof(buff)));
					pValue++;
					stat = STAT_Start;
				} else {
					SetError_NumberIsExpected(sig, ch);
					break;
				}
			} else if (ch == _T('u')) {
				if (pValue->IsInvalid()) {
					PutInvalid(flags);
					pValue++;
					stat = STAT_Start;
				} else if (pValue->IsNumber()) {
					PutString(flags, Conv_u(flags,
						static_cast<unsigned int>(pValue->GetNumber()), buff, sizeof(buff)));
					pValue++;
					stat = STAT_Start;
				} else {
					SetError_NumberIsExpected(sig, ch);
					break;
				}
			} else if (ch == _T('o')) {
				if (pValue->IsInvalid()) {
					PutInvalid(flags);
					pValue++;
					stat = STAT_Start;
				} else if (pValue->IsNumber()) {
					PutString(flags, Conv_o(flags,
						static_cast<unsigned int>(pValue->GetNumber()), buff, sizeof(buff)));
					pValue++;
					stat = STAT_Start;
				} else {
					SetError_NumberIsExpected(sig, ch);
					break;
				}
			} else if (ch == _T('x') || ch == _T('X')) {
				if (pValue->IsInvalid()) {
					PutInvalid(flags);
					pValue++;
					stat = STAT_Start;
				} else if (pValue->IsNumber()) {
					PutString(flags, Conv_x(flags,
						static_cast<unsigned int>(pValue->GetNumber()), buff, sizeof(buff),
						ch == _T('X')));
					pValue++;
					stat = STAT_Start;
				} else {
					SetError_NumberIsExpected(sig, ch);
					break;
				}
			} else if (ch == _T('e') || ch == _T('E')) {
				if (pValue->IsInvalid()) {
					PutInvalid(flags);
					pValue++;
					stat = STAT_Start;
				} else if (pValue->IsNumber()) {
					PutString(flags, Conv_e(flags,
						pValue->GetNumber(), buff, sizeof(buff), ch == _T('E')));
					pValue++;
					stat = STAT_Start;
				} else if (pValue->IsComplex()) {
					PutString(flags, Conv_e(flags,
						pValue->GetComplex().real(), buff, sizeof(buff), ch == _T('E')));
					PlusMode plusMode = flags.plusMode;
					flags.plusMode = PLUSMODE_Plus;
					PutString(flags, Conv_e(flags,
						pValue->GetComplex().imag(), buff, sizeof(buff), ch == _T('E')));
					flags.plusMode = plusMode;
					PutChar(_T('j'));
					pValue++;
					stat = STAT_Start;
				} else {
					SetError_NumberOrComplexIsExpected(sig, ch);
					break;
				}
			} else if (ch == _T('f') || ch == _T('F')) {
				if (pValue->IsInvalid()) {
					PutInvalid(flags);
					pValue++;
					stat = STAT_Start;
				} else if (pValue->IsNumber()) {
					PutString(flags, Conv_f(flags,
						pValue->GetNumber(), buff, sizeof(buff), ch == _T('F')));
					pValue++;
					stat = STAT_Start;
				} else if (pValue->IsComplex()) {
					PutString(flags, Conv_f(flags,
						pValue->GetComplex().real(), buff, sizeof(buff), ch == _T('F')));
					PlusMode plusMode = flags.plusMode;
					flags.plusMode = PLUSMODE_Plus;
					PutString(flags, Conv_f(flags,
						pValue->GetComplex().imag(), buff, sizeof(buff), ch == _T('F')));
					flags.plusMode = plusMode;
					PutChar(_T('j'));
					pValue++;
					stat = STAT_Start;
				} else {
					SetError_NumberOrComplexIsExpected(sig, ch);
					break;
				}
			} else if (ch == _T('g') || ch == _T('G')) {
				if (pValue->IsInvalid()) {
					PutInvalid(flags);
					pValue++;
					stat = STAT_Start;
				} else if (pValue->IsNumber()) {
					PutString(flags, Conv_g(flags,
						pValue->GetNumber(), buff, sizeof(buff), ch == _T('G')));
					pValue++;
					stat = STAT_Start;
				} else if (pValue->IsComplex()) {
					PutString(flags, Conv_g(flags,
						pValue->GetComplex().real(), buff, sizeof(buff), ch == _T('G')));
					PlusMode plusMode = flags.plusMode;
					flags.plusMode = PLUSMODE_Plus;
					PutString(flags, Conv_g(flags,
						pValue->GetComplex().imag(), buff, sizeof(buff), ch == _T('G')));
					flags.plusMode = plusMode;
					PutChar(_T('j'));
					pValue++;
					stat = STAT_Start;
				} else {
					SetError_NumberOrComplexIsExpected(sig, ch);
					break;
				}
			} else if (ch == _T('s')) {
				if (pValue->IsInvalid()) {
					PutInvalid(flags);
					pValue++;
					stat = STAT_Start;
				} else {
					PutString(flags, pValue->ToString(sig, false).c_str(), flags.precision);
					pValue++;
					stat = STAT_Start;
				}
			} else if (ch == _T('c')) {
				if (pValue->IsInvalid()) {
					PutInvalid(flags);
					pValue++;
					stat = STAT_Start;
				} else {
					PutChar(static_cast<TCHAR>(pValue->GetNumber()));
					pValue++;
					stat = STAT_Start;
				}
			} else {
				SetError_WrongFormat(sig);
				break;
			}
		} else if (stat == STAT_FlagsAfterWhite) {
			if (ch == _T(' ')) {
				SetError_WrongFormat(sig);
				break;
			} else {
				eatNextFlag = false;
				stat = STAT_Flags;
			}
		} else if (stat == STAT_Padding) {
			if (_T('0') <= ch && ch <= _T('9')) {
				flags.fieldMinWidth = flags.fieldMinWidth * 10 + (ch - _T('0'));
			} else {
				eatNextFlag = false;
				stat = STAT_Flags;
			}
		} else if (stat == STAT_PrecisionPre) {
			if (ch == _T('*')) {
				if (!pValue->IsNumber()) {
					SetError_NumberIsExpected(sig,
									_T("*, specifier of variable precision"));
					break;
				}
				flags.precision = static_cast<int>(pValue->GetNumber());
				if (flags.precision < 0) flags.precision = 0;
				pValue++;
				if (pValue == valList.end()) {
					SetError_NotEnoughArguments(sig);
					break;
				}
				stat = STAT_Flags;
			} else if (_T('0') < ch && ch <= _T('9')) {
				eatNextFlag = false;
				stat = STAT_Precision;
			} else {
				stat = STAT_Flags;
			}
		} else if (stat == STAT_Precision) {
			if (_T('0') <= ch && ch <= _T('9')) {
				flags.precision = flags.precision * 10 + (ch - _T('0'));
			} else {
				eatNextFlag = false;
				stat = STAT_Flags;
			}
		}
		if (eatNextFlag) formatp++;
	}
	return !sig.IsSignalled();
}

void FormatterBase::PutString(const Flags &flags, const TCHAR *p, int cntMax)
{
	int cnt = ::_tcslen(p);
	if (cntMax >= 0 && cnt > cntMax) cnt = cntMax;
	int cntPadding = flags.fieldMinWidth - ::_tcslen(p);
	if (flags.leftAlignFlag) {
		for ( ; cnt > 0; p++, cnt--) PutChar(*p);
		while (cntPadding-- > 0) PutChar(flags.charPadding);
	} else {
		while (cntPadding-- > 0) PutChar(flags.charPadding);
		for ( ; cnt > 0; p++, cnt--) PutChar(*p);
	}
}

void FormatterBase::PutInvalid(const Flags &flags)
{
	if (!_nilVisibleFlag) return;
	std::string str;
	//str += _T('(');
	str += AScript_Symbol(nil)->GetName();
	//str += _T(')');
	PutString(flags, str.c_str());
}

const TCHAR *FormatterBase::Conv_d(const Flags &flags, int value,
												TCHAR *buff, size_t size)
{
	TCHAR *p = buff + size - 1;
	*p = _T('\0');
	if (value == 0) {
		if (flags.precision == 0) {
			// empty string
		} else {
			p--;
			*p = _T('0');
		}
	} else if (value > 0) {
		int nCols = 0;
		for ( ; value != 0; value /= 10, nCols++) {
			p--;
			*p = (value % 10) + _T('0');
		}
		if (nCols < flags.precision) {
			int precision = std::min(flags.precision, static_cast<int>(size) - 2);
			int cnt = precision - nCols;
			while (cnt-- > 0) {
				p--;
				*p = _T('0');
			}
		}
		if (flags.plusMode == PLUSMODE_Space) {
			p--;
			*p = _T(' ');
		} else if (flags.plusMode == PLUSMODE_Plus) {
			p--;
			*p = _T('+');
		}
	} else {
		int nCols = 0;
		value = -value;
		for ( ; value != 0; value /= 10, nCols++) {
			p--;
			*p = (value % 10) + _T('0');
		}
		if (nCols < flags.precision) {
			int precision = std::min(flags.precision, static_cast<int>(size) - 2);
			int cnt = precision - nCols;
			while (cnt-- > 0) {
				p--;
				*p = _T('0');
			}
		}
		p--;
		*p = _T('-');
	}
	return p;
}

const TCHAR *FormatterBase::Conv_u(const Flags &flags, unsigned int value,
												TCHAR *buff, size_t size)
{
	TCHAR *p = buff + size - 1;
	*p = _T('\0');
	if (value == 0) {
		if (flags.precision == 0) {
			// empty string
		} else {
			p--;
			*p = _T('0');
		}
	} else {
		int nCols = 0;
		for ( ; value != 0; value /= 10, nCols++) {
			p--;
			*p = (value % 10) + _T('0');
		}
		if (nCols < flags.precision) {
			int precision = std::min(flags.precision, static_cast<int>(size) - 1);
			int cnt = precision - nCols;
			while (cnt-- > 0) {
				p--;
				*p = _T('0');
			}
		}
	}
	return p;
}

const TCHAR *FormatterBase::Conv_o(const Flags &flags, unsigned int value,
												TCHAR *buff, size_t size)
{
	TCHAR *p = buff + size - 1;
	*p = _T('\0');
	if (value == 0) {
		if (flags.precision == 0) {
			// empty string
		} else {
			p--;
			*p = _T('0');
		}
	} else {
		int nCols = 0;
		for ( ; value != 0; value >>= 3, nCols++) {
			p--;
			*p = _T('0') + (value & 0x7);
		}
		if (nCols < flags.precision) {
			int precision = std::min(flags.precision, static_cast<int>(size) - 1);
			int cnt = precision - nCols;
			while (cnt-- > 0) {
				p--;
				*p = _T('0');
			}
		} else if (flags.sharpFlag) {
			p--;
			*p = _T('0');
		}
	}
	return p;
}

const TCHAR *FormatterBase::Conv_x(const Flags &flags, unsigned int value,
									TCHAR *buff, size_t size, bool upperFlag)
{
	TCHAR *p = buff + size - 1;
	*p = _T('\0');
	const TCHAR *convTbl = upperFlag?
						_T("0123456789ABCDEF") : _T("0123456789abcdef");
	if (value == 0) {
		if (flags.precision == 0) {
			// empty string
		} else {
			p--;
			*p = _T('0');
		}
	} else {
		int nCols = 0;
		for ( ; value != 0; value >>= 4, nCols++) {
			p--;
			*p = convTbl[value & 0xf];
		}
		if (nCols < flags.precision) {
			int precision = std::min(flags.precision, static_cast<int>(size) - 3);
			int cnt = precision - nCols;
			while (cnt-- > 0) {
				p--;
				*p = _T('0');
			}
		}
		if (flags.sharpFlag) {
			p--;
			*p = upperFlag? _T('X') : _T('x');
			p--;
			*p = _T('0');
		}
	}
	return p;
}

const TCHAR *FormatterBase::Conv_e(const Flags &flags, double value,
							TCHAR *buff, size_t size, bool upperFlag)
{
	int count = (flags.precision < 0)? 6 : flags.precision;
	int dec, sign;
	char *srcp = ::_ecvt(value, count, &dec, &sign);
	dec -= 1;
	TCHAR *dstp = buff;
	if (sign) {
		*dstp++ = _T('-');
	} else if (flags.plusMode == PLUSMODE_Space) {
		*dstp++ = _T(' ');
	} else if (flags.plusMode == PLUSMODE_Plus) {
		*dstp++ = _T('+');
	}
	*dstp++ = static_cast<TCHAR>(*srcp++);
	if (*srcp != '\0') {
		*dstp++ = _T('.');
		dstp = CopyDigits(dstp, buff + size, srcp);
	}
	if (dstp < buff + size - 5) {
		*dstp++ = upperFlag? _T('E') : _T('e');
		if (dec >= 0) {
			*dstp++ = _T('+');
		} else {
			*dstp++ = _T('-');
			dec = -dec;
		}
		*dstp++ = _T('0') + (dec / 10) % 10;
		*dstp++ = _T('0') + (dec % 10);
		*dstp++ = _T('\0');
	}
	return buff;
}

const TCHAR *FormatterBase::Conv_f(const Flags &flags, double value,
							TCHAR *buff, size_t size, bool upperFlag)
{
	int count = (flags.precision < 0)? 6 : flags.precision;
	int dec, sign;
	char *srcp = ::_fcvt(value, count, &dec, &sign);
	TCHAR *dstp = buff;
	if (sign) {
		*dstp++ = _T('-');
	} else if (flags.plusMode == PLUSMODE_Space) {
		*dstp++ = _T(' ');
	} else if (flags.plusMode == PLUSMODE_Plus) {
		*dstp++ = _T('+');
	}
	if (dec <= 0) {
		*dstp++ = _T('0');
		*dstp++ = _T('.');
		dstp = FillZeroDigit(dstp, buff + size, -dec);
		CopyDigits(dstp, buff + size, srcp);
	} else {
		dstp = CopyDigits(dstp, buff + size, srcp, dec);
		if (::strlen(srcp) > static_cast<size_t>(dec)) {
			*dstp++ = _T('.');
			CopyDigits(dstp, buff + size, srcp + dec);
		}
	}
	return buff;
}

const TCHAR *FormatterBase::Conv_g(const Flags &flags, double value,
							TCHAR *buff, size_t size, bool upperFlag)
{
	char *buffRaw = new char[size];
	int digits = (flags.precision < 0)? 6 : flags.precision;
	char *srcp = ::_gcvt(value, digits, buffRaw);
	TCHAR *dstp = buff;
	if (*srcp == '-') {
		// nothing to do
	} else if (flags.plusMode == PLUSMODE_Space) {
		*dstp++ = _T(' ');
	} else if (flags.plusMode == PLUSMODE_Plus) {
		*dstp++ = _T('+');
	}
	if (upperFlag) {
		CopyUpperDigits(dstp, buff + size, srcp);
	} else {
		CopyDigits(dstp, buff + size, srcp);
	}
	delete[] buffRaw;
	return buff;
}

TCHAR *FormatterBase::FillZeroDigit(TCHAR *dstp, TCHAR *dstpEnd, int cnt)
{
	for ( ; cnt > 0 && dstp < dstpEnd - 1; cnt--, dstp++) {
		*dstp = _T('0');
	}
	*dstp = _T('\0');
	return dstp;
}

TCHAR *FormatterBase::CopyDigits(TCHAR *dstp, TCHAR *dstpEnd, const char *srcp)
{
	for ( ; *srcp != '\0' && dstp < dstpEnd - 1; srcp++, dstp++) {
		*dstp = static_cast<TCHAR>(*srcp);
	}
	*dstp = _T('\0');
	return dstp;
}

TCHAR *FormatterBase::CopyDigits(TCHAR *dstp, TCHAR *dstpEnd, const char *srcp, int cnt)
{
	for ( ; *srcp != '\0' && dstp < dstpEnd - 1 && cnt > 0; srcp++, dstp++, cnt--) {
		*dstp = static_cast<TCHAR>(*srcp);
	}
	for ( ; dstp < dstpEnd - 1 && cnt > 0; dstp++, cnt--) {
		*dstp = _T('0');
	}
	*dstp = _T('\0');
	return dstp;
}

TCHAR *FormatterBase::CopyUpperDigits(TCHAR *dstp, TCHAR *dstpEnd, const char *srcp)
{
	for ( ; *srcp != '\0' && dstp < dstpEnd - 1; srcp++, dstp++) {
		char ch = *srcp;
		if ('a' <= ch && ch <= 'z') ch = ch - 'a' + 'A';
		*dstp = static_cast<TCHAR>(ch);
	}
	*dstp = _T('\0');
	return dstp;
}

//-----------------------------------------------------------------------------
// Formatter
//-----------------------------------------------------------------------------
String Formatter::Format(Signal sig,
							const TCHAR *format, const ValueList &valList)
{
	Formatter formatter;
	formatter.DoFormat(sig, format, valList);
	return formatter._str;
}

void Formatter::PutChar(TCHAR ch)
{
	_str += ch;
}

}
