//    Copyright (C) 2023 Jakub Melka
//
//    This file is part of PDF4QT.
//
//    PDF4QT is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Lesser General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    with the written consent of the copyright owner, any later version.
//
//    PDF4QT is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public License
//    along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.

#include "pdfwidgetannotation.h"
#include "pdfdrawwidget.h"
#include "pdfwidgetutils.h"
#include "pdfwidgetformmanager.h"
#include "pdfdrawspacecontroller.h"
#include "pdfselectpagesdialog.h"
#include "pdfobjecteditorwidget.h"
#include "pdfdocumentbuilder.h"

#include <QMenu>
#include <QDialog>
#include <QApplication>
#include <QMouseEvent>
#include <QGroupBox>
#include <QScrollArea>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QLabel>
#include <QStyleOptionButton>

namespace pdf
{

PDFWidgetAnnotationManager::PDFWidgetAnnotationManager(PDFDrawWidgetProxy* proxy, QObject* parent) :
    BaseClass(proxy->getFontCache(), proxy->getCMSManager(), proxy->getOptionalContentActivity(), proxy->getMeshQualitySettings(), proxy->getFeatures(), Target::View, parent),
    m_proxy(proxy)
{
    Q_ASSERT(proxy);
    m_proxy->registerDrawInterface(this);
}

PDFWidgetAnnotationManager::~PDFWidgetAnnotationManager()
{
    m_proxy->unregisterDrawInterface(this);
}

void PDFWidgetAnnotationManager::setDocument(const PDFModifiedDocument& document)
{
    BaseClass::setDocument(document);

    if (document.hasReset() || document.getFlags().testFlag(PDFModifiedDocument::Annotation))
    {
        m_editableAnnotation = PDFObjectReference();
        m_editableAnnotationPage = PDFObjectReference();
    }
}

void PDFWidgetAnnotationManager::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
    Q_UNUSED(widget);
    Q_UNUSED(event);
}

void PDFWidgetAnnotationManager::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
    Q_UNUSED(widget);
    Q_UNUSED(event);
}

void PDFWidgetAnnotationManager::keyReleaseEvent(QWidget* widget, QKeyEvent* event)
{
    Q_UNUSED(widget);
    Q_UNUSED(event);
}

void PDFWidgetAnnotationManager::mousePressEvent(QWidget* widget, QMouseEvent* event)
{
    Q_UNUSED(widget);

    updateFromMouseEvent(event);

    // Show context menu?
    if (event->button() == Qt::RightButton)
    {
        PDFWidget* pdfWidget = m_proxy->getWidget();
        std::vector<PDFInteger> currentPages = pdfWidget->getDrawWidget()->getCurrentPages();

        if (!hasAnyPageAnnotation(currentPages))
        {
            // All pages doesn't have annotation
            return;
        }

        m_editableAnnotation = PDFObjectReference();
        m_editableAnnotationPage = PDFObjectReference();
        for (PDFInteger pageIndex : currentPages)
        {
            PageAnnotations& pageAnnotations = getPageAnnotations(pageIndex);
            for (PageAnnotation& pageAnnotation : pageAnnotations.annotations)
            {
                if (!pageAnnotation.isHovered)
                {
                    continue;
                }

                if (!PDFAnnotation::isTypeEditable(pageAnnotation.annotation->getType()))
                {
                    continue;
                }

                m_editableAnnotation = pageAnnotation.annotation->getSelfReference();
                m_editableAnnotationPage = pageAnnotation.annotation->getPageReference();

                if (!m_editableAnnotationPage.isValid())
                {
                    m_editableAnnotationPage = m_document->getCatalog()->getPage(pageIndex)->getPageReference();
                }
                break;
            }
        }

        QPoint menuPosition = pdfWidget->mapToGlobal(event->pos());
        showAnnotationMenu(m_editableAnnotation, m_editableAnnotationPage, menuPosition);
    }
}

void PDFWidgetAnnotationManager::showAnnotationMenu(PDFObjectReference annotationReference,
                                                    PDFObjectReference pageReference,
                                                    QPoint globalMenuPosition)
{
    m_editableAnnotation = annotationReference;
    m_editableAnnotationPage = pageReference;

    if (m_editableAnnotation.isValid())
    {
        PDFWidget* pdfWidget = m_proxy->getWidget();

        QMenu menu(tr("Annotation"), pdfWidget);
        QAction* showPopupAction = menu.addAction(tr("Show Popup Window"));
        QAction* copyAction = menu.addAction(tr("Copy to Multiple Pages"));
        QAction* editAction = menu.addAction(tr("Edit"));
        QAction* deleteAction = menu.addAction(tr("Delete"));
        connect(showPopupAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onShowPopupAnnotation);
        connect(copyAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onCopyAnnotation);
        connect(editAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onEditAnnotation);
        connect(deleteAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onDeleteAnnotation);

        m_editableAnnotationGlobalPosition = globalMenuPosition;
        menu.exec(m_editableAnnotationGlobalPosition);
    }
}

void PDFWidgetAnnotationManager::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event)
{
    Q_UNUSED(widget);
    Q_UNUSED(event);
}

void PDFWidgetAnnotationManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event)
{
    Q_UNUSED(widget);

    updateFromMouseEvent(event);
}

void PDFWidgetAnnotationManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event)
{
    Q_UNUSED(widget);

    updateFromMouseEvent(event);
}

void PDFWidgetAnnotationManager::wheelEvent(QWidget* widget, QWheelEvent* event)
{
    Q_UNUSED(widget);
    Q_UNUSED(event);
}

void PDFWidgetAnnotationManager::updateFromMouseEvent(QMouseEvent* event)
{
    PDFWidget* widget = m_proxy->getWidget();
    std::vector<PDFInteger> currentPages = widget->getDrawWidget()->getCurrentPages();

    if (!hasAnyPageAnnotation(currentPages))
    {
        // All pages doesn't have annotation
        return;
    }

    m_tooltip = QString();
    m_cursor = std::nullopt;
    bool appearanceChanged = false;

    // We must update appearance states, and update tooltip
    PDFWidgetSnapshot snapshot = m_proxy->getSnapshot();
    const bool isDown = event->buttons().testFlag(Qt::LeftButton);
    const PDFAppeareanceStreams::Appearance hoverAppearance = isDown ? PDFAppeareanceStreams::Appearance::Down : PDFAppeareanceStreams::Appearance::Rollover;

    for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items)
    {
        PageAnnotations& pageAnnotations = getPageAnnotations(snapshotItem.pageIndex);
        for (PageAnnotation& pageAnnotation : pageAnnotations.annotations)
        {
            if (pageAnnotation.annotation->isReplyTo())
            {
                // Annotation is reply to another annotation, do not interact with it
                continue;
            }

            const PDFAppeareanceStreams::Appearance oldAppearance = pageAnnotation.appearance;
            QRectF annotationRect = pageAnnotation.annotation->getRectangle();
            QTransform matrix = prepareTransformations(snapshotItem.pageToDeviceMatrix, widget, pageAnnotation.annotation->getEffectiveFlags(), m_document->getCatalog()->getPage(snapshotItem.pageIndex), annotationRect);
            QPainterPath path;
            path.addRect(annotationRect);
            path = matrix.map(path);

            if (path.contains(event->pos()))
            {
                pageAnnotation.appearance = hoverAppearance;
                pageAnnotation.isHovered = true;

                // Generate tooltip
                if (m_tooltip.isEmpty())
                {
                    const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation();
                    if (markupAnnotation)
                    {
                        QString title = markupAnnotation->getWindowTitle();
                        if (title.isEmpty())
                        {
                            title = markupAnnotation->getSubject();
                        }
                        if (title.isEmpty())
                        {
                            title = PDFTranslationContext::tr("Info");
                        }

                        const size_t repliesCount = pageAnnotations.getReplies(pageAnnotation).size();
                        if (repliesCount > 0)
                        {
                            title = PDFTranslationContext::tr("%1 (%2 replies)").arg(title).arg(repliesCount);
                        }

                        m_tooltip = QString("<p><b>%1</b></p><p>%2</p>").arg(title, markupAnnotation->getContents());
                    }
                }

                const PDFAction* linkAction = nullptr;
                const AnnotationType annotationType = pageAnnotation.annotation->getType();
                if (annotationType == AnnotationType::Link)
                {
                    const PDFLinkAnnotation* linkAnnotation = dynamic_cast<const PDFLinkAnnotation*>(pageAnnotation.annotation.data());
                    Q_ASSERT(linkAnnotation);

                    // We must check, if user clicked to the link area
                    QPainterPath activationPath = linkAnnotation->getActivationRegion().getPath();
                    activationPath = snapshotItem.pageToDeviceMatrix.map(activationPath);
                    if (activationPath.contains(event->pos()) && linkAnnotation->getAction())
                    {
                        m_cursor = QCursor(Qt::PointingHandCursor);
                        linkAction = linkAnnotation->getAction();
                    }
                }
                if (annotationType == AnnotationType::Widget)
                {
                    if (m_formManager && m_formManager->hasFormFieldWidgetText(pageAnnotation.annotation->getSelfReference()))
                    {
                        m_cursor = QCursor(Qt::IBeamCursor);
                    }
                    else
                    {
                        m_cursor = QCursor(Qt::ArrowCursor);
                    }
                }

                // Generate popup window
                if (event->type() == QEvent::MouseButtonPress && event->button() == Qt::LeftButton)
                {
                    const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation();
                    if (markupAnnotation)
                    {
                        QDialog* dialog = createDialogForMarkupAnnotations(widget, pageAnnotation, pageAnnotations);

                        // Set proper dialog position - according to the popup annotation. If we
                        // do not have popup annotation, then try to use annotations rectangle.
                        if (const PageAnnotation* popupAnnotation = pageAnnotations.getPopupAnnotation(pageAnnotation))
                        {
                            QPoint popupPoint = snapshotItem.pageToDeviceMatrix.map(popupAnnotation->annotation->getRectangle().bottomLeft()).toPoint();
                            popupPoint = widget->mapToGlobal(popupPoint);
                            dialog->move(popupPoint);
                        }
                        else if (markupAnnotation->getRectangle().isValid())
                        {
                            QPoint popupPoint = snapshotItem.pageToDeviceMatrix.map(markupAnnotation->getRectangle().bottomRight()).toPoint();
                            popupPoint = widget->mapToGlobal(popupPoint);
                            dialog->move(popupPoint);
                        }

                        dialog->exec();
                    }

                    if (linkAction)
                    {
                        Q_EMIT actionTriggered(linkAction);
                    }
                }
            }
            else
            {
                pageAnnotation.appearance = PDFAppeareanceStreams::Appearance::Normal;
                pageAnnotation.isHovered = false;
            }

            const bool currentAppearanceChanged = oldAppearance != pageAnnotation.appearance;
            if (currentAppearanceChanged)
            {
                // We have changed appearance - we must mark stream as dirty
                pageAnnotation.appearanceStream.dirty();
                appearanceChanged = true;
            }
        }
    }

    // If appearance has changed, then we must redraw the page
    if (appearanceChanged)
    {
        Q_EMIT widget->getDrawWidgetProxy()->repaintNeeded();
    }
}

void PDFWidgetAnnotationManager::onShowPopupAnnotation()
{
    PDFWidgetSnapshot snapshot = m_proxy->getSnapshot();
    for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items)
    {
        PageAnnotations& pageAnnotations = getPageAnnotations(snapshotItem.pageIndex);
        for (PageAnnotation& pageAnnotation : pageAnnotations.annotations)
        {
            if (pageAnnotation.annotation->isReplyTo())
            {
                // Annotation is reply to another annotation, do not interact with it
                continue;
            }

            if (pageAnnotation.annotation->getSelfReference() == m_editableAnnotation)
            {
                QDialog* dialog = createDialogForMarkupAnnotations(m_proxy->getWidget(), pageAnnotation, pageAnnotations);
                dialog->move(m_editableAnnotationGlobalPosition);
                dialog->exec();
                return;
            }
        }
    }
}

void PDFWidgetAnnotationManager::onCopyAnnotation()
{
    pdf::PDFSelectPagesDialog dialog(tr("Copy Annotation"), tr("Copy Annotation onto Multiple Pages"),
                                     m_document->getCatalog()->getPageCount(), m_proxy->getWidget()->getDrawWidget()->getCurrentPages(), m_proxy->getWidget());
    if (dialog.exec() == QDialog::Accepted)
    {
        std::vector<PDFInteger> pages = dialog.getSelectedPages();
        const PDFInteger currentPageIndex = m_document->getCatalog()->getPageIndexFromPageReference(m_editableAnnotationPage);

        for (PDFInteger& pageIndex : pages)
        {
            --pageIndex;
        }

        auto it = std::find(pages.begin(), pages.end(), currentPageIndex);
        if (it != pages.end())
        {
            pages.erase(it);
        }

        if (pages.empty())
        {
            return;
        }

        PDFDocumentModifier modifier(m_document);
        modifier.markAnnotationsChanged();

        for (const PDFInteger pageIndex : pages)
        {
            modifier.getBuilder()->copyAnnotation(m_document->getCatalog()->getPage(pageIndex)->getPageReference(), m_editableAnnotation);
        }

        if (modifier.finalize())
        {
            Q_EMIT documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags()));
        }
    }
}

void PDFWidgetAnnotationManager::onEditAnnotation()
{
    PDFEditObjectDialog dialog(EditObjectType::Annotation, m_proxy->getWidget());

    PDFObject originalObject = m_document->getObjectByReference(m_editableAnnotation);
    dialog.setObject(originalObject);

    if (dialog.exec() == PDFEditObjectDialog::Accepted)
    {
        PDFObject object = dialog.getObject();
        if (object != originalObject)
        {
            PDFDocumentModifier modifier(m_document);
            modifier.markAnnotationsChanged();
            modifier.getBuilder()->setObject(m_editableAnnotation, object);
            modifier.getBuilder()->updateAnnotationAppearanceStreams(m_editableAnnotation);

            if (modifier.finalize())
            {
                Q_EMIT documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags()));
            }
        }
    }
}

void PDFWidgetAnnotationManager::onDeleteAnnotation()
{
    if (m_editableAnnotation.isValid())
    {
        PDFDocumentModifier modifier(m_document);
        modifier.markAnnotationsChanged();
        modifier.getBuilder()->removeAnnotation(m_editableAnnotationPage, m_editableAnnotation);

        if (modifier.finalize())
        {
            Q_EMIT documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags()));
        }
    }
}

QDialog* PDFWidgetAnnotationManager::createDialogForMarkupAnnotations(PDFWidget* widget,
                                                                      const PageAnnotation& pageAnnotation,
                                                                      const PageAnnotations& pageAnnotations)
{
    QDialog* dialog = new QDialog(widget->getDrawWidget()->getWidget(), Qt::Popup);
    dialog->setAttribute(Qt::WA_DeleteOnClose, true);
    createWidgetsForMarkupAnnotations(dialog, pageAnnotation, pageAnnotations);
    return dialog;
}

void PDFWidgetAnnotationManager::createWidgetsForMarkupAnnotations(QWidget* parentWidget,
                                                                   const PageAnnotation& pageAnnotation,
                                                                   const PageAnnotations& pageAnnotations)
{
    std::vector<const PageAnnotation*> replies = pageAnnotations.getReplies(pageAnnotation);
    replies.insert(replies.begin(), &pageAnnotation);

    QScrollArea* scrollArea = new QScrollArea(parentWidget);
    scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);

    QVBoxLayout* layout = new QVBoxLayout(parentWidget);
    layout->addWidget(scrollArea);
    layout->setContentsMargins(QMargins());

    QWidget* frameWidget = new QWidget(scrollArea);
    QVBoxLayout* frameLayout = new QVBoxLayout(frameWidget);
    frameLayout->setSpacing(0);
    scrollArea->setWidget(frameWidget);

    const PDFMarkupAnnotation* markupMainAnnotation = pageAnnotation.annotation->asMarkupAnnotation();
    QColor color = markupMainAnnotation->getDrawColorFromAnnotationColor(markupMainAnnotation->getColor(), 1.0);
    QColor titleColor = QColor::fromHslF(color.hueF(), color.saturationF(), 0.2f, 1.0f);
    QColor backgroundColor = QColor::fromHslF(color.hueF(), color.saturationF(), 0.9f, 1.0f);

    QString style = "QGroupBox { "
                    "border: 2px solid black; "
                    "border-color: rgb(%4, %5, %6); "
                    "margin-top: 3ex; "
                    "background-color: rgb(%1, %2, %3); "
                    "}"
                    "QGroupBox::title { "
                    "subcontrol-origin: margin; "
                    "subcontrol-position: top center; "
                    "padding: 0px 8192px; "
                    "background-color: rgb(%4, %5, %6); "
                    "color: #FFFFFF;"
                    "}";
    style = style.arg(backgroundColor.red()).arg(backgroundColor.green()).arg(backgroundColor.blue()).arg(titleColor.red()).arg(titleColor.green()).arg(titleColor.blue());

    for (const PageAnnotation* annotation : replies)
    {
        const PDFMarkupAnnotation* markupAnnotation = annotation->annotation->asMarkupAnnotation();

        if (!markupAnnotation)
        {
            // This should not happen...
            continue;
        }

        QGroupBox* groupBox = new QGroupBox(scrollArea);
        frameLayout->addWidget(groupBox);

        QString title = markupAnnotation->getWindowTitle();
        if (title.isEmpty())
        {
            title = markupAnnotation->getSubject();
        }

        QString dateTimeString = QLocale::system().toString(markupAnnotation->getCreationDate().toLocalTime(), QLocale::LongFormat);
        title = QString("%1 (%2)").arg(title, dateTimeString).trimmed();

        groupBox->setStyleSheet(style);
        groupBox->setTitle(title);
        QVBoxLayout* groupBoxLayout = new QVBoxLayout(groupBox);

        QLabel* label = new QLabel(groupBox);
        label->setTextInteractionFlags(Qt::TextBrowserInteraction);
        label->setWordWrap(true);
        label->setText(markupAnnotation->getContents());
        label->setFixedWidth(PDFWidgetUtils::scaleDPI_x(label, 250));
        label->setMinimumHeight(label->sizeHint().height());
        groupBoxLayout->addWidget(label);
    }

    frameWidget->setFixedSize(frameWidget->minimumSizeHint());
    parentWidget->setFixedSize(scrollArea->sizeHint());
}

void PDFWidgetAnnotationManager::drawPage(QPainter* painter,
                                          PDFInteger pageIndex,
                                          const PDFPrecompiledPage* compiledPage,
                                          PDFTextLayoutGetter& layoutGetter,
                                          const QTransform& pagePointToDevicePointMatrix,
                                          const PDFColorConvertor& convertor,
                                          QList<PDFRenderError>& errors) const
{
    BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, convertor, errors);
}

}   // namespace pdf
