/***************************************************************************
 *   Copyright (C) 2008 by Pino Toscano <pino@kde.org>                     *
 *   Copyright (C) 2012 by Guillermo A. Amaral B. <gamaral@kde.org>        *
 *   Copyright (C) 2017    Klarälvdalens Datakonsult AB, a KDAB Group      *
 *                         company, info@kdab.com. Work sponsored by the   *
 *                         LiMux project of the city of Munich             *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "annots.h"

#include <poppler-annotation.h>

// qt/kde includes
#include <QLoggingCategory>
#include <QVariant>

#include <core/annotations.h>
#include <core/area.h>

#include "config-okular-poppler.h"
#include "debug_pdf.h"
#include "generator_pdf.h"
#include "popplerembeddedfile.h"

Q_DECLARE_METATYPE(Poppler::Annotation *)

extern Okular::Sound *createSoundFromPopplerSound(const Poppler::SoundObject *popplerSound);
extern Okular::Movie *createMovieFromPopplerMovie(const Poppler::MovieObject *popplerMovie);
extern Okular::Movie *createMovieFromPopplerScreen(const Poppler::LinkRendition *popplerScreen);
extern QPair<Okular::Movie *, Okular::EmbeddedFile *> createMovieFromPopplerRichMedia(const Poppler::RichMediaAnnotation *popplerRichMedia);

static void disposeAnnotation(const Okular::Annotation *ann)
{
    Poppler::Annotation *popplerAnn = qvariant_cast<Poppler::Annotation *>(ann->nativeId());
    delete popplerAnn;
}

static QPointF normPointToPointF(const Okular::NormalizedPoint &pt)
{
    return QPointF(pt.x, pt.y);
}

static QRectF normRectToRectF(const Okular::NormalizedRect &rect)
{
    return QRectF(QPointF(rect.left, rect.top), QPointF(rect.right, rect.bottom));
}

// Poppler and Okular share the same flag values, but we don't want to export internal flags
static int maskExportedFlags(int flags)
{
    return flags &
        (Okular::Annotation::Hidden | Okular::Annotation::FixedSize | Okular::Annotation::FixedRotation | Okular::Annotation::DenyPrint | Okular::Annotation::DenyWrite | Okular::Annotation::DenyDelete |
         Okular::Annotation::ToggleHidingOnMouse);
}

// BEGIN PopplerAnnotationProxy implementation
PopplerAnnotationProxy::PopplerAnnotationProxy(Poppler::Document *doc, QMutex *userMutex, QHash<Okular::Annotation *, Poppler::Annotation *> *annotsOnOpenHash)
    : ppl_doc(doc)
    , mutex(userMutex)
    , annotationsOnOpenHash(annotsOnOpenHash)
{
}

PopplerAnnotationProxy::~PopplerAnnotationProxy()
{
}

bool PopplerAnnotationProxy::supports(Capability cap) const
{
    switch (cap) {
    case Addition:
    case Modification:
    case Removal:
        return true;
    default:
        return false;
    }
}

void PopplerAnnotationProxy::notifyAddition(Okular::Annotation *okl_ann, int page)
{
    // Export annotation to DOM
    QDomDocument doc;
    QDomElement dom_ann = doc.createElement(QStringLiteral("root"));
    Okular::AnnotationUtils::storeAnnotation(okl_ann, dom_ann, doc);

    QMutexLocker ml(mutex);

    // Create poppler annotation
    Poppler::Annotation *ppl_ann = Poppler::AnnotationUtils::createAnnotation(dom_ann);

    // Poppler doesn't render StampAnnotations yet
    if (ppl_ann->subType() != Poppler::Annotation::AStamp)
        okl_ann->setFlags(okl_ann->flags() | Okular::Annotation::ExternallyDrawn);

    // Poppler stores highlight points in swapped order
    if (ppl_ann->subType() == Poppler::Annotation::AHighlight) {
        Poppler::HighlightAnnotation *hlann = static_cast<Poppler::HighlightAnnotation *>(ppl_ann);
        QList<Poppler::HighlightAnnotation::Quad> quads = hlann->highlightQuads();
        QMutableListIterator<Poppler::HighlightAnnotation::Quad> it(quads);
        while (it.hasNext()) {
            Poppler::HighlightAnnotation::Quad &q = it.next();
            QPointF t;
            t = q.points[3];
            q.points[3] = q.points[0];
            q.points[0] = t;
            t = q.points[2];
            q.points[2] = q.points[1];
            q.points[1] = t;
        }
        hlann->setHighlightQuads(quads);
    }

    // Bind poppler object to page
    Poppler::Page *ppl_page = ppl_doc->page(page);
    ppl_page->addAnnotation(ppl_ann);
    delete ppl_page;

    // Set pointer to poppler annotation as native Id
    okl_ann->setNativeId(QVariant::fromValue(ppl_ann));
    okl_ann->setDisposeDataFunction(disposeAnnotation);

    qCDebug(OkularPdfDebug) << okl_ann->uniqueName();
}

void PopplerAnnotationProxy::notifyModification(const Okular::Annotation *okl_ann, int page, bool appearanceChanged)
{
    Q_UNUSED(page);
    Q_UNUSED(appearanceChanged);

    Poppler::Annotation *ppl_ann = qvariant_cast<Poppler::Annotation *>(okl_ann->nativeId());

    if (!ppl_ann) // Ignore non-native annotations
        return;

    QMutexLocker ml(mutex);

    if (okl_ann->flags() & (Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized)) {
        // Okular ui already renders the annotation on its own
        ppl_ann->setFlags(Poppler::Annotation::Hidden);
        return;
    }

    // Set basic properties
    // Note: flags and boundary must be set first in order to correctly handle
    // FixedRotation annotations.
    ppl_ann->setFlags(maskExportedFlags(okl_ann->flags()));
    ppl_ann->setBoundary(normRectToRectF(okl_ann->boundingRectangle()));

    ppl_ann->setAuthor(okl_ann->author());
    ppl_ann->setContents(okl_ann->contents());

    // Set style
    Poppler::Annotation::Style s;
    s.setColor(okl_ann->style().color());
    s.setWidth(okl_ann->style().width());
    s.setOpacity(okl_ann->style().opacity());
    ppl_ann->setStyle(s);

    // Set type-specific properties (if any)
    switch (ppl_ann->subType()) {
    case Poppler::Annotation::AText: {
        const Okular::TextAnnotation *okl_txtann = static_cast<const Okular::TextAnnotation *>(okl_ann);
        Poppler::TextAnnotation *ppl_txtann = static_cast<Poppler::TextAnnotation *>(ppl_ann);
        ppl_txtann->setTextIcon(okl_txtann->textIcon());
        ppl_txtann->setTextFont(okl_txtann->textFont());
#ifdef HAVE_POPPLER_0_69
        ppl_txtann->setTextColor(okl_txtann->textColor());
#endif // HAVE_POPPLER_0_69
        ppl_txtann->setInplaceAlign(okl_txtann->inplaceAlignment());
        ppl_txtann->setCalloutPoints(QVector<QPointF>());
        ppl_txtann->setInplaceIntent((Poppler::TextAnnotation::InplaceIntent)okl_txtann->inplaceIntent());
        break;
    }
    case Poppler::Annotation::ALine: {
        const Okular::LineAnnotation *okl_lineann = static_cast<const Okular::LineAnnotation *>(okl_ann);
        Poppler::LineAnnotation *ppl_lineann = static_cast<Poppler::LineAnnotation *>(ppl_ann);
        QLinkedList<QPointF> points;
        const QLinkedList<Okular::NormalizedPoint> annotPoints = okl_lineann->linePoints();
        for (const Okular::NormalizedPoint &p : annotPoints) {
            points.append(normPointToPointF(p));
        }
        ppl_lineann->setLinePoints(points);
        ppl_lineann->setLineStartStyle((Poppler::LineAnnotation::TermStyle)okl_lineann->lineStartStyle());
        ppl_lineann->setLineEndStyle((Poppler::LineAnnotation::TermStyle)okl_lineann->lineEndStyle());
        ppl_lineann->setLineClosed(okl_lineann->lineClosed());
        ppl_lineann->setLineInnerColor(okl_lineann->lineInnerColor());
        ppl_lineann->setLineLeadingForwardPoint(okl_lineann->lineLeadingForwardPoint());
        ppl_lineann->setLineLeadingBackPoint(okl_lineann->lineLeadingBackwardPoint());
        ppl_lineann->setLineShowCaption(okl_lineann->showCaption());
        ppl_lineann->setLineIntent((Poppler::LineAnnotation::LineIntent)okl_lineann->lineIntent());
        break;
    }
    case Poppler::Annotation::AGeom: {
        const Okular::GeomAnnotation *okl_geomann = static_cast<const Okular::GeomAnnotation *>(okl_ann);
        Poppler::GeomAnnotation *ppl_geomann = static_cast<Poppler::GeomAnnotation *>(ppl_ann);
        ppl_geomann->setGeomType((Poppler::GeomAnnotation::GeomType)okl_geomann->geometricalType());
        ppl_geomann->setGeomInnerColor(okl_geomann->geometricalInnerColor());
        break;
    }
    case Poppler::Annotation::AHighlight: {
        const Okular::HighlightAnnotation *okl_hlann = static_cast<const Okular::HighlightAnnotation *>(okl_ann);
        Poppler::HighlightAnnotation *ppl_hlann = static_cast<Poppler::HighlightAnnotation *>(ppl_ann);
        ppl_hlann->setHighlightType((Poppler::HighlightAnnotation::HighlightType)okl_hlann->highlightType());
        break;
    }
    case Poppler::Annotation::AStamp: {
        const Okular::StampAnnotation *okl_stampann = static_cast<const Okular::StampAnnotation *>(okl_ann);
        Poppler::StampAnnotation *ppl_stampann = static_cast<Poppler::StampAnnotation *>(ppl_ann);
        ppl_stampann->setStampIconName(okl_stampann->stampIconName());
        break;
    }
    case Poppler::Annotation::AInk: {
        const Okular::InkAnnotation *okl_inkann = static_cast<const Okular::InkAnnotation *>(okl_ann);
        Poppler::InkAnnotation *ppl_inkann = static_cast<Poppler::InkAnnotation *>(ppl_ann);
        QList<QLinkedList<QPointF>> paths;
        const QList<QLinkedList<Okular::NormalizedPoint>> inkPathsList = okl_inkann->inkPaths();
        for (const QLinkedList<Okular::NormalizedPoint> &path : inkPathsList) {
            QLinkedList<QPointF> points;
            for (const Okular::NormalizedPoint &p : path) {
                points.append(normPointToPointF(p));
            }
            paths.append(points);
        }
        ppl_inkann->setInkPaths(paths);
        break;
    }
    default:
        qCDebug(OkularPdfDebug) << "Type-specific property modification is not implemented for this annotation type";
        break;
    }

    qCDebug(OkularPdfDebug) << okl_ann->uniqueName();
}

void PopplerAnnotationProxy::notifyRemoval(Okular::Annotation *okl_ann, int page)
{
    Poppler::Annotation *ppl_ann = qvariant_cast<Poppler::Annotation *>(okl_ann->nativeId());

    if (!ppl_ann) // Ignore non-native annotations
        return;

    QMutexLocker ml(mutex);

    Poppler::Page *ppl_page = ppl_doc->page(page);
    annotationsOnOpenHash->remove(okl_ann);
    ppl_page->removeAnnotation(ppl_ann); // Also destroys ppl_ann
    delete ppl_page;

    okl_ann->setNativeId(QVariant::fromValue(0)); // So that we don't double-free in disposeAnnotation

    qCDebug(OkularPdfDebug) << okl_ann->uniqueName();
}
// END PopplerAnnotationProxy implementation

Okular::Annotation *createAnnotationFromPopplerAnnotation(Poppler::Annotation *ann, bool *doDelete)
{
    Okular::Annotation *annotation = nullptr;
    *doDelete = true;
    bool tieToOkularAnn = false;
    bool externallyDrawn = false;
    switch (ann->subType()) {
    case Poppler::Annotation::AFileAttachment: {
        Poppler::FileAttachmentAnnotation *attachann = static_cast<Poppler::FileAttachmentAnnotation *>(ann);
        Okular::FileAttachmentAnnotation *f = new Okular::FileAttachmentAnnotation();
        annotation = f;
        tieToOkularAnn = true;
        *doDelete = false;

        f->setFileIconName(attachann->fileIconName());
        f->setEmbeddedFile(new PDFEmbeddedFile(attachann->embeddedFile()));

        break;
    }
    case Poppler::Annotation::ASound: {
        Poppler::SoundAnnotation *soundann = static_cast<Poppler::SoundAnnotation *>(ann);
        Okular::SoundAnnotation *s = new Okular::SoundAnnotation();
        annotation = s;

        s->setSoundIconName(soundann->soundIconName());
        s->setSound(createSoundFromPopplerSound(soundann->sound()));

        break;
    }
    case Poppler::Annotation::AMovie: {
        Poppler::MovieAnnotation *movieann = static_cast<Poppler::MovieAnnotation *>(ann);
        Okular::MovieAnnotation *m = new Okular::MovieAnnotation();
        annotation = m;
        tieToOkularAnn = true;
        *doDelete = false;

        m->setMovie(createMovieFromPopplerMovie(movieann->movie()));

        break;
    }
    case Poppler::Annotation::AWidget: {
        annotation = new Okular::WidgetAnnotation();
        break;
    }
    case Poppler::Annotation::AScreen: {
        Okular::ScreenAnnotation *m = new Okular::ScreenAnnotation();
        annotation = m;
        tieToOkularAnn = true;
        *doDelete = false;
        break;
    }
    case Poppler::Annotation::ARichMedia: {
        Poppler::RichMediaAnnotation *richmediaann = static_cast<Poppler::RichMediaAnnotation *>(ann);
        const QPair<Okular::Movie *, Okular::EmbeddedFile *> result = createMovieFromPopplerRichMedia(richmediaann);

        if (result.first) {
            Okular::RichMediaAnnotation *r = new Okular::RichMediaAnnotation();
            tieToOkularAnn = true;
            *doDelete = false;
            annotation = r;

            r->setMovie(result.first);
            r->setEmbeddedFile(result.second);
        }

        break;
    }
    case Poppler::Annotation::AText:
    case Poppler::Annotation::ALine:
    case Poppler::Annotation::AGeom:
    case Poppler::Annotation::AHighlight:
    case Poppler::Annotation::AInk:
    case Poppler::Annotation::ACaret:
        externallyDrawn = true;
        /* fallthrough */
    case Poppler::Annotation::AStamp:
        tieToOkularAnn = true;
        *doDelete = false;
        /* fallthrough */
    default: {
        // this is uber ugly but i don't know a better way to do it without introducing a poppler::annotation dependency on core
        QDomDocument doc;
        QDomElement root = doc.createElement(QStringLiteral("root"));
        doc.appendChild(root);
        Poppler::AnnotationUtils::storeAnnotation(ann, root, doc);
        annotation = Okular::AnnotationUtils::createAnnotation(root);
        break;
    }
    }
    if (annotation) {
        // the Contents field might have lines separated by \r
        QString contents = ann->contents();
        contents.replace(QLatin1Char('\r'), QLatin1Char('\n'));

        annotation->setAuthor(ann->author());
        annotation->setContents(contents);
        annotation->setUniqueName(ann->uniqueName());
        annotation->setModificationDate(ann->modificationDate());
        annotation->setCreationDate(ann->creationDate());
        annotation->setFlags(ann->flags() | Okular::Annotation::External);
        annotation->setBoundingRectangle(Okular::NormalizedRect::fromQRectF(ann->boundary()));

        if (externallyDrawn)
            annotation->setFlags(annotation->flags() | Okular::Annotation::ExternallyDrawn);

        // Poppler stores highlight points in swapped order
        if (annotation->subType() == Okular::Annotation::AHighlight) {
            Okular::HighlightAnnotation *hlann = static_cast<Okular::HighlightAnnotation *>(annotation);
            for (Okular::HighlightAnnotation::Quad &quad : hlann->highlightQuads()) {
                const Okular::NormalizedPoint p3 = quad.point(3);
                quad.setPoint(quad.point(0), 3);
                quad.setPoint(p3, 0);
                const Okular::NormalizedPoint p2 = quad.point(2);
                quad.setPoint(quad.point(1), 2);
                quad.setPoint(p2, 1);
            }
        }

        if (annotation->subType() == Okular::Annotation::AText) {
            Okular::TextAnnotation *txtann = static_cast<Okular::TextAnnotation *>(annotation);

            if (txtann->textType() == Okular::TextAnnotation::Linked) {
                Poppler::TextAnnotation *ppl_txtann = static_cast<Poppler::TextAnnotation *>(ann);

                // Poppler and Okular assume a different default icon name in XML
                // We re-read it via getter, which always tells the right one
                txtann->setTextIcon(ppl_txtann->textIcon());
            }
        }

        // TODO clone style
        // TODO clone window
        // TODO clone revisions
        if (tieToOkularAnn) {
            annotation->setNativeId(QVariant::fromValue(ann));
            annotation->setDisposeDataFunction(disposeAnnotation);
        }
    }
    return annotation;
}
