/*
    SPDX-FileCopyrightText: 2006 Chu Xiaodong <xiaodongchu@gmail.com>
    SPDX-FileCopyrightText: 2006 Pino Toscano <pino@kde.org>

    Work sponsored by the LiMux project of the city of Munich:
    SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>

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

#include "annotwindow.h"

// qt/kde includes
#include <KLocalizedString>
#include <KStandardAction>
#include <KTextEdit>
#include <QAction>
#include <QApplication>
#include <QDebug>
#include <QEvent>
#include <QFont>
#include <QFontInfo>
#include <QFontMetrics>
#include <QLabel>
#include <QLayout>
#include <QMenu>
#include <QPushButton>
#include <QSizeGrip>
#include <QStyle>
#include <QToolButton>

// local includes
#include "core/annotations.h"
#include "core/document.h"
#include "latexrenderer.h"
#include <KMessageBox>
#include <core/utils.h>

class CloseButton : public QPushButton
{
    Q_OBJECT

public:
    explicit CloseButton(QWidget *parent = Q_NULLPTR)
        : QPushButton(parent)
    {
        setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        QSize size = QSize(14, 14).expandedTo(QApplication::globalStrut());
        setFixedSize(size);
        setIcon(style()->standardIcon(QStyle::SP_DockWidgetCloseButton));
        setIconSize(size);
        setToolTip(i18n("Close this note"));
        setCursor(Qt::ArrowCursor);
    }
};

class MovableTitle : public QWidget
{
    Q_OBJECT

public:
    explicit MovableTitle(AnnotWindow *parent)
        : QWidget(parent)
    {
        QVBoxLayout *mainlay = new QVBoxLayout(this);
        mainlay->setContentsMargins(0, 0, 0, 0);
        mainlay->setSpacing(0);
        // close button row
        QHBoxLayout *buttonlay = new QHBoxLayout();
        mainlay->addLayout(buttonlay);
        titleLabel = new QLabel(this);
        QFont f = titleLabel->font();
        f.setBold(true);
        titleLabel->setFont(f);
        titleLabel->setCursor(Qt::SizeAllCursor);
        buttonlay->addWidget(titleLabel);
        dateLabel = new QLabel(this);
        dateLabel->setAlignment(Qt::AlignTop | Qt::AlignRight);
        f = dateLabel->font();
        f.setPointSize(QFontInfo(f).pointSize() - 2);
        dateLabel->setFont(f);
        dateLabel->setCursor(Qt::SizeAllCursor);
        buttonlay->addWidget(dateLabel);
        CloseButton *close = new CloseButton(this);
        connect(close, &QAbstractButton::clicked, parent, &QWidget::close);
        buttonlay->addWidget(close);
        // option button row
        QHBoxLayout *optionlay = new QHBoxLayout();
        mainlay->addLayout(optionlay);
        authorLabel = new QLabel(this);
        authorLabel->setCursor(Qt::SizeAllCursor);
        authorLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
        optionlay->addWidget(authorLabel);
        optionButton = new QToolButton(this);
        QString opttext = i18n("Options");
        optionButton->setText(opttext);
        optionButton->setAutoRaise(true);
        QSize s = QFontMetrics(optionButton->font()).boundingRect(opttext).size() + QSize(8, 8);
        optionButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        optionButton->setFixedSize(s);
        optionlay->addWidget(optionButton);
        // ### disabled for now
        optionButton->hide();
        latexButton = new QToolButton(this);
        QHBoxLayout *latexlay = new QHBoxLayout();
        QString latextext = i18n("This annotation may contain LaTeX code.\nClick here to render.");
        latexButton->setText(latextext);
        latexButton->setAutoRaise(true);
        s = QFontMetrics(latexButton->font()).boundingRect(0, 0, this->width(), this->height(), 0, latextext).size() + QSize(8, 8);
        latexButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        latexButton->setFixedSize(s);
        latexButton->setCheckable(true);
        latexButton->setVisible(false);
        latexlay->addSpacing(1);
        latexlay->addWidget(latexButton);
        latexlay->addSpacing(1);
        mainlay->addLayout(latexlay);
        connect(latexButton, &QToolButton::clicked, parent, &AnnotWindow::renderLatex);
        connect(parent, &AnnotWindow::containsLatex, latexButton, &QWidget::setVisible);

        titleLabel->installEventFilter(this);
        dateLabel->installEventFilter(this);
        authorLabel->installEventFilter(this);
    }

    bool eventFilter(QObject *obj, QEvent *e) override
    {
        if (obj != titleLabel && obj != authorLabel && obj != dateLabel) {
            return false;
        }

        QMouseEvent *me = nullptr;
        switch (e->type()) {
        case QEvent::MouseButtonPress:
            me = (QMouseEvent *)e;
            mousePressPos = me->pos();
            parentWidget()->raise();
            break;
        case QEvent::MouseButtonRelease:
            mousePressPos = QPoint();
            break;
        case QEvent::MouseMove: {
            me = (QMouseEvent *)e;

            // viewport info
            const QPoint topLeftPoint = parentWidget()->parentWidget()->pos();
            const int viewportHeight = parentWidget()->parentWidget()->height();
            const int viewportWidth = parentWidget()->parentWidget()->width();

            // annotation's popup window info
            QPoint newPositionPoint = me->pos() - mousePressPos + parentWidget()->pos();
            const int annotHeight = parentWidget()->height();
            const int annotWidth = parentWidget()->width();

            // make sure x is in range
            if (newPositionPoint.x() < topLeftPoint.x()) {
                newPositionPoint.setX(topLeftPoint.x());
            } else if (newPositionPoint.x() + annotWidth > topLeftPoint.x() + viewportWidth) {
                newPositionPoint.setX(topLeftPoint.x() + viewportWidth - annotWidth);
            }

            // make sure y is in range
            if (newPositionPoint.y() < topLeftPoint.y()) {
                newPositionPoint.setY(topLeftPoint.y());
            } else if (newPositionPoint.y() + annotHeight > topLeftPoint.y() + viewportHeight) {
                newPositionPoint.setY(topLeftPoint.y() + viewportHeight - annotHeight);
            }

            parentWidget()->move(newPositionPoint);
            break;
        }
        default:
            return false;
        }
        return true;
    }

    void setTitle(const QString &title)
    {
        titleLabel->setText(QStringLiteral(" ") + title);
    }

    void setDate(const QDateTime &dt)
    {
        dateLabel->setText(QLocale().toString(dt.toTimeSpec(Qt::LocalTime), QLocale::ShortFormat) + QLatin1Char(' '));
    }

    void setAuthor(const QString &author)
    {
        authorLabel->setText(QStringLiteral(" ") + author);
    }

    void connectOptionButton(QObject *recv, const char *method)
    {
        connect(optionButton, SIGNAL(clicked()), recv, method);
    }

    void uncheckLatexButton()
    {
        latexButton->setChecked(false);
    }

private:
    QLabel *titleLabel;
    QLabel *dateLabel;
    QLabel *authorLabel;
    QPoint mousePressPos;
    QToolButton *optionButton;
    QToolButton *latexButton;
};

// Qt::SubWindow is needed to make QSizeGrip work
AnnotWindow::AnnotWindow(QWidget *parent, Okular::Annotation *annot, Okular::Document *document, int page)
    : QFrame(parent, Qt::SubWindow)
    , m_annot(annot)
    , m_document(document)
    , m_page(page)
{
    setAutoFillBackground(true);
    setFrameStyle(Panel | Raised);
    setAttribute(Qt::WA_DeleteOnClose);
    setObjectName(QStringLiteral("AnnotWindow"));

    const bool canEditAnnotation = m_document->canModifyPageAnnotation(annot);

    textEdit = new KTextEdit(this);
    textEdit->setAcceptRichText(false);
    textEdit->setPlainText(m_annot->contents());
    textEdit->installEventFilter(this);
    textEdit->setUndoRedoEnabled(false);

    m_prevCursorPos = textEdit->textCursor().position();
    m_prevAnchorPos = textEdit->textCursor().anchor();

    connect(textEdit, &KTextEdit::textChanged, this, &AnnotWindow::slotsaveWindowText);
    connect(textEdit, &KTextEdit::cursorPositionChanged, this, &AnnotWindow::slotsaveWindowText);
    connect(textEdit, &KTextEdit::aboutToShowContextMenu, this, &AnnotWindow::slotUpdateUndoAndRedoInContextMenu);
    connect(m_document, &Okular::Document::annotationContentsChangedByUndoRedo, this, &AnnotWindow::slotHandleContentsChangedByUndoRedo);

    if (!canEditAnnotation) {
        textEdit->setReadOnly(true);
    }

    QVBoxLayout *mainlay = new QVBoxLayout(this);
    mainlay->setContentsMargins(2, 2, 2, 2);
    mainlay->setSpacing(0);
    m_title = new MovableTitle(this);
    mainlay->addWidget(m_title);
    mainlay->addWidget(textEdit);
    QHBoxLayout *lowerlay = new QHBoxLayout();
    mainlay->addLayout(lowerlay);
    lowerlay->addItem(new QSpacerItem(5, 5, QSizePolicy::Expanding, QSizePolicy::Fixed));
    QSizeGrip *sb = new QSizeGrip(this);
    lowerlay->addWidget(sb);

    m_latexRenderer = new GuiUtils::LatexRenderer();
    // The Q_EMIT below is not wrong even if emitting signals from the constructor it's usually wrong
    // in this case the signal it's connected to inside MovableTitle constructor a few lines above
    Q_EMIT containsLatex(GuiUtils::LatexRenderer::mightContainLatex(m_annot->contents())); // clazy:exclude=incorrect-emit

    m_title->setTitle(m_annot->window().summary());
    m_title->connectOptionButton(this, SLOT(slotOptionBtn()));

    setGeometry(10, 10, 300, 300);

    reloadInfo();
}

AnnotWindow::~AnnotWindow()
{
    delete m_latexRenderer;
}

Okular::Annotation *AnnotWindow::annotation() const
{
    return m_annot;
}

void AnnotWindow::updateAnnotation(Okular::Annotation *a)
{
    m_annot = a;
}

void AnnotWindow::reloadInfo()
{
    QColor newcolor;
    if (m_annot->subType() == Okular::Annotation::AText) {
        Okular::TextAnnotation *textAnn = static_cast<Okular::TextAnnotation *>(m_annot);
        if (textAnn->textType() == Okular::TextAnnotation::InPlace && textAnn->inplaceIntent() == Okular::TextAnnotation::TypeWriter) {
            newcolor = QColor(0xfd, 0xfd, 0x96);
        }
    }
    if (!newcolor.isValid()) {
        newcolor = m_annot->style().color().isValid() ? QColor(m_annot->style().color().red(), m_annot->style().color().green(), m_annot->style().color().blue(), 255) : Qt::yellow;
    }
    if (newcolor != m_color) {
        m_color = newcolor;
        setPalette(QPalette(m_color));
        QPalette pl = textEdit->palette();
        pl.setColor(QPalette::Base, m_color);
        textEdit->setPalette(pl);
    }
    m_title->setAuthor(m_annot->author());
    m_title->setDate(m_annot->modificationDate());
}

int AnnotWindow::pageNumber() const
{
    return m_page;
}

void AnnotWindow::showEvent(QShowEvent *event)
{
    QFrame::showEvent(event);

    // focus the content area by default
    textEdit->setFocus();
}

bool AnnotWindow::eventFilter(QObject *o, QEvent *e)
{
    if (e->type() == QEvent::ShortcutOverride) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
        if (keyEvent->key() == Qt::Key_Escape) {
            e->accept();
            return true;
        }
    } else if (e->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
        if (keyEvent == QKeySequence::Undo) {
            m_document->undo();
            return true;
        } else if (keyEvent == QKeySequence::Redo) {
            m_document->redo();
            return true;
        } else if (keyEvent->key() == Qt::Key_Escape) {
            close();
            return true;
        }
    } else if (e->type() == QEvent::FocusIn) {
        raise();
    }
    return QFrame::eventFilter(o, e);
}

void AnnotWindow::slotUpdateUndoAndRedoInContextMenu(QMenu *menu)
{
    if (!menu) {
        return;
    }

    QList<QAction *> actionList = menu->actions();
    enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs };

    QAction *kundo = KStandardAction::create(
        KStandardAction::Undo,
        m_document,
        [doc = m_document] {
            // We need a QueuedConnection because undoing may end up destroying the menu this action is on
            // because it will undo the addition of the annotation. If it's not queued things gets unhappy
            // because the menu is destroyed in the middle of processing the click on the menu itself
            QMetaObject::invokeMethod(doc, &Okular::Document::undo, Qt::QueuedConnection);
        },
        menu);
    QAction *kredo = KStandardAction::create(KStandardAction::Redo, m_document, SLOT(redo()), menu);
    connect(m_document, &Okular::Document::canUndoChanged, kundo, &QAction::setEnabled);
    connect(m_document, &Okular::Document::canRedoChanged, kredo, &QAction::setEnabled);
    kundo->setEnabled(m_document->canUndo());
    kredo->setEnabled(m_document->canRedo());

    QAction *oldUndo, *oldRedo;
    oldUndo = actionList[UndoAct];
    oldRedo = actionList[RedoAct];

    menu->insertAction(oldUndo, kundo);
    menu->insertAction(oldRedo, kredo);

    menu->removeAction(oldUndo);
    menu->removeAction(oldRedo);
}

void AnnotWindow::slotOptionBtn()
{
    // TODO: call context menu in pageview
    // Q_EMIT sig...
}

void AnnotWindow::slotsaveWindowText()
{
    const QString contents = textEdit->toPlainText();
    const int cursorPos = textEdit->textCursor().position();
    if (contents != m_annot->contents()) {
        m_document->editPageAnnotationContents(m_page, m_annot, contents, cursorPos, m_prevCursorPos, m_prevAnchorPos);
        Q_EMIT containsLatex(GuiUtils::LatexRenderer::mightContainLatex(textEdit->toPlainText()));
    }
    m_prevCursorPos = cursorPos;
    m_prevAnchorPos = textEdit->textCursor().anchor();
}

void AnnotWindow::renderLatex(bool render)
{
    if (render) {
        textEdit->setReadOnly(true);
        disconnect(textEdit, &KTextEdit::textChanged, this, &AnnotWindow::slotsaveWindowText);
        disconnect(textEdit, &KTextEdit::cursorPositionChanged, this, &AnnotWindow::slotsaveWindowText);
        textEdit->setAcceptRichText(true);
        QString contents = m_annot->contents();
        contents = Qt::convertFromPlainText(contents);
        QColor fontColor = textEdit->textColor();
        int fontSize = textEdit->fontPointSize();
        QString latexOutput;
        GuiUtils::LatexRenderer::Error errorCode = m_latexRenderer->renderLatexInHtml(contents, fontColor, fontSize, Okular::Utils::realDpi(nullptr).width(), latexOutput);
        switch (errorCode) {
        case GuiUtils::LatexRenderer::LatexNotFound:
            KMessageBox::error(this, i18n("Cannot find latex executable."), i18n("LaTeX rendering failed"));
            m_title->uncheckLatexButton();
            renderLatex(false);
            break;
        case GuiUtils::LatexRenderer::DvipngNotFound:
            KMessageBox::error(this, i18n("Cannot find dvipng executable."), i18n("LaTeX rendering failed"));
            m_title->uncheckLatexButton();
            renderLatex(false);
            break;
        case GuiUtils::LatexRenderer::LatexFailed:
            KMessageBox::detailedError(this, i18n("A problem occurred during the execution of the 'latex' command."), latexOutput, i18n("LaTeX rendering failed"));
            m_title->uncheckLatexButton();
            renderLatex(false);
            break;
        case GuiUtils::LatexRenderer::DvipngFailed:
            KMessageBox::error(this, i18n("A problem occurred during the execution of the 'dvipng' command."), i18n("LaTeX rendering failed"));
            m_title->uncheckLatexButton();
            renderLatex(false);
            break;
        case GuiUtils::LatexRenderer::NoError:
        default:
            textEdit->setHtml(contents);
            break;
        }
    } else {
        textEdit->setAcceptRichText(false);
        textEdit->setPlainText(m_annot->contents());
        connect(textEdit, &KTextEdit::textChanged, this, &AnnotWindow::slotsaveWindowText);
        connect(textEdit, &KTextEdit::cursorPositionChanged, this, &AnnotWindow::slotsaveWindowText);
        textEdit->setReadOnly(false);
    }
}

void AnnotWindow::slotHandleContentsChangedByUndoRedo(Okular::Annotation *annot, const QString &contents, int cursorPos, int anchorPos)
{
    if (annot != m_annot) {
        return;
    }

    textEdit->setPlainText(contents);
    QTextCursor c = textEdit->textCursor();
    c.setPosition(anchorPos);
    c.setPosition(cursorPos, QTextCursor::KeepAnchor);
    m_prevCursorPos = cursorPos;
    m_prevAnchorPos = anchorPos;
    textEdit->setTextCursor(c);
    textEdit->setFocus();
    Q_EMIT containsLatex(GuiUtils::LatexRenderer::mightContainLatex(m_annot->contents()));
}

#include "annotwindow.moc"
