/*
    KmPlot - a math. function plotter for the KDE-Desktop

    SPDX-FileCopyrightText: 1998, 1999, 2000, 2002 Klaus-Dieter Möller <kd.moeller@t-online.de>
    SPDX-FileCopyrightText: 2006 David Saxton <david@bluehaze.org>

    This file is part of the KDE Project.
    KmPlot is part of the KDE-EDU Project.

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

*/

#include "function.h"
#include "ksliderwindow.h"
#include "settings.h"
#include "view.h"
#include "xparser.h"

#include <QImage>
#include <QLinearGradient>
#include <QPainter>

#include <assert.h>
#include <cmath>

int MAX_PM = 4;

// BEGIN class Value
Value::Value(const QString &expression)
{
    m_value = 0.0;
    if (expression.isEmpty())
        m_expression = '0';
    else
        updateExpression(expression);
}

Value::Value(double value)
{
    updateExpression(value);
}

bool Value::updateExpression(const QString &expression)
{
    Parser::Error error;
    double newValue = XParser::self()->eval(expression, &error);
    if (error != Parser::ParseSuccess)
        return false;

    m_value = newValue;
    m_expression = expression;
    return true;
}

void Value::updateExpression(double value)
{
    m_value = value;
    m_expression = Parser::number(value);
}

bool Value::operator==(const Value &other) const
{
    return m_expression == other.expression();
}
// END class Value

// BEGIN class PlotAppearance
PlotAppearance::PlotAppearance()
{
    lineWidth = 0.3;
    color = Qt::black;
    useGradient = false;
    visible = false;
    style = Qt::SolidLine;
    showExtrema = false;
    showTangentField = false;
    showPlotName = false;
}

bool PlotAppearance::operator!=(const PlotAppearance &other) const
{
    return (lineWidth != other.lineWidth) || (color != other.color) || (useGradient != other.useGradient) || (gradient.stops() != other.gradient.stops())
        || (visible != other.visible) || (style != other.style) || (showExtrema != other.showExtrema) || (showTangentField != other.showTangentField)
        || (showPlotName != other.showPlotName);
}

QString PlotAppearance::penStyleToString(Qt::PenStyle style)
{
    switch (style) {
    case Qt::NoPen:
        return "NoPen";

    case Qt::SolidLine:
        return "SolidLine";

    case Qt::DashLine:
        return "DashLine";

    case Qt::DotLine:
        return "DotLine";

    case Qt::DashDotLine:
        return "DashDotLine";

    case Qt::DashDotDotLine:
        return "DashDotDotLine";

    case Qt::MPenStyle:
    case Qt::CustomDashLine:
        qWarning() << "Unsupported pen style\n";
        break;
    }

    qWarning() << "Unknown style " << style;
    return "SolidLine";
}

Qt::PenStyle PlotAppearance::stringToPenStyle(const QString &style)
{
    if (style == "NoPen")
        return Qt::NoPen;

    if (style == "SolidLine")
        return Qt::SolidLine;

    if (style == "DashLine")
        return Qt::DashLine;

    if (style == "DotLine")
        return Qt::DotLine;

    if (style == "DashDotLine")
        return Qt::DashDotLine;

    if (style == "DashDotDotLine")
        return Qt::DashDotDotLine;

    qWarning() << "Unknown style " << style;
    return Qt::SolidLine;
}
// END class PlotAppearance

// BEGIN class DifferentialState
DifferentialState::DifferentialState()
{
    x = 0;
}

DifferentialState::DifferentialState(int order)
{
    x = 0;
    setOrder(order);
}

void DifferentialState::setOrder(int order)
{
    bool orderWasZero = (y0.size() == 0);

    y.resize(order);
    y0.resize(order);

    if (orderWasZero && order >= 1)
        y0[0].updateExpression("1");

    resetToInitial();
}

bool DifferentialStates::setStep(const Value &step)
{
    if (step.value() <= 0)
        return false;

    m_step = step;
    return true;
}

void DifferentialState::resetToInitial()
{
    x = x0.value();
    y = y0;
}

bool DifferentialState::operator==(const DifferentialState &other) const
{
    return (x0 == other.x0) && (x == other.x) && (y0 == other.y0) && (y == other.y);
}
// END class DifferentialState

// BEGIN class DifferentialStates
DifferentialStates::DifferentialStates()
{
    m_uniqueState = false;
    m_order = 0;
    m_step.updateExpression(0.05);
}

void DifferentialStates::setOrder(int order)
{
    m_order = order;
    for (int i = 0; i < m_data.size(); ++i)
        m_data[i].setOrder(order);
}

DifferentialState *DifferentialStates::add()
{
    if (!m_uniqueState || m_data.isEmpty())
        m_data << DifferentialState(order());
    else
        qDebug() << "Unable to add another state!\n";

    return &m_data[size() - 1];
}

void DifferentialStates::setUniqueState(bool unique)
{
    m_uniqueState = unique;
    if (m_uniqueState && m_data.size() > 1) {
        // Remove any states other than the first
        m_data.resize(1);
    }
}

void DifferentialStates::resetToInitial()
{
    for (int i = 0; i < m_data.size(); ++i)
        m_data[i].resetToInitial();
}
// END class DifferentialStates

// BEGIN class Equation
Equation::Equation(Type type, Function *parent)
    : m_type(type)
    , m_parent(parent)
{
    m_usesParameter = false;
    mptr = 0;

    if (type == Differential || type == Cartesian) {
        differentialStates.setUniqueState(type == Cartesian);
        differentialStates.setOrder(order());
        differentialStates.add();
    }
}

Equation::~Equation()
{
}

int Equation::order() const
{
    if (type() == Cartesian) {
        // For drawing integrals
        return 1;
    } else
        return name(false).count('\'');
}

int Equation::pmCount() const
{
    return m_fstr.count(PmSymbol);
}

QString Equation::name(bool removePrimes) const
{
    if (m_fstr.isEmpty())
        return QString();

    int open = m_fstr.indexOf('(');
    int equals = m_fstr.indexOf('=');

    if ((equals == -1) && (open == -1))
        return QString();

    int pos;
    if (((equals > open) && (open != -1)) || (equals == -1))
        pos = open;
    else
        pos = equals;

    QString n = m_fstr.left(pos).trimmed();

    if (removePrimes)
        n.remove('\'');

    return n;
}

bool Equation::looksLikeFunction() const
{
    int open = m_fstr.indexOf('(');
    int equals = m_fstr.indexOf('=');

    if ((open != -1) && (open < equals))
        return true;

    switch (type()) {
    case Cartesian:
    case Differential:
    case ParametricY:
        return (name() != "y");

    case Polar:
        return (name() != "r");

    case ParametricX:
        return (name() != "x");

    case Implicit:
        return false;

    case Constant:
        return false;
    }

    return true;
}

void Equation::updateVariables()
{
    if (type() == Constant) {
        return;
    }

    m_variables.clear();

    if (looksLikeFunction()) {
        int p1 = m_fstr.indexOf('(');
        int p2 = m_fstr.indexOf(')');

        const QStringList listSplit = ((p1 != -1) && (p2 != -1)) ? m_fstr.mid(p1 + 1, p2 - p1 - 1).split(',', Qt::SkipEmptyParts) : QStringList();

        // Variables shouldn't contain spaces!
        for (QString s : listSplit) {
            s = s.remove(' ');
            if (!s.isEmpty())
                m_variables << s;
        }
    } else
        switch (type()) {
        case Cartesian:
        case Differential:
            m_variables << "x"
                        << "k";
            break;

        case Polar:
            m_variables << QChar(0x3b8) << "k"; // (theta)
            break;

        case ParametricX:
        case ParametricY:
            m_variables << "t"
                        << "k";
            break;

        case Implicit:
            m_variables << "x"
                        << "y"
                        << "k";
            break;

        case Constant:
            break;
        }

    // If we are a differential equation, then add on y, y', etc
    if (type() == Differential && !name().isEmpty()) {
        QString n = name();

        int order = this->order();
        for (int i = 0; i < order; ++i) {
            m_variables << n;
            n += '\'';
        }
    }

    // BEGIN Update whether we accept a parameter or not
    int expectedNumVariables = 0;

    switch (m_type) {
    case Cartesian:
    case ParametricX:
    case ParametricY:
    case Polar:
        expectedNumVariables = 1;
        break;

    case Implicit:
        expectedNumVariables = 2;
        break;

    case Differential:
        expectedNumVariables = order() + 1;
        break;

    case Constant:
        expectedNumVariables = 0;
        break;
    }

    m_usesParameter = (variables().size() > expectedNumVariables);
    // END Update whether we accept a parameter or not
}

QString Equation::parameterName() const
{
    if (!usesParameter())
        return QString();

    int parAt = (type() == Implicit) ? 2 : 1;
    return variables()[parAt];
}

bool Equation::setFstr(const QString &fstr, int *error, int *errorPosition, bool force)
{
#define HANDLE_ERROR                                                                                                                                           \
    if (!force) {                                                                                                                                              \
        m_fstr = prevFstr;                                                                                                                                     \
        updateVariables();                                                                                                                                     \
    } else {                                                                                                                                                   \
        qDebug() << "fstr " << fstr << " invalid, but forcing anyway: " << Parser::errorString(Parser::Error(*error)) << " at position " << *errorPosition;    \
        mem.clear();                                                                                                                                           \
    }

    int temp1, temp2;
    if (!error)
        error = &temp1;
    if (!errorPosition)
        errorPosition = &temp2;

    *error = Parser::ParseSuccess;
    *errorPosition = -1;

    QString prevFstr = m_fstr;
    m_fstr = fstr;
    updateVariables();

    if (!fstr.contains('=') || QString(fstr).right(fstr.length() - fstr.indexOf('=') - 1).simplified().isEmpty()) {
        *error = Parser::SyntaxError;
        HANDLE_ERROR;
        return false;
    }

    // require order to be greater than 0 for differential equations
    if ((type() == Differential) && (order() < 1)) {
        *error = Parser::ZeroOrder;
        HANDLE_ERROR;
        /// \todo indicate the position of the error
        return false;
    }

    int maxArg = order() + ((type() == Implicit) ? 3 : 2);
    if (variables().size() > maxArg) {
        *error = Parser::TooManyArguments;
        HANDLE_ERROR;
        /// \todo indicate the position of the invalid argument?
        return false;
    }

    XParser::self()->initEquation(this, (Parser::Error *)error, errorPosition);
    if (*error != Parser::ParseSuccess) {
        HANDLE_ERROR;
        if (!force)
            XParser::self()->initEquation(this);
        return false;
    }

    differentialStates.setOrder(order());
    return true;
}

void Equation::setPMSignature(QVector<bool> pmSignature)
{
    differentialStates.resetToInitial();
    m_pmSignature = pmSignature;
}

bool Equation::operator!=(const Equation &other)
{
    return (fstr() != other.fstr()) || (differentialStates != other.differentialStates);
}

Equation &Equation::operator=(const Equation &other)
{
    setFstr(other.fstr());
    differentialStates = other.differentialStates;

    return *this;
}
// END class Equation

// BEGIN class Function
Function::Function(Type type)
    : m_type(type)
{
    x = y = 0;
    m_implicitMode = UnfixedXY;

    usecustomxmin = false;
    usecustomxmax = false;

    dmin.updateExpression(QChar('0'));
    if (Settings::anglemode() == Parser::Radians)
        dmax.updateExpression(QString(QChar('2')) + PiSymbol);
    else
        dmax.updateExpression("360");

    switch (m_type) {
    case Cartesian:
        eq << new Equation(Equation::Cartesian, this);
        break;

    case Polar:
        eq << new Equation(Equation::Polar, this);
        usecustomxmin = true;
        usecustomxmax = true;
        break;

    case Parametric:
        eq << new Equation(Equation::ParametricX, this);
        eq << new Equation(Equation::ParametricY, this);
        usecustomxmin = true;
        usecustomxmax = true;
        break;

    case Implicit:
        eq << new Equation(Equation::Implicit, this);
        break;

    case Differential:
        eq << new Equation(Equation::Differential, this);
        break;
    }

    m_id = 0;
    f0.visible = true;

    k = 0;
}

Function::~Function()
{
    for (Equation *e : qAsConst(eq))
        delete e;
}

bool Function::copyFrom(const Function &function)
{
    bool changed = false;
    int i = 0;
#define COPY_AND_CHECK(s)                                                                                                                                      \
    {                                                                                                                                                          \
        if (s != function.s) {                                                                                                                                 \
            s = function.s;                                                                                                                                    \
            changed = true;                                                                                                                                    \
        }                                                                                                                                                      \
    }                                                                                                                                                          \
    i++;

    COPY_AND_CHECK(f0); // 0
    if (type() == Cartesian) {
        COPY_AND_CHECK(f1); // 1
        COPY_AND_CHECK(f2); // 2
        COPY_AND_CHECK(f3); // 3
        COPY_AND_CHECK(integral); // 4
    }
    COPY_AND_CHECK(dmin); // 5,1
    COPY_AND_CHECK(dmax); // 6,2
    COPY_AND_CHECK(usecustomxmin); // 7,3
    COPY_AND_CHECK(usecustomxmax); // 8,4
    COPY_AND_CHECK(m_parameters); // 9,5

    // handle equations separately
    for (int i = 0; i < eq.size(); ++i) {
        if (*eq[i] != *function.eq[i]) {
            changed = true;
            *eq[i] = *function.eq[i];
        }
    }

    return changed;
}

QString Function::name() const
{
    QString n = eq[0]->fstr();
    for (int i = 1; i < eq.size(); ++i)
        n += '\n' + eq[i]->fstr();

    return n;
}

PlotAppearance &Function::plotAppearance(PMode plot)
{
    // NOTE: This function is identical to the const one, so changes to this should be applied to both

    switch (plot) {
    case Function::Derivative0:
        return f0;
    case Function::Derivative1:
        return f1;
    case Function::Derivative2:
        return f2;
    case Function::Derivative3:
        return f3;
    case Function::Integral:
        return integral;
    }

    qCritical() << "Unknown plot " << plot;
    return f0;
}
PlotAppearance Function::plotAppearance(PMode plot) const
{
    // NOTE: This function is identical to the none-const one, so changes to this should be applied to both

    switch (plot) {
    case Function::Derivative0:
        return f0;
    case Function::Derivative1:
        return f1;
    case Function::Derivative2:
        return f2;
    case Function::Derivative3:
        return f3;
    case Function::Integral:
        return integral;
    }

    qCritical() << "Unknown plot " << plot;
    return f0;
}

bool Function::allPlotsAreHidden() const
{
    return !f0.visible && !f1.visible && !f2.visible && !integral.visible;
}

QString Function::typeToString(Type type)
{
    switch (type) {
    case Cartesian:
        return "cartesian";

    case Parametric:
        return "parametric";

    case Polar:
        return "polar";

    case Implicit:
        return "implicit";

    case Differential:
        return "differential";
    }

    qWarning() << "Unknown type " << type;
    return "unknown";
}

Function::Type Function::stringToType(const QString &type)
{
    if (type == "cartesian")
        return Cartesian;

    if (type == "parametric")
        return Parametric;

    if (type == "polar")
        return Polar;

    if (type == "implicit")
        return Implicit;

    if (type == "differential")
        return Differential;

    qWarning() << "Unknown type " << type;
    return Cartesian;
}

QList<Plot> Function::plots(PlotCombinations combinations) const
{
    QList<Plot> list;

    if (allPlotsAreHidden())
        return list;

    Plot plot;
    plot.setFunctionID(id());
    plot.plotNumberCount = m_parameters.useList ? m_parameters.list.size() + (m_parameters.useSlider ? 1 : 0) : 1;

    bool singlePlot = (!m_parameters.useList && !m_parameters.useSlider) || m_parameters.animating || (~combinations & DifferentParameters)
        || (!m_parameters.useSlider && m_parameters.useList && m_parameters.list.isEmpty());

    if (singlePlot) {
        if (m_parameters.animating)
            plot.parameter = Parameter(Parameter::Animated);

        list << plot;
    } else {
        int i = 0;

        if (m_parameters.useSlider) {
            Parameter param(Parameter::Slider);
            param.setSliderID(m_parameters.sliderID);
            plot.parameter = param;
            plot.plotNumber = i++;
            list << plot;
        }

        if (m_parameters.useList) {
            const int listsize = m_parameters.list.size();
            for (int pos = 0; pos < listsize; ++pos) {
                Parameter param(Parameter::List);
                param.setListPos(pos);
                plot.parameter = param;
                plot.plotNumber = i++;
                list << plot;
            }
        }
    }

    // Copy each plot in the list for other variations
    if ((type() == Cartesian) && (combinations & DifferentDerivatives)) {
        QList<Plot> duplicated;

        for (PMode p = Derivative0; p <= Integral; p = PMode(p + 1)) {
            for (Plot plot : qAsConst(list)) {
                if (!plotAppearance(p).visible)
                    continue;
                plot.plotMode = p;
                duplicated << plot;
            }
        }

        list = duplicated;
    }

    if ((type() == Differential) && (combinations & DifferentInitialStates)) {
        QList<Plot> duplicated;

        for (int i = 0; i < eq[0]->differentialStates.size(); ++i) {
            for (Plot plot : qAsConst(list)) {
                plot.stateNumber = i;
                duplicated << plot;
            }
        }

        list = duplicated;
    }

    if (combinations & DifferentPMSignatures) {
        int size = 0;
        for (Equation *equation : qAsConst(eq))
            size += equation->pmCount();

        unsigned max = unsigned(std::pow(2.0, (double)size));
        QVector<QVector<bool>> signatures(max);

        for (unsigned i = 0; i < max; ++i) {
            QVector<bool> sig(size);

            for (int j = 0; j < size; ++j)
                sig[j] = i & (1 << j);

            signatures[i] = sig;
        }

        // Generate a plot for each signature in signatures
        QList<Plot> duplicated;
        for (const QVector<bool> &signature : qAsConst(signatures)) {
            int at = 0;
            QList<QVector<bool>> pmSignature;

            for (Equation *equation : qAsConst(eq)) {
                int pmCount = equation->pmCount();
                QVector<bool> sig(pmCount);
                for (int i = 0; i < pmCount; ++i)
                    sig[i] = signature[i + at];
                at += pmCount;

                pmSignature << sig;
            }

            for (Plot plot : qAsConst(list)) {
                plot.pmSignature = pmSignature;
                duplicated << plot;
            }
        }
        list = duplicated;
    }

    return list;
}

void Function::addFunctionDependency(Function *function)
{
    if (!function || m_dependencies.contains(function->id()))
        return;

    Q_ASSERT_X(!function->dependsOn(this), "addFunctionDependency", "circular dependency");

    m_dependencies << function->id();
}

bool Function::dependsOn(Function *function) const
{
    if (!function)
        return false;

    if (m_dependencies.contains(function->id()))
        return true;

    for (int functionId : qAsConst(m_dependencies)) {
        Function *f = XParser::self()->functionWithID(functionId);

        if (f->dependsOn(function))
            return true;
    }

    return false;
}
// END class Function

// BEGIN class ParameterSettings
ParameterSettings::ParameterSettings()
{
    animating = false;
    useSlider = false;
    sliderID = 0;
    useList = false;
}

bool ParameterSettings::operator==(const ParameterSettings &other) const
{
    return (useSlider == other.useSlider) && (sliderID == other.sliderID) && (useList == other.useList) && (list == other.list);
}
// END class ParameterSettings

// BEGIN class Parameter
Parameter::Parameter(Type type)
    : m_type(type)
{
    m_sliderID = -1;
    m_listPos = -1;
}

bool Parameter::operator==(const Parameter &other) const
{
    return (type() == other.type()) && (listPos() == other.listPos()) && (sliderID() == other.sliderID());
}
// END class Parameter

// BEGIN class Plot
Plot::Plot()
{
    stateNumber = -1;
    plotNumberCount = 1;
    plotNumber = 0;
    m_function = 0;
    m_functionID = -1;
    plotMode = Function::Derivative0;
}

bool Plot::operator==(const Plot &other) const
{
    return (m_functionID == other.functionID()) && (plotMode == other.plotMode) && (parameter == other.parameter) && (stateNumber == other.stateNumber);
}

void Plot::setFunctionID(int id)
{
    m_functionID = id;
    updateCached();
}

void Plot::updateCached()
{
    m_function = XParser::self()->functionWithID(m_functionID);
}

QString Plot::name() const
{
    if (!m_function)
        return QString();

    QString n = m_function->name();

    if (m_function->eq[0]->usesParameter())
        n += QString("\n%1 = %2").arg(m_function->eq[0]->parameterName()).arg(Parser::number(parameterValue()));

    if (plotMode == Function::Derivative1)
        n = n.section('=', 0, 0).replace('(', "\'(");

    if (plotMode == Function::Derivative2)
        n = n.section('=', 0, 0).replace('(', "\'\'(");

    if (plotMode == Function::Integral) {
        QString functionName = n.section('=', 0, 0);
        n = QChar(0x222B) + ' ' + functionName + 'd' + functionName.section('(', 1, 1).remove(')').section(',', 0, 0);
    }

    return n;
}

void Plot::updateFunction() const
{
    if (!m_function)
        return;

    // Update the plus-minus signature
    assert(pmSignature.size() <= m_function->eq.size());
    for (int i = 0; i < pmSignature.size(); ++i)
        m_function->eq[i]->setPMSignature(pmSignature[i]);

    if (parameter.type() != Parameter::Animated)
        m_function->setParameter(parameterValue());
}

double Plot::parameterValue() const
{
    switch (parameter.type()) {
    case Parameter::Unknown:
        return 0;

    case Parameter::Slider: {
        KSliderWindow *sw = View::self()->m_sliderWindow;

        if (!sw) {
            // Slider window isn't open. Ask View to open it
            View::self()->updateSliders();

            // It should now be open
            sw = View::self()->m_sliderWindow;
            assert(sw);
        }

        return sw->value(parameter.sliderID());
    }

    case Parameter::List: {
        if ((parameter.listPos() >= 0) && (parameter.listPos() < m_function->m_parameters.list.size()))
            return m_function->m_parameters.list[parameter.listPos()].value();
        return 0;
    }

    case Parameter::Animated: {
        qWarning() << "Shouldn't use this function for animated parameter!\n";
        return 0;
    }
    }

    return 0;
}

void Plot::differentiate()
{
    switch (plotMode) {
    case Function::Integral:
        plotMode = Function::Derivative0;
        break;

    case Function::Derivative0:
        plotMode = Function::Derivative1;
        break;

    case Function::Derivative1:
        plotMode = Function::Derivative2;
        break;

    case Function::Derivative2:
        plotMode = Function::Derivative3;
        break;

    case Function::Derivative3:
        qWarning() << "Can't handle this yet!\n";
        break;
    }
}

void Plot::integrate()
{
    switch (plotMode) {
    case Function::Integral:
        qWarning() << "Can't handle this yet!\n";
        break;

    case Function::Derivative0:
        plotMode = Function::Integral;
        break;

    case Function::Derivative1:
        plotMode = Function::Derivative0;
        break;

    case Function::Derivative2:
        plotMode = Function::Derivative1;
        break;

    case Function::Derivative3:
        plotMode = Function::Derivative2;
        break;
    }
}

QColor Plot::color() const
{
    Function *f = function();
    assert(f); // Shouldn't call color without a function
    PlotAppearance appearance = f->plotAppearance(plotMode);

    if ((plotNumberCount <= 1) || !appearance.useGradient)
        return appearance.color;

    // Is a gradient

    int x = plotNumber;
    int l = plotNumberCount;

    QLinearGradient lg(0, 0, l - 1, 0);
    lg.setStops(appearance.gradient.stops());
    QImage im(l, 1, QImage::Format_RGB32);
    QPainter p(&im);
    p.setPen(QPen(lg, 1));
    p.drawLine(0, 0, l, 0);
    return im.pixel(x, 0);
}

int Plot::derivativeNumber() const
{
    switch (plotMode) {
    case Function::Integral:
        return -1;
    case Function::Derivative0:
        return 0;
    case Function::Derivative1:
        return 1;
    case Function::Derivative2:
        return 2;
    case Function::Derivative3:
        return 3;
    }

    qWarning() << "Unknown derivative number.\n";
    return 0;
}

DifferentialState *Plot::state() const
{
    if (!function() || (stateNumber < 0))
        return 0;

    if (function()->eq[0]->differentialStates.size() <= stateNumber)
        return 0;

    return &function()->eq[0]->differentialStates[stateNumber];
}
// END class Plot
