#include "TCore.h"
#pragma hdrstop

#ifdef PLATFORM_WIN32
#include <float.h>
#endif
#ifdef PLATFORM_POSIX
#include <math.h>
#endif
#ifdef PLATFORM_SOLARIS
#include <ieeefp.h>
#endif

NAMESPACE_UPP

bool IsNan(double t)
{
#ifdef PLATFORM_WIN32
	return _isnan(t);
#elif defined(PLATFORM_POSIX)
	return isnan(t);
#else
	#error
	return false;
#endif
}

bool IsInf(double t)
{
#ifdef PLATFORM_WIN32
	return !_finite(t);
#elif defined(PLATFORM_POSIX)
	return !finite(t);
#else
	#error
	return false;
#endif
}

void WeakBase::Chk() const
{
	if(!this)
		return;
	const WeakBase *ptr = GetPtr(), *p;
	int cl = 0, cr = 0;
	for(p = GetNext(); p != ptr; p = p -> GetNext(), cr++)
		if(p -> GetNext() -> GetPrev() != p)
		{ NEVER(); }
	for(p = GetPrev(); p != ptr; p = p -> GetPrev(), cl++)
		if(p -> GetPrev() -> GetNext() != p)
		{ NEVER(); }
	ASSERT(cl == cr);
}

Point Transform(Point pt, const Rect& from_rc, const Rect& to_rc)
{
	return IsNull(pt) ? Point(Null)
	: to_rc.TopLeft() + iscale(pt - from_rc.TopLeft(), to_rc.Size(), from_rc.Size());
}

Rect Transform(const Rect& rc, const Rect& from_rc, const Rect& to_rc, bool normalize)
{
	if(IsNull(rc))
		return Null;
	Size fs = from_rc.Size(), ts = to_rc.Size();
	Point fp = from_rc.TopLeft(), tp = to_rc.TopLeft();
	Point tl = tp + iscale(rc.TopLeft() - fp, ts, fs);
	Point br = tp + iscale((normalize ? rc.BottomRight() - 1 : rc.BottomRight()) - fp, ts, fs);
	return normalize ? RectSort(tl, br) : Rect(tl, br);
}

static void SplitCommandLineRaw(Vector<String>& out, const char* cmdline, int response_nesting)
{
	for(;;)
	{
		while((byte)*cmdline <= ' ')
			if(*cmdline++ == 0)
				return;

		// beginning of an argument
		String s;
		if(*cmdline == '\"')
		{ // quoted argument
			while(*++cmdline != 0 && (*cmdline != '\"' || *++cmdline == '\"'))
				s += *cmdline;
		}
		else if(*cmdline == '#')
		{ // comment until end of line
			while(*cmdline && *cmdline != '\n')
				cmdline++;
		}
		else
		{ // normal argument - skip to next blank
			while((byte)*cmdline > ' ')
				s += *cmdline++;
		}
		if(!IsNull(s))
			if(response_nesting > 0 && *s == '@')
				SplitCommandLineRaw(out, LoadFile(s.Begin() + 1), response_nesting - 1);
			else
				out.Add(s);
	}
}

Vector<String> SplitCommandLine(const char *cmdline, bool response_files)
{
	Vector<String> result;
	SplitCommandLineRaw(result, cmdline, response_files ? 10 : 1);
	result.Shrink();
	return result;
}

/* TRC 2/12/2003: moved to Web
static void GetLineIndex(String file, HashBase& hash, Vector<String>& lines)
{
	const char *p = file;
	while(*p)
	{
		while(*p && *p != '\n' && (byte)*p <= ' ')
			p++;
		const char *b = p;
		while(*p && *p++ != '\n')
			;
		const char *e = p;
		while(e > b && (byte)e[-1] <= ' ')
			e--;
		String s(b, e);
		hash.Add(GetHashValue(s));
		lines.Add(s);
	}
}

int LocateLine(String old_file, int old_line, String new_file)
{
	HashBase old_hash, new_hash;
	Vector<String> old_lines, new_lines;
	GetLineIndex(old_file, old_hash, old_lines);
	GetLineIndex(new_file, new_hash, new_lines);
	if(old_line <= 0)
		return 0;
	if(old_line >= old_lines.GetCount())
		return new_lines.GetCount();
	String line = old_lines[old_line];
	int hash = old_hash[old_line];
	int best_match = 0, best_value = 0;
	int fore_count = old_lines.GetCount() - old_line - 1;
	for(int r = 0; r < 10 && !best_value; r++)
	{
		int src = (r & 1 ? old_line + (r >> 1) + 1 : old_line - (r >> 1));
		if(src < 0 || src >= old_lines.GetCount())
			continue;
		dword hash = old_hash[src];
		for(int i = new_hash.Find(hash); i >= 0; i = new_hash.FindNext(i))
			if(new_lines[i] == old_lines[src])
			{
				int max_back = min(i, src);
				int max_fore = min(new_lines.GetCount() - i, old_lines.GetCount() - src) - 1;
				if(max_back + max_fore <= best_value)
					continue;
				int back = 1;
				while(back <= max_back && new_hash[i - back] == old_hash[src - back]
					&& new_lines[i - back] == old_lines[src - back])
					back++;
				int fore = 1;
				while(fore < max_fore && new_hash[i + fore] == old_hash[src + fore]
					&& new_lines[i + fore] == old_lines[src + fore])
					fore++;
				if(back + fore > best_value)
				{
					best_value = back + fore;
					best_match = minmax(i, 0, new_lines.GetCount());
				}
			}
	}
	return best_match;
}
*/

longlong_t GetHotKey(byte c, bool ascii_only)
{
	if(IsDigit(c))
		return LL_(1) << (c - '0');
	if(!ascii_only)
		c = ToAscii(c);
	c = ToUpper(c);
	if(IsUpper(c))
		return (LL_(1) << 10) << (c - 'A');
	return 0;
}

longlong_t GetHotKey(const char* text, bool ascii_only)
{
	for(; *text && *text != '\t'; text++)
		if(*text == '&' && text[1] && *++text != '&')
		{
			longlong_t key = GetHotKey(*text, ascii_only);
			if(key)
				return key;
		}

	return 0;
}

char GetHotKeyText(longlong_t hotkey)
{
	int i = -1;
	if(hotkey)
		for(i = 0; !(hotkey & 1); hotkey >>= 1, i++)
			;
	if(i >= 0 && i < 10)
		return '0' + i;
	if(i >= 10 && i < 36)
		return 'A' + i - 10;
	return 0;
}

String StripHotText(const char* s)
{
	String result;
	for(; *s; result += *s++)
		if(*s == '&' && *++s == 0)
			break;
	return result;
}

String HotTextToSmartText(const char *s)
{
	String result;
	while(*s)
	{
		const char *p = s;
		while(*s && *s != '&')
			s++;
		result.Cat(p, s - p);
		if(*s == '&' && *++s && *s != '&')
			result << '\a' << *s++ << '\a';
	}
	return result;
}

int PutGetMW(Stream& stream, int value)
{
	if(stream.IsLoading())
		return stream.Get16be();
	stream.Put16be(value);
	return value;
}

int PutGetIW(Stream& stream, int value)
{
	if(stream.IsLoading())
		return stream.Get16le();
	stream.Put16le(value);
	return value;
}

int PutGetML(Stream& stream, int value)
{
	if(stream.IsLoading())
		return stream.Get32be();
	stream.Put32be(value);
	return value;
}

int PutGetIL(Stream& stream, int value)
{
	if(stream.IsLoading())
		return stream.Get32le();
	stream.Put32le(value);
	return value;
}

int64 PutGetM64(Stream& stream, int64 value)
{
	if(stream.IsLoading())
		return stream.Get64be();
	stream.Put64be(value);
	return value;
}

int64 PutGetI64(Stream& stream, int64 value)
{
	if(stream.IsLoading())
		return stream.Get64le();
	stream.Put64le(value);
	return value;
}

static void Swap8(byte *data)
{
	byte temp[8];
	temp[0] = data[7];
	temp[1] = data[6];
	temp[2] = data[5];
	temp[3] = data[4];
	temp[4] = data[3];
	temp[5] = data[2];
	temp[6] = data[1];
	temp[7] = data[0];
	memcpy(data, temp, 8);
}

void StreamID(Stream& stream, double& value)
{
	ASSERT(sizeof(value) == 8);
#ifndef CPU_LITTLE_ENDIAN
	if(stream.IsStoring())
		Swap8((byte *)&value);
#endif
	stream.SerializeRaw((byte *)&value, 8);
#ifndef CPU_LITTLE_ENDIAN
	if(stream.IsLoading())
		Swap8((byte *)&value);
#endif
	if(stream.IsLoading() && value < -1e300)
		value = Null;
}

void StreamMD(Stream& stream, double& value)
{
	ASSERT(sizeof(value) == 8);
#ifdef CPU_LITTLE_ENDIAN
	if(stream.IsStoring())
		Swap8((byte *)&value);
#endif
	stream.SerializeRaw((byte *)&value, 8);
#ifdef CPU_LITTLE_ENDIAN
	if(stream.IsLoading())
		Swap8((byte *)&value);
#endif
	if(stream.IsLoading() && value < -1e300)
		value = Null;
}

void StreamIFP(Stream& stream, Pointf& pt)
{
#ifdef CPU_LITTLE_ENDIAN
	ASSERT(sizeof(double) == 8);
	stream.SerializeRaw((byte *)&pt, sizeof(pt));
#else
	StreamID(stream, pt.x);
	StreamID(stream, pt.y);
#endif
}

void StreamIFR(Stream& stream, Rectf& rect)
{
#ifdef CPU_LITTLE_ENDIAN
	ASSERT(sizeof(double) == 8);
	stream.SerializeRaw((byte *)&rect, sizeof(rect));
#else
	StreamID(stream, rect.left);
	StreamID(stream, rect.top);
	StreamID(stream, rect.right);
	StreamID(stream, rect.bottom);
#endif
}

/*
String StringSample(const char *p, int limit)
{
	const char *e = (const char *)memchr(p, 0, limit + 1);
	if(e)
		return String(p, e);
	return String(p, limit) + "...";
}
*/

String StringReplace(const String& str, const String& find, const String& replace)
{
	int flen = find.GetLength();
	int count = str.GetLength() - flen;
	if(count < 0)
		return str;
	const char *s = str, *e = s + count, *f = find, *p = s;
	String result;
	while(s <= e)
		if(*s == *f && !memcmp(s, f, flen))
		{
			result.Cat(p, s - p);
			result += replace;
			p = (s += flen);
		}
		else
			s++;
	result.Cat(p, str.End() - p);
	return result;
}

const char *StringFind(const char *sbegin, const char *send, const char *subbegin, const char *subend)
{
	if(!send) send = sbegin + strlen(sbegin);
	if(!subend) subend = subbegin + strlen(subbegin);
	int len = send - sbegin;
	int sublen = subend - subbegin;
	ASSERT(sublen);
	if(sublen == 0)
		return sbegin;
	if(len < sublen)
		return 0;
	if(sublen == 1)
		return reinterpret_cast<const char *>(memchr(sbegin, *subbegin, len - 1));
	for(send -= sublen - 1; sbegin < send; sbegin++)
		if(*sbegin == *subbegin && !memcmp(sbegin, subbegin, sublen))
			return sbegin;
	return 0;
}

int StringFind(const String& string, const String& substring, int start_pos)
{
	ASSERT(start_pos >= 0 && start_pos <= string.GetLength());
	const char *p = StringFind(string.Begin() + start_pos, string.End(), substring.Begin(), substring.End());
	return p ? p - ~string : -1;
}

String FromCString(const char *cstring)
{
	try
	{
		CParser parser(cstring);
		return parser.ReadOneString();
	}
	catch(CParser::Error e)
	{
		return Null;
	}
}

bool HasNlsLetters(WString s)
{
	for(const wchar *p = s.Begin(), *e = s.End(); p < e; p++)
		if(IsLetter(*p) && *p != ToAscii(*p))
			return true;
	return false;
}

/*
#ifdef PLATFORM_WIN32
String GetLocaleInfo(LCTYPE type, LCID locale)
{
	int count = GetLocaleInfo(locale, type, NULL, 0);
	if(--count <= 0)
		return Null;
	String temp;
	GetLocaleInfo(locale, type, temp.GetBuffer(count), count + 1);
	temp.ReleaseBuffer(count);
	return temp;
}
#endif
*/

/*
static const char *NlsFindDigits(const char *src, String& dest)
{
	if(*src && !IsDigit(*src))
	{
		const char *start = src;
		while(*++src && !IsDigit(*src))
			;
		dest.Cat(start, src - start);
	}
	return src;
}
*/

/*
static const char *NlsCopyDigits(const char *src, String& dest)
{
	if(IsDigit(*(src = NlsFindDigits(src, dest))))
	{
		const char *p = src;
		while(IsDigit(*++src))
			;
		int first = (src - p + 2) % 3 + 1;
		while(p < src)
		{
			dest.Cat(p, first);
			if((p += first) < src)
			{
#ifdef PLATFORM_WIN32
				static String thousands = GetLocaleInfo(LOCALE_STHOUSAND);
#else
				static String thousands = " ";
#endif
				dest.Cat(thousands);
				first = 3;
			}
		}
	}
	return src;
}
*/

/*
static String NlsFormatRaw(const char *n)
{
	if(*n == 0)
		return Null;
	String result;
	n = NlsCopyDigits(n, result);
	if(*n == '.')
	{ // decimal separator
#ifdef PLATFORM_WIN32
		static String decimals = GetLocaleInfo(LOCALE_SDECIMAL);
#else
		static String decimals = ",";
#endif
		n++;
		result.Cat(decimals);
		const char *s = n;
		while(IsDigit(*n))
			n++;
		result.Cat(s, n - s);
	}
	if(*(n = NlsCopyDigits(n, result)))
		result.Cat(n);
	return result;
}
*/

String NlsFormat(int value) { return GetLanguageInfo().FormatInt(value); }
/*
{
	if(IsNull(value))
		return Null;
	String dest;
	String is = IntStr(value);
	const char *p = NlsCopyDigits(is, dest);
	if(*p)
		dest.Cat(p);
	return dest;
}
*/

String NlsFormat(double value, int decimal_places)
{ return GetLanguageInfo().FormatDouble(value, tabs(decimal_places), decimal_places >= 0 ? 0 : FD_ZERO); }
/*
String NlsFormat(double value, int decimal_places)
{
	return NlsFormatRaw(FormatDouble(value, tabs(decimal_places), decimal_places >= 0 ? 0 : FD_ZERO));
}
*/

String NlsFormatRel(double value, int relative_places)
{ return GetLanguageInfo().FormatDouble(value, tabs(relative_places), FD_REL | (relative_places >= 0 ? 0 : FD_ZERO)); }

/*
String NlsFormatRel(double value, int relative_places)
{
	return NlsFormatRaw(FormatDouble(value, tabs(relative_places), FD_REL | (relative_places >= 0 ? 0 : FD_ZERO)));
}
*/

static String CvDbl(double d, int places, bool nls)
{
	if(nls)
		return NlsFormatRel(d, places);
	else
		return FormatDouble(d, places, FD_REL);
}

String FormatFraction(double factor, const char *pattern)
{
	if(IsNull(factor))
		return Null;
	if(factor >= 1)
		return NFormat(pattern, factor) + " : 1";
	else if(factor > 0)
		return "1 : " + NFormat(pattern, 1 / factor);
	else
		return NFormat(pattern, factor);
}

Value ConvertFraction::Format(const Value& value) const
{
//	RLOG("ConvertFraction(" << StdFormat(value) << ")");
	if(IsNull(value) || IsString(value))
		return value;
	return FormatFraction(value, pattern);
}

GLOBAL_VARP(const ConvertFraction, StdConvertFraction, (Null, Null, false))

Value ConvertFraction::Scan(const Value& value) const
{
	String s = value;
	const char *p = s;
	while(*p == ' ')
		p++;
	if(*p == 0)
		return notnull ? ErrorValue(t_("Null value not allowed.")) : Null;

	double d = ScanDouble(p, &p);
	if(IsNull(d))
		return ErrorValue(t_("Real number expected."));
	while(*p == ' ')
		p++;
	if(*p == ':')
	{
		while(*++p == ' ')
			;
		double n = ScanDouble(p, &p);
		if(IsNull(n))
			return ErrorValue(t_("Real number expected."));
		if(fabs(n) <= 1e-100)
			return ErrorValue(t_("Fraction denominator is too small."));
		d /= n;
		while(*p == ' ')
			p++;
	}
	if(*p)
		return ErrorValue(NFormat(t_("Illegal trailing character '%c' (0x%1:02x)"), (int)(byte)*p));
	if(!IsNull(minval) && d < minval)
		return ErrorValue(t_("Lower bound exceeded."));
	if(!IsNull(maxval) && d > maxval)
		return ErrorValue(t_("Upper bound exceeded."));

	return d;
}

int ConvertFraction::Filter(int c) const
{
	return c == ':' ? c : ConvertDouble::Filter(c);
}

class CFormatConvertCls : public Convert
{
public:
	virtual Value Format(const Value& v) const;
	virtual Value Scan(const Value& v) const;
};

Value CFormatConvertCls::Format(const Value& v) const
{
	String vs = StdFormat(v);
	String out;
//	out.SetCharset(vs.GetCharset());
	for(const char *p = vs, *e = vs.End(); p < e; p++)
		if((byte)*p >= ' ' && *p != '\x7F' && *p != '\xFF' && *p != '\\')
			out.Cat(*p);
		else
			switch(*p)
			{
			case '\\': out << "\\\\"; break;
			case '\v': out << "\\v"; break;
			case '\f': out << "\\f"; break;
			case '\t': out << "\\t"; break;
			case '\n': out << "\\n"; break;
			case '\r': out << "\\r"; break;
			case '\a': out << "\\a"; break;
			case '\b': out << "\\b"; break;
			default:
				if(*p >= 0 && *p <= 7 && (p + 1 >= e || !IsDigit(p[1])))
				{
					out.Cat('\\');
					out.Cat('0' + *p);
				}
				else
				{
					out.Cat("\\x");
					out.Cat(FormatIntHex((byte)*p, 2));
				}
				break;
			}
	return out;
}

Value CFormatConvertCls::Scan(const Value& v) const
{
	String vs = StdFormat(v);
	String out;
//	out.SetCharset(vs.GetCharset());
	for(const char *p = vs; *p; p++)
		if(*p != '\\')
			out.Cat(*p);
		else
			switch(*++p)
			{
			case 0: return out;
			case 'v':  out.Cat('\v'); break;
			case 'f':  out.Cat('\f'); break;
			case 't':  out.Cat('\t'); break;
			case 'n':  out.Cat('\n'); break;
			case 'r':  out.Cat('\r'); break;
			case 'a':  out.Cat('\a'); break;
			case 'b':  out.Cat('\b'); break;
			case '\\': out.Cat('\\'); break;
			case 'x':
				if(IsXDigit(p[1]))
				{
					char hex = ctoi(*++p);
					if(IsXDigit(p[1]))
						hex = hex * 16 + ctoi(*++p);
					out.Cat(hex);
				}
				else
					out.Cat(*p);
				break;

			default:
				if(*p >= '0' && *p <= '7')
				{
					char oct = *p - '0';
					if(p[1] >= '0' && p[1] <= '7')
					{
						oct = 8 * oct + *++p - '0';
						if(p[1] >= '0' && p[1] <= '7')
							oct = 8 * oct + *++p - '0';
					}
					out.Cat(oct);
				}
				else
					out.Cat(*p);
				break;
			}
	return out;
}

const Convert& CFormatConvert()
{
	static CFormatConvertCls cls;
	return cls;
}

String FormatIntCsPlural(int i, const char *b)
{
	if(IsNull(i))
		return Null;
	String out;
	out << NlsFormat(i) << ' ';
	i = tabs(i) % 100;
	if(i < 10 || i > 20)
		i %= 10;
	const char *p = b;
	while(*p && *p != '/')
		p++;
	if(p > b)
		out.Cat(b, p - b);
	if(*p)
		p++;
	b = p;
	while(*p && *p != '/')
		p++;
	if(i == 1)
	{
		out.Cat(b, p - b);
		return out;
	}
	if(*p)
		p++;
	b = p;
	while(*p && *p != '/')
		p++;
	if(i >= 2 && i <= 4)
	{
		out.Cat(b, p - b);
		return out;
	}
	if(*p)
		p++;
	out.Cat(p);
	return out;
}

/* TRC 7/12/2003: moved to Core/Convert.cpp
int ScanInt(const char *ptr, const char **endptr, int base)
{
	const char *s = ptr;
	bool minus = false;
	while(*s && (byte)*s <= ' ')
		s++;
	if(*s == '+' || *s == '-')
		minus = (*s++ == '-');
	unsigned u = stou(s, endptr, base);
	if(~u)
		return (minus ? -(int)u : (int)u);
	else
		return Null;
}
*/

/*
unsigned ctoi(char c)
{
	if(c >= '0' && c <= '9')
		return c - '0';
	if(c >= 'A' && c <= 'Z')
		return c - 'A' + 10;
	if(c >= 'a' && c <= 'z')
		return c - 'a' + 10;
	return (unsigned)-1;
}
*/

/*
unsigned stou(const void *ptr, void *endptr, unsigned base)
{
	ASSERT(base >= 2 && base <= 36);
	const char *s = (const char *)ptr;
	unsigned digit = ctoi(*s);
	if(digit >= base)
	{ // error
		if(endptr)
			*(const char **)endptr = s;
		return ~0;
	}
	unsigned value = digit;
	while((digit = ctoi(*++s)) < base)
		value = value * base + digit;
	if(endptr)
		*(const char **)endptr = s;
	return value;
}
*/

/* TRC 7/12/2003: moved to Core/Convert.cpp
double ScanDouble(const char *ptr, const char **endptr)
{
	static const char *dummy;
	const char *s = ptr;
	const char **end = (endptr ? endptr : &dummy);
	String temp;
	while(*s && (byte)*s <= ' ')
		s++;
	if(*s == '+' || *s == '-')
		temp << *s++;
	const char *last = s;
	if(IsDigit(*s))
	{
		while(IsDigit(*++s))
			;
		temp.Cat(last, s - last);
		last = s;
		if(*s == '.')
		{
			last++;
			if(IsDigit(*++s))
			{
				temp << '.';
				while(IsDigit(*++s))
					;
			}
		}
	}
	else if(*s == '.')
	{
		if(!IsDigit(*++s))
		{ // error
			*end = s;
			return Null;
		}
		temp << "0.";
		last = s;
		while(IsDigit(*s))
			temp.Cat((char)*s++);
	}
	else
	{
		*end = ptr;
		return Null;
	}
	temp.Cat(last, s - last);
	if(*s == 'e' || *s == 'E')
	{
		s++;
		temp << 'e';
		if(*s == '+' || *s == '-')
			temp << *s++;
		if(!IsDigit(*s))
		{ // error in exponent
			*end = s;
			return Null;
		}
		last = s;
		while(IsDigit(*++s))
			;
		temp.Cat(last, s - last);
	}
	last = NULL;
	errno = 0;
	double result = strtod(temp, (char **)&last);
	*end = s;
	if(errno || last != (const char *)temp + temp.GetLength())
		result = Null;
	return result;
}
*/

/* TRC 7/12/2003: moved to Core/Convert.cpp
double ScanDouble(const wchar *ptr, const wchar **endptr)
{
	static const wchar *dummy;
	const wchar *s = ptr;
	const wchar **end = (endptr ? endptr : &dummy);
	String temp;
	if(*s == '+' || *s == '-')
		temp << (char)*s++;
	if(*s <= 2047 && IsDigit(*s))
	{
		while(*s <= 2047 && IsDigit(*s))
			temp.Cat((char)*s++);
		if(*s == '.')
		{
			if(*s <= 2047 && IsDigit(*++s))
			{
				temp.Cat('.');
				while(*s <= 2047 && IsDigit(*s))
					temp.Cat((char)*s++);
			}
		}
	}
	else if(*s == '.')
	{
		if(*s > 2047 || !IsDigit(*++s))
		{ // error
			*end = s;
			return Null;
		}
		temp.Cat("0.");
		while(*s <= 2047 && IsDigit(*s))
			temp.Cat((char)*s++);
	}
	else
	{
		*end = ptr;
		return Null;
	}
	if(*s == 'e' || *s == 'E')
	{
		s++;
		temp << 'e';
		if(*s == '+' || *s == '-')
			temp << *s++;
		if(!IsDigit(*s))
		{ // error in exponent
			*end = s;
			return Null;
		}
		while(IsDigit(*s))
			temp.Cat((char)*s++);
	}
	errno = 0;
	char *endp;
	double result = strtod(temp, &endp);
	*end = s;
	if(errno || endp != temp.End())
		result = Null;
	return result;
}
*/

/*
double stod(const void *ptr, void *endptr, bool comma)
{
	static const char *dummy;
	const char *s = (const char *)ptr;
	const char **end = (endptr ? (const char **)endptr : &dummy);
	String temp;
	if(*s == '+' || *s == '-')
		temp << *s++;
	const char *last = s;
	if(IsDigit(*s))
	{
		while(IsDigit(*++s))
			;
		temp.Cat(last, s - last);
		last = s;
		if(*s == '.' || comma && *s == ',')
		{
			last++;
			if(IsDigit(*++s))
			{
				temp << '.';
				while(IsDigit(*++s))
					;
			}
		}
	}
	else if(*s == '.' || comma && *s == ',')
	{
		if(!IsDigit(*++s))
		{ // error
			*end = s;
			return Null;
		}
		temp << "0.";
		last = s;
		while(IsDigit(*++s))
			;
	}
	else
	{
		*end = (const char *)ptr;
		return Null;
	}
	temp.Cat(last, s - last);
	if(*s == 'e' || *s == 'E')
	{
		s++;
		temp << 'e';
		if(*s == '+' || *s == '-')
			temp << *s++;
		if(!IsDigit(*s))
		{ // error in exponent
			*end = s;
			return Null;
		}
		last = s;
		while(IsDigit(*++s))
			;
		temp.Cat(last, s - last);
	}
	last = NULL;
	errno = 0;
	double result = strtod(temp, (char **)&last);
	*end = s;
	if(errno || last != (const char *)temp + temp.GetLength())
		result = Null;
	return result;
}
*/

bool SaveFileChanges(String fn, String data)
{
	return LoadFile(fn) == data || SaveFile(fn, data);
}

bool SaveFileBackup(String fn, String data, bool keep_backup)
{
	if(LoadFile(fn) == data)
		return true;
	String tfn = fn + ".$old";
	FileDelete(tfn);
	FileMove(fn, tfn);
	bool res = SaveFile(fn, data);
	if(!res)
		FileDelete(fn);
	else if(!keep_backup)
		FileDelete(tfn);
	return res;
}

bool FileFlush(FileStream& stream)
{
	ASSERT(stream.IsOpen());
#ifdef PLATFORM_WIN32
	return !!FlushFileBuffers(stream.GetHandle());
#endif
#ifdef PLATFORM_POSIX
	return !fsync(stream.GetHandle());
#endif
}
bool EqualsPath(const char *a, const char *b, int length)
{
	if(!length)
		return true;
#ifdef PLATFORM_WIN32
	return !MemICmp(a, b, length);
#else
	return !memcmp(a, b, length);
#endif
}

bool EqualsPath(String fa, String fb)
{
	return fa.GetLength() == fb.GetLength() && EqualsPath(fa, fb, fa.GetLength());
}

String MakePathLower(String path)
{
#ifdef PLATFORM_WIN32
	return ToLower(path);
#endif
#ifdef PLATFORM_POSIX
	return path;
#endif
}

VectorMap<String, String> LoadKeyMap(const char *p)
{
	VectorMap<String, String> map;
	while(*p)
	{
		while(*p && (byte)*p <= ' ')
			p++;
		if(*p != '#')
		{
			const char *b = p;
			while(*p && *p != '\n' && *p != '=')
				p++;
			if(*p == '=')
			{
				const char *e = p++;
				while(e > b && (byte)e[-1] <= ' ')
					e--;
				String key(b, e);
				b = p;
				while(*p && *p != '\n')
					p++;
				e = p;
				while(e > b && (byte)e[-1] <= ' ')
					e--;
				map.Add(key, String(b, e));
			}
		}
		while(*p && *p++ != '\n')
			;
	}
	return map;
}

VectorMap<String, String> LoadKeyMapFile(const char *filename)
{
	return LoadKeyMap(LoadFile(filename));
}

String GetFileOnSystemPath(const char *file)
{
#ifdef PLATFORM_WIN32
	char temp[MAX_PATH], *dummy;
	*temp = 0;
	SearchPath(NULL, file, NULL, sizeof(temp), temp, &dummy);
	return temp;
#endif
#ifdef PLATFORM_POSIX
//	puts(STR "SearchPath(" << filename << ", " << path << ")");
	if(strchr(file, '/'))
	{
//		puts("-> full path");
		return file; // full path
	}

	if(const char *path = getenv("PATH"))
	{
//		puts(STR "-> on path: " << path);
		String ulist = String::GetVoid();
		for(const char *s = path; *s;)
			if(*s != ':' && *s != ';')
			{
				const char *b = s;
				while(*s && *s != ':' && *s != ';')
					s++;
				String trypath = AppendFileName(String(b, s), file);
//				puts(STR "-> trypath = " << trypath);
				if(FileExists(trypath))
					return trypath;
			}
			else
				s++;
	}
//	puts("-> not found");
	return file;
#endif
}

const char *FetchCmdArg(const char *arg, int& i)
{
	if(!*arg) {
		const Vector<String>& cmd = CommandLine();
		if(++i >= cmd.GetCount())
			throw Exc(NFormat("option '%s' missing argument", cmd[i - 1]));
		arg = cmd[i];
	}
	return arg;
}

#ifdef PLATFORM_WIN32
String SearchPath(String file)
{
	return GetFileOnPath(file, Environment().Get("PATH", Null));
}

String InstallServiceCmd(String service_name, String app_name, String arguments)
{
	String spath = "SYSTEM\\CurrentControlSet\\Services\\" + service_name;
	String s = GetWinRegString("ImagePath", spath, HKEY_LOCAL_MACHINE);
	if(IsNull(s))
	{
		String instsrv = SearchPath("instsrv.exe");
		if(IsNull(instsrv))
			return t_("'instsrv.exe' not found.");
		String srvany = SearchPath("srvany.exe");
		if(IsNull(srvany))
			return t_("'svrany.exe' not found.");
		String cmdline = instsrv + " \"" + service_name + "\" " + srvany;
		if(WinExec(cmdline, SW_SHOW) <= 31)
			return NFormat(t_("Error running '%s'."), instsrv);
		while(IsNull(s = GetWinRegString("ImagePath", spath, HKEY_LOCAL_MACHINE)))
			Sleep(1000);
	}

	spath << "\\Parameters";
	SetWinRegString(app_name, "Application", spath, HKEY_LOCAL_MACHINE);
	SetWinRegString(arguments, "AppParameters", spath, HKEY_LOCAL_MACHINE);
	return Null;
}
#endif

String AppendPath(String s, String new_path)
{
	String np = new_path;
	if(np.IsEmpty())
		return s;
	if(*np.Last() == DIR_SEP)
		np.Trim(np.GetLength() - 1);
	const char *p = s;
	while(*p) {
		const char *b = p;
		while(*p && *p != ';')
			p++;
		int dl = p - b - np.GetLength();
		if((unsigned)dl <= 1u && !ComparePath(b, np, np.GetLength()))
			return s; // path found
		if(*p)
			p++;
	}
	String ns = s;
	if(!IsNull(ns) && *ns.Last() != ';')
		ns.Cat(';');
	ns.Cat(np);
	return ns;
}

String AppendPathList(String s, String path_list)
{
	String result = s;
	const char *p = path_list;
	while(*p) {
		const char *s = p;
		while(*p && *p != ';')
			p++;
		if(p != s)
			result = AppendPath(result, String(s, p));
		if(*p)
			p++;
	}
	return result;
}

END_UPP_NAMESPACE
