#include "Core.h"
//#BLITZ_APPROVE

namespace Upp {

// Old format ---------------------------

String  VFormat(const char *fmt, va_list ptr) {
	int limit = 2 * (int)strlen(fmt) + 1024;
	Buffer<char> buffer(limit);
	vsprintf(buffer, fmt, ptr);
	va_end(ptr);
	int len = (int)strlen(buffer);
	ASSERT(len <= limit);
	return String(buffer, len);
}

// Formatting routines ---------------------------

String FormatIntBase(int i, int base, int width, char lpad, int sign, bool upper)
{
	enum { BUFFER = sizeof(int) * 8 + 1 };
	if(base < 2 || base > 36)
		return "<invalid base>";
	char buffer[BUFFER];
	char *const e = buffer + (int)BUFFER;
	char *p = e;
	const char *itoc = upper ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" : "0123456789abcdefghijklmnopqrstuvwxyz";
	if(sign < 0 || !IsNull(i))
	{
		unsigned x = i;
		if(sign >= 0 && i < 0)
			x = -i;
		do
			*--p = itoc[x % base];
		while(x /= base);
	}
	bool minus = (sign >= 0 && i < 0 && !IsNull(i));
	bool do_sign = (sign > 0 || sign >= 0 && minus);
	if(do_sign && lpad != '0')
		*--p = (minus ? '-' : '+');
	if(width > e - p)
	{
		char *b = e - min<int>(width, BUFFER);
		while(p > b)
			*--p = lpad;
	}
	if(do_sign && lpad == '0')
		*--p = (minus ? '-' : '+');
	int dwd = (int)(e - p);
	int pad = (width = max(width, dwd)) - dwd;
	StringBuffer out(width);
	char *o = out;
	if(dwd < width)
		memset(o, lpad, pad);
	memcpy(o + pad, p, dwd);
	return out;
}

String FormatInt(int i)
{
	return FormatIntBase(i, 10, 0, ' ', 0);
}

String FormatIntDec(int i, int width, char lpad, bool always_sign)
{
	return FormatIntBase(i, 10, width, lpad, always_sign ? 1 : 0);
}

String FormatIntHex(int i, int width, char lpad)
{
	return FormatIntBase(i, 16, width, lpad, -1);
}

String FormatIntHexUpper(int i, int width, char lpad)
{
	return FormatIntBase(i, 16, width, lpad, -1, true);
}

String FormatIntOct(int i, int width, char lpad)
{
	return FormatIntBase(i, 8, width, lpad, -1);
}

String FormatIntAlpha(int i, bool upper)
{
	if(IsNull(i) || i == 0)
		return Null;
	String out;
	if(i < 0)
	{
		out << '-';
		i = -i;
	}
	char temp[10], *p = temp;
	i--;
	char start = (upper ? 'A' : 'a');
	do
		*p++ = start + (i % 26);
	while(i /= 26);
	while(p > temp)
		out << *--p;
	return out;
}

String FormatIntRoman(int i, bool upper)
{
	if(IsNull(i) || i == 0)
		return Null;

	String out;
	if(i < 0)
	{
		out << '-';
		i = -i;
	}
	int m = i / 1000;
	if(m)
	{
		out.Cat('M', m);
		i -= 1000 * m;
	}

	char shift = upper ? 0 : 'a' - 'A';
	static const int  value[] =  { 1000, 500, 100, 50,  10,  5,   1 };
	static const char letter[] = { 'M',  'D', 'C', 'L', 'X', 'V', 'I' };
	for(int n = 0; i && n < __countof(value); n++)
	{
		int v = value[n];
		while(i >= v)
		{
			out << (char)(letter[n] + shift);
			i -= v;
		}
		if(n < __countof(value) - 1)
			for(int j = n + (value[n + 1] * 2 >= v ? 2 : 1); j < __countof(value); j++)
				if(i >= v - value[j])
				{ // subtraction scheme
					out << (char)(letter[j] + shift) << (char)(letter[n] + shift);
					i -= v - value[j];
					break;
				}
	}
	return out;
}

String Format64(uint64 a)
{
	char b[50];
	char *p = b + 50;
	do {
		*--p = char(a % 10 + '0');
		a /= 10;
	}
	while(a);
	return String(p, b + 50);
}

String Format64Hex(uint64 a)
{
	char b[50];
	char *p = b + 50;
	do {
		*--p = "0123456789abcdef"[a & 15];
		a >>= 4;
	}
	while(a);
	return String(p, b + 50);
}

String FormatInteger(int a)            { return IsNull(a) ? String() : FormatInt(a); }
String FormatUnsigned(unsigned long a) { return Sprintf("%u", a); }
String FormatDouble(double a)          { return IsNull(a) ? String() : IsNaN(a) ? "?" : FormatDouble(a, 17, FD_REL); }
String FormatBool(bool a)              { return a ? "true" : "false"; }
String FormatPtr(const void *p)        { return "0x" + FormatHex(p); }

String FormatInt64(int64 a)
{
	return IsNull(a) ? String() : a < 0 ? "-" + Format64(-a) : Format64(a);
}

static char *PutDigits(char *out, unsigned number, int count)
{
	char temp[10];
	char *t = temp;
	do
		*t++ = number % 10 + '0';
	while(number /= 10);
	ASSERT(t - temp <= __countof(temp));
	if((count -= (int)(t - temp)) > 0)
	{
		char *e = out + count;
		while(out < e)
			*out++ = '0';
	}
	while(t > temp)
		*out++ = *--t;
	return out;
}

enum { DBL_DIGITS = (int)(DBL_MANT_DIG * M_LN2 / M_LN10) + 2 };

String FormatDoubleDigits(double d, int raw_digits, int flags, int& exponent)
{
	if(IsNull(d))
	{
		exponent = Null;
		return Null;
	}
	d = fabs(normalize(d, exponent));
	if(IsNull(exponent))
		return "0";
	int digits = raw_digits + (flags & FD_REL ? 0 : exponent + 1);
	int count = minmax<int>(digits, 1, DBL_DIGITS - 2);
	Buffer<char> buffer(max(digits, 0) + 10);
	char *p = buffer;
	if(count == 0)
		d /= 10;
	else
	{
		static double powtbl[] = { 1e0, 1e1, 1e2, 1e3, 1e4 };
		int part1 = min(count, 5);
		unsigned i = (unsigned)(d * powtbl[part1 - 1]);
		p = PutDigits(p, i, 0);
		d = double((long double)d * (long double)powtbl[part1 - 1] - i);
		count -= part1;
		while(count >= 4)
		{
			i = minmax<unsigned>(0, (unsigned)(d * 10000), 9999);
			p = PutDigits(p, i, 4);
			d = double((long double)d * (long double)10000 - i);
			count -= 4;
		}
		if(count > 0)
		{
			i = (unsigned)(d * powtbl[count]);
			p = PutDigits(p, i, count);
			d = d * powtbl[count] - i;
		}
	}
	if(p - ~buffer < DBL_DIGITS && d >= 0.5)
	{ // round
		while(p > ~buffer && p[-1] == '9')
			p--;
		if(p == ~buffer)
		{
			*p++ = '1';
			exponent++;
			if(!(flags & FD_REL))
				digits++;
		}
		else
			p[-1]++;
	}
	if(flags & FD_ZEROS)
	{
		char *e = p;
		while(e > ~buffer && e[-1] == '0')
			e--;
		if(e == buffer)
			exponent = Null;
		e = ~buffer + digits;
		while(p < e)
			*p++ = '0';
	}
	else
	{
		while(p > ~buffer && p[-1] == '0')
			p--;
		if(p == ~buffer)
		{
			exponent = Null;
			*p++ = '0';
		}
	}
	return String(~buffer, (int)(p - ~buffer));
}

String FormatDouble(double d, int digits, int flags, int pad_exp)
{
	if(IsNull(d))
		return Null;

	double ad = fabs(d);
	bool is_exp = (flags & FD_EXP);
	if(!(flags & FD_FIX))
	{
		is_exp = ad && (ad <= 1e-15 || ad >= 1e15);
		if(flags & FD_REL)
		{
			double bd = ipow10(2 * digits);
			if(ad && (ad * bd <= 1 || ad >= bd))
				is_exp = true;
		}
	}
	if(is_exp)
		return FormatDoubleExp(d, digits, flags, pad_exp);
	else
		return FormatDoubleFix(d, digits, flags);
}

String FormatDoubleFix(double d, int digits, int flags)
{
	if(IsNull(d))
		return Null;
	int exp;
	String dig = FormatDoubleDigits(d, digits, flags, exp);
	if(flags & FD_REL)
		digits = max(0, digits - Nvl(exp, 0) - 1);
	String out;
	if(flags & FD_SIGN || d < 0 && !IsNull(exp))
		out.Cat(d >= 0 ? '+' : '-');
	int pointchar = (flags & FD_COMMA) ? ',' : '.';
	if(IsNull(exp) || exp < -digits) {
		out.Cat('0');
		if((flags & FD_ZEROS) && digits) {
			out.Cat(pointchar);
			out.Cat('0', digits);
		}
	}
	else if(exp < 0) {
		out.Cat('0');
		out.Cat(pointchar);
		out.Cat('0', -1 - exp);
		int fill = digits + exp + 1;
		if(!(flags & FD_ZEROS) || dig.GetLength() >= fill)
			out.Cat(dig, min(fill, dig.GetLength()));
		else {
			out.Cat(dig);
			out.Cat('0', fill - dig.GetLength());
		}
	}
	else if(exp < dig.GetLength()) {
		out.Cat(dig, ++exp);
		if(digits > 0 && ((flags & FD_ZEROS) || dig.GetLength() > exp)) {
			out.Cat(pointchar);
			if(!(flags & FD_ZEROS) || dig.GetLength() - exp >= digits)
				out.Cat(dig.Begin() + exp, min(dig.GetLength() - exp, digits));
			else {
				out.Cat(dig.Begin() + exp, dig.GetLength() - exp);
				out.Cat('0', digits - (dig.GetLength() - exp));
			}
		}
	}
	else
	{
		out.Cat(dig);
		out.Cat('0', exp - dig.GetLength() + 1);
		if(digits > 0 && (flags & FD_ZEROS))
		{
			out.Cat(pointchar);
			out.Cat('0', digits);
		}
	}
	return out;
}

String FormatDoubleExp(double d, int digits, int flags, int fill_exp)
{
	if(IsNull(d))
		return Null;
	int exp;
	int pointchar = (flags & FD_COMMA) ? ',' : '.';
	String dig = FormatDoubleDigits(d, digits, flags | FD_REL, exp);
	exp = Nvl(exp, 0);
	StringBuffer out;
	if(flags & FD_SIGN || d < 0 && !IsNull(exp))
		out.Cat(d >= 0 ? '+' : '-');
	out.Cat(dig[0]);
	if(dig.GetLength() > 1)
	{
		out.Cat(pointchar);
		out.Cat(dig.Begin() + 1, dig.GetLength() - 1);
	}
	out.Cat(flags & FD_CAP_E ? 'E' : 'e');
	out.Cat(FormatIntDec(exp, fill_exp, '0', flags & FD_SIGN_EXP));
	return out;
}

String FormatDate(Date date, const char *format, int language)
{
	if(IsNull(date))
		return Null;
	if(!format || !*format)
		return Format(date);
	return FormatTime(ToTime(date), format, language);
}

String FormatTime(Time t, const char *s, int language)
{
	if(IsNull(t))
		return Null;
	String result;
	if(!s || !*s)
		return Format(t);
	while(*s) {
		int q = 0;
		if(*s == 'M') {
			while(*s == 'M') { s++; q++; }
			if(q == 1)
				result.Cat(Format("%d", t.month));
			else
				result.Cat(Format("%02d", t.month));
		}
		else
		if(*s == 'D') {
			while(*s == 'D') { s++; q++; }
			if(q == 1)
				result.Cat(Format("%d", t.day));
			else
				result.Cat(Format("%02d", t.day));
		}
		else
		if(*s == 'Y') {
			while(*s == 'Y') { s++; q++; }
			if(q == 1)
				result.Cat(Format("%d", t.year % 100));
			else
			if(q == 2)
				result.Cat(Format("%02d", t.year % 100));
			else
				result.Cat(Format("%d", t.year));
		}
		else
		if(*s == 'h') {
			while(*s == 'h') { s++; q++; }
			if(q == 1)
				result.Cat(Format("%d", t.hour));
			else
				result.Cat(Format("%02d", t.hour));
		}
		else
		if(*s == 'H') {
			while(*s == 'H') { s++; q++; }
			int h = ((t.hour + 11) % 12 + 1);
			if(q == 1)
				result.Cat(Format("%d", h));
			else
				result.Cat(Format("%02d", h));
		}
		else
		if(*s == '<') {
			s++;
			while(*s && *s != '/') {
				if(t.hour <= 12)
					result.Cat(*s);
				s++;
			}
			if(!*s) break;
			s++;
			while(*s && *s != '>') {
				if(t.hour > 12)
					result.Cat(*s);
				s++;
			}
			if(!*s) break;
			s++;
		}
		else
		if(*s == 'm') {
			while(*s == 'm') { s++; q++; }
			if(q == 1)
				result.Cat(Format("%d", t.minute));
			else
				result.Cat(Format("%02d", t.minute));
		}
		else
		if(*s == 's') {
			while(*s == 's') { s++; q++; }
			if(q == 1)
				result.Cat(Format("%d", t.second));
			else
				result.Cat(Format("%02d", t.second));
		}
		else
		if(*s == '`' && s[1]) {
			s++;
			result.Cat(*s++);
		}
		else
			result.Cat(*s++);
	}
	return result;
}

// New format ----------------------------


void TrimChar(String& s, int n)
{
	if(GetDefaultCharset() == CHARSET_UTF8) {
		WString h(s);
		h.Trim(n);
		s = h.ToString();
	}
	else
		s.Trim(n);
}

struct FormId : Moveable<FormId> {
	FormId(String id, int type) : id(id), type(type) {}
	String id;
	int    type;
};

unsigned GetHashValue(const FormId& fid)
{
	return CombineHash(fid.type, GetHashValue(fid.id));
}

bool operator==(const FormId& a, const FormId& b)
{
	return a.type == b.type && a.id == b.id;
}

VectorMap<FormId, Formatter>& formatmap()
{
	return Single< VectorMap<FormId, Formatter> > ();
}

void RegisterFormatter(int type, const char *id, Formatter f)
{
	AssertST();
	INTERLOCKED {
		FormId fid(id, type);
		formatmap().FindAdd(fid, f);
		formatmap().Find(fid);
	}
}

void RegisterValueFormatter(const char *id, Formatter f)
{
	RegisterFormatter(VALUE_V, id, f);
}

void RegisterNullFormatter(const char *id, Formatter f)
{
	RegisterFormatter(VOID_V, id, f);
	RegisterFormatter(ERROR_V, id, f);
}

void RegisterNumberFormatter(const char *id, Formatter f)
{
	RegisterFormatter(DOUBLE_V, id, f);
	RegisterFormatter(INT64_V, id, f);
	RegisterFormatter(INT_V, id, f);
	RegisterFormatter(BOOL_V, id, f);
	RegisterNullFormatter(id, f);
}

void RegisterStringFormatter(const char *id, Formatter f)
{
	RegisterFormatter(WSTRING_V, id, f);
	RegisterFormatter(STRING_V, id, f);
	RegisterNullFormatter(id, f);
}

void RegisterDateTimeFormatter(const char *id, Formatter f)
{
	RegisterFormatter(TIME_V, id, f);
	RegisterFormatter(DATE_V, id, f);
	RegisterNullFormatter(id, f);
}

String IntFormatter(const Formatting& f)
{
	if(f.format.GetCount() == 0 && f.id[0] == 'd' && f.id[1] == 0)
		return AsString((int)f.arg);
	StringBuffer q;
	q.SetLength(1000);
	q.SetLength(sprintf(q, '%' + f.format + f.id, (int)f.arg));
	return q;
}

String Int64Formatter(const Formatting& f)
{
	StringBuffer q;
	q.SetLength(1000);
	q.SetLength(sprintf(q, '%' + f.format + f.id, (int64)f.arg));
	return q;
}

String IntLowerAlphaFormatter(const Formatting& f)
{
	return FormatIntAlpha(f.arg, false);
}

String IntUpperAlphaFormatter(const Formatting& f)
{
	return FormatIntAlpha(f.arg, true);
}

String IntLowerRomanFormatter(const Formatting& f)
{
	return FormatIntRoman(f.arg, false);
}

String IntUpperRomanFormatter(const Formatting& f)
{
	return FormatIntRoman(f.arg, true);
}

String RealFormatter(const Formatting& f)
{
	if(IsNull(f.arg))
		return Null;
	double value = f.arg;
	const char *s = f.format;
	int digits = 6, fill_exp = 0;
	const char *id = f.id;
	int flags = (*id++ == 'v' ? FD_REL : 0);
	if(*s == '+') {
		flags |= FD_SIGN;
		s++;
	}
	if(IsDigit(*s) || *s == '-' && IsDigit(s[1])) {
		digits = (int)strtol(s, NULL, 10);
		while(IsDigit(*++s))
			;
	}
	if(*s == '@') { s++; flags |= FD_NOTHSEPS; }
	if(*s == ',') { s++; flags |= FD_COMMA; }
	if(*s == '!') { s++; flags |= FD_ZEROS; }
	if(*s == '^') {
		if(*++s == '+') {
			flags |= FD_SIGN_EXP;
			s++;
		}
		if(IsDigit(*s)) {
			fill_exp = (int)strtol(s, NULL, 10);
			while(IsDigit(*++s))
				;
		}
	}
	bool lng = false;
	if(*id == 'l') {
		lng = true;
		id++;
	}
	if(*id == 'e') flags |= FD_EXP;
	else if(*id == 'f') flags |= FD_FIX;
	if(lng)
		return GetLanguageInfo(f.language).FormatDouble(value, digits, flags, fill_exp);
	else
		return FormatDouble(value, digits, flags, fill_exp);
}

String StringFormatter(const Formatting& f)
{
	const String& s = f.arg;
	if(f.format.GetCount() == 0 && f.id[0] == 's' && f.id[1] == 0)
		return s;
	int len = s.GetCharCount();
	int width = len;
	int precision = len;
	bool lpad = false;
	CParser p(f.format);
	if(p.Char('-'))
		lpad = true;
	if(p.IsNumber())
		width = p.ReadInt();
	if(p.Char('.') && p.IsNumber())
		precision = p.ReadInt();
//	if(precision > len)
//		return WString(~s, precision).ToString();
	String q = s;
	if(precision < len) {
		TrimChar(q, precision);
		len = precision;
	}
	String r;
	if(lpad)
		r.Cat(q);
	if(width > len)
		r.Cat(' ', width - len);
	if(!lpad)
		r.Cat(q);
	return r;
}

static inline
void sFixPoint(char *s) // We do not want locale to affect decimal point, convert ',' to '.'
{
	while(*s) {
		if(*s == ',')
			*s = '.';
		s++;
	}
}

String FloatFormatter(const Formatting& f)
{
	double d = f.arg;
	String fmt = '%' + f.format + f.id;
	char h[256];
#ifdef COMPILER_MSC
	int n = _snprintf(h, 256, fmt, d);
	if(n < 0)
#else
	int n = snprintf(h, 255, fmt, d);
	if(n >= 254)
#endif
	{
#ifdef COMPILER_MSC
		n = _scprintf(fmt, d);
#endif
		Buffer<char> ah(n + 1);
		sprintf(ah, fmt, d);
		sFixPoint(ah);
		return String(ah, n);
	}
	if(n < 0)
		return String();
	sFixPoint(h);
	return String(h, n);
}

String DateFormatter(const Formatting& f)
{
	return GetLanguageInfo(f.language).FormatDate(f.arg);
}

String TimeFormatter(const Formatting& f)
{
	return GetLanguageInfo(f.language).FormatTime(f.arg);
}

String SwitchFormatter(const Formatting& f)
{
	const char *s = f.format;
	int i = f.arg;
	int o = i;
	for(;;) {
		int n = 0;
		while(IsDigit(*s))
			n = 10 * n + *s++ - '0';
		if(!*s) return Null;
		if(*s == '%') {
			o = i % max(n, 1);
			s++;
		}
		else
		if(*s == ',' || *s == ':') {
			if(o == n) {
				while(*s && *s != ':')
					s++;
				if(!*s) return Null;
				++s;
				const char *b = s;
				while(*s && *s != ';')
					s++;
				return String(b, s);
			}
			if(*s == ':')
				while(*s && *s != ';')
					s++;
			if(!*s) return Null;
			s++;
		}
		else
			return s;
	}
	return String();
}

String StdFormatFormatter(const Formatting& f)
{
	String out = StdFormat(f.arg);
	if(!IsNull(out))
		return out;
	return f.format;
}

String MonthFormatter(const Formatting& f)
{
	return MonthName((int)f.arg - 1, f.language);
}

String MONTHFormatter(const Formatting& f)
{
	return ToUpper(MonthFormatter(f), GetLNGCharset(f.language));
}

String monthFormatter(const Formatting& f)
{
	return ToLower(MonthFormatter(f), GetLNGCharset(f.language));
}

String MonFormatter(const Formatting& f)
{
	return MonName((int)f.arg - 1, f.language);
}

String MONFormatter(const Formatting& f)
{
	return ToUpper(MonFormatter(f), GetLNGCharset(f.language));
}

String monFormatter(const Formatting& f)
{
	return ToLower(MonFormatter(f), GetLNGCharset(f.language));
}

String DayFormatter(const Formatting& f)
{
	return DayName((int)f.arg, f.language);
}

String DAYFormatter(const Formatting& f)
{
	return ToUpper(DayFormatter(f), GetLNGCharset(f.language));
}

String dayFormatter(const Formatting& f)
{
	return ToLower(DayFormatter(f), GetLNGCharset(f.language));
}

String DyFormatter(const Formatting& f)
{
	return DyName((int)f.arg, f.language);
}

String DYFormatter(const Formatting& f)
{
	return ToUpper(DyFormatter(f), GetLNGCharset(f.language));
}

String dyFormatter(const Formatting& f)
{
	return ToLower(DyFormatter(f), GetLNGCharset(f.language));
}

String twFormatter(const Formatting& f)
{
	int q = f.arg;
	return Sprintf(*f.format == '0' ? "%02d" : "%d", q ? q % 12 : 12);
}

String NumberFormatter(const Formatting& f)
{
	int q = f.arg;
	return AsString(q);
}

void IntDoubleRegister(int type)
{
	RegisterFormatter(type, "", &NumberFormatter);

	RegisterFormatter(type, "c", &IntFormatter);
	RegisterFormatter(type, "d", &IntFormatter);
	RegisterFormatter(type, "i", &IntFormatter);
	RegisterFormatter(type, "o", &IntFormatter);
	RegisterFormatter(type, "x", &IntFormatter);
	RegisterFormatter(type, "X", &IntFormatter);
	RegisterFormatter(type, "ld", &IntFormatter);
	RegisterFormatter(type, "li", &IntFormatter);
	RegisterFormatter(type, "lo", &IntFormatter);
	RegisterFormatter(type, "lx", &IntFormatter);
	RegisterFormatter(type, "lX", &IntFormatter);

	RegisterFormatter(type, "lld", &Int64Formatter);
	RegisterFormatter(type, "lli", &Int64Formatter);
	RegisterFormatter(type, "llo", &Int64Formatter);
	RegisterFormatter(type, "llx", &Int64Formatter);
	RegisterFormatter(type, "llX", &Int64Formatter);

	RegisterFormatter(type, "e", &FloatFormatter);
	RegisterFormatter(type, "E", &FloatFormatter);
	RegisterFormatter(type, "f", &FloatFormatter);
	RegisterFormatter(type, "g", &FloatFormatter);
	RegisterFormatter(type, "G", &FloatFormatter);

	RegisterFormatter(type, "s", &SwitchFormatter);

	RegisterFormatter(type, "month", &monthFormatter);
	RegisterFormatter(type, "Month", &MonthFormatter);
	RegisterFormatter(type, "MONTH", &MONTHFormatter);
	RegisterFormatter(type, "mon", &monFormatter);
	RegisterFormatter(type, "Mon", &MonFormatter);
	RegisterFormatter(type, "MON", &MONFormatter);
	RegisterFormatter(type, "Day", &DayFormatter);
	RegisterFormatter(type, "DAY", &DAYFormatter);
	RegisterFormatter(type, "day", &dayFormatter);
	RegisterFormatter(type, "Dy", &DyFormatter);
	RegisterFormatter(type, "DY", &DYFormatter);
	RegisterFormatter(type, "dy", &dyFormatter);
	RegisterFormatter(type, "tw", &twFormatter);
}

static void sRegisterFormatters()
{
	ONCELOCK {
		IntDoubleRegister(BOOL_V);
		IntDoubleRegister(INT_V);
		IntDoubleRegister(INT64_V);
		IntDoubleRegister(DOUBLE_V);

		RegisterStringFormatter("s", &StringFormatter);
		RegisterNullFormatter("", &DateFormatter);
		RegisterFormatter(DATE_V, "", &DateFormatter);
		RegisterFormatter(TIME_V, "", &TimeFormatter);

		RegisterNumberFormatter("n",  &RealFormatter);
		RegisterNumberFormatter("ne", &RealFormatter);
		RegisterNumberFormatter("nf", &RealFormatter);
		RegisterNumberFormatter("nl", &RealFormatter);
		RegisterNumberFormatter("nle", &RealFormatter);
		RegisterNumberFormatter("nlf", &RealFormatter);
		RegisterNumberFormatter("v",  &RealFormatter);
		RegisterNumberFormatter("ve", &RealFormatter);
		RegisterNumberFormatter("vf", &RealFormatter);
		RegisterNumberFormatter("vl", &RealFormatter);
		RegisterNumberFormatter("vle", &RealFormatter);
		RegisterNumberFormatter("vlf", &RealFormatter);

		// real number formats (n = fixed decimals, v = valid decimals)
		// ne, ve - force exponential notation; nf, vf - force fixed notation; nl, vl - language-based formatting
		// Options: [+][[-]<digits>][!][^[+]<expdig>]
		// + .. always prepend sign
		// [-]<digits> .. number of decimals to print (negative = left of decimal point, default = 6)
		// ! .. keep insignificant zeros
		// ^ .. exponent options:
		// + .. always prepend sign to exponent
		// <expdig> exponent padding width

		RegisterNumberFormatter("a", &IntLowerAlphaFormatter);
		RegisterNumberFormatter("A", &IntUpperAlphaFormatter);
		RegisterNumberFormatter("r", &IntLowerRomanFormatter);
		RegisterNumberFormatter("R", &IntUpperRomanFormatter);

		RegisterValueFormatter("vt", &StdFormatFormatter);
		RegisterValueFormatter("", &StdFormatFormatter);
	}
}

INITBLOCK {
	sRegisterFormatters();
}

String NFormat0(int language, const char *s, const Value **v, int count)
{
	sRegisterFormatters();
	Formatting f;
	f.language = language;
	String result;
	int pos = 0;
	const char *b;
	for(;;) {
		int n = 0;
		b = s;
		for(;;) {
			while(*s && *s != '%')
				++s;
			result.Cat(b, (int)(s - b));
			if(*s == '\0')
				return result;
			++s;
			if(*s == '%') {
				result.Cat('%');
				++s;
			}
			else
				break;
			b = s;
		}
		f.format.Clear();
		f.id.Clear();
		b = s;
		int pad = -1;
		int padn = 0;
		String nvl_value = String::GetVoid();
		for(;;) {
			if(*s == '$') {
				pos = n - 1;
				b = ++s;
				n = 0;
			}
			else
			if(*s == ':') {
				pos = n - 1;
				b = ++s;
				n = 0;
			}
			else
			if(*s == '*') {
				f.format.Cat(b, (int)(s - b));
				b = ++s;
				int i = *v[pos++];
				if(*s == ':' || *s == '$' || *s == '<' || *s == '>' || *s == '=')
					n = i;
				else
					f.format.Cat(FormatInt(i));
			}
			else
			if(*s == '<') {
				padn = n;
				pad = ALIGN_LEFT;
				b = ++s;
				n = 0;
			}
			else
			if(*s == '>') {
				padn = n;
				pad = ALIGN_RIGHT;
				b = ++s;
				n = 0;
			}
			else
			if(*s == '=') {
				padn = n;
				pad = ALIGN_CENTER;
				b = ++s;
				n = 0;
			}
			else
			if(*s == '[') {
				f.format.Cat(b, (int)(s - b));
				s++;
				b = s;
				while(*s && *s != ']')
					s++;
				f.format.Cat(b, (int)(s - b));
				if(*s) s++;
				b = s;
				if(!IsAlpha(*s) && *s != '~') break;
			}
			else if(*s == '~') {
				nvl_value = f.format;
				f.format = Null;
				b = ++s;
			}
			else
			if(!*s || *s == '`' || IsAlpha(*s))
				break;
			else {
				if(!*s)
					return result + "<unexpected end>";
				if(IsDigit(*s))
					n = 10 * n + *s - '0';
				else
					n = 0;
				s++;
			}
		}
		f.format.Cat(b, (int)(s - b));
		b = s;
		while(IsAlpha(*s))
			s++;
		f.id = String(b, s);
		if(pos < 0 || pos >= count)
		{
			result << "<invalid pos=" << pos << ">";
			if(*s == '`')
				s++;
			continue;
		}
		f.arg = *v[pos++];
		String r;
		if(!nvl_value.IsVoid() && IsNull(f.arg))
			r = nvl_value;
		else
		{
			Formatter ft = NULL;
#ifdef _DEBUG
			int fi = formatmap().Find(FormId(f.id, f.arg.GetType()));
			if(fi < 0) fi = formatmap().Find(FormId(f.id, VALUE_V));
			if(fi >= 0)
				ft = formatmap()[fi];
#else
			for(;;) {
				int fi = formatmap().Find(FormId(f.id, f.arg.GetType()));
				if(fi < 0) fi = formatmap().Find(FormId(f.id, VALUE_V));
				if(fi >= 0)
				{
					ft = formatmap()[fi];
					break;
				}
				if(f.id.GetLength() == 0) break;
				f.id.Trim(f.id.GetLength() - 1);
				s--;
			}
#endif
			if(ft)
				r = (*ft)(f);
			else
				r << "<N/A '" << f.id << "' for type " << (int)f.arg.GetType() << ">";
		}
		int len;
		if(padn < 0 || padn > 1000)
			r << "<invalid padding>";
		else
		switch(pad) {
		case ALIGN_LEFT:
			len = r.GetCharCount();
			if(len < padn)
				r.Cat(' ', padn - len);
			else
				TrimChar(r, padn);
			break;
		case ALIGN_RIGHT:
			len = r.GetCharCount();
			if(len < padn) {
				String fill(' ', padn - len);
				r = fill + r;
			}
			else
				TrimChar(r, padn);
			break;
		case ALIGN_CENTER:
			len = r.GetCharCount();
			if(len < padn) {
				int ll = (padn - len) / 2;
				r = String(' ', ll) + r;
				r.Cat(' ', padn - len - ll);
			}
			else
				TrimChar(r, padn);
			break;
		}
		result.Cat(r);
		if(*s == '`')
			s++;
	}
}

String NFormat0(const char *s, const Value **v, int count)
{
	return NFormat0(GetCurrentLanguage(), s, v, count);
}

String NFormat(int language, const char *s, const Vector<Value>& v)
{
	Buffer<const Value *> bv(v.GetCount());
	for(int i = 0; i < v.GetCount(); i++)
		bv[i] = &v[i];
	return NFormat0(language, s, bv, v.GetCount());
}

String NFormat(const char *s, const Vector<Value>& v) { return NFormat(GetCurrentLanguage(), s, v); }

//$-
#define E__NFSetArg(I) arg[I - 1] = &COMBINE(p, I)
#define E__NFValue(I)  const Value& COMBINE(p, I)

#define E__NFBody(I) \
String NFormat(const char *fmt, __List##I(E__NFValue)) \
{ \
	const Value *arg[I]; \
	__List##I(E__NFSetArg); \
	return NFormat0(fmt, arg, I); \
} \
String NFormat(int language, const char *fmt, __List##I(E__NFValue)) \
{ \
	const Value *arg[I]; \
	__List##I(E__NFSetArg); \
	return NFormat0(language, fmt, arg, I); \
}

__Expand10(E__NFBody)

#if 1

#define E__FBody(I) \
String Format(const char *fmt, __List##I(E__NFValue)) \
{\
	const Value *arg[I]; \
	__List##I(E__NFSetArg); \
	return NFormat0(fmt, arg, I); \
} \
String Format(int language, const char *fmt, __List##I(E__NFValue)) \
{ \
	const Value *arg[I]; \
	__List##I(E__NFSetArg); \
	return NFormat0(language, fmt, arg, I); \
}

__Expand20(E__FBody)
//$+
String Format(const char *s, const Vector<Value>& v)
{
	return NFormat(s, v);
}

String Format(int language, const char *s, const Vector<Value>& v)
{
	return NFormat(language, s, v);
}

#else

String  Format(const char *fmt, ...) {
	va_list argptr;
	va_start(argptr, fmt);
	return VFormat(fmt, argptr);
}

#endif

String Sprintf(const char *fmt, ...) {
	va_list argptr;
	va_start(argptr, fmt);
	return VFormat(fmt, argptr);
}

String DeFormat(const char *text)
{
	StringBuffer x;
	while(*text) {
		if(*text == '%')
			x.Cat('%');
		x.Cat(*text++);
	}
	return x;
}

}
