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

    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 "equationedit.h"
#include "xparser.h"

#include <QHBoxLayout>
#include <QIcon>
#include <QPushButton>

#include <assert.h>

#include "equationeditor.h"
#include "equationeditorwidget.h"
#include "equationeditwidget.h"
#include "equationhighlighter.h"

CharMap EquationEdit::m_replaceMap;

EquationEdit::EquationEdit(QWidget *parent)
    : QWidget(parent)
{
    m_cleaningText = false;
    m_settingText = false;
    m_forcingRehighlight = false;
    m_inputType = Expression;

    m_equationEditWidget = new EquationEditWidget(this);
    m_highlighter = new EquationHighlighter(this);
    m_equation = new Equation(Equation::Cartesian, nullptr);
    m_editButton = new QPushButton(QIcon::fromTheme(QStringLiteral("document-properties")), nullptr, this);
    setFocusProxy(m_equationEditWidget);

    connect(m_equationEditWidget, &EquationEditWidget::textChanged, this, &EquationEdit::slotTextChanged);
    connect(m_editButton, &QPushButton::clicked, this, &EquationEdit::invokeEquationEditor);
    connect(m_equationEditWidget, &EquationEditWidget::cursorPositionChanged, this, &EquationEdit::reHighlight);

    QHBoxLayout *layout = new QHBoxLayout(this);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->addWidget(m_equationEditWidget);
    layout->addWidget(m_editButton);
}

void EquationEdit::setTabChain(QWidget *next)
{
    QWidget::setTabOrder(m_equationEditWidget, m_editButton);
    QWidget::setTabOrder(m_editButton, next);
}

void EquationEdit::setEquationType(Equation::Type type)
{
    delete m_equation;
    m_equation = new Equation(type, nullptr);
}

void EquationEdit::showEditButton(bool show)
{
    m_editButton->setVisible(show);
}

void EquationEdit::reHighlight()
{
    if (m_forcingRehighlight)
        return;
    m_forcingRehighlight = true;

    m_highlighter->setDocument(nullptr);
    m_highlighter->setDocument(m_equationEditWidget->document());

    m_forcingRehighlight = false;
}

void EquationEdit::invokeEquationEditor()
{
    QPointer<EquationEditor> edit = new EquationEditor(this);
    edit->edit()->setInputType(m_inputType);
    edit->edit()->setEquationType(m_equation->type());
    edit->edit()->setValidatePrefix(m_validatePrefix);
    edit->edit()->setText(text());

    edit->exec();

    setText(edit->text());
    edit->deleteLater();
    Q_EMIT editingFinished();
}

void EquationEdit::setInputType(InputType type)
{
    m_inputType = type;
}

double EquationEdit::value(bool *ok)
{
    assert(m_inputType == Expression); // Can't really get a value of a function as that requires an input

    Parser::Error error;
    double value = XParser::self()->eval(text(), &error);

    if (ok)
        *ok = (error == Parser::ParseSuccess);

    return value;
}

void EquationEdit::slotTextChanged()
{
    if (m_forcingRehighlight)
        return;

    // BEGIN tidy up mathematical characters
    if (m_cleaningText)
        return;
    m_cleaningText = true;

    QTextDocument *doc = m_equationEditWidget->document();

    if (m_replaceMap.isEmpty()) {
        m_replaceMap['*'] = QChar(0x2219);
        m_replaceMap['-'] = MinusSymbol;
        m_replaceMap['|'] = AbsSymbol;
    }

    QTextCursor cursor;
    for (CharMap::iterator i = m_replaceMap.begin(); i != m_replaceMap.end(); ++i) {
        int at = 0;
        while (!(cursor = doc->find(i.key(), at)).isNull()) {
            cursor.joinPreviousEditBlock();
            at = cursor.position() + 1;
            cursor.deleteChar();
            cursor.insertText(i.value());
            cursor.endEditBlock();
        }
    }

    m_cleaningText = false;
    // END tidy up mathematical characters

    Q_EMIT textChanged(text());
    if (!m_settingText)
        Q_EMIT textEdited(text());
}

void EquationEdit::checkTextValidity()
{
    QString text = m_validatePrefix + EquationEdit::text();

    Parser::Error error;
    int intError, errorPosition;

    if (m_inputType == Function) {
        m_equation->setFstr(text, &intError, &errorPosition);
        error = (Parser::Error)intError;
    } else
        XParser::self()->eval(text, &error, &errorPosition);

    if (error == Parser::ParseSuccess)
        setError(QString(), -1);
    else
        setError(XParser::self()->errorString(error), errorPosition - m_validatePrefix.length());
}

void EquationEdit::setError(const QString &message, int position)
{
    m_equationEditWidget->setToolTip(message);
    m_highlighter->setErrorPosition(position);
}

void EquationEdit::setText(const QString &text)
{
    m_settingText = true;
    m_equationEditWidget->setPlainText(text);
    QTextCursor cursor(m_equationEditWidget->textCursor());
    cursor.movePosition(QTextCursor::End);
    m_equationEditWidget->setTextCursor(cursor);
    m_settingText = false;
}

void EquationEdit::setValidatePrefix(const QString &prefix)
{
    m_validatePrefix = prefix;
    reHighlight();
}

void EquationEdit::wrapSelected(const QString &before, const QString &after)
{
    QTextCursor cursor(m_equationEditWidget->textCursor());
    QString newText = before + cursor.selectedText() + after;
    cursor.insertText(newText);
    cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, after.length());
    m_equationEditWidget->setTextCursor(cursor);
}

QString EquationEdit::text() const
{
    return m_equationEditWidget->toPlainText();
}

void EquationEdit::clear()
{
    m_equationEditWidget->clear();
}

void EquationEdit::selectAll()
{
    m_equationEditWidget->selectAll();
}

void EquationEdit::insertText(const QString &text)
{
    m_equationEditWidget->insertPlainText(text);
}

#include "moc_equationedit.cpp"
