//////////////////////////////////////////////////////////////////////
// tadv/CalcBasic.cpp: basic calculator operations.

#include "TCoreCalc.h"
#pragma hdrstop
#pragma BLITZ_APPROVE

#include "CalcBasic.h"

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

NAMESPACE_UPP

void UseCalcBasic() {} // to bring in globally constructed objects

String PluralFormat(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;
}

String GroupComp()   { return t_("Comparison"); }
String GroupBitOp()  { return t_("Bit field operations"); }
String GroupArith()  { return t_("Arithmetical operations"); }
String GroupLog()    { return t_("Logical operations"); }
String GroupString() { return t_("Text operations"); }
String GroupDate()   { return t_("Date operations"); }
String GroupArray()  { return t_("Array functions"); }
String GroupConst()  { return t_("Constants"); }
String GroupTrans()  { return t_("Transcendental functions"); }
String GroupSystem() { return t_("System functions"); }

//////////////////////////////////////////////////////////////////////
// equality / inequality

inline bool CeqVV(Value a, Value b) { return a == b; }
inline bool CneVV(Value a, Value b) { return a != b; }

FDECL("==", CeqVV, &GroupComp)
FDECL("!=", CneVV, &GroupComp)

inline bool CltNN(double a, double b) { return a < b; }
inline bool CleNN(double a, double b) { return a <= b; }
inline bool CgtNN(double a, double b) { return a > b; }
inline bool CgeNN(double a, double b) { return a >= b; }

FDECL("<",  CltNN, &GroupComp)
FDECL("<=", CleNN, &GroupComp)
FDECL(">",  CgtNN, &GroupComp)
FDECL(">=", CgeNN, &GroupComp)

inline bool CltTT(Time a, Time b) { return a < b; }
inline bool CleTT(Time a, Time b) { return a <= b; }
inline bool CgtTT(Time a, Time b) { return a > b; }
inline bool CgeTT(Time a, Time b) { return a >= b; }

FDECL("<",  CltTT, &GroupComp)
FDECL("<=", CleTT, &GroupComp)
FDECL(">",  CgtTT, &GroupComp)
FDECL(">=", CgeTT, &GroupComp)

inline bool CltSS(CalcPacket& packet, WString a, WString b) { return GetLanguageInfo(packet.context.language).Compare(a, b) < 0; }
inline bool CleSS(CalcPacket& packet, WString a, WString b) { return GetLanguageInfo(packet.context.language).Compare(a, b) <= 0; }
inline bool CgtSS(CalcPacket& packet, WString a, WString b) { return GetLanguageInfo(packet.context.language).Compare(a, b) > 0; }
inline bool CgeSS(CalcPacket& packet, WString a, WString b) { return GetLanguageInfo(packet.context.language).Compare(a, b) >= 0; }

FDECLA("<",  CltSS, &GroupString)
FDECLA("<=", CleSS, &GroupString)
FDECLA(">",  CgtSS, &GroupString)
FDECLA(">=", CgeSS, &GroupString)

//////////////////////////////////////////////////////////////////////
// bit-op's

inline int CbitAndNN(int a, int b) { return a & b; }
inline int CbitOrNN(int a, int b)  { return a | b; }
inline int CbitXorNN(int a, int b) { return a ^ b; }

FDECL0("&", CbitAndNN, &GroupBitOp)
FDECL0("|", CbitOrNN, &GroupBitOp)
FDECL0("^", CbitXorNN, &GroupBitOp)

//////////////////////////////////////////////////////////////////////
// unaries

inline double CplusN(double value)      { return +value; }
inline double CminusN(double value)     { return -value; }
inline int    CbitNotN(int value)       { return ~value; }
inline bool   ClogNotN(double value)    { return value ? 0 : 1; }
inline bool   ClogNotS(WString s)       { return s.IsEmpty(); }

FDECL0("+", CplusN, &GroupArith)
FDECL0("-", CminusN, &GroupArith)
FDECL0("~", CbitNotN, &GroupBitOp)
FDECL("!", ClogNotS, &GroupLog)
FDECL("!", ClogNotN, &GroupLog)

//////////////////////////////////////////////////////////////////////
// additives

inline double  CaddNN(double a, double b)               { return a + b; }
FDECL0("+", CaddNN, &GroupArith)

inline WString CaddSS(WString a, WString b)             { return a + b; }
FDECL ("+", CaddSS, &GroupString)

inline Time    CaddDN(Time a, double b)                 { return a + fround64(b * 86400.0 + 0.5); }
FDECL0("+", CaddDN, &GroupDate)

inline Time    CaddND(double a, Time b)                 { return b + fround64(a * 86400.0 + 0.5); }
FDECL0("+", CaddND, &GroupDate)

inline double CsubNN(double a, double b)               { return a - b; }
inline Time   CsubDN(Time a, double b)                 { return a - fround64(b * 86400.0); }
inline double CsubDD(Time a, Time b)                   { return (a - b) / 86400.0; }

FDECL0("-", CsubNN, &GroupArith)
FDECL0("-", CsubDN, &GroupDate)
FDECL0("-", CsubDD, &GroupDate)

//////////////////////////////////////////////////////////////////////
// shifts

inline int CsalNN(int a, int b) { return a << b; }
inline int CsarNN(int a, int b) { return a >> b; }

FDECL0("<<", CsalNN, &GroupBitOp)
FDECL0(">>", CsarNN, &GroupBitOp)

//////////////////////////////////////////////////////////////////////
// multiplicatives

inline double CmulNN(double a, double b)               { return a * b; }
inline WString CmulSN(WString s, int i)
{
	if(i <= 0 || IsNull(s))
		return Null;
	if(i == 1)
		return s;
	int len = s.GetLength() * i;
	WStringBuffer result(len);
	memsetex(result, s, sizeof(wchar) * s.GetLength(), i);
	return result;
}
inline WString CmulNS(int i, WString s) { return CmulSN(s, i); }

FDECL0("*", CmulNN, &GroupArith)
FDECL0("*", CmulSN, &GroupString)
FDECL0("*", CmulNS, &GroupString)

inline double CdivNN(double a, double b)
{
	if(!b)
		throw Exc(t_("division by zero"));
	return a / b;
}

FDECL0("/", CdivNN, &GroupArith)

inline double CmodNN(double a, double b)
{
	if(!b)
		throw Exc(t_("division by zero"));
	return fmod(a, b);
}

FDECL0("%", CmodNN, &GroupArith)

//////////////////////////////////////////////////////////////////////
// rounding

static void ChkDigits(int c)
{
	if(c < -20 || c >= 20)
		throw Exc(NFormat(t_("invalid number of places (%d)"), c));
}

inline double GetTimes(int c)
{
	ChkDigits(c);
	return ipow10(c);
}

inline double CroundN(double a)          { return floor(a + 0.5); }
inline double CroundNN(double a, int c)  { double t = GetTimes(c); return floor(a * t + 0.5) / t; }
inline double CroundrNN(double a, int c) { ChkDigits(c); return roundr(a, c); }

FDECLP0(round, N, &GroupArith)
FDECLP0(round, NN, &GroupArith)
FDECLP0(roundr, NN, &GroupArith)

inline double CfloorN(double a)          { return floor(a); }
inline double CfloorNN(double a, int c)  { double t = GetTimes(c); return floor(a * t) / t; }
inline double CfloorrNN(double a, int c) { ChkDigits(c); return floorr(a, c); }

FDECLP0(floor, N, &GroupArith)
FDECLP0(floor, NN, &GroupArith)
FDECLP0(floorr, NN, &GroupArith)

inline double CceilN(double a)          { return ceil(a); }
inline double CceilNN(double a, int c)  { double t = GetTimes(c); return ceil(a * t) / t; }
inline double CceilrNN(double a, int c) { ChkDigits(c); return ceilr(a, c); }

FDECLP0(ceil, N, &GroupArith)
FDECLP0(ceil, NN, &GroupArith)
FDECLP0(ceilr, NN, &GroupArith)

inline double CminNN(double a, double b) { return min(a, b); }
inline double CmaxNN(double a, double b) { return max(a, b); }
inline double CminmaxNNN(double a, double l, double h) { return minmax(a, l, h); }

FDECLP(min, NN, &GroupComp)
FDECLP(max, NN, &GroupComp)
FDECLP(minmax, NNN, &GroupComp)

inline Time CminTT(Time a, Time b) { return min(a, b); }
inline Time CmaxTT(Time a, Time b) { return max(a, b); }
inline Time CminmaxTTT(Time a, Time l, Time h) { return minmax(a, l, h); }

FDECLP(min, TT, &GroupComp)
FDECLP(max, TT, &GroupComp)
FDECLP(minmax, TTT, &GroupComp)

inline WString CminSS(CalcPacket& packet, WString a, WString b)
{
	return GetLanguageInfo(packet.context.language).Compare(a, b) <= 0 ? a : b;
}

inline WString CmaxSS(CalcPacket& packet, WString a, WString b)
{
	return GetLanguageInfo(packet.context.language).Compare(a, b) >= 0 ? a : b;
}

inline WString CminmaxSSS(CalcPacket& packet, WString a, WString l, WString h)
{
	return CminSS(packet, CmaxSS(packet, a, l), h);
}

FDECLAP(min, SS, &GroupComp)
FDECLAP(max, SS, &GroupComp)
FDECLAP(minmax, SSS, &GroupComp)

inline double Ce0()  { return M_E; }
inline double Cpi0() { return M_PI; }

FDECLP(e, 0, &GroupConst)
FDECLP(pi, 0, &GroupConst)

inline double CabsN(double x) { return fabs(x); }

FDECLP(abs, N, &GroupArith)

inline int    CsgnN(double x) { return sgn(x); }

FDECLP(sgn, N, &GroupArith)

inline int      CascS(WString s) { return (byte)*s; }
inline WString  CchrN(int c)     { return WString(c, 1); }
inline int      ClenS(WString s) { return s.GetLength(); }

FDECLP(asc, S, &GroupString)
FDECLP(chr, N, &GroupString)
FDECLP(len, S, &GroupString)

inline String CcountNS(int i, WString s)
{
	return PluralFormat(i, s.ToString());
}

FDECLP(count, NS, &GroupString)

inline String        CnlsN(double d)               { return NlsFormat(d, 3); }
inline String        CnlsNN(double d, int places)  { return NlsFormat(d, places); }
inline String        CnlsrN(double d)              { return NlsFormatRel(d, 3); }
inline String        CnlsrNN(double d, int places) { return NlsFormatRel(d, places); }

FDECLP0(nls, N, &GroupString)
FDECLP0(nls, NN, &GroupString)
FDECLP0(nlsr, N, &GroupString)
FDECLP0(nlsr, NN, &GroupString)

inline String        CtextN(double d)               { return FormatDouble(d, 10); }
inline String        CitextN(int i)                 { return FormatInt(i); }
inline String        CitextNN(int i, int p)         { return FormatIntDec(i, p, ' '); }
inline String        Citext0NN(int i, int p)        { return FormatIntDec(i, p, '0'); }
inline String        CftextN(double d)              { return AsString(d, 0); }
inline String        CftextNI(double d, int places) { return AsString(d, places); }

FDECLP(text, N, &GroupString)
FDECLP0(itext, N, &GroupString)
FDECLP0(itext, NN, &GroupString)
FDECLP0(itext0, NN, &GroupString)
FDECLP0(ftext, N, &GroupString)
FDECLP0(ftext, NI, &GroupString)

inline WString CtextS(WString s) { return s; }

inline String  CtextD(CalcPacket& packet, Time d)
{ return GetLanguageInfo(packet.context.language).FormatDate(d); }

FDECLP(text, S, &GroupString)
FDECLAP(text, D, &GroupString)

inline String CtextA(CalcPacket& packet, ValueArray va)
{
	String out;
	out << "[";
	for(int i = 0; i < va.GetCount(); i++)
		out << (i ? ", " : "") << packet.context.FormatText(va[i]);
	out << "]";
	return out;
}

FDECLAP(text, A, &GroupString)

inline WString CleftSN(WString s, int n)  { return s.Left(minmax(n, 0, s.GetLength())); }
inline WString CrightSN(WString s, int n) { return s.Right(minmax(n, 0, s.GetLength())); }
inline WString CmidSNN(WString s, int m, int n)
{
	m = minmax(m, 0, s.GetLength());
	n = minmax(n, 0, s.GetLength() - m);
	return s.Mid(m, n);
}

inline WString CmidSN(WString s, int m) { return CmidSNN(s, m, max(0, s.GetLength() - m)); }

FDECLP(left, SN, &GroupString)
FDECLP(right, SN, &GroupString)
FDECLP(mid, SN, &GroupString)
FDECLP(mid, SNN, &GroupString)

inline WString CcatSSS(WString a, WString b, WString c)
{
	if(IsNull(c))
		return a;
	if(IsNull(a))
		return c;
	return a + b + c;
}

FDECLP(cat, SSS, &GroupString)

inline WString ClpadSNS(WString a, int len, WString b)
{
	int al = a.GetLength(), bl = b.GetLength(), add = len - al;
	if(bl == 0 || add <= 0)
		return a;
	WString out;
	if(bl == 1)
		out.Cat(*b, add);
	else
	{
		while(add >= bl)
		{
			out.Cat(b);
			add -= bl;
		}
		if(add > 0)
			out.Cat(b, add);
	}
	out.Cat(a);
	return out;
}

FDECLP(lpad, SNS, &GroupString)

inline WString ClpadSN(WString a, int len) { return ClpadSNS(a, len, WString(' ', 1)); }

FDECLP(lpad, SN, &GroupString)

WString CllpadSNS(WString a, int len, WString b)
{
	if(a.GetLength() > len)
		return a.Right(len);
	return ClpadSNS(a, len, b);
}

FDECLP(llpad, SNS, &GroupString)

inline WString CllpadSN(WString a, int len) { return CllpadSNS(a, len, WString(' ', 1)); }

FDECLP(llpad, SN, &GroupString)

inline WString CrpadSNS(WString a, int len, WString b)
{
	int al = a.GetLength(), bl = b.GetLength(), add = len - al;
	if(bl == 0 || add <= 0)
		return a;
	if(bl == 1)
		a.Cat(*b, add);
	else
	{
		while(add >= bl)
		{
			a.Cat(b);
			add -= bl;
		}
		if(add > 0)
			a.Cat(b, add);
	}
	return a;
}

FDECLP(rpad, SNS, &GroupString)

inline WString CrpadSN(WString a, int len) { return CrpadSNS(a, len, WString(' ', 1)); }

FDECLP(rpad, SN, &GroupString)

inline WString CrrpadSNS(WString a, int len, WString b)
{
	if(a.GetLength() > len)
	{
		a.Trim(len);
		return a;
	}
	return CrpadSNS(a, len, b);
}

FDECLP(rrpad, SNS, &GroupString)

inline WString CrrpadSN(WString a, int len) { return CrrpadSNS(a, len, WString(' ', 1)); }

FDECLP(rrpad, SN, &GroupString)

inline double CnumberS(String s)
{
	const char *p = s;
	double d = ScanDouble(p, &p);
	if(IsNull(d) || *p)
		throw Exc(NFormat(t_("invalid numeric format: '%s'"), s));
	return d;
}

inline double CnumberSN(String s, double dflt)
{
	const char *p = s;
	double d = ScanDouble(p, &p);
#ifdef PLATFORM_WIN32
	return (IsNull(d) || !_finite(d) || _isnan(d) || *p ? dflt : d);
#elif defined(PLATFORM_POSIX)
	return (IsNull(d) || !finite(d) || *p ? dflt : d); // PLATFORM_SOLARIS
//	return (IsNull(d) || isinf(d) || isnan(d) || *p ? dflt : d);
#else
	#error
#endif
}

FDECLP(number, S, &GroupString)
FDECLP(number, SN, &GroupString)

inline bool IsValidYear(int year) { return year >= -4000 && year <= +4000; }

inline Date CdateNNN(int day, int month, int year)
{
	if(!IsValidYear(year))       throw Exc(NFormat(t_("invalid year number (%d)"), year));
	if(month <= 0 || month > 12) throw Exc(NFormat(t_("invalid month number (%d)"), month));
	Date date(year, month, 1);
	if(day <= 0 || day > LastDayOfMonth(date).day)
		throw Exc(NFormat(t_("invalid day number (%d)"), day));
	date.day = day;
	return date;
}

inline Date CdateNN(int day, int month) { return CdateNNN(day, month, GetSysDate().year); }
inline Date CdateN(int day)
{
	Date date = GetSysDate();
	return CdateNNN(day, date.month, date.year);
}
inline Date Cdate0() { return GetSysDate(); }

FDECLP0(date, 0, &GroupDate)
FDECLP0(date, N, &GroupDate)
FDECLP0(date, NN, &GroupDate)
FDECLP0(date, NNN, &GroupDate)

inline Date CdateSD(CalcPacket& packet, String s, Date dflt)
{
	Date out;
	const char *p = StrToDate(out, s);
	return p ? out : dflt;
}

inline Date CdateS(CalcPacket& packet, String s)
{
	return CdateSD(packet, s, Null);
}

inline Date CdateT(Time t)
{
	return t;
}

FDECLAP(date, S, &GroupDate)
FDECLAP(date, SD, &GroupDate)
FDECLP(date, T, &GroupDate)

inline Time Ctime0() { return GetSysTime(); }

FDECLP0(time, 0, &GroupDate)

bool CtimeNNNNNN(CalcPacket& packet)
{
	if(packet.help)
	{
		packet.args.SetCount(6, CalcType<int>::Describe());
		packet.result = CalcType<Time>::Describe();
		return true;
	}
	if(packet.args.GetCount() != 6)
		return false;
	bool isnull = false;
	for(int i = 0; i < 6; i++)
		if(IsNull(packet.args[i]))
			isnull = true;
		else if(!CalcType<int>::IsType(packet.args[i]))
			return false;
	if(isnull)
	{
		packet.result = Value();
		return true;
	}
	Date d = CdateNNN(packet.args[0], packet.args[1], packet.args[2]);
	int h = packet.args[3], m = packet.args[4], s = packet.args[5];
	if((unsigned)h >= 24)
		throw Exc(NFormat(t_("invalid hour number: %d"), h));
	if((unsigned)m >= 60)
		throw Exc(NFormat(t_("invalid minute number: %d"), m));
	if((unsigned)s >= 60)
		throw Exc(NFormat(t_("invalid second number: %d"), s));
	packet.result = Time(d.year, d.month, d.day, m, h, s);
	return true;
}

FDECLQ("CtimeNNNNNN", "time", &GroupDate, &CtimeNNNNNN)

inline Time CtimeSD(CalcPacket& packet, String s, Time dflt)
{
	return Nvl((Time)Scan(TIME_V, s), dflt);
}

inline Time CtimeS(CalcPacket& packet, String s)
{
	return CtimeSD(packet, s, Null);
}

inline Time CtimeD(Date d)
{
	return ToTime(d);
}

FDECLAP(time, S, &GroupDate)
FDECLAP(time, SD, &GroupDate)
FDECLP(time, D, &GroupDate)

inline int CyearD(Date date)    { return date.year; }
inline int CmonthD(Date date)   { return date.month; }
inline int CdayD(Date date)     { return date.day; }
inline int ChourT(Time time)    { return time.hour; }
inline int CminuteT(Time time)  { return time.minute; }
inline int CsecondT(Time time)  { return time.second; }

FDECLP0(year, D, &GroupDate)
FDECLP0(month, D, &GroupDate)
FDECLP0(day, D, &GroupDate)
FDECLP0(hour, T, &GroupDate)
FDECLP0(minute, T, &GroupDate)
FDECLP0(second, T, &GroupDate)

inline Date Cadd_monthsDN(Date date, int months)
{
	months += date.month - 1;
	int year = idivfloor(months, 12);
	months -= 12 * year;
	year += date.year;
	if(!IsValidYear(year))
		throw Exc(t_("result date is out of range"));
	date.year = year;
	date.month = months + 1;
	Date lod = LastDayOfMonth(date);
	if(date.day > lod.day)
		date.day = lod.day;
	return date;
}

FDECLP(add_months, DN, &GroupDate)

inline Date Cadd_yearsDN(Date date, int years)
{
	if(!IsValidYear(date.year + years))
		throw Exc(t_("result date is out of range"));
	date.year += years;
	Date lod = LastDayOfMonth(date);
	if(date.day > lod.day)
		date.day = lod.day;
	return date;
}

FDECLP(add_years, DN, &GroupDate)

static int Cyears_betweenDD(Date a, Date b)
{
	if(IsNull(b) || IsNull(a))
		return Null;
	int d = b.year - a.year;
	if(a.month > b.month || a.month == b.month && a.day > b.day)
		d--;
	return d;
}

FDECLP(years_between, DD, &GroupDate)

inline Value Cnull0()             { return Value(); }
inline int   Cis_errorV(Value v)  { return IsError(v); }
inline int   Cis_nullV(Value v)   { return IsNull(v); }
inline int   Cis_numberV(Value v) { return IsNumber(v); }
inline int   Cis_textV(Value v)   { return IsString(v); }
inline int   Cis_dateV(Value v)   { return IsDateTime(v); }
inline int   Cis_arrayV(Value v)  { return IsValueArray(v); }

FDECLP(null, 0, &GroupConst)
FDECLP(is_number, V, &GroupComp)
FDECLP(is_text, V, &GroupComp)
FDECLP(is_date, V, &GroupComp)
FDECLP(is_null, V, &GroupComp)
FDECLP(is_error, V, &GroupComp)
FDECLP(is_array, V, &GroupComp)

inline Value CnvlVV(Value a, Value b) { return Nvl(a, b); }

FDECLP(nvl, VV, &GroupComp)

//////////////////////////////////////////////////////////////////////
// external globals

inline double CsqrN(double x) { return x * x; }

FDECLP0(sqr, N, &GroupArith)

inline double CsqrtN(double x) { return sqrt(x); }

FDECLP0(sqrt, N, &GroupTrans)

inline double ChypotNN(double x, double y) { return hypot(x, y); }

FDECLP0(hypot, NN, &GroupTrans)

inline double CpowNN(double x, double y) { return pow(x, y); }

FDECLP0(pow, NN, &GroupTrans);

inline double CexpN(double x) { return exp(x); }

FDECLP0(exp, N, &GroupTrans)

inline double ClogN(double x) { return log10(x); }

FDECLP0(log, N, &GroupTrans)

inline double ClnN(double x) { return log(x); }

FDECLP0(ln, N, &GroupTrans)

inline double CsinN(double x) { return sin(x); }

FDECLP0(sin, N, &GroupTrans)

inline double CcosN(double x) { return cos(x); }

FDECLP0(cos, N, &GroupTrans)

inline double CtgN(double x) { return tan(x); }

FDECLP0(tg, N, &GroupTrans)

inline double CarcsinN(double x) { return asin(x); }

FDECLP0(arcsin, N, &GroupTrans)

inline double CarccosN(double x) { return acos(x); }

FDECLP0(arccos, N, &GroupTrans)

inline double CarctgN(double x) { return atan(x); }

FDECLP0(arctg, N, &GroupTrans)

FDECLT(BTA1, std, "Cday_of_weekD", "day_of_week", &GroupDate, DayOfWeek)

FDECLT(BTA1, std, "Cfirst_day_of_monthD", "first_day_of_month", &GroupDate, FirstDayOfMonth)

FDECLT(BTA1, std, "Clast_day_of_monthD", "last_day_of_month", &GroupDate, LastDayOfMonth);

//////////////////////////////////////////////////////////////////////
// arrays

inline bool CalcArrayCtr(CalcPacket& packet)
{
	if(packet.help)
	{
		packet.result = CalcType<ValueArray>::Describe();
		packet.args.Add(CalcType<Value>::Describe());
		packet.args.Add(CalcType<Value>::Describe());
		packet.args.Add("...");
		return true;
	}
	ValueArray va;
	va.SetCount(packet.args.GetCount());
	for(int i = 0; i < packet.args.GetCount(); i++)
		va.Set(i, packet.args[i]);
	packet.result = va;
	return true;
}

FDECLQ("CarrayCtr", "[,,]", &GroupArray, &CalcArrayCtr)

inline ValueArray CarrayRange(double lo, double hi)
{
	double lr = lo, hr = hi, df = ceil(hr - lr + 0.5);
	if(df <= 0)
		return ValueArray();
	if(df >= 1000000)
		throw Exc(NFormat(t_("array is too long (%d elements)"), df));
	ValueArray va;
	int nelem = fround(df);
	va.SetCount(nelem);
	for(int i = 0; i < nelem; i++)
		va.Set(i, i + lr);
	return va;
}

FDECL("[..]", CarrayRange, &GroupArray)

inline Value CarrayIndex(ValueArray va, int index)
{
	if(index < 0 || index >= va.GetCount())
		throw Exc(NFormat(t_("invalid index %d, array has just %d elements"), index, va.GetCount()));
	return va[index];
}

FDECL("[]", CarrayIndex, &GroupArray)

static String CstringIndex(String s, int index)
{
	if(index < 0 || index >= s.GetLength())
		throw Exc(NFormat(t_("invalid index %d, string has just %d characters"), index, s.GetLength()));
	return s.Mid(index, 1);
}

FDECL("[]", CstringIndex, &GroupString)

inline ValueArray CaddAA(ValueArray a, ValueArray b)
{
	int ac = a.GetCount(), bc = b.GetCount();
	ValueArray out = a;
	out.SetCount(ac + bc);
	for(int i = 0; i < bc; i++)
		out.Set(i + ac, b[i]);
	return out;
}
FDECL("+", CaddAA, &GroupArray)

inline Value CsumA(CalcPacket& packet, ValueArray a)
{
	int i = 0;
	while(i < a.GetCount() && IsNull(a[i]))
		i++;
	if(i >= a.GetCount())
		return Value();
	Value first = a[i++];
	if(IsNumber(first))
	{
		double d = first;
		for(; i < a.GetCount(); i++)
			if(IsNull(a[i]))
				;
			else if(IsNumber(a[i]))
				d += double(a[i]);
			else
				throw Exc(NFormat(t_("array element has invalid data type (index %d), number expected: %s"), i, CtextA(packet, a)));
		return d;
	}

	if(IsString(first))
	{
		String s = first;
		for(; i < a.GetCount(); i++)
			if(IsNull(a[i]))
				;
			else if(IsString(a[i]))
				s.Cat(String(a[i]));
			else
				throw Exc(NFormat(t_("array element has invalid data type (index %d), string expected: %s"), i, CtextA(packet, a)));
		return s;
	}

	if(IsValueArray(first))
	{
		ValueArray va = first;
		for(; i < a.GetCount(); i++)
			if(IsNull(a[i]))
				;
			else if(IsValueArray(a[i]))
			{
				ValueArray ai = a[i];
				for(int t = 0, nt = ai.GetCount(); t < nt; t++)
					va.Add(ai[t]);
			}
			else
				throw Exc(NFormat(t_("array element has invalid data type (index %d), array expected: %s"), i, CtextA(packet, a)));
		return va;
	}

	throw Exc(NFormat(t_("array element has invalid data type (index %d), number or string expected: %s"), i, CtextA(packet, a)));
}

FDECLAP(sum, A, &GroupArray)

inline Value CsumASC(CalcPacket& packet, ValueArray va, String var, const CalcNode *node)
{
	if(IsNull(var))
		throw Exc(t_("control variable has empty name"));
	ValueArray vout;
	vout.SetCount(va.GetCount());
	if(node)
	{
		CalcContext::Nesting level(packet.context);
		for(int i = 0; i < va.GetCount(); i++)
		{
			packet.context.Set(var, va[i]);
			vout.Set(i, node -> Calc(packet.context));
		}
	}
	return CsumA(packet, vout);
}

FDECLAP(sum, ASC, &GroupArray)

inline ValueArray CmulAC(CalcPacket& packet, ValueArray va, const CalcNode *node)
{
	ValueArray vout;
	vout.SetCount(va.GetCount());
	if(node)
	{
		CalcContext::Nesting level(packet.context);
		static const String i_("I"), a_("A");
		for(int i = 0; i < va.GetCount(); i++)
		{
			packet.context.Set(i_, i);
			packet.context.Set(a_, va[i]);
			vout.Set(i, node -> Calc(packet.context));
		}
	}
	return vout;
}

FDECLA("*", CmulAC, &GroupArray)

inline int CcountA(ValueArray va) { return va.GetCount(); }
FDECLP(count, A, &GroupArray)

inline Value CmaxA(CalcPacket& packet, ValueArray va)
{
	Value out = Null;
	for(int i = 0; i < va.GetCount(); i++)
		if(!IsNull(va[i]))
			if(IsNull(out) || StdValueLess(out, va[i], packet.context.language))
				out = va[i];
	return out;
}

FDECLAP(max, A, &GroupArray)

inline Value CminA(CalcPacket& packet, ValueArray va)
{
	Value out = Null;
	for(int i = 0; i < va.GetCount(); i++)
		if(!IsNull(va[i]))
			if(IsNull(out) || StdValueLess(va[i], out, packet.context.language))
				out = va[i];
	return out;
}

FDECLAP(min, A, &GroupArray)

inline double CavgA(ValueArray va)
{
	double sum = 0;
	int cnt = 0;
	for(int i = 0; i < va.GetCount(); i++)
		if(!IsNull(va[i]))
			if(IsNumber(va[i]))
			{
				sum += double(va[i]);
				cnt++;
			}
			else
				throw Exc(NFormat("[%d]: očekáváno číslo, nalezeno: %s", i, StdFormat(va[i])));
	return cnt ? sum / cnt : double(Null);
}

FDECLP(avg, A, &GroupArray)

inline ValueArray CleftAN(ValueArray va, int count)
{
	if(count < 0 || count > va.GetCount())
		throw Exc(t_("invalid number of array elements"));
	ValueArray out;
	for(int i = 0; i < count; i++)
		out.Add(va[i]);
	return out;
}
FDECLP(left, AN, &GroupArray)

inline ValueArray CrightAN(ValueArray va, int count)
{
	if(count < 0 || count > va.GetCount())
		throw Exc(t_("invalid number of array elements"));
	ValueArray out;
	for(int i = 0, p = va.GetCount() - count; i < count; i++)
		out.Add(va[p++]);
	return out;
}
FDECLP(right, AN, &GroupArray)

inline ValueArray CmidANN(ValueArray va, int first, int count)
{
	if(first < 0 || first > va.GetCount())
		throw Exc(t_("invalid character index"));
	if(count < 0 || first + count > va.GetCount())
		throw Exc(t_("invalid number of array elements"));
	ValueArray out;
	while(--count >= 0)
		out.Add(va[first++]);
	return out;
}
FDECLP(mid, ANN, &GroupArray)

inline ValueArray CmidAN(ValueArray va, int first)
{
	return CmidANN(va, first, va.GetCount() - first);
}
FDECLP(mid, AN, &GroupArray)

inline bool CinVA(Value v, ValueArray va)
{
	for(int i = 0; i < va.GetCount(); i++)
		if(v == va[i])
			return true;
	return false;
}
FDECLP(in, VA, &GroupArray)

inline ValueArray CindexA(ValueArray a)
{
	ValueArray out;
	int n = a.GetCount();
	out.SetCount(n);
	for(int i = 0; i < n; i++)
		out.Set(i, i);
	return out;
}
FDECLP(index, A, &GroupArray)

struct ValueLess {
	ValueLess(int language) : language(language) {}
	bool operator () (Value a, Value b) const { return StdValueLess(a, b, language); }
	int language;
};

inline ValueArray CsortA(CalcPacket& packet, ValueArray a)
{
	if(a.GetCount() <= 1)
		return a;
	Vector<Value> out;
	out <<= a.Get();
	Sort(out, ValueLess(packet.context.language));
	return ValueArray(out);
}
FDECLAP(sort, A, &GroupArray)

inline ValueArray CsortASC(CalcPacket& packet, ValueArray a, String ident, const CalcNode *lambda)
{
	if(a.GetCount() <= 1)
		return a;
	Vector<Value> out;
	out <<= a.Get();
	CalcContext::Nesting nesting(packet.context);
	Vector<Value> order;
	order.SetCount(out.GetCount());
	for(int i = 0; i < out.GetCount(); i++) {
		packet.context.Set(ident, out[i]);
		order[i] = lambda->Calc(packet.context);
	}
	IndexSort(order, out, ValueLess(packet.context.language));
	return ValueArray(out);
}
FDECLAP(sort, ASC, &GroupArray)

inline ValueArray CdistinctA(ValueArray a)
{
	if(a.GetCount() <= 1)
		return a;
	Index<Value> out;
	for(int i = 0; i < a.GetCount(); i++)
		out.FindAdd(a[i]);
	return ValueArray(out.PickKeys());
}
FDECLP(distinct, A, &GroupArray)

inline ValueArray CdistinctASC(CalcPacket& packet, ValueArray a, String ident, const CalcNode *lambda)
{
	if(a.GetCount() <= 1)
		return a;
	VectorMap<Value, Value> vimap;
	CalcContext::Nesting nesting(packet.context);
	for(int i = 0; i < a.GetCount(); i++) {
		packet.context.Set(ident, a[i]);
		vimap.FindAdd(lambda->Calc(packet.context), a[i]);
	}
	return ValueArray(vimap.PickValues());
}
FDECLAP(distinct, ASC, &GroupArray)

static bool CwithVSRC(CalcPacket& packet)
{
	if(packet.help) {
		packet.args.Add(CalcType<Value>::Describe());
		packet.args.Add(CalcType<String>::Describe());
		packet.args.Add("...");
		packet.args.Add(CalcType<const CalcNode *>::Describe());
		return true;
	}
	int nargs = packet.args.GetCount();
	if(nargs < 3 || !(nargs & 1) || !CalcType<const CalcNode *>::IsType(packet.args[nargs - 1]))
		return false;
	int npairs = nargs >> 1;
	CalcContext::Nesting nesting(packet.context);
	for(int i = 0; i < npairs; i++) {
		if(!CalcType<Value>::IsType(packet.args[2 * i + 0])
		|| !CalcType<String>::IsType(packet.args[2 * i + 1]))
			return false;
		packet.context.Set(packet.args[2 * i + 1], packet.args[2 * i]);
	}
	packet.result = CalcType<const CalcNode *>::ValueTo(packet.args[nargs - 1])->Calc(packet.context);
	return true;
}

FDECLQ("CwithVSRC", "with", &GroupSystem, &CwithVSRC)

static ValueArray CgroupAASCC(CalcPacket& packet, ValueArray data, ValueArray ctrl, String var,
	const CalcNode *grp, const CalcNode *val)
{
	Index<Value> ix(ctrl.Get(), 0);
	Vector<Value> out;
	out.SetCount(ctrl.GetCount());
	CalcContext::Nesting nesting(packet.context);
	for(int i = 0; i < data.GetCount(); i++) {
		Value v = data[i];
		packet.context.Set(var, v);
		int f = ix.Find(grp ? grp->Calc(packet.context) : v);
		if(f >= 0) {
			Value o = (val ? val->Calc(packet.context) : v);
			if(IsNull(o))
				continue;
			if(IsNull(out[f]))
				out[f] = o;
			else if(IsNumber(out[f]) && IsNumber(o))
				out[f] = (double)out[f] + (double)o;
			else if(IsString(out[f]) && IsString(o))
				out[f] = (String)out[f] + (String)o;
			else if(IsValueArray(out[f]) && IsValueArray(o)) {
				ValueArray va = out[f], ov = o;
				for(int t = 0; t < ov.GetCount(); t++)
					va.Add(ov[t]);
				out[f] = va;
			}
			else
				throw Exc(NFormat("chybná kombinace výstupních hodnot: %s (%s), %s (%s)",
					packet.context.FormatNull(out[f]), packet.context.GetTypeName(out[f]),
					packet.context.FormatNull(o), packet.context.GetTypeName(o)));
		}
	}
	return ValueArray(out);
}

FDECLAP(group, AASCC, &GroupArray)

static ValueArray CgroupAASC(CalcPacket& packet, ValueArray data, ValueArray ctrl, String var, const CalcNode *grp)
{
	return CgroupAASCC(packet, data, ctrl, var, grp, NULL);
}

FDECLAP(group, AASC, &GroupArray)

static bool CcallSV(CalcPacket& packet)
{
	if(packet.help) {
		packet.result = CalcType<Value>::Describe();
		packet.args.Add(CalcType<String>::Describe());
		packet.args.Add(CalcType<Value>::Describe());
		packet.args.Add("...");
		return true;
	}
	if(packet.args.GetCount() < 1 || !CalcType<String>::IsType(packet.args[0]))
		return false;
	CalcPacket subpacket(packet.context, CalcType<String>::ValueTo(packet.args[0]));
	if(IsNull(subpacket.name)) {
		packet.result = Value();
		return true;
	}
	subpacket.args.Append(packet.args, 1, packet.args.GetCount() - 1);
	packet.result = packet.context.Calc(subpacket);
	return true;
}

FDECLQ("CcallSV", "call", &GroupSystem, &CcallSV)

static String CreplaceSSS(String s, String what, String with)
{
	int wl = what.GetLength();
	if(wl == 0 || wl > s.GetLength())
		return s;
	String out;
	char first = what[0];
	const char *b = s.Begin();
	const char *p = b;
	const char *e = s.End();
	const char *q = e - what.GetLength();
	while(p <= q)
		if(*p == first && (wl == 1 || !memcmp(p, what, wl)))
		{
			out.Cat(b, p - b);
			out.Cat(with);
			b = p += wl;
		}
		else
			p++;
	if(b < e)
		out.Cat(b, e - b);
	return out;
}

FDECLP(replace, SSS, GroupString)

static inline int CfindSN(String s, int i) { return s.Find(i); }

FDECLP(find, SN, GroupString)

static inline int CfindSS(String s, String sub) { return StringFind(s, sub); }

FDECLP(find, SS, GroupString)

static inline bool ClikeSS(WString s, WString m)
{
	One<LanguageInfo::WildcardCompare> wc = GetLanguageInfo().GetWildcardCompare(m);
	return wc && wc->Matches(s);
}

FDECLP(like, SS, GroupString)

static String CltrimSS(String s, String trim)
{
	int tl = trim.GetLength(), sl = s.GetLength();
	if(tl == 0 || sl == 0)
		return s;
	const char *p = s, *e = s.End();
	if(tl == 1) {
		char c = trim[0];
		while(p < e && *p == c)
			p++;
	}
	else if(tl == 2) {
		char c1 = trim[0], c2 = trim[1];
		while(p < e && (*p == c1 || *p == c2))
			p++;
	}
	else {
		while(p < e && memchr(trim, *p, tl))
			p++;
	}
	return String(p, e);
}

static String CltrimS(String s) { return CltrimSS(s, " "); }

FDECLP(ltrim, S, GroupString)
FDECLP(ltrim, SS, GroupString)

static String CrtrimSS(String s, String trim)
{
	int tl = trim.GetLength(), sl = s.GetLength();
	if(tl == 0 || sl == 0)
		return s;
	const char *p = s, *e = s.End();
	if(tl == 1) {
		char c = trim[0];
		while(p < e && e[-1] == c)
			e--;
	}
	else if(tl == 2) {
		char c1 = trim[0], c2 = trim[1];
		while(p < e && (e[-1] == c1 || e[-1] == c2))
			e--;
	}
	else {
		while(p < e && memchr(trim, e[-1], tl))
			e--;
	}
	return String(p, e);
}

static String CrtrimS(String s) { return CrtrimSS(s, " "); }

FDECLP(rtrim, S, GroupString)
FDECLP(rtrim, SS, GroupString)

static String CtrimSS(String s, String trim)
{
	int tl = trim.GetLength(), sl = s.GetLength();
	if(tl == 0 || sl == 0)
		return s;
	const char *p = s, *e = s.End();
	if(tl == 1) {
		char c = trim[0];
		while(p < e && *p == c)
			p++;
		while(p < e && e[-1] == c)
			e--;
	}
	else if(tl == 2) {
		char c1 = trim[0], c2 = trim[1];
		while(p < e && (*p == c1 || *p == c2))
			p++;
		while(p < e && (e[-1] == c1 || e[-1] == c2))
			e--;
	}
	else {
		while(p < e && memchr(trim, *p, tl))
			p++;
		while(p < e && memchr(trim, e[-1], tl))
			e--;
	}
	return String(p, e);
}

static String CtrimS(String s) { return CtrimSS(s, " "); }

FDECLP(trim, S, GroupString)
FDECLP(trim, SS, GroupString)

END_UPP_NAMESPACE
