/*
    PIP - Platform Independent Primitives
    Evaluator designed for stream computing
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "pievaluator.h"


/*! \class PIEvaluator
 * \brief This class provide mathematical evaluations of custom expression
 *
 * \section PIEvaluator_sec0 Synopsis
 * %PIEvaluator developed for stream evaluations of once set expression.
 * It`s create internal list of instructions on function \a check() and
 * executes very fast on function \a evaluate(). Once given expression
 * can be evaluated any times with different variable values. Evaluator
 * supports many common mathematic functions described below. Also it`s
 * automatic puts unnecessarily signs and bracets. Processed expression
 * can be obtains with function \a expression(). If there is an error
 * in expression you can get it with function \a error(). Last evaluated
 * result you can get with function \a lastResult().
 * \section PIEvaluator_sec1 Using
 * First you should set your variables with function \a setVariable().
 * Next give your expression with function \a check() and check for error
 * with functions \a isCorrect() and \a error(). If expression is correct
 * you can get processed expression with function \a expression() and
 * evaluate it with function \a evaluate(). You can change variable values
 * without rechecking expression.
 *
 * \section PIEvaluator_sec2 Functions
 * %PIEvaluator supports arithmetical operations with complex numbers, this
 * is their list in priority order:
 * * ^ (power)
 * * * (multiply)
 * * / (divide)
 * * % (residue)
 * * + (add)
 * * - (subtract)
 *
 * In addition there are compare and logical operations:
 * * == (equal)
 * * != (not equal)
 * * > (greater)
 * * < (smaller)
 * * >= (greater or equal)
 * * <= (smaller or equal)
 * * && (and)
 * * || (or)
 *
 * Compare and logical functions works with real operators part and returns 0 or 1.
 *
 * Mathematical functions:
 * * sin(x) - sine
 * * cos(x) - cosine
 * * tg(x) - tangent
 * * ctg(x) - cotangent
 * * arcsin(x) - arcsine
 * * arccos(x) - arccosine
 * * arctg(x) - arctangent
 * * arcctg(x) - arccotangent
 * * sh(x) - hyperbolical sine
 * * ch(x) - hyperbolical cosine
 * * th(x) - hyperbolical tangent
 * * cth(x) - hyperbolical cotangent
 * * sqr(x) - square
 * * sqrt(x) - square root
 * * abs(x) - absolute value
 * * sign(x) - sign of real part (-1 or 1)
 * * exp(x) - exponent
 * * pow(x, p) - x in power p
 * * ln(x) - natural logarithm
 * * lg(x) - decimal logarithm
 * * log(x, b) - logarithm of x with base b
 * * im(x) - imaginary part of complex number
 * * re(x) - real part of complex number
 * * arg(x) - argument of complex number
 * * len(x) - length of complex number
 * * conj(x) - length of complex number
 * * rad(x) - convert degrees to radians
 * * deg(x) - convert radians to degrees
 * * j0(x) - Bessel function first kind order 0
 * * j1(x) - Bessel function first kind order 1
 * * jn(x, n) - Bessel function first kind order n
 * * y0(x) - Bessel function second kind order 0
 * * y1(x) - Bessel function second kind order 1
 * * yn(x, n) - Bessel function second kind order n
 * * random(s, a) - regular random with shift s and amp a
 * * randomn(s, a) - normalize random with shift s and amp a
 * * min(x0, x1, ...) - minimum of x0, x1, ...
 * * max(x0, x1, ...) - maximum of x0, x1, ...
 * * clamp(x, a, b) - trim x on range [a, b]
 * * step(x, s) - 0 if x < s, else 1
 * * mix(x, a, b) - interpolate between a and b linear for x (a * (1 - x) + b * x)
 * * round(x) - round
 *
 * There are some built-in constans:
 * * i (imaginary 1)
 * * e
 * * pi
 *
 * All trigonometric functions takes angle in radians.
 *
 * \section PIEvaluator_sec3 Example
 * \snippet pievaluator.cpp main
 */

using namespace PIEvaluatorTypes;


PIEvaluatorContent::PIEvaluatorContent() {
	addFunction(PIStringAscii("arcsin"), 1);
	addFunction(PIStringAscii("arccos"), 1);
	addFunction(PIStringAscii("arctg"), 1);
	addFunction(PIStringAscii("arcctg"), 1);
	addFunction(PIStringAscii("random"), 2);
	addFunction(PIStringAscii("randomn"), 2);
	addFunction(PIStringAscii("sin"), 1);
	addFunction(PIStringAscii("cos"), 1);
	addFunction(PIStringAscii("ctg"), 1);
	addFunction(PIStringAscii("tg"), 1);
	addFunction(PIStringAscii("exp"), 1);
	addFunction(PIStringAscii("cth"), 1);
	addFunction(PIStringAscii("sh"), 1);
	addFunction(PIStringAscii("ch"), 1);
	addFunction(PIStringAscii("th"), 1);
	addFunction(PIStringAscii("sqrt"), 1);
	addFunction(PIStringAscii("sqr"), 1);
	addFunction(PIStringAscii("pow"), 2);
	addFunction(PIStringAscii("abs"), 1);
	addFunction(PIStringAscii("ln"), 1);
	addFunction(PIStringAscii("lg"), 1);
	addFunction(PIStringAscii("log"), 2);
	addFunction(PIStringAscii("im"), 1);
	addFunction(PIStringAscii("re"), 1);
	addFunction(PIStringAscii("arg"), 1);
	addFunction(PIStringAscii("len"), 1);
	addFunction(PIStringAscii("conj"), 1);
	addFunction(PIStringAscii("sign"), 1);
	addFunction(PIStringAscii("rad"), 1);
	addFunction(PIStringAscii("deg"), 1);
	addFunction(PIStringAscii("j0"), 1);
	addFunction(PIStringAscii("j1"), 1);
	addFunction(PIStringAscii("jn"), 2);
	addFunction(PIStringAscii("y0"), 1);
	addFunction(PIStringAscii("y1"), 1);
	addFunction(PIStringAscii("yn"), 2);
	addFunction(PIStringAscii("min"), -2);  // (x0,x1,...)
	addFunction(PIStringAscii("max"), -2);  // (x0,x1,...)
	addFunction(PIStringAscii("clamp"), 3); // (x,a,b) =  x < a ? a : (x > b ? b : x)
	addFunction(PIStringAscii("step"), 2);  // (x,s) =     x >= s ? 1. : 0.  (1 if 'x' >= 's', else 0)
	addFunction(PIStringAscii("mix"), 3);   // (x,a,b) =    a*(1.-x) + b*x    (interpolate between 'a' and 'b' linear for 'x')
	addFunction(PIStringAscii("defined"), 1);
	addFunction(PIStringAscii("round"), 1);
	clearCustomVariables();
}


bool PIEvaluatorContent::setVariableValue(int index, complexd new_value) {
	if (index < 0 || index >= variables.size_s()) return false;
	variables[index].value = new_value;
	return true;
}


bool PIEvaluatorContent::setVariableName(int index, const PIString & new_name) {
	if (index < 0 || index >= variables.size_s()) return false;
	variables[index].name = new_name;
	return true;
}


void PIEvaluatorContent::addFunction(const PIString & name, int args) {
	functions << PIEvaluatorTypes::Function(name, args, getBaseFunction(name));
}


int PIEvaluatorContent::addVariable(const PIString & name, const complexd & val) {
	variables << PIEvaluatorTypes::Variable(name, val);
	return variables.size_s() - 1;
}


void PIEvaluatorContent::addCustomFunction(const PIString & name, int args_count, PIEvaluatorTypes::FuncHanlder func) {
	functions << PIEvaluatorTypes::Function(name, args_count, func);
}


int PIEvaluatorContent::customVariablesCount() const {
	return variables.size() - cv_count;
}


int PIEvaluatorContent::findFunction(const PIString & name) const {
	for (uint i = 0; i < functions.size(); i++)
		if (functions[i].identifier == name) return i;
	return -1;
}


int PIEvaluatorContent::findVariable(const PIString & var_name) const {
	for (uint i = 0; i < variables.size(); i++)
		if (variables[i].name == var_name) return i;
	return -1;
}


PIEvaluatorTypes::Function PIEvaluatorContent::function(int index) {
	if (index < 0 || index >= functions.size_s()) return PIEvaluatorTypes::Function();
	return functions[index];
}


PIEvaluatorTypes::Variable PIEvaluatorContent::variable(int index) {
	if (index < 0 || index >= variables.size_s()) return PIEvaluatorTypes::Variable();
	return variables[index];
}


PIEvaluatorTypes::Variable PIEvaluatorContent::customVariable(int index) {
	if (index < 0 || index >= variables.size_s() - cv_count) return PIEvaluatorTypes::Variable();
	return variables[index + cv_count];
}


void PIEvaluatorContent::clearCustomVariables() {
	variables.clear();
	addVariable(PIStringAscii("i"), complexd_i);
	addVariable(PIStringAscii("pi"), 3.141592653589793238462643383280);
	addVariable(PIStringAscii("e"), exp(1.));
	cv_count = variables.size_s();
}


BaseFunctions PIEvaluatorContent::getBaseFunction(const PIString & name) {
	if (name == PIStringAscii("sin")) return bfSin;
	if (name == PIStringAscii("cos")) return bfCos;
	if (name == PIStringAscii("tg")) return bfTg;
	if (name == PIStringAscii("ctg")) return bfCtg;
	if (name == PIStringAscii("arcsin")) return bfArcsin;
	if (name == PIStringAscii("arccos")) return bfArccos;
	if (name == PIStringAscii("arctg")) return bfArctg;
	if (name == PIStringAscii("arcctg")) return bfArcctg;
	if (name == PIStringAscii("exp")) return bfExp;
	if (name == PIStringAscii("random")) return bfRandom;
	if (name == PIStringAscii("randomn")) return bfRandomn;
	if (name == PIStringAscii("sh")) return bfSh;
	if (name == PIStringAscii("ch")) return bfCh;
	if (name == PIStringAscii("th")) return bfTh;
	if (name == PIStringAscii("cth")) return bfCth;
	if (name == PIStringAscii("sqrt")) return bfSqrt;
	if (name == PIStringAscii("sqr")) return bfSqr;
	if (name == PIStringAscii("pow")) return bfPow;
	if (name == PIStringAscii("abs")) return bfAbs;
	if (name == PIStringAscii("ln")) return bfLn;
	if (name == PIStringAscii("lg")) return bfLg;
	if (name == PIStringAscii("log")) return bfLog;
	if (name == PIStringAscii("im")) return bfIm;
	if (name == PIStringAscii("re")) return bfRe;
	if (name == PIStringAscii("arg")) return bfArg;
	if (name == PIStringAscii("len")) return bfLen;
	if (name == PIStringAscii("conj")) return bfConj;
	if (name == PIStringAscii("sign")) return bfSign;
	if (name == PIStringAscii("rad")) return bfRad;
	if (name == PIStringAscii("deg")) return bfDeg;
	if (name == PIStringAscii("j0")) return bfJ0;
	if (name == PIStringAscii("j1")) return bfJ1;
	if (name == PIStringAscii("jn")) return bfJN;
	if (name == PIStringAscii("y0")) return bfY0;
	if (name == PIStringAscii("y1")) return bfY1;
	if (name == PIStringAscii("yn")) return bfYN;
	if (name == PIStringAscii("min")) return bfMin;
	if (name == PIStringAscii("max")) return bfMax;
	if (name == PIStringAscii("clamp")) return bfClamp;
	if (name == PIStringAscii("step")) return bfStep;
	if (name == PIStringAscii("mix")) return bfMix;
	if (name == PIStringAscii("defined")) return bfDefined;
	if (name == PIStringAscii("round")) return bfRound;
	return bfUnknown;
}


void PIEvaluatorContent::dump() {
	for (int i = 0; i < variables.size_s(); ++i) {
		Variable v(variables[i]);
		piCout << i << v.name << "=" << v.value;
	}
}


const PIString & PIEvaluator::prepare(const PIString & string) {
	currentString = string.trimmed();
	if (currentString.isEmpty()) currentString = PIStringAscii("0");
	replaceOperators();
	removeSpaces();
	checkBrackets();
	while (fillElements())
		checkBrackets();
	while (setSignes())
		fillElements();
	removeJunk();
	findUnknownVariables();
	return currentString;
}


void PIEvaluator::removeSpaces() {
	PIString tmps = currentString;
	for (int i = 0; i < tmps.length(); i++) {
		if (tmps[i] == ' ' || tmps[i] == '\t') {
			tmps.remove(i, 1);
			i--;
		}
	}
	currentString = tmps;
}


void PIEvaluator::removeJunk() {
	PIChar cc;
	bool junk = true;
	int bcnt;
	while (junk) {
		if (currentString.isEmpty()) return;
		if (currentString.front() != '(' || currentString.back() != ')') return;
		bcnt = 1;
		junk = false;
		for (int i = 1; i < currentString.length(); i++) {
			cc = currentString[i];
			if (cc == '(') bcnt++;
			if (cc == ')') bcnt--;
			if (bcnt == 0) {
				if (i == currentString.length() - 1) {
					currentString = currentString.mid(1, currentString.length() - 2);
					elements.pop_front();
					elements.pop_back();
					junk = true;
					break;
				} else
					break;
			}
		}
	}
}


void PIEvaluator::replaceOperators() {
	currentString.replaceAll(PIStringAscii("=="), '=');
	currentString.replaceAll(PIStringAscii("!="), ':');
	currentString.replaceAll(PIStringAscii(">="), '}');
	currentString.replaceAll(PIStringAscii("<="), '{');
	currentString.replaceAll(PIStringAscii("&&"), '&');
	currentString.replaceAll(PIStringAscii("||"), '|');
	currentString.replaceAll(PIString::fromUTF8("≠"), ':');
	currentString.replaceAll(PIString::fromUTF8("≥"), '}');
	currentString.replaceAll(PIString::fromUTF8("≤"), '{');
	currentString.replaceAll(PIString::fromUTF8("⋀"), '&');
	currentString.replaceAll(PIString::fromUTF8("⋁"), '|');
}


void PIEvaluator::makeOutput(PIString & string) {
	string.replaceAll(PIStringAscii(":"), PIString::fromUTF8("≠"));
	string.replaceAll(PIStringAscii("}"), PIString::fromUTF8("≥"));
	string.replaceAll(PIStringAscii("{"), PIString::fromUTF8("≤"));
	string.replaceAll(PIStringAscii("&"), PIString::fromUTF8("⋀"));
	string.replaceAll(PIStringAscii("|"), PIString::fromUTF8("⋁"));
}


void PIEvaluator::findUnknownVariables() {
	PIString cvar;
	unknownVars.clear();
	usedVars.clear();
	for (int i = 0; i < currentString.length(); i++) {
		if (elements[i].var_num >= 0)
			usedVars << content.variable(elements[i].var_num).name;
		else if (elements[i].var_num == -666)
			cvar += currentString[i];
		else {
			if (cvar.length() == 0) continue;
			unknownVars << cvar;
			cvar.clear();
		}
	}
	if (cvar.length() > 0) unknownVars << cvar;
	unknownVars.removeDuplicates();
	usedVars.removeDuplicates();
	usedVars.removeOne("i");
	usedVars.removeOne("pi");
	usedVars.removeOne("e");
}


bool PIEvaluator::isSign(const PIChar & ch) {
	static PIString signs = PIStringAscii("+-*/%^=:><}{&|");
	if (!ch.isAscii()) return false;
	return signs.contains(ch.toAscii());
}


void PIEvaluator::checkBrackets() {
	PIString tmps = currentString;
	PIChar fc, sc;
	int bcnt = 0, bpos = 0, inserted = 0;
	currentString = tmps;
	for (int i = 0; i < tmps.length(); i++) {
		if (tmps[i] == '(') {
			if (bcnt == 0) bpos = i;
			bcnt++;
		}
		if (tmps[i] == ')') {
			if (bcnt == 0) {
				currentString.insert(bpos + inserted, '(');
				inserted++;
			} else
				bcnt--;
		}
	}
	if (bcnt > 0) currentString += PIString(bcnt, ')');
	tmps = currentString;
	for (int i = 0; i < tmps.length() - 1; i++) {
		fc = tmps[i].toLower();
		sc = tmps[i + 1].toLower();
		if ((fc == ')' && sc == '(') || (fc == ')' && sc >= '0' && sc <= '9') || (fc == ')' && sc >= 'a' && sc <= 'z'))
			tmps.insert(++i, '*');
	}
	currentString = tmps;
}


bool PIEvaluator::fillElements() {
	int fstart, flen, cnum = 0, cpart = 0, cfunc;
	PIChar cc, nc, pc, fc             = '!';
	bool numFound = false;
	PIString curfind, tmps = currentString;
	elements.resize(tmps.length());
	for (uint i = 0; i < elements.size(); i++) {
		elements[i].type    = etVariable;
		elements[i].var_num = -666;
	}
	currentVariables.clear();
	for (int i = 0; i < content.functionsCount(); i++) {
		curfind = content.function(i).identifier;
		cfunc   = i;
		flen    = curfind.length();
		fstart  = 0;
		while (fstart >= 0) {
			fstart = tmps.find(curfind, fstart);
			if (fstart < 0) break;
			if (tmps[fstart + flen] != '(') {
				fstart++;
				continue;
			}
			for (int j = fstart; j < fstart + flen; j++) {
				elements[j].set(etFunction, cnum, cfunc);
				tmps.replace(j, 1, fc);
			}
			cnum++;
		}
	}
	cnum = 0;
	typedef PIPair<int, int> PairII;
	PIVector<PairII> var_index;
	for (int i = 0; i < content.variables.size_s(); ++i) {
		var_index << PairII(i, content.variables[i].name.length());
	}
	var_index.sort([](const PairII & v1, const PairII & v2) { return v1.second > v2.second; });
	for (int i = 0; i < var_index.size_s(); i++) {
		int vind = var_index[i].first;
		curfind  = content.variables[vind].name;
		flen     = curfind.length();
		fstart   = 0;
		while (fstart >= 0) {
			fstart = tmps.find(curfind, fstart);
			if (fstart < 0) break;
			for (int j = fstart; j < fstart + flen; j++) {
				elements[j].set(etVariable, cnum, vind);
				tmps.replace(j, 1, fc);
			}
			cnum++;
		}
	}
	curfind.clear();
	cnum = 1;
	for (int i = 0; i < tmps.length(); i++) {
		cc = tmps[i];
		switch (cpart) {
		case 0:
			if ((cc >= '0' && cc <= '9')) {
				curfind += cc;
				cpart = 1;
				continue;
			}
			if (cc == '.') {
				curfind += cc;
				cpart = 2;
				continue;
			}
			if (cc == 'E') {
				curfind += cc;
				cpart = 3;
				continue;
			}
			break;
		case 1:
			if (cc >= '0' && cc <= '9') {
				curfind += cc;
				continue;
			}
			if (cc == '.') {
				curfind += cc;
				cpart = 2;
				continue;
			}
			if (cc == 'E') {
				curfind += cc;
				cpart = 3;
				continue;
			}
			numFound = true;
			break;
		case 2:
			if (cc >= '0' && cc <= '9') {
				curfind += cc;
				continue;
			}
			if (cc == 'E') {
				curfind += cc;
				cpart = 3;
				continue;
			}
			numFound = true;
			break;
		case 3:
			if ((cc >= '0' && cc <= '9') || cc == '-' || cc == '+') {
				curfind += cc;
				cpart = 4;
				continue;
			}
			numFound = true;
			break;
		case 4:
			if (cc >= '0' && cc <= '9') {
				curfind += cc;
				continue;
			}
			numFound = true;
			break;
		}
		if (numFound) {
			currentVariables.push_back(Variable(PIStringAscii("tmp") + PIString::fromNumber(cnum), curfind.toDouble()));
			for (int j = i - curfind.length(); j < i; j++) {
				elements[j].set(etNumber, cnum, -cnum);
				tmps.replace(j, 1, fc);
			}
			curfind.clear();
			cnum++;
			cpart    = 0;
			numFound = false;
		}
	}
	if (cpart > 0) {
		currentVariables.push_back(Variable(PIStringAscii("tmp") + PIString::fromNumber(cnum), curfind.toDouble()));
		for (int j = tmps.length() - curfind.length(); j < tmps.length(); j++) {
			elements[j].set(etNumber, cnum, -cnum);
			tmps.replace(j, 1, fc);
		}
	}
	cc = nc = fc;
	for (int i = 0; i < tmps.length(); i++) {
		cc = tmps[i];
		if (i > 0)
			pc = tmps[i - 1];
		else
			pc = fc;
		if (i < tmps.length() - 1)
			nc = tmps[i + 1];
		else
			nc = fc;
		if (cc == '(' || cc == ')' || cc == ',') {
			elements[i].set(etOperator, -1);
			continue;
		}
		if (cc == '-' || cc == '+') {
			elements[i].set(etOperator, -1);
			if (i < tmps.length() - 1)
				if (elements[i + 1].type == etVariable || elements[i + 1].type == etFunction) continue;
			if ((pc == '(' || isSign(pc) || i == 0) && i < tmps.length() - 1) {
				if (elements[i + 1].type != etOperator) {
					cnum = elements[i + 1].num;
					elements[i].set(etNumber, cnum);
					tmps.replace(i, 1, fc);
					/// cout << "found sign " << cc << " :" << cnum - 1 << endl;
					if (cc == '-' && currentVariables.size_s() >= cnum)
						currentVariables[cnum - 1].value = -currentVariables[cnum - 1].value;
					continue;
				}
			}
		}
		if (isSign(cc)) {
			elements[i].set(etOperator, -1);
			continue;
		}
	}
	/*cout << " ";
	for (int i = 0; i < elements.size(); i++) {
	    switch (elements[i].type) {
	    case etFunction: cout << "f"; break;
	    case etNumber: cout << "n"; break;
	    case etOperator: cout << "o"; break;
	    case etVariable: cout << "v"; break;
	    }
	}
	cout << endl; */
	return false;
}


bool PIEvaluator::setSignes() {
	int inserted = 0, ni, pi = 0, needInsert = 0;
	PIChar fc, sc, pc;
	PIString tmps = currentString;
	for (int i = 0; i < tmps.length() - 1; i++) {
		needInsert = 0;
		ni         = i + 1;
		if (i > 0) pi = i - 1;
		fc = tmps[i].toLower();
		sc = tmps[ni].toLower();
		pc = tmps[pi].toLower();
		if (fc == ',' || sc == ',') continue;
		if (elements[i].type == etOperator && elements[ni].type == etOperator) continue;
		if (fc == ')' && (elements[ni].type == etNumber || elements[ni].type == etVariable || elements[ni].type == etFunction))
			needInsert = 1;
		if (sc == '(' && (elements[i].type == etNumber || elements[i].type == etVariable)) needInsert = 1;
		if (elements[i].type == etNumber && elements[ni].type == etNumber && elements[i].num != elements[ni].num) needInsert = 1;
		if (elements[i].type == etVariable && elements[ni].type == etVariable && elements[i].num != elements[ni].num) needInsert = 1;
		if ((elements[i].type == etNumber && elements[ni].type == etVariable) ||
		    (elements[i].type == etVariable && elements[ni].type == etNumber))
			needInsert = 1;
		if ((elements[i].type == etNumber || elements[i].type == etVariable) && elements[ni].type == etFunction) needInsert = 1;
		if (elements[i].type == etFunction && elements[ni].type == etFunction && elements[i].num != elements[ni].num) needInsert = 2;
		if (elements[i].type == etFunction && elements[ni].type != etFunction && sc != '(') needInsert = 2;
		if (elements[pi].type == etOperator && (elements[ni].type == etFunction || elements[ni].type == etVariable) && fc == '-')
			needInsert = 3;
		switch (needInsert) {
		case 1:
			currentString.insert(ni + inserted, '*');
			elements.insert(ni + inserted, Element(etOperator, -1));
			return true;
		case 3:
			currentString.insert(ni + inserted, PIStringAscii("1*"));
			elements.insert(ni + inserted, Element(etOperator, -1));
			return true;
		}
	}
	return false;
}


void PIEvaluator::convert() {
	int j;
	Element ce, pe;
	for (int i = 0; i < currentString.length(); i++) {
		pe = elements[i];
		if (pe.type != etFunction) continue;
		j = i + 1;
		while (j < currentString.length()) {
			ce = elements[j];
			if (ce != pe) break;
			j++;
		}
		currentString.replace(i, j - i, ' ');
		for (int k = i + 1; k < j; k++)
			elements.remove(i);
	}
	for (int i = 0; i < currentString.length(); i++) {
		pe = elements[i];
		if (pe.type != etNumber) continue;
		j = i + 1;
		while (j < currentString.length()) {
			ce = elements[j];
			if (ce != pe) break;
			j++;
		}
		currentString.replace(i, j - i, ' ');
		for (int k = i + 1; k < j; k++)
			elements.remove(i);
	}
	for (int i = 0; i < currentString.length(); i++) {
		pe = elements[i];
		if (pe.type != etVariable) continue;
		j = i + 1;
		while (j < currentString.length()) {
			ce = elements[j];
			if (ce != pe) break;
			j++;
		}
		currentString.replace(i, j - i, ' ');
		for (int k = i + 1; k < j; k++)
			elements.remove(i);
	}
	/*cout << " ";
	for (int i = 0; i < elements.size(); i++) {
	    switch (elements[i].type) {
	    case etFunction: cout << "f"; break;
	    case etNumber: cout << "n"; break;
	    case etOperator: cout << "o"; break;
	    case etVariable: cout << "v"; break;
	    }
	}
	cout << endl; */
}


PIString PIEvaluator::preprocess(const PIString & string) {
	PIString ret;
	int lind;
	ret = prepare(string);
	convert();
	instructions.clear();
	variables = currentVariables;
	lind      = parse(currentString);
	if (instructions.size() == 0) {
		variables.push_back(Variable());
		instructions.push_back(Instruction(oNone, PIVector<short>(1, lind), -variables.size_s()));
	}
	/*cout << endl << "variables:" << endl;
	for (int i = 0; i < variables.size(); i++)
	    cout << i << " value = " << variables[i].value << endl;

	cout << endl << "instructions:" << endl;
	for (int i = 0; i < instructions.size(); i++) {
	    cout << i << endl;
	    cout << " operation " << instructions[i].operation << endl;
	    cout << " operators: ";
	    for (int j = 0; j < instructions[i].operators.size(); j++)
	        cout << instructions[i].operators[j] << "; ";
	    cout << endl << " function " << instructions[i].function << endl;
	    cout << " out " << instructions[i].out << endl;
	} */
	makeOutput(ret);
	return ret;
}


int PIEvaluator::parse(const PIString & string, int offset) {
	int slen = string.length(), /*facnt,*/ farg, bcnt, k;
	PIChar cc;
	Element ce;
	Function cfunc;
	PIString sbrackets, carg;
	PIVector<short> args, atmp;
	PIVector<Operation> opers;

	// cout << "           "; for (int i = 0; i < slen; i++) cout << preproc->elements[i + offset].type; cout << endl;
	for (int i = 0; i < slen; i++) {
		ce = elements[i + offset];
		cc = string[i];
		switch (ce.type) {
		case etNumber: args.push_back(ce.var_num); continue;
		case etVariable: args.push_back(ce.var_num); continue;
		case etFunction:
			i++;
			cfunc = content.function(ce.var_num);
			atmp.clear();
			bcnt = farg = 1;
			carg.clear();
			k = i + 1;
			while (bcnt > 0) {
				cc = string[k];
				switch (cc.toAscii()) {
				case '(': bcnt++; break;
				case ')':
					bcnt--;
					if (bcnt == 0) {
						/// qDebug() << "arument: " << carg;
						atmp.push_back(parse(carg, k + offset - carg.length()));
						k++;
						carg.clear();
						if (atmp.size_s() > 0)
							if (atmp.back() < 0 && farg > 0) farg = atmp.back();
						continue;
					}
					break;
				case ',':
					if (bcnt == 1) {
						/// qDebug() << "arument: " << carg;
						atmp.push_back(parse(carg, k + offset - carg.length()));
						k++;
						carg.clear();
						if (atmp.size_s() > 0)
							if (atmp.back() < 0 && farg > 0) farg = atmp.back();
						continue;
					}
					break;
				}
				carg += cc;
				k++;
			}
			i = k - 1;
			if (farg > 0) {
				variables.push_back(Variable());
				farg = -variables.size_s();
			}
			instructions.push_back(Instruction(oFunction, atmp, farg, ce.var_num));
			args.push_back(farg);
			// for (int i = 0; i < args.size_s(); i++) cout << preproc->currentVariables[-args[i]].value << endl;
			continue;
		case etOperator:
			if (cc == '(') {
				sbrackets = inBrackets(string.right(slen - i));
				args.push_back(parse(sbrackets, i + offset + 1));
				i += sbrackets.length() + 1;
				continue;
			}
			if (cc == '+') {
				opers.push_back(oAdd);
				continue;
			}
			if (cc == '-') {
				opers.push_back(oSubtract);
				continue;
			}
			if (cc == '*') {
				opers.push_back(oMultiply);
				continue;
			}
			if (cc == '/') {
				opers.push_back(oDivide);
				continue;
			}
			if (cc == '%') {
				opers.push_back(oResidue);
				continue;
			}
			if (cc == '^') {
				opers.push_back(oPower);
				continue;
			}
			if (cc == '=') {
				opers.push_back(oEqual);
				continue;
			}
			if (cc == ':') {
				opers.push_back(oNotEqual);
				continue;
			}
			if (cc == '}') {
				opers.push_back(oGreaterEqual);
				continue;
			}
			if (cc == '{') {
				opers.push_back(oSmallerEqual);
				continue;
			}
			if (cc == '>') {
				opers.push_back(oGreater);
				continue;
			}
			if (cc == '<') {
				opers.push_back(oSmaller);
				continue;
			}
			if (cc == '&') {
				opers.push_back(oAnd);
				continue;
			}
			if (cc == '|') {
				opers.push_back(oOr);
				continue;
			}
		}
	}
	/*cout << "stack: " << endl << "args: ";
	for (int i = 0; i < args.size_s(); i++) cout << args[i] << ", ";
	cout << endl << "opers: ";
	for (int i = 0; i < opers.size_s(); i++) cout << opers[i] << ", "; */
	if (opers.size_s() == 0) {
		if (args.size_s() > 0)
			return args.back();
		else
			return -666;
	}

	int oprior = -1;
	PIVector<Operation> opv;
	while (1) {
		operationsByPriority(++oprior, opv);
		if (opv.isEmpty()) break;
		for (int j = 0; j < opers.size_s(); j++) {
			if (!opv.contains(opers[j])) continue;
			atmp.clear();
			if (j < args.size_s() && j >= 0)
				atmp.push_back(args[j]);
			else
				atmp.push_back(-666);
			if (j + 1 < args.size_s() && j >= -1)
				atmp.push_back(args[j + 1]);
			else
				atmp.push_back(-666);
			farg = 1;
			if (atmp[0] < 0)
				farg = atmp[0];
			else {
				if (atmp[1] < 0)
					farg = atmp[1];
				else {
					variables.push_back(Variable());
					farg = -variables.size_s();
				}
			}
			instructions.push_back(Instruction(opers[j], atmp, farg));
			if (j >= 0 && j < args.size_s()) {
				args.remove(j);
				if (j < args.size_s()) args[j] = farg;
			}
			opers.remove(j);
			j--;
		}
	}
	return instructions.back().out;
	// cout << endl;
}


bool PIEvaluator::check() {
	Instruction ci;
	bool error;
	if (unknownVars.size_s() > 0) {
		lastError = PIStringAscii("Unknown variables: \"") + unknownVars.join("\", \"") + PIStringAscii("\"");
		return false;
	}
	for (int i = 0; i < instructions.size_s(); i++) {
		error = false;
		ci    = instructions[i];
		Function cf;
		int fac, gac;
		switch (ci.operation) {
		case oNone: break;
		case oFunction:
			cf  = content.function(ci.function);
			fac = cf.arguments;
			gac = ci.operators.size_s();
			for (int j = 0; j < ci.operators.size_s(); j++) {
				if (ci.operators[j] == -666) {
					error = true;
					gac--;
				}
			}
			if (fac > 0) {
				if (gac != fac) {
					lastError = PIStringAscii("Invalid arguments count for function \"") + cf.identifier + PIStringAscii("\", expected ") +
					            PIString::fromNumber(fac) + PIStringAscii(" but ") + PIString::fromNumber(gac) + PIStringAscii(" given");
					return false;
				}
				if (error) {
					lastError = PIStringAscii("Invalid at least one of function \"") + cf.identifier + PIStringAscii("\" argument");
					return false;
				}
			}
			if (fac < 0) {
				if (gac < -fac) {
					lastError = PIStringAscii("Invalid arguments count for function \"") + cf.identifier +
					            PIStringAscii("\", expected at least ") + PIString::fromNumber(-fac) + PIStringAscii(" but ") +
					            PIString::fromNumber(gac) + PIStringAscii(" given");
					return false;
				}
				if (error) {
					lastError = PIStringAscii("Invalid at least one of function \"") + cf.identifier + PIStringAscii("\" argument");
					return false;
				}
			}
			break;
		default:
			if (ci.operators[0] == -666 || ci.operators[1] == -666) error = true;
			if (ci.operators.size_s() != 2 || error) {
				lastError = PIStringAscii("Invalid arguments count for operation \" ") + operationChar(ci.operation) + PIStringAscii(" \"");
				return false;
			}
			break;
		}
		if (ci.out < -variables.size_s()) {
			lastError = PIStringAscii("Invalid variable index \"") + PIString::fromNumber(ci.out) + PIStringAscii("\"");
			return false;
		}
		for (int j = 0; j < ci.operators.size_s(); j++) {
			if (ci.operators[j] < -variables.size_s() || ci.operators[j] >= content.variables.size_s()) {
				lastError = PIStringAscii("Invalid variable index \"") + PIString::fromNumber(ci.operators[j]) + PIStringAscii("\"");
				return false;
			}
		}
	}
	return true;
}


PIString PIEvaluator::inBrackets(const PIString & string) {
	int slen = string.length(), bcnt = 0;
	PIChar cc;
	for (int i = 0; i < slen; i++) {
		cc = string[i];
		if (cc == '(') bcnt++;
		if (cc == ')') {
			bcnt--;
			if (bcnt == 0) return string.mid(1, i - 1);
		}
	}
	return PIString();
}


PIString PIEvaluator::operationChar(const Operation & operation) {
	switch (operation) {
	case oAdd: return PIStringAscii("+");
	case oSubtract: return PIStringAscii("-");
	case oMultiply: return PIStringAscii("*");
	case oDivide: return PIStringAscii("/");
	case oPower: return PIStringAscii("^");
	case oResidue: return PIStringAscii("%");
	case oEqual: return PIStringAscii("=");
	case oNotEqual: return PIString::fromUTF8("≠");
	case oGreaterEqual: return PIString::fromUTF8("≥");
	case oSmallerEqual: return PIString::fromUTF8("≤");
	case oGreater: return PIStringAscii(">");
	case oSmaller: return PIStringAscii("<");
	case oAnd: return PIString::fromUTF8("⋀");
	case oOr: return PIString::fromUTF8("⋁");
	default: break;
	}
	return PIStringAscii("???");
}


void PIEvaluator::operationsByPriority(int p, PIVector<Operation> & ret) {
	ret.clear();
	switch (p) {
	case 0: ret << oPower; break;
	case 1: ret << oMultiply << oDivide << oResidue; break;
	case 2: ret << oAdd << oSubtract; break;
	case 3: ret << oEqual << oNotEqual << oGreaterEqual << oSmallerEqual << oGreater << oSmaller; break;
	case 4: ret << oAnd; break;
	case 5: ret << oOr; break;
	default: break;
	}
}


inline complexd PIEvaluator::residue(const complexd & f, const complexd & s) {
	complexd ret;
	if (s.real() != 0.) ret = complexd(f.real() - ((int)(f.real() / s.real())) * s.real(), 0.);
	if (s.imag() != 0.) ret = complexd(ret.real(), f.imag() - ((int)(f.imag() / s.imag())) * s.imag());
	return ret;
}


inline void PIEvaluator::execFunction(const Instruction & ci) {
	const Function & cfunc(content.function(ci.function));
	int oi = -ci.out - 1;
	complexd tmp, stmp, ttmp;
	switch (cfunc.type) {
	case bfSin: tmpvars[oi].value = sin(value(ci.operators[0])); break;
	case bfCos: tmpvars[oi].value = cos(value(ci.operators[0])); break;
	case bfTg: tmpvars[oi].value = tan(value(ci.operators[0])); break;
	case bfCtg:
		tmp = tan(value(ci.operators[0]));
		if (tmp == complexd_0)
			tmpvars[oi].value = 0.;
		else
			tmpvars[oi].value = complexd_1 / tmp;
		break;
	case bfArcsin: tmpvars[oi].value = asinc(value(ci.operators[0])); break;
	case bfArccos: tmpvars[oi].value = acosc(value(ci.operators[0])); break;
	case bfArctg: tmpvars[oi].value = atanc(value(ci.operators[0])); break;
	case bfArcctg: tmpvars[oi].value = atanc(-value(ci.operators[0])) + M_PI_2; break;
	case bfSh: tmpvars[oi].value = sinh(value(ci.operators[0])); break;
	case bfCh: tmpvars[oi].value = cosh(value(ci.operators[0])); break;
	case bfTh: tmpvars[oi].value = tanh(value(ci.operators[0])); break;
	case bfCth:
		tmp = tanh(value(ci.operators[0]));
		if (tmp == complexd_0)
			tmpvars[oi].value = 0.;
		else
			tmpvars[oi].value = complexd_1 / tmp;
		break;
	case bfAbs: tmpvars[oi].value = abs(value(ci.operators[0])); break;
	case bfSqrt: tmpvars[oi].value = sqrt(value(ci.operators[0])); break;
	case bfSqr: tmpvars[oi].value = value(ci.operators[0]) * value(ci.operators[0]); break;
	case bfExp: tmpvars[oi].value = exp(value(ci.operators[0])); break;
	case bfPow: tmpvars[oi].value = pow(value(ci.operators[0]), value(ci.operators[1])); break;
	case bfLn: tmpvars[oi].value = log(value(ci.operators[0])); break;
	case bfLg: tmpvars[oi].value = log10(value(ci.operators[0])); break;
	case bfLog:
		tmp = log(value(ci.operators[1]));
		if (tmp == complexd_0)
			tmpvars[oi].value = 0.;
		else
			tmpvars[oi].value = log(value(ci.operators[0])) / tmp;
		break;
	case bfRe: tmpvars[oi].value = value(ci.operators[0]).real(); break;
	case bfIm: tmpvars[oi].value = value(ci.operators[0]).imag(); break;
	case bfArg: tmpvars[oi].value = arg(value(ci.operators[0])); break;
	case bfLen: tmpvars[oi].value = abs(value(ci.operators[0])); break;
	case bfConj: tmpvars[oi].value = conj(value(ci.operators[0])); break;
	case bfSign: tmpvars[oi].value = value(ci.operators[0]).real() >= 0. ? complexd_1 : -complexd_1; break;
	case bfRad: tmpvars[oi].value = value(ci.operators[0]) * complexd(deg2rad, 0.); break;
	case bfDeg: tmpvars[oi].value = value(ci.operators[0]) * complexd(rad2deg, 0.); break;
	case bfJ0: tmpvars[oi].value = piJ0(value(ci.operators[0]).real()); break;
	case bfJ1: tmpvars[oi].value = piJ1(value(ci.operators[0]).real()); break;
	case bfJN: tmpvars[oi].value = piJn(piRoundd(value(ci.operators[1]).real()), value(ci.operators[0]).real()); break;
	case bfY0: tmpvars[oi].value = piY0(value(ci.operators[0]).real()); break;
	case bfY1: tmpvars[oi].value = piY1(value(ci.operators[0]).real()); break;
	case bfYN: tmpvars[oi].value = piYn(piRoundd(value(ci.operators[1]).real()), value(ci.operators[0]).real()); break;
	case bfMin:
		tmp = value(ci.operators[0]);
		for (int i = 1; i < ci.operators.size_s(); ++i) {
			stmp = value(ci.operators[i]);
			tmp  = complexd(piMind(tmp.real(), stmp.real()), piMind(tmp.imag(), stmp.imag()));
		}
		tmpvars[oi].value = tmp;
		break;
	case bfMax:
		tmp = value(ci.operators[0]);
		for (int i = 1; i < ci.operators.size_s(); ++i) {
			stmp = value(ci.operators[i]);
			tmp  = complexd(piMaxd(tmp.real(), stmp.real()), piMaxd(tmp.imag(), stmp.imag()));
		}
		tmpvars[oi].value = tmp;
		break;
	case bfClamp:
		tmp               = value(ci.operators[0]);
		stmp              = value(ci.operators[1]);
		ttmp              = value(ci.operators[2]);
		tmpvars[oi].value = complexd(piClampd(tmp.real(), stmp.real(), ttmp.real()), piClampd(tmp.imag(), stmp.imag(), ttmp.imag()));
		break;
	case bfStep:
		tmpvars[oi].value = complexd(value(ci.operators[0]).real() >= value(ci.operators[1]).real() ? complexld_1 : complexld_0);
		break;
	case bfMix:
		tmp               = value(ci.operators[0]);
		stmp              = value(ci.operators[1]);
		ttmp              = value(ci.operators[2]);
		tmpvars[oi].value = stmp.real() * (1. - tmp.real()) + ttmp.real() * tmp.real();
		break;
	case bfDefined: tmpvars[oi].value = value(ci.operators[0]).real() > 0. ? complexd_1 : complexd_0; break;
	case bfRandom: tmpvars[oi].value = value(ci.operators[0]) + randomd() * value(ci.operators[1]); break;
	case bfRandomn: tmpvars[oi].value = randomn(value(ci.operators[0]).real(), value(ci.operators[1]).real()); break;
	case bfRound: tmpvars[oi].value = piRoundd(value(ci.operators[0]).real()); break;
	default: break;
	}
}


inline bool PIEvaluator::execInstructions() {
	int oi;
	tmpvars = variables;
	// cout << "var count " << tmpvars.size_s() << endl;
	for (int i = 0; i < instructions.size_s(); i++) {
		const Instruction & ci(instructions[i]);
		oi = -ci.out - 1;
		// cout << value(ci.operators[0]) << operationChar(ci.operation) << value(ci.operators[1]) << ", " << oi << endl;
		switch (ci.operation) {
		case oAdd: tmpvars[oi].value = value(ci.operators[0]) + value(ci.operators[1]); break;
		case oSubtract: tmpvars[oi].value = value(ci.operators[0]) - value(ci.operators[1]); break;
		case oMultiply: tmpvars[oi].value = value(ci.operators[0]) * value(ci.operators[1]); break;
		case oDivide: {
			complexd tmp = value(ci.operators[1]);
			if (tmp == complexd(0., 0.))
				tmpvars[oi].value = 0.;
			else
				tmpvars[oi].value = value(ci.operators[0]) / tmp;
		} break;
		case oResidue: tmpvars[oi].value = residue(value(ci.operators[0]), value(ci.operators[1])); break;
		case oPower: tmpvars[oi].value = pow(value(ci.operators[0]), value(ci.operators[1])); break;
		case oEqual: tmpvars[oi].value = value(ci.operators[0]) == value(ci.operators[1]); break;
		case oNotEqual: tmpvars[oi].value = value(ci.operators[0]) != value(ci.operators[1]); break;
		case oGreaterEqual: tmpvars[oi].value = value(ci.operators[0]).real() >= value(ci.operators[1]).real(); break;
		case oSmallerEqual: tmpvars[oi].value = value(ci.operators[0]).real() <= value(ci.operators[1]).real(); break;
		case oGreater: tmpvars[oi].value = value(ci.operators[0]).real() > value(ci.operators[1]).real(); break;
		case oSmaller: tmpvars[oi].value = value(ci.operators[0]).real() < value(ci.operators[1]).real(); break;
		case oAnd: tmpvars[oi].value = value(ci.operators[0]).real() > 0. && value(ci.operators[1]).real() > 0.; break;
		case oOr: tmpvars[oi].value = value(ci.operators[0]).real() > 0. || value(ci.operators[1]).real() > 0.; break;
		case oFunction: execFunction(ci); break;
		case oNone: tmpvars[oi].value = value(ci.operators[0]); break;
		default: break;
		}
	}
	if (!instructions.isEmpty()) out = value(instructions.back().out);
	return true;
}


bool PIEvaluator::check(const PIString & string) {
	currentString = preprocess(string);
	correct       = check();
	if (!correct) {
		instructions.clear();
		return false;
	}
	lastError = PIStringAscii("Correct");
	return true;
}


int PIEvaluator::setVariable(const PIString & name, complexd value) {
	int ind = content.findVariable(name);
	if (ind < 0)
		ind = content.addVariable(name, value);
	else
		content.setVariableValue(name, value);
	return ind;
}


void PIEvaluator::setVariable(int index, complexd value) {
	content.setVariableValue(index, value);
}


complexd PIEvaluator::evaluate() {
	if (!execInstructions()) out = 0.;
	if (fabs(out.real()) < 1E-300) out = complexd(0., out.imag());
	if (fabs(out.imag()) < 1E-300) out = complexd(out.real(), 0.);
	return out;
}


PIByteArray PIEvaluator::save() const {
	PIByteArray ret;
	ret << content << currentVariables << unknownVars << instructions << currentString << lastError << out << correct << usedVars;
	return ret;
}


void PIEvaluator::load(PIByteArray ba) {
	if (ba.size() <= 4) return;
	ba >> content >> currentVariables >> unknownVars >> instructions >> currentString >> lastError >> out >> correct;
	if (ba.size() >= 4) ba >> usedVars;
	variables = currentVariables;
}
