/*
    This file is part of Kig, a KDE program for Interactive Geometry.
    SPDX-FileCopyrightText: 2002 Maurizio Paolini <paolini@dmf.unicatt.it>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "kignumerics.h"
#include "common.h"

using std::fabs;

/*
 * compute one of the roots of a cubic polynomial
 * if xmin << 0 or xmax >> 0 then autocompute a bound for all the
 * roots
 */

double calcCubicRoot(double xmin, double xmax, double a, double b, double c, double d, int root, bool &valid, int &numroots)
{
    // renormalize: positive a and infinity norm = 1

    double infnorm = fabs(a);
    if (infnorm < fabs(b))
        infnorm = fabs(b);
    if (infnorm < fabs(c))
        infnorm = fabs(c);
    if (infnorm < fabs(d))
        infnorm = fabs(d);
    if (a < 0)
        infnorm = -infnorm;
    a /= infnorm;
    b /= infnorm;
    c /= infnorm;
    d /= infnorm;

    const double small = 1e-7;
    valid = false;
    if (fabs(a) < small) {
        if (fabs(b) < small) {
            if (fabs(c) < small) { // degree = 0;
                numroots = 0;
                return 0.0;
            }
            // degree = 1
            double rootval = -d / c;
            numroots = 1;
            if (rootval < xmin || xmax < rootval)
                numroots--;
            if (root > numroots)
                return 0.0;
            valid = true;
            return rootval;
        }
        // degree = 2
        if (b < 0) {
            b = -b;
            c = -c;
            d = -d;
        }
        double discrim = c * c - 4 * b * d;
        numroots = 2;
        if (discrim < 0) {
            numroots = 0;
            return 0.0;
        }
        discrim = std::sqrt(discrim) / (2 * fabs(b));
        double rootmiddle = -c / (2 * b);
        if (rootmiddle - discrim < xmin)
            numroots--;
        if (rootmiddle + discrim > xmax)
            numroots--;
        if (rootmiddle + discrim < xmin)
            numroots--;
        if (rootmiddle - discrim > xmax)
            numroots--;
        if (root > numroots)
            return 0.0;
        valid = true;
        if (root == 2 || rootmiddle - discrim < xmin)
            return rootmiddle + discrim;
        return rootmiddle - discrim;
    }

    if (xmin < -1e8 || xmax > 1e8) {
        // compute a bound for all the real roots:

        xmax = fabs(d / a);
        if (fabs(c / a) + 1 > xmax)
            xmax = fabs(c / a) + 1;
        if (fabs(b / a) + 1 > xmax)
            xmax = fabs(b / a) + 1;
        xmin = -xmax;
    }

    // computing the coefficients of the Sturm sequence
    double p1a = 2 * b * b - 6 * a * c;
    double p1b = b * c - 9 * a * d;
    double p0a = c * p1a * p1a + p1b * (3 * a * p1b - 2 * b * p1a);

    int varbottom = calcCubicVariations(xmin, a, b, c, d, p1a, p1b, p0a);
    int vartop = calcCubicVariations(xmax, a, b, c, d, p1a, p1b, p0a);
    numroots = vartop - varbottom;
    valid = false;
    if (root <= varbottom || root > vartop)
        return 0.0;

    valid = true;

    // now use bisection to separate the required root
    double dx = (xmax - xmin) / 2;
    while (vartop - varbottom > 1) {
        if (fabs(dx) < 1e-8)
            return (xmin + xmax) / 2;
        double xmiddle = xmin + dx;
        int varmiddle = calcCubicVariations(xmiddle, a, b, c, d, p1a, p1b, p0a);
        if (varmiddle < root) // I am below
        {
            xmin = xmiddle;
            varbottom = varmiddle;
        } else {
            xmax = xmiddle;
            vartop = varmiddle;
        }
        dx /= 2;
    }

    /*
     * now [xmin, xmax] enclose a single root, try using Newton
     */
    if (vartop - varbottom == 1) {
        double fval1 = a; // double check...
        double fval2 = a;
        fval1 = b + xmin * fval1;
        fval2 = b + xmax * fval2;
        fval1 = c + xmin * fval1;
        fval2 = c + xmax * fval2;
        fval1 = d + xmin * fval1;
        fval2 = d + xmax * fval2;
        assert(fval1 * fval2 <= 0);
        return calcCubicRootwithNewton(xmin, xmax, a, b, c, d, 1e-8);
    } else // probably a double root here!
        return (xmin + xmax) / 2;
}

/*
 * computation of the number of sign changes in the sturm sequence for
 * a third degree polynomial at x.  This number counts the number of
 * roots of the polynomial on the left of point x.
 *
 * a, b, c, d: coefficients of the third degree polynomial (a*x^3 + ...)
 *
 * the second degree polynomial in the sturm sequence is just minus the
 * derivative, so we don't need to compute it.
 *
 * p1a*x + p1b: is the third (first degree) polynomial in the sturm sequence.
 *
 * p0a: is the (constant) fourth polynomial of the sturm sequence.
 */

int calcCubicVariations(double x, double a, double b, double c, double d, double p1a, double p1b, double p0a)
{
    double fval, fpval;
    fval = fpval = a;
    fval = b + x * fval;
    fpval = fval + x * fpval;
    fval = c + x * fval;
    fpval = fval + x * fpval;
    fval = d + x * fval;

    double f1val = p1a * x + p1b;

    bool f3pos = fval >= 0;
    bool f2pos = fpval <= 0;
    bool f1pos = f1val >= 0;
    bool f0pos = p0a >= 0;

    int variations = 0;
    if (f3pos != f2pos)
        variations++;
    if (f2pos != f1pos)
        variations++;
    if (f1pos != f0pos)
        variations++;
    return variations;
}

/*
 * use newton to solve a third degree equation with already isolated
 * root
 */

inline void calcCubicDerivatives(double x, double a, double b, double c, double d, double &fval, double &fpval, double &fppval)
{
    fval = fpval = fppval = a;
    fval = b + x * fval;
    fpval = fval + x * fpval;
    fppval = fpval + x * fppval; // this is really half the second derivative
    fval = c + x * fval;
    fpval = fval + x * fpval;
    fval = d + x * fval;
}

double calcCubicRootwithNewton(double xmin, double xmax, double a, double b, double c, double d, double tol)
{
    double fval, fpval, fppval;

    double fval1, fval2, fpval1, fpval2, fppval1, fppval2;
    calcCubicDerivatives(xmin, a, b, c, d, fval1, fpval1, fppval1);
    calcCubicDerivatives(xmax, a, b, c, d, fval2, fpval2, fppval2);
    assert(fval1 * fval2 <= 0);

    assert(xmax > xmin);
    while (xmax - xmin > tol) {
        // compute the values of function, derivative and second derivative:
        assert(fval1 * fval2 <= 0);
        if (fppval1 * fppval2 < 0 || fpval1 * fpval2 < 0) {
            double xmiddle = (xmin + xmax) / 2;
            calcCubicDerivatives(xmiddle, a, b, c, d, fval, fpval, fppval);
            if (fval1 * fval <= 0) {
                xmax = xmiddle;
                fval2 = fval;
                fpval2 = fpval;
                fppval2 = fppval;
            } else {
                xmin = xmiddle;
                fval1 = fval;
                fpval1 = fpval;
                fppval1 = fppval;
            }
        } else {
            // now we have first and second derivative of constant sign, we
            // can start with Newton from the Fourier point.
            double x = xmin;
            if (fval2 * fppval2 > 0)
                x = xmax;
            double p = 1.0;
            int iterations = 0;
            while (fabs(p) > tol && iterations++ < 100) {
                calcCubicDerivatives(x, a, b, c, d, fval, fpval, fppval);
                p = fval / fpval;
                x -= p;
            }
            if (iterations >= 100) {
                // Newton scheme did not converge..
                // we should end up with an invalid Coordinate
                return double_inf;
            };
            return x;
        }
    }

    // we cannot apply Newton, (perhaps we are at an inflection point)

    return (xmin + xmax) / 2;
}

/*
 * This function computes the LU factorization of a mxn matrix, with
 * m typically less than n.  This is done with complete pivoting; the
 * exchanges in columns are recorded in the integer vector "exchange"
 */
bool GaussianElimination(double *matrix[], int numrows, int numcols, int exchange[])
{
    // start gaussian elimination
    for (int k = 0; k < numrows; ++k) {
        // ricerca elemento di modulo massimo
        double maxval = -double_inf;
        int imax = k;
        int jmax = k;
        for (int i = k; i < numrows; ++i) {
            for (int j = k; j < numcols; ++j) {
                if (fabs(matrix[i][j]) > maxval) {
                    maxval = fabs(matrix[i][j]);
                    imax = i;
                    jmax = j;
                }
            }
        }

        // row exchange
        if (imax != k)
            for (int j = k; j < numcols; ++j) {
                double t = matrix[k][j];
                matrix[k][j] = matrix[imax][j];
                matrix[imax][j] = t;
            }

        // column exchange
        if (jmax != k)
            for (int i = 0; i < numrows; ++i) {
                double t = matrix[i][k];
                matrix[i][k] = matrix[i][jmax];
                matrix[i][jmax] = t;
            }

        // remember this column exchange at step k
        exchange[k] = jmax;

        // we can't usefully eliminate a singular matrix..
        if (maxval == 0.)
            return false;

        // ciclo sulle righe
        for (int i = k + 1; i < numrows; ++i) {
            double mik = matrix[i][k] / matrix[k][k];
            matrix[i][k] = mik; // ricorda il moltiplicatore... (not necessary)
            // ciclo sulle colonne
            for (int j = k + 1; j < numcols; ++j) {
                matrix[i][j] -= mik * matrix[k][j];
            }
        }
    }
    return true;
}

/*
 * solve an undetermined homogeneous triangular system. the matrix is nonzero
 * on its diagonal. The last unknown(s) are chosen to be 1. The
 * vector "exchange" contains exchanges to be performed on the
 * final solution components.
 */

void BackwardSubstitution(double *matrix[], int numrows, int numcols, int exchange[], double solution[])
{
    // the system is homogeneous and underdetermined, the last unknown(s)
    // are chosen = 1
    for (int j = numrows; j < numcols; ++j) {
        solution[j] = 1.0; // other choices are possible here
    };

    for (int k = numrows - 1; k >= 0; --k) {
        // backward substitution
        solution[k] = 0.0;
        for (int j = k + 1; j < numcols; ++j) {
            solution[k] -= matrix[k][j] * solution[j];
        }
        solution[k] /= matrix[k][k];
    }

    // ultima fase: riordinamento incognite

    for (int k = numrows - 1; k >= 0; --k) {
        int jmax = exchange[k];
        double t = solution[k];
        solution[k] = solution[jmax];
        solution[jmax] = t;
    }
}

bool Invert3by3matrix(const double m[3][3], double inv[3][3])
{
    double det = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
        + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
    if (det == 0)
        return false;

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            int i1 = (i + 1) % 3;
            int i2 = (i + 2) % 3;
            int j1 = (j + 1) % 3;
            int j2 = (j + 2) % 3;
            inv[j][i] = (m[i1][j1] * m[i2][j2] - m[i1][j2] * m[i2][j1]) / det;
        }
    }
    return true;
}
