/* poppler-link.cc: qt interface to poppler
 * Copyright (C) 2006-2007, 2013, 2016-2019, Albert Astals Cid
 * Copyright (C) 2007-2008, Pino Toscano <pino@kde.org>
 * Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
 * Copyright (C) 2012, Tobias Koenig <tokoe@kdab.com>
 * Copyright (C) 2012, Guillermo A. Amaral B. <gamaral@kde.org>
 * Copyright (C) 2018 Intevation GmbH <intevation@intevation.de>
 * Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
 * Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de>
 * Adapting code from
 *   Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>
 *
 * 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, or (at your option)
 * any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <poppler-qt6.h>
#include <poppler-link-private.h>
#include <poppler-private.h>
#include <poppler-media.h>

#include <QtCore/QStringList>

#include "poppler-annotation-private.h"

#include "Link.h"
#include "Rendition.h"

namespace Poppler {

class LinkDestinationPrivate : public QSharedData
{
public:
    LinkDestinationPrivate();

    LinkDestination::Kind kind; // destination type
    QString name;
    int pageNum; // page number
    double left, bottom; // position
    double right, top;
    double zoom; // zoom factor
    bool changeLeft : 1, changeTop : 1; // for destXYZ links, which position
    bool changeZoom : 1; //   components to change
};

LinkDestinationPrivate::LinkDestinationPrivate()
{
    // sane defaults
    kind = LinkDestination::destXYZ;
    pageNum = 0;
    left = 0;
    bottom = 0;
    right = 0;
    top = 0;
    zoom = 1;
    changeLeft = true;
    changeTop = true;
    changeZoom = false;
}

class LinkGotoPrivate : public LinkPrivate
{
public:
    LinkGotoPrivate(const QRectF &area, const LinkDestination &dest);

    QString extFileName;
    LinkDestination destination;
};

LinkGotoPrivate::LinkGotoPrivate(const QRectF &area, const LinkDestination &dest) : LinkPrivate(area), destination(dest) { }

class LinkExecutePrivate : public LinkPrivate
{
public:
    LinkExecutePrivate(const QRectF &area);

    QString fileName;
    QString parameters;
};

LinkExecutePrivate::LinkExecutePrivate(const QRectF &area) : LinkPrivate(area) { }

class LinkBrowsePrivate : public LinkPrivate
{
public:
    LinkBrowsePrivate(const QRectF &area);

    QString url;
};

LinkBrowsePrivate::LinkBrowsePrivate(const QRectF &area) : LinkPrivate(area) { }

class LinkActionPrivate : public LinkPrivate
{
public:
    LinkActionPrivate(const QRectF &area);

    LinkAction::ActionType type;
};

LinkActionPrivate::LinkActionPrivate(const QRectF &area) : LinkPrivate(area) { }

class LinkSoundPrivate : public LinkPrivate
{
public:
    LinkSoundPrivate(const QRectF &area);
    ~LinkSoundPrivate() override;

    double volume;
    bool sync : 1;
    bool repeat : 1;
    bool mix : 1;
    SoundObject *sound;
};

LinkSoundPrivate::LinkSoundPrivate(const QRectF &area) : LinkPrivate(area), sound(nullptr) { }

LinkSoundPrivate::~LinkSoundPrivate()
{
    delete sound;
}

class LinkRenditionPrivate : public LinkPrivate
{
public:
    LinkRenditionPrivate(const QRectF &area, ::MediaRendition *rendition, ::LinkRendition::RenditionOperation operation, const QString &script, const Ref ref);
    ~LinkRenditionPrivate() override;

    MediaRendition *rendition;
    LinkRendition::RenditionAction action;
    QString script;
    Ref annotationReference;
};

LinkRenditionPrivate::LinkRenditionPrivate(const QRectF &area, ::MediaRendition *r, ::LinkRendition::RenditionOperation operation, const QString &javaScript, const Ref ref)
    : LinkPrivate(area), rendition(r ? new MediaRendition(r) : nullptr), action(LinkRendition::PlayRendition), script(javaScript), annotationReference(ref)
{
    switch (operation) {
    case ::LinkRendition::NoRendition:
        action = LinkRendition::NoRendition;
        break;
    case ::LinkRendition::PlayRendition:
        action = LinkRendition::PlayRendition;
        break;
    case ::LinkRendition::StopRendition:
        action = LinkRendition::StopRendition;
        break;
    case ::LinkRendition::PauseRendition:
        action = LinkRendition::PauseRendition;
        break;
    case ::LinkRendition::ResumeRendition:
        action = LinkRendition::ResumeRendition;
        break;
    }
}

LinkRenditionPrivate::~LinkRenditionPrivate()
{
    delete rendition;
}

class LinkJavaScriptPrivate : public LinkPrivate
{
public:
    LinkJavaScriptPrivate(const QRectF &area);

    QString js;
};

LinkJavaScriptPrivate::LinkJavaScriptPrivate(const QRectF &area) : LinkPrivate(area) { }

class LinkMoviePrivate : public LinkPrivate
{
public:
    LinkMoviePrivate(const QRectF &area, LinkMovie::Operation operation, const QString &title, const Ref reference);

    LinkMovie::Operation operation;
    QString annotationTitle;
    Ref annotationReference;
};

LinkMoviePrivate::LinkMoviePrivate(const QRectF &area, LinkMovie::Operation _operation, const QString &title, const Ref reference) : LinkPrivate(area), operation(_operation), annotationTitle(title), annotationReference(reference) { }

static void cvtUserToDev(::Page *page, double xu, double yu, int *xd, int *yd)
{
    double ctm[6];

    page->getDefaultCTM(ctm, 72.0, 72.0, 0, false, true);
    *xd = (int)(ctm[0] * xu + ctm[2] * yu + ctm[4] + 0.5);
    *yd = (int)(ctm[1] * xu + ctm[3] * yu + ctm[5] + 0.5);
}

LinkDestination::LinkDestination(const LinkDestinationData &data) : d(new LinkDestinationPrivate)
{
    bool deleteDest = false;
    const LinkDest *ld = data.ld;

    if (data.namedDest && !ld && !data.externalDest) {
        deleteDest = true;
        ld = data.doc->doc->findDest(data.namedDest).release();
    }
    // in case this destination was named one, and it was not resolved
    if (data.namedDest && !ld) {
        d->name = QString::fromLatin1(data.namedDest->c_str());
    }

    if (!ld)
        return;

    if (ld->getKind() == ::destXYZ)
        d->kind = destXYZ;
    else if (ld->getKind() == ::destFit)
        d->kind = destFit;
    else if (ld->getKind() == ::destFitH)
        d->kind = destFitH;
    else if (ld->getKind() == ::destFitV)
        d->kind = destFitV;
    else if (ld->getKind() == ::destFitR)
        d->kind = destFitR;
    else if (ld->getKind() == ::destFitB)
        d->kind = destFitB;
    else if (ld->getKind() == ::destFitBH)
        d->kind = destFitBH;
    else if (ld->getKind() == ::destFitBV)
        d->kind = destFitBV;

    if (!ld->isPageRef())
        d->pageNum = ld->getPageNum();
    else {
        const Ref ref = ld->getPageRef();
        d->pageNum = data.doc->doc->findPage(ref);
    }
    double left = ld->getLeft();
    double bottom = ld->getBottom();
    double right = ld->getRight();
    double top = ld->getTop();
    d->zoom = ld->getZoom();
    d->changeLeft = ld->getChangeLeft();
    d->changeTop = ld->getChangeTop();
    d->changeZoom = ld->getChangeZoom();

    int leftAux = 0, topAux = 0, rightAux = 0, bottomAux = 0;

    if (!data.externalDest) {
        ::Page *page;
        if (d->pageNum > 0 && d->pageNum <= data.doc->doc->getNumPages() && (page = data.doc->doc->getPage(d->pageNum))) {
            cvtUserToDev(page, left, top, &leftAux, &topAux);
            cvtUserToDev(page, right, bottom, &rightAux, &bottomAux);

            d->left = leftAux / (double)page->getCropWidth();
            d->top = topAux / (double)page->getCropHeight();
            d->right = rightAux / (double)page->getCropWidth();
            d->bottom = bottomAux / (double)page->getCropHeight();
        } else
            d->pageNum = 0;
    }

    if (deleteDest)
        delete ld;
}

LinkDestination::LinkDestination(const QString &description) : d(new LinkDestinationPrivate)
{
    const QStringList tokens = description.split(';');
    if (tokens.size() >= 10) {
        d->kind = static_cast<Kind>(tokens.at(0).toInt());
        d->pageNum = tokens.at(1).toInt();
        d->left = tokens.at(2).toDouble();
        d->bottom = tokens.at(3).toDouble();
        d->right = tokens.at(4).toDouble();
        d->top = tokens.at(5).toDouble();
        d->zoom = tokens.at(6).toDouble();
        d->changeLeft = static_cast<bool>(tokens.at(7).toInt());
        d->changeTop = static_cast<bool>(tokens.at(8).toInt());
        d->changeZoom = static_cast<bool>(tokens.at(9).toInt());
    }
}

LinkDestination::LinkDestination(const LinkDestination &other) : d(other.d) { }

LinkDestination::~LinkDestination() { }

LinkDestination::Kind LinkDestination::kind() const
{
    return d->kind;
}

int LinkDestination::pageNumber() const
{
    return d->pageNum;
}

double LinkDestination::left() const
{
    return d->left;
}

double LinkDestination::bottom() const
{
    return d->bottom;
}

double LinkDestination::right() const
{
    return d->right;
}

double LinkDestination::top() const
{
    return d->top;
}

double LinkDestination::zoom() const
{
    return d->zoom;
}

bool LinkDestination::isChangeLeft() const
{
    return d->changeLeft;
}

bool LinkDestination::isChangeTop() const
{
    return d->changeTop;
}

bool LinkDestination::isChangeZoom() const
{
    return d->changeZoom;
}

QString LinkDestination::toString() const
{
    QString s = QString::number((qint8)d->kind);
    s += ";" + QString::number(d->pageNum);
    s += ";" + QString::number(d->left);
    s += ";" + QString::number(d->bottom);
    s += ";" + QString::number(d->right);
    s += ";" + QString::number(d->top);
    s += ";" + QString::number(d->zoom);
    s += ";" + QString::number((qint8)d->changeLeft);
    s += ";" + QString::number((qint8)d->changeTop);
    s += ";" + QString::number((qint8)d->changeZoom);
    return s;
}

QString LinkDestination::destinationName() const
{
    return d->name;
}

LinkDestination &LinkDestination::operator=(const LinkDestination &other)
{
    if (this == &other)
        return *this;

    d = other.d;
    return *this;
}

// Link
Link::~Link()
{
    delete d_ptr;
}

Link::Link(const QRectF &linkArea) : d_ptr(new LinkPrivate(linkArea)) { }

Link::Link(LinkPrivate &dd) : d_ptr(&dd) { }

Link::LinkType Link::linkType() const
{
    return None;
}

QRectF Link::linkArea() const
{
    Q_D(const Link);
    return d->linkArea;
}

QVector<Link *> Link::nextLinks() const
{
    return d_ptr->nextLinks;
}

// LinkGoto
LinkGoto::LinkGoto(const QRectF &linkArea, const QString &extFileName, const LinkDestination &destination) : Link(*new LinkGotoPrivate(linkArea, destination))
{
    Q_D(LinkGoto);
    d->extFileName = extFileName;
}

LinkGoto::~LinkGoto() { }

bool LinkGoto::isExternal() const
{
    Q_D(const LinkGoto);
    return !d->extFileName.isEmpty();
}

QString LinkGoto::fileName() const
{
    Q_D(const LinkGoto);
    return d->extFileName;
}

LinkDestination LinkGoto::destination() const
{
    Q_D(const LinkGoto);
    return d->destination;
}

Link::LinkType LinkGoto::linkType() const
{
    return Goto;
}

// LinkExecute
LinkExecute::LinkExecute(const QRectF &linkArea, const QString &file, const QString &params) : Link(*new LinkExecutePrivate(linkArea))
{
    Q_D(LinkExecute);
    d->fileName = file;
    d->parameters = params;
}

LinkExecute::~LinkExecute() { }

QString LinkExecute::fileName() const
{
    Q_D(const LinkExecute);
    return d->fileName;
}
QString LinkExecute::parameters() const
{
    Q_D(const LinkExecute);
    return d->parameters;
}

Link::LinkType LinkExecute::linkType() const
{
    return Execute;
}

// LinkBrowse
LinkBrowse::LinkBrowse(const QRectF &linkArea, const QString &url) : Link(*new LinkBrowsePrivate(linkArea))
{
    Q_D(LinkBrowse);
    d->url = url;
}

LinkBrowse::~LinkBrowse() { }

QString LinkBrowse::url() const
{
    Q_D(const LinkBrowse);
    return d->url;
}

Link::LinkType LinkBrowse::linkType() const
{
    return Browse;
}

// LinkAction
LinkAction::LinkAction(const QRectF &linkArea, ActionType actionType) : Link(*new LinkActionPrivate(linkArea))
{
    Q_D(LinkAction);
    d->type = actionType;
}

LinkAction::~LinkAction() { }

LinkAction::ActionType LinkAction::actionType() const
{
    Q_D(const LinkAction);
    return d->type;
}

Link::LinkType LinkAction::linkType() const
{
    return Action;
}

// LinkSound
LinkSound::LinkSound(const QRectF &linkArea, double volume, bool sync, bool repeat, bool mix, SoundObject *sound) : Link(*new LinkSoundPrivate(linkArea))
{
    Q_D(LinkSound);
    d->volume = volume;
    d->sync = sync;
    d->repeat = repeat;
    d->mix = mix;
    d->sound = sound;
}

LinkSound::~LinkSound() { }

Link::LinkType LinkSound::linkType() const
{
    return Sound;
}

double LinkSound::volume() const
{
    Q_D(const LinkSound);
    return d->volume;
}

bool LinkSound::synchronous() const
{
    Q_D(const LinkSound);
    return d->sync;
}

bool LinkSound::repeat() const
{
    Q_D(const LinkSound);
    return d->repeat;
}

bool LinkSound::mix() const
{
    Q_D(const LinkSound);
    return d->mix;
}

SoundObject *LinkSound::sound() const
{
    Q_D(const LinkSound);
    return d->sound;
}

// LinkRendition
LinkRendition::LinkRendition(const QRectF &linkArea, ::MediaRendition *rendition, int operation, const QString &script, const Ref annotationReference)
    : Link(*new LinkRenditionPrivate(linkArea, rendition, static_cast<enum ::LinkRendition::RenditionOperation>(operation), script, annotationReference))
{
}

LinkRendition::~LinkRendition() { }

Link::LinkType LinkRendition::linkType() const
{
    return Rendition;
}

MediaRendition *LinkRendition::rendition() const
{
    Q_D(const LinkRendition);
    return d->rendition;
}

LinkRendition::RenditionAction LinkRendition::action() const
{
    Q_D(const LinkRendition);
    return d->action;
}

QString LinkRendition::script() const
{
    Q_D(const LinkRendition);
    return d->script;
}

bool LinkRendition::isReferencedAnnotation(const ScreenAnnotation *annotation) const
{
    Q_D(const LinkRendition);
    if (d->annotationReference != Ref::INVALID() && d->annotationReference == annotation->d_ptr->pdfObjectReference()) {
        return true;
    }

    return false;
}

// LinkJavaScript
LinkJavaScript::LinkJavaScript(const QRectF &linkArea, const QString &js) : Link(*new LinkJavaScriptPrivate(linkArea))
{
    Q_D(LinkJavaScript);
    d->js = js;
}

LinkJavaScript::~LinkJavaScript() { }

Link::LinkType LinkJavaScript::linkType() const
{
    return JavaScript;
}

QString LinkJavaScript::script() const
{
    Q_D(const LinkJavaScript);
    return d->js;
}

// LinkMovie
LinkMovie::LinkMovie(const QRectF &linkArea, Operation operation, const QString &annotationTitle, const Ref annotationReference) : Link(*new LinkMoviePrivate(linkArea, operation, annotationTitle, annotationReference)) { }

LinkMovie::~LinkMovie() { }

Link::LinkType LinkMovie::linkType() const
{
    return Movie;
}

LinkMovie::Operation LinkMovie::operation() const
{
    Q_D(const LinkMovie);
    return d->operation;
}

bool LinkMovie::isReferencedAnnotation(const MovieAnnotation *annotation) const
{
    Q_D(const LinkMovie);
    if (d->annotationReference != Ref::INVALID() && d->annotationReference == annotation->d_ptr->pdfObjectReference()) {
        return true;
    } else if (!d->annotationTitle.isNull()) {
        return (annotation->movieTitle() == d->annotationTitle);
    }

    return false;
}

LinkOCGState::LinkOCGState(LinkOCGStatePrivate *ocgp) : Link(*ocgp) { }

LinkOCGState::~LinkOCGState() { }

Link::LinkType LinkOCGState::linkType() const
{
    return OCGState;
}

// LinkHide
LinkHide::LinkHide(LinkHidePrivate *lhidep) : Link(*lhidep) { }

LinkHide::~LinkHide() { }

Link::LinkType LinkHide::linkType() const
{
    return Hide;
}

QVector<QString> LinkHide::targets() const
{
    Q_D(const LinkHide);
    return QVector<QString>() << d->targetName;
}

bool LinkHide::isShowAction() const
{
    Q_D(const LinkHide);
    return d->isShow;
}
}
