/*
    PIP - Platform Independent Primitives
    PIMathSolver
    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 "pimathsolver.h"

const char PIMathSolver::methods_desc[]          = "b{Methods:}\
\n  -1 - Global settings\
\n  01 - Eyler 1\
\n  02 - Eyler 2\
\n  14 - Runge-Kutta 4\
\n  23 - Adams-Bashfort-Moulton 3\
\n  24 - Adams-Bashfort-Moulton 4\
\n  32 - Polynomial Approximation 2\
\n  33 - Polynomial Approximation 3\
\n  34 - Polynomial Approximation 4\
\n  35 - Polynomial Approximation 5";

PIMathSolver::Method PIMathSolver::method_global = PIMathSolver::Eyler_2;


PIMathSolver::PIMathSolver() {
	times.resize(4);
}


void PIMathSolver::solve(double u, double h) {
	switch (method) {
	case Global:
		switch (method_global) {
		case Eyler_1: solveEyler1(u, h); break;
		case Eyler_2: solveEyler2(u, h); break;
		case RungeKutta_4: solveRK4(u, h); break;
		case AdamsBashfortMoulton_2: solveABM2(u, h); break;
		case AdamsBashfortMoulton_3: solveABM3(u, h); break;
		case AdamsBashfortMoulton_4:
		default: solveABM4(u, h); break;
		case PolynomialApproximation_2: solvePA2(u, h); break;
		case PolynomialApproximation_3: solvePA3(u, h); break;
		case PolynomialApproximation_4: solvePA4(u, h); break;
		case PolynomialApproximation_5: solvePA5(u, h); break;
		}
		break;
	case Eyler_1: solveEyler1(u, h); break;
	case Eyler_2: solveEyler2(u, h); break;
	case RungeKutta_4: solveRK4(u, h); break;
	case AdamsBashfortMoulton_2: solveABM2(u, h); break;
	case AdamsBashfortMoulton_3: solveABM3(u, h); break;
	case AdamsBashfortMoulton_4:
	default: solveABM4(u, h); break;
	case PolynomialApproximation_2: solvePA2(u, h); break;
	case PolynomialApproximation_3: solvePA3(u, h); break;
	case PolynomialApproximation_4: solvePA4(u, h); break;
	case PolynomialApproximation_5: solvePA5(u, h); break;
	}
	step++;
}


void PIMathSolver::fromTF(const TransferFunction & TF) {
	if (TF.vector_An.size() >= TF.vector_Bm.size())
		size = TF.vector_An.size() - 1;
	else {
		piCout << "PIMathSolver error: {A} should be greater than {B}";
		return;
	}
	if (size == 0) return;

	step = 0;
	times.fill(0.);
	A.resize(size, size);
	d.resize(size + 1);
	d.fill(0.);
	a1.resize(size + 1);
	a1.fill(0.);
	b1.resize(size + 1);
	b1.fill(0.);
	X.resize(size);
	X.fill(0.);
	F.resize(5);
	for (uint i = 0; i < 5; ++i)
		F[i].resize(size), F[i].fill(0.);
	k1.resize(size);
	k1.fill(0.);
	k2.resize(size);
	k2.fill(0.);
	k3.resize(size);
	k3.fill(0.);
	k4.resize(size);
	k4.fill(0.);
	xx.resize(size);
	xx.fill(0.);
	XX.resize(size);
	XX.fill(0.);
	for (uint i = 0; i < size + 1; ++i)
		a1[size - i] = TF.vector_An[i];
	for (uint i = 0; i < TF.vector_Bm.size(); ++i)
		b1[size - i] = TF.vector_Bm[i];
	double a0 = a1[0];
	a1 /= a0;
	b1 /= a0;

	d[0] = b1[0];
	for (uint i = 1; i < size + 1; ++i) {
		sum = 0.;
		for (uint m = 0; m < i; ++m)
			sum += a1[i - m] * d[m];
		d[i] = b1[i] - sum;
	}

	for (uint i = 0; i < size - 1; ++i)
		for (uint j = 0; j < size; ++j)
			A[j][i] = (j == i + 1);
	for (uint i = 0; i < size; ++i)
		A[i][size - 1] = -a1[size - i];
	for (uint i = 0; i < size; ++i)
		d[i] = d[i + 1];
}


void PIMathSolver::setTime(double time) {
	times.pop_back();
	times.push_front(time);
}


void PIMathSolver::solveEyler1(double u, double h) {
	F[0] = A * X + d * u;
	X += F[0] * h;
	moveF();
}


void PIMathSolver::solveEyler2(double u, double h) {
	F[0] = A * X + d * u;
	X += (F[0] + F[1]) * h / 2.;
	moveF();
}


void PIMathSolver::solveRK4(double u, double h) {
	td = X[0] - F[0][0];
	k1 = A * X + d * u;
	xx = k1 * h / 2.;
	XX = X + xx;
	k2 = A * (XX + k1 * h / 2.) + d * (u + td / 3.);
	xx = k2 * h / 2.;
	XX += xx;
	k3 = A * (XX + k2 * h / 2.) + d * (u + td * 2. / 3.);
	xx = k3 * h;
	XX += xx;
	k4 = A * (XX + k3 * h) + d * (u + td);
	// cout << k1 << k2 << k3 << k4 << endl;
	X += (k1 + k2 * 2. + k3 * 2. + k4) * h / 6.;
	F[0] = X;
}


void PIMathSolver::solveABM2(double u, double h) {
	F[0] = A * X + d * u;
	XX   = X + (F[0] * 3. - F[1]) * (h / 2.);
	F[1] = A * XX + d * u;
	X += (F[1] + F[0]) * (h / 2.);
	moveF();
}


void PIMathSolver::solveABM3(double u, double h) {
	F[0] = A * X + d * u;
	XX   = X + (F[0] * 23. - F[1] * 16. + F[2] * 5.) * (h / 12.);
	F[2] = A * XX + d * u;
	X += (F[2] * 5. + F[0] * 8. - F[1]) * (h / 12.);
	moveF();
}


void PIMathSolver::solveABM4(double u, double h) {
	F[0] = A * X + d * u;
	XX   = X + (F[0] * 55. - F[1] * 59. + F[2] * 37. - F[3] * 9.) * (h / 24.);
	F[3] = A * XX + d * u;
	X += (F[3] * 9. + F[0] * 19. - F[1] * 5. + F[2]) * (h / 24.);
	moveF();
}


void PIMathSolver::solvePA(double u, double h, uint deg) {
	F[0] = A * X + d * u;
	M.resize(deg, deg);
	Y.resize(deg);
	pY.resize(deg);

	for (uint k = 0; k < size; ++k) {
		for (uint i = 0; i < deg; ++i) {
			td = 1.;
			ct = times[i];
			for (uint j = 0; j < deg; ++j) {
				M[j][i] = td;
				td *= ct;
			}
		}
		for (uint i = 0; i < deg; ++i)
			Y[i] = F[i][k];
		/// find polynom
		// if (step == 1) cout << M << endl << Y << endl;
		M.invert(&ok, &Y);
		// if (step == 1) cout << Y << endl;
		if (!ok) {
			solveEyler2(u, h);
			break;
		}
		// calc last piece
		x0 = 0.;
		td = 1.;
		t  = times[0];
		for (uint i = 0; i < deg; ++i) {
			x0 += Y[i] * td / (i + 1.);
			td *= t;
		}
		x0 *= t;

		x1 = 0.;
		td = 1.;
		t  = times[1];
		for (uint i = 0; i < deg; ++i) {
			x1 += Y[i] * td / (i + 1.);
			td *= t;
		}
		x1 *= t;
		lp = x0 - x1;

		if (deg > 2) {
			// calc prev piece
			x0 = 0.;
			td = 1.;
			dh = times[1] - times[2];
			if (dh != 0. && step > 1) {
				t = times[2];
				for (uint i = 0; i < deg; ++i) {
					x0 += Y[i] * td / (i + 1.);
					td *= t;
				}
				x0 *= t;
				ct = x1 - x0;
				// calc correction
				ct -= pY[k];
			}
			// calc final
			X[k] += lp - ct;
			pY[k] = lp;
		} else {
			X[k] += lp;
			pY[k] = lp;
		}
	}
	moveF();
}


void PIMathSolver::solvePA2(double u, double h) {
	if (step > 0)
		solvePA(u, h, 2);
	else
		solveEyler1(u, h);
}


void PIMathSolver::solvePA3(double u, double h) {
	if (step > 1)
		solvePA(u, h, 3);
	else
		solvePA2(u, h);
}


void PIMathSolver::solvePA4(double u, double h) {
	if (step > 2)
		solvePA(u, h, 4);
	else
		solvePA3(u, h);
}


void PIMathSolver::solvePA5(double u, double h) {
	if (step > 3)
		solvePA(u, h, 5);
	else
		solvePA4(u, h);
}


void PIMathSolver::moveF() {
	for (uint i = F.size() - 1; i > 0; --i)
		F[i] = F[i - 1];
}
