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

    SPDX-FileCopyrightText: 1998-2002 Klaus-Dieter Möller <kd.moeller@t-online.de>
    SPDX-FileCopyrightText: 2004 Fredrik Edemar <f_edemar@linux.se>
    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 "functioneditor.h"
#include "equationedit.h"
#include "kgradientdialog.h"
#include "kmplotio.h"
#include "maindlg.h"
#include "parameterswidget.h"
#include "ui_functioneditorwidget.h"
#include "view.h"
#include "xparser.h"

#include <KColorButton>
#include <KMessageBox>

#include <QDebug>
#include <QRadioButton>
#include <QTimer>

#include <assert.h>

class FunctionEditorWidget : public QWidget, public Ui::FunctionEditorWidget
{
public:
    FunctionEditorWidget(QWidget *parent = 0)
        : QWidget(parent)
    {
        setupUi(this);
    }
};

// BEGIN class FunctionEditor
FunctionEditor::FunctionEditor(QMenu *createNewPlotsMenu, QWidget *parent)
    : QDockWidget(i18n("Functions"), parent)
{
    m_functionID = -1;

    // need a name for saving and restoring the position of this dock widget
    setObjectName(QStringLiteral("FunctionEditor"));

    setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
    setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);

    for (int i = 0; i < 5; ++i) {
        m_saveTimer[i] = new QTimer(this);
        m_saveTimer[i]->setSingleShot(true);
    }

    m_syncFunctionListTimer = new QTimer(this);
    m_syncFunctionListTimer->setSingleShot(true);

    connect(m_saveTimer[Function::Cartesian], &QTimer::timeout, this, &FunctionEditor::saveCartesian);
    connect(m_saveTimer[Function::Polar], &QTimer::timeout, this, &FunctionEditor::savePolar);
    connect(m_saveTimer[Function::Parametric], &QTimer::timeout, this, &FunctionEditor::saveParametric);
    connect(m_saveTimer[Function::Implicit], &QTimer::timeout, this, &FunctionEditor::saveImplicit);
    connect(m_saveTimer[Function::Differential], &QTimer::timeout, this, &FunctionEditor::saveDifferential);
    connect(m_syncFunctionListTimer, &QTimer::timeout, this, &FunctionEditor::syncFunctionList);

    m_editor = new FunctionEditorWidget;
    m_functionList = m_editor->functionList;

    m_editor->createNewPlot->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
    m_editor->deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));

    // BEGIN initialize equation edits
    m_editor->cartesianEquation->setInputType(EquationEdit::Function);
    m_editor->cartesianEquation->setEquationType(Equation::Cartesian);
    m_editor->cartesianParameters->associateEquationEdit(m_editor->cartesianEquation);

    m_editor->polarEquation->setInputType(EquationEdit::Function);
    m_editor->polarEquation->setEquationType(Equation::Polar);
    m_editor->polarParameters->associateEquationEdit(m_editor->polarEquation);

    m_editor->parametricX->setInputType(EquationEdit::Function);
    m_editor->parametricX->setEquationType(Equation::ParametricX);
    m_editor->parametricParameters->associateEquationEdit(m_editor->parametricX);

    m_editor->parametricY->setInputType(EquationEdit::Function);
    m_editor->parametricY->setEquationType(Equation::ParametricY);
    m_editor->parametricParameters->associateEquationEdit(m_editor->parametricY);

    m_editor->implicitEquation->setInputType(EquationEdit::Function);
    m_editor->implicitEquation->setEquationType(Equation::Implicit);

    m_editor->differentialEquation->setInputType(EquationEdit::Function);
    m_editor->differentialEquation->setEquationType(Equation::Differential);
    m_editor->differentialParameters->associateEquationEdit(m_editor->differentialEquation);
    // END initialize equation edits

    for (unsigned i = 0; i < 5; ++i)
        m_editor->stackedWidget->widget(i)->layout()->setContentsMargins(0, 0, 0, 0);

    connect(m_editor->deleteButton, &QPushButton::clicked, this, &FunctionEditor::deleteCurrent);
    connect(m_functionList, &FunctionListWidget::currentItemChanged, this, &FunctionEditor::functionSelected);
    connect(m_functionList, &FunctionListWidget::itemClicked, this, &FunctionEditor::saveItem);

    // BEGIN connect up all editing widgets
#define CONNECT_WIDGETS(name, signal)                                                                                                                          \
    {                                                                                                                                                          \
        QList<name *> widgets = m_editor->findChildren<name *>();                                                                                              \
        for (name * w : qAsConst(widgets))                                                                                                                     \
            connect(w, SIGNAL(signal), this, SLOT(save()));                                                                                                    \
    }

    CONNECT_WIDGETS(QLineEdit, editingFinished());
    CONNECT_WIDGETS(EquationEdit, editingFinished());
    CONNECT_WIDGETS(QCheckBox, stateChanged(int));
    CONNECT_WIDGETS(KColorButton, changed(const QColor &));
    CONNECT_WIDGETS(QRadioButton, toggled(bool));
    CONNECT_WIDGETS(QComboBox, currentIndexChanged(int));
    CONNECT_WIDGETS(ParametersWidget, parameterListChanged());
    CONNECT_WIDGETS(KGradientButton, gradientChanged(const QGradient &));

    connect(m_editor->initialConditions, &InitialConditionsEditor::dataChanged, this, &FunctionEditor::save);
    // END connect up all editing widgets

    connect(XParser::self(), &XParser::functionAdded, this, &FunctionEditor::functionsChanged);
    connect(XParser::self(), &XParser::functionRemoved, this, &FunctionEditor::functionsChanged);

    m_editor->createNewPlot->setMenu(createNewPlotsMenu);

    resetFunctionEditing();
    setWidget(m_editor);
}

FunctionEditor::~FunctionEditor()
{
}

void FunctionEditor::deleteCurrent()
{
    m_editor->initialConditions->init(0);

    FunctionListItem *functionItem = static_cast<FunctionListItem *>(m_functionList->currentItem());
    if (!functionItem) {
        qDebug() << "Nothing currently selected!\n";
        return;
    }

    if (!XParser::self()->removeFunction(functionItem->function())) {
        qDebug() << "Couldn't delete function.\n";
        // couldn't delete it, as e.g. another function depends on it
        return;
    }

    MainDlg::self()->requestSaveCurrentState();
    View::self()->drawPlot();
}

void FunctionEditor::functionsChanged()
{
    m_syncFunctionListTimer->start(0);
}

void FunctionEditor::syncFunctionList()
{
    int oldFunctionCount = m_functionList->count();

    QListWidgetItem *currentItem = m_functionList->currentItem();
    QString currentText = currentItem ? currentItem->text() : QString();

    // build up a list of IDs that we have
    QMap<int, FunctionListItem *> currentIDs;
    QList<FunctionListItem *> currentFunctionItems;
    for (int row = 0; row < m_functionList->count(); ++row) {
        FunctionListItem *item = static_cast<FunctionListItem *>(m_functionList->item(row));
        currentFunctionItems << item;
        currentIDs[item->function()] = item;

        // also update what is displayed
        item->update();
    }

    FunctionListItem *toSelect = 0l;
    int newFunctionCount = 0;

    for (QMap<int, Function *>::iterator it = XParser::self()->m_ufkt.begin(); it != XParser::self()->m_ufkt.end(); ++it) {
        Function *function = *it;

        if (currentIDs.contains(function->id())) {
            // already have the function
            currentFunctionItems.removeAll(currentIDs[function->id()]);
            currentIDs.remove(function->id());
            continue;
        }

        toSelect = new FunctionListItem(m_functionList, function->id());
        newFunctionCount++;
    }

    if (newFunctionCount != 1) {
        // only select a new functionlistitem if there was precisely one added
        toSelect = 0l;
    }

    // Now, any IDs left in currentIDs are of functions that have been deleted
    for (FunctionListItem *item : qAsConst(currentFunctionItems)) {
        if (m_functionID == item->function())
            m_functionID = -1;

        delete m_functionList->takeItem(m_functionList->row(item));
    }

    m_functionList->sortItems();

    // Try and see if there is an item with the same text as was initially selected, if we have
    // the same number of functions
    if ((oldFunctionCount == m_functionList->count()) && !currentText.isEmpty()) {
        QList<QListWidgetItem *> matchedItems = m_functionList->findItems(currentText, Qt::MatchExactly);
        if (matchedItems.count() == 1)
            toSelect = static_cast<FunctionListItem *>(matchedItems.first());
    }

    if (toSelect)
        m_functionList->setCurrentItem(toSelect);

    if (m_functionList->count() == 0)
        resetFunctionEditing();
}

void FunctionEditor::setCurrentFunction(int functionID)
{
    for (int row = 0; row < m_functionList->count(); ++row) {
        FunctionListItem *item = static_cast<FunctionListItem *>(m_functionList->item(row));
        if (item->function() != functionID)
            continue;

        m_functionList->setCurrentRow(row);
        return;
    }
}

void FunctionEditor::functionSelected(QListWidgetItem *item)
{
    m_editor->deleteButton->setEnabled(item != 0);
    if (!item)
        return;

    // If there are any pending save events, then cancel them
    for (int i = 0; i < 5; ++i)
        m_saveTimer[i]->stop();

    FunctionListItem *functionItem = static_cast<FunctionListItem *>(item);

    m_functionID = functionItem->function();
    Function *f = XParser::self()->functionWithID(m_functionID);
    if (!f)
        return;

    switch (f->type()) {
    case Function::Cartesian:
        initFromCartesian();
        break;

    case Function::Polar:
        initFromPolar();
        break;

    case Function::Parametric:
        initFromParametric();
        break;

    case Function::Implicit:
        initFromImplicit();
        break;

    case Function::Differential:
        initFromDifferential();
    }

    functionItem->update();
}

void FunctionEditor::initFromCartesian()
{
    Function *f = XParser::self()->functionWithID(m_functionID);

    if (!f) {
        qWarning() << "No f! (id=" << m_functionID << ")\n";
        return;
    }

    m_editor->cartesianEquation->setText(f->eq[0]->fstr());
    m_editor->cartesian_f0->init(f->plotAppearance(Function::Derivative0), Function::Cartesian);
    m_editor->cartesian_f1->init(f->plotAppearance(Function::Derivative1), Function::Cartesian);
    m_editor->cartesian_f2->init(f->plotAppearance(Function::Derivative2), Function::Cartesian);
    m_editor->cartesian_integral->init(f->plotAppearance(Function::Integral), Function::Cartesian);

    m_editor->showDerivative1->setChecked(f->plotAppearance(Function::Derivative1).visible);
    m_editor->showDerivative2->setChecked(f->plotAppearance(Function::Derivative2).visible);

    m_editor->cartesianCustomMin->setChecked(f->usecustomxmin);
    m_editor->cartesianMin->setText(f->dmin.expression());

    m_editor->cartesianCustomMax->setChecked(f->usecustomxmax);
    m_editor->cartesianMax->setText(f->dmax.expression());

    m_editor->cartesianParameters->init(f->m_parameters);

    m_editor->showIntegral->setChecked(f->plotAppearance(Function::Integral).visible);
    m_editor->integralStep->setText(f->eq[0]->differentialStates.step().expression());

    DifferentialState state = f->eq[0]->differentialStates[0];
    m_editor->txtInitX->setText(state.x0.expression());
    m_editor->txtInitY->setText(state.y0[0].expression());

    m_editor->stackedWidget->setCurrentIndex(0);
    m_editor->tabWidget->setCurrentIndex(0);
    m_editor->cartesianEquation->setFocus();
}

void FunctionEditor::initFromPolar()
{
    Function *f = XParser::self()->functionWithID(m_functionID);

    if (!f)
        return;

    QString function = f->eq[0]->fstr();
    m_editor->polarEquation->setText(function);
    m_editor->polarMin->setText(f->dmin.expression());
    m_editor->polarMax->setText(f->dmax.expression());
    m_editor->polar_f0->init(f->plotAppearance(Function::Derivative0), Function::Polar);

    m_editor->polarParameters->init(f->m_parameters);

    m_editor->stackedWidget->setCurrentIndex(2);
    m_editor->polarEquation->setFocus();
}

void FunctionEditor::initFromParametric()
{
    Function *f = XParser::self()->functionWithID(m_functionID);

    if (!f)
        return;

    m_editor->parametricX->setText(f->eq[0]->fstr());
    m_editor->parametricY->setText(f->eq[1]->fstr());

    m_editor->parametricMin->setText(f->dmin.expression());
    m_editor->parametricMax->setText(f->dmax.expression());

    m_editor->parametricParameters->init(f->m_parameters);

    m_editor->parametric_f0->init(f->plotAppearance(Function::Derivative0), Function::Parametric);

    m_editor->stackedWidget->setCurrentIndex(1);
    m_editor->parametricX->setFocus();
}

void FunctionEditor::initFromImplicit()
{
    Function *f = XParser::self()->functionWithID(m_functionID);

    if (!f)
        return;

    QString name, expression;
    splitImplicitEquation(f->eq[0]->fstr(), &name, &expression);

    m_editor->implicitEquation->setValidatePrefix(name + '=');

    m_editor->implicitName->setText(name);
    m_editor->implicitEquation->setText(expression);
    m_editor->implicit_f0->init(f->plotAppearance(Function::Derivative0), Function::Implicit);

    m_editor->implicitParameters->init(f->m_parameters);

    m_editor->stackedWidget->setCurrentIndex(3);
    m_editor->implicitEquation->setFocus();
}

void FunctionEditor::initFromDifferential()
{
    Function *f = XParser::self()->functionWithID(m_functionID);

    if (!f)
        return;

    m_editor->differentialEquation->setText(f->eq[0]->fstr());
    m_editor->differentialStep->setText(f->eq[0]->differentialStates.step().expression());

    m_editor->differential_f0->init(f->plotAppearance(Function::Derivative0), Function::Differential);
    m_editor->differentialParameters->init(f->m_parameters);
    m_editor->initialConditions->init(f);

    m_editor->differentialTabWidget->setCurrentIndex(0);
    m_editor->stackedWidget->setCurrentIndex(4);
    m_editor->differentialEquation->setFocus();
}

void FunctionEditor::splitImplicitEquation(const QString &equation, QString *name, QString *expression)
{
    int equalsPos = equation.indexOf('=');
    assert(equalsPos >= 0);
    *name = equation.left(equalsPos).trimmed();
    *expression = equation.right(equation.length() - equalsPos - 1).trimmed();
}

void FunctionEditor::resetFunctionEditing()
{
    m_functionID = -1;

    // page 5 is an empty page
    m_editor->stackedWidget->setCurrentIndex(5);

    // assume that if there are functions in the list, then one will be selected
    m_editor->deleteButton->setEnabled(m_functionList->count() != 0);
}

void FunctionEditor::createCartesian()
{
    QString name;
    if (Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function)
        name = XParser::self()->findFunctionName(QStringLiteral("f"), -1) + "(x)";
    else
        name = 'y';

    createFunction(name + " = 0", QString(), Function::Cartesian);
}

void FunctionEditor::createParametric()
{
    QString name =
        XParser::self()->findFunctionName(QStringLiteral("f"), -1, QStringList() << QStringLiteral("%1") << QStringLiteral("%1_x") << QStringLiteral("%1_y"));

    QString name_x, name_y;

    if (Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function) {
        name_x = QStringLiteral("%1_x(t)").arg(name);
        name_y = QStringLiteral("%1_y(t)").arg(name);
    } else {
        name_x = 'x';
        name_y = 'y';
    }

    createFunction(name_x + " = 0", name_y + " = 0", Function::Parametric);
}

void FunctionEditor::createPolar()
{
    QString name;
    if (Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function)
        name = XParser::self()->findFunctionName(QStringLiteral("f"), -1) + "(x)";
    else
        name = 'r';

    createFunction(name + " = 0", QString(), Function::Polar);
}

void FunctionEditor::createImplicit()
{
    QString name = XParser::self()->findFunctionName(QStringLiteral("f"), -1);
    if (Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function)
        name += QLatin1String("(x,y)");

    createFunction(name + " = y² = x³ − x + 1", QString(), Function::Implicit);
}

void FunctionEditor::createDifferential()
{
    QString name;
    if (Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function)
        name = QStringLiteral("%1''(x) = -%1").arg(XParser::self()->findFunctionName(QStringLiteral("f"), -1));
    else
        name = QLatin1String("y'' = -y");

    createFunction(name, QString(), Function::Differential);
}

void FunctionEditor::createFunction(const QString &eq0, const QString &eq1, Function::Type type)
{
    m_functionID = XParser::self()->Parser::addFunction(eq0, eq1, type);
    assert(m_functionID != -1);
    MainDlg::self()->requestSaveCurrentState();
}

void FunctionEditor::save()
{
    Function *f = XParser::self()->functionWithID(m_functionID);
    if (!f)
        return;

    m_saveTimer[f->type()]->start(0);
}

// TODO This should be a part of a model. The proper model should be created later.
void FunctionEditor::saveItem(QListWidgetItem *item)
{
    if (item != m_functionList->currentItem()) {
        m_functionList->setCurrentItem(item);
        if (item->checkState() == Qt::Checked) {
            item->setCheckState(Qt::Unchecked);
        } else {
            item->setCheckState(Qt::Checked);
        }
    }

    save();
}

void FunctionEditor::saveCartesian()
{
    FunctionListItem *functionListItem = static_cast<FunctionListItem *>(m_functionList->currentItem());
    if (!functionListItem)
        return;

    QString f_str(m_editor->cartesianEquation->text());
    XParser::self()->fixFunctionName(f_str, Equation::Cartesian, m_functionID);

    Function tempFunction(Function::Cartesian);
    tempFunction.setId(m_functionID);

    tempFunction.usecustomxmin = m_editor->cartesianCustomMin->isChecked();
    if (!tempFunction.dmin.updateExpression(m_editor->cartesianMin->text()))
        return;

    tempFunction.usecustomxmax = m_editor->cartesianCustomMax->isChecked();
    if (!tempFunction.dmax.updateExpression(m_editor->cartesianMax->text()))
        return;

    tempFunction.plotAppearance(Function::Derivative0) = m_editor->cartesian_f0->plot((functionListItem->checkState() == Qt::Checked));
    tempFunction.plotAppearance(Function::Derivative1) = m_editor->cartesian_f1->plot(m_editor->showDerivative1->isChecked());
    tempFunction.plotAppearance(Function::Derivative2) = m_editor->cartesian_f2->plot(m_editor->showDerivative2->isChecked());
    tempFunction.plotAppearance(Function::Integral) = m_editor->cartesian_integral->plot(m_editor->showIntegral->isChecked());

    DifferentialState *state = &tempFunction.eq[0]->differentialStates[0];
    state->setOrder(1);
    state->x0.updateExpression(m_editor->txtInitX->text());
    state->y0[0].updateExpression(m_editor->txtInitY->text());

    if (!tempFunction.eq[0]->differentialStates.setStep(m_editor->integralStep->text()))
        return;
    tempFunction.m_parameters = m_editor->cartesianParameters->parameterSettings();

    if (!tempFunction.eq[0]->setFstr(f_str))
        return;

    saveFunction(&tempFunction);
}

void FunctionEditor::savePolar()
{
    FunctionListItem *functionListItem = static_cast<FunctionListItem *>(m_functionList->currentItem());
    if (!functionListItem)
        return;

    QString f_str = m_editor->polarEquation->text();

    XParser::self()->fixFunctionName(f_str, Equation::Polar, m_functionID);
    Function tempFunction(Function::Polar); // all settings are saved here until we know that no errors have appeared
    tempFunction.setId(m_functionID);

    if (!tempFunction.dmin.updateExpression(m_editor->polarMin->text()))
        return;
    if (!tempFunction.dmax.updateExpression(m_editor->polarMax->text()))
        return;

    tempFunction.m_parameters = m_editor->polarParameters->parameterSettings();
    tempFunction.plotAppearance(Function::Derivative0) = m_editor->polar_f0->plot((functionListItem->checkState() == Qt::Checked));

    if (!tempFunction.eq[0]->setFstr(f_str))
        return;

    saveFunction(&tempFunction);
}

void FunctionEditor::saveParametric()
{
    FunctionListItem *functionListItem = static_cast<FunctionListItem *>(m_functionList->currentItem());
    if (!functionListItem)
        return;

    Function tempFunction(Function::Parametric);
    tempFunction.setId(m_functionID);

    QString f_str = m_editor->parametricX->text();
    XParser::self()->fixFunctionName(f_str, Equation::ParametricX, m_functionID);
    if (!tempFunction.eq[0]->setFstr(f_str))
        return;

    f_str = m_editor->parametricY->text();
    XParser::self()->fixFunctionName(f_str, Equation::ParametricY, m_functionID);
    if (!tempFunction.eq[1]->setFstr(f_str))
        return;

    if (!tempFunction.dmin.updateExpression(m_editor->parametricMin->text()))
        return;

    if (!tempFunction.dmax.updateExpression(m_editor->parametricMax->text()))
        return;

    tempFunction.m_parameters = m_editor->parametricParameters->parameterSettings();
    tempFunction.plotAppearance(Function::Derivative0) = m_editor->parametric_f0->plot((functionListItem->checkState() == Qt::Checked));

    saveFunction(&tempFunction);
}

void FunctionEditor::saveImplicit()
{
    FunctionListItem *functionListItem = static_cast<FunctionListItem *>(m_functionList->currentItem());
    if (!functionListItem)
        return;

    // find a name not already used
    if (m_editor->implicitName->text().isEmpty()) {
        QString fname;
        XParser::self()->fixFunctionName(fname, Equation::Implicit, m_functionID);
        int const pos = fname.indexOf('(');
        m_editor->implicitName->setText(fname.mid(1, pos - 1));
    }

    QString prefix = m_editor->implicitName->text() + " = ";
    QString f_str = prefix + m_editor->implicitEquation->text();
    m_editor->implicitEquation->setValidatePrefix(prefix);

    Function tempFunction(Function::Implicit); // all settings are saved here until we know that no errors have appeared
    tempFunction.setId(m_functionID);

    tempFunction.m_parameters = m_editor->implicitParameters->parameterSettings();
    tempFunction.plotAppearance(Function::Derivative0) = m_editor->implicit_f0->plot((functionListItem->checkState() == Qt::Checked));

    if (!tempFunction.eq[0]->setFstr(f_str))
        return;

    saveFunction(&tempFunction);
}

void FunctionEditor::saveDifferential()
{
    FunctionListItem *functionListItem = static_cast<FunctionListItem *>(m_functionList->currentItem());
    if (!functionListItem)
        return;

    Function tempFunction(Function::Differential); // all settings are saved here until we know that no errors have appeared
    tempFunction.setId(m_functionID);

    QString f_str = m_editor->differentialEquation->text();
    if (!tempFunction.eq[0]->setFstr(f_str))
        return;

    tempFunction.m_parameters = m_editor->differentialParameters->parameterSettings();
    tempFunction.plotAppearance(Function::Derivative0) = m_editor->differential_f0->plot((functionListItem->checkState() == Qt::Checked));

    m_editor->initialConditions->setOrder(tempFunction.eq[0]->order());
    tempFunction.eq[0]->differentialStates = *m_editor->initialConditions->differentialStates();
    if (!tempFunction.eq[0]->differentialStates.setStep(m_editor->differentialStep->text()))
        return;

    saveFunction(&tempFunction);
}

void FunctionEditor::saveFunction(Function *tempFunction)
{
    FunctionListItem *functionListItem = static_cast<FunctionListItem *>(m_functionList->currentItem());
    Function *f = XParser::self()->functionWithID(m_functionID);
    if (!f || !functionListItem)
        return;

    for (Equation *eq : qAsConst(f->eq))
        eq->differentialStates.resetToInitial();

    // save all settings in the function now when we know no errors have appeared
    bool changed = f->copyFrom(*tempFunction);
    if (!changed)
        return;

    qDebug() << "Changed\n";

    if (f->eq[0]->looksLikeFunction())
        Settings::setDefaultEquationForm(Settings::EnumDefaultEquationForm::Function);
    else
        Settings::setDefaultEquationForm(Settings::EnumDefaultEquationForm::Implicit);
    Settings::self()->save();

    MainDlg::self()->requestSaveCurrentState();
    functionListItem->update();
    View::self()->drawPlot();
}
// END class FunctionEditor

// BEGIN class FunctionListWidget
FunctionListWidget::FunctionListWidget(QWidget *parent)
    : QListWidget(parent)
{
    setAcceptDrops(true);
    setDragEnabled(true);
    show();
}

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QMimeData *FunctionListWidget::mimeData(const QList<QListWidgetItem *> items) const
#else
QMimeData *FunctionListWidget::mimeData(const QList<QListWidgetItem *> &items) const
#endif
{
    QDomDocument doc(QStringLiteral("kmpdoc"));
    QDomElement root = doc.createElement(QStringLiteral("kmpdoc"));
    doc.appendChild(root);

    KmPlotIO io;

    for (QListWidgetItem *item : qAsConst(items)) {
        int f = static_cast<FunctionListItem *>(item)->function();

        if (Function *function = XParser::self()->functionWithID(f))
            io.addFunction(doc, root, function);
    }

    QMimeData *md = new QMimeData;
    md->setData(QStringLiteral("text/kmplot"), doc.toByteArray());

    return md;
}

QStringList FunctionListWidget::mimeTypes() const
{
    QStringList mt;
    mt << QStringLiteral("text/kmplot");
    return mt;
}

void FunctionListWidget::dragEnterEvent(QDragEnterEvent *event)
{
    const QMimeData *md = event->mimeData();
    if (md->hasFormat(QStringLiteral("text/kmplot")))
        event->acceptProposedAction();
}

void FunctionListWidget::dropEvent(QDropEvent *event)
{
    const QMimeData *md = event->mimeData();

    QDomDocument doc(QStringLiteral("kmpdoc"));
    doc.setContent(md->data(QStringLiteral("text/kmplot")));
    QDomElement element = doc.documentElement();

    KmPlotIO io;

    for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
        if (n.nodeName() == QLatin1String("function"))
            io.parseFunction(n.toElement(), true);
        else
            qWarning() << "Unexpected node with name " << n.nodeName();
    }
}
// END class FunctionListWidget

// BEGIN class FunctionListItem
FunctionListItem::FunctionListItem(QListWidget *parent, int function)
    : QListWidgetItem(parent)
{
    m_function = function;
    assert(m_function != -1);
    update();
}

void FunctionListItem::update()
{
    Function *f = XParser::self()->functionWithID(m_function);

    if (!f) {
        // The function was probably deleted
        return;
    }

    setText(f->name());
    setCheckState(f->plotAppearance(Function::Derivative0).visible ? Qt::Checked : Qt::Unchecked);
    setForeground(f->plotAppearance(Function::Derivative0).color);
}
// END class FunctionListItem
