/* poppler-private.cc: qt interface to poppler
 * Copyright (C) 2005, Net Integration Technologies, Inc.
 * Copyright (C) 2006, 2011, 2015, 2017-2020, 2024, 2025 by Albert Astals Cid <aacid@kde.org>
 * Copyright (C) 2008, 2010, 2011, 2014 by Pino Toscano <pino@kde.org>
 * Copyright (C) 2013 by Thomas Freitag <Thomas.Freitag@alfa.de>
 * Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
 * Copyright (C) 2016 Jakub Alba <jakubalba@gmail.com>
 * Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
 * Copyright (C) 2018-2020 Adam Reichold <adam.reichold@t-online.de>
 * Copyright (C) 2019, 2020, 2024 Oliver Sander <oliver.sander@tu-dresden.de>
 * Copyright (C) 2019 João Netto <joaonetto901@gmail.com>
 * Copyright (C) 2021 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>
 * Copyright (C) 2021 Mahmoud Khalil <mahmoudkhalil11@gmail.com>
 * Copyright (C) 2023 Shivodit Gill <shivodit.gill@gmail.com>
 * Copyright (C) 2024,-2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
 * Inspired on code by
 * Copyright (C) 2004 by Albert Astals Cid <tsdgeos@terra.es>
 * 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-private.h"
#include "poppler-form.h"

#include <QtCore/QByteArray>
#include <QtCore/QDebug>
#include <QtCore/QVariant>

#include <Link.h>
#include <Outline.h>
#include <PDFDocEncoding.h>
#include <UnicodeMap.h>
#include <UTF.h>
#include <goo/gmem.h>

#ifdef ANDROID
#    include <QtCore/QString>
#    include <QtCore/QDir>
#    include <QtCore/QFile>
#    include <QtCore/QFileInfo>
#    include <QtCore/QStandardPaths>
#    include <QtCore/QDirIterator>
#endif

namespace Poppler {

namespace Debug {

static void qDebugDebugFunction(const QString &message, const QVariant & /*closure*/)
{
    qDebug() << message;
}

PopplerDebugFunc debugFunction = qDebugDebugFunction;
QVariant debugClosure;

}

void setDebugErrorFunction(PopplerDebugFunc function, const QVariant &closure)
{
    Debug::debugFunction = function ? function : Debug::qDebugDebugFunction;
    Debug::debugClosure = closure;
}

void qt5ErrorFunction(ErrorCategory /*category*/, Goffset pos, const char *msg)
{
    QString emsg;

    if (pos >= 0) {
        emsg = QStringLiteral("Error (%1): ").arg(pos);
    } else {
        emsg = QStringLiteral("Error: ");
    }
    emsg += QString::fromLatin1(msg);
    (*Debug::debugFunction)(emsg, Debug::debugClosure);
}

QString unicodeToQString(const Unicode *u, int len)
{
    const UnicodeMap *utf8Map = globalParams->getUtf8Map();

    // ignore the last characters if they are 0x0
    while ((len > 0) && (u[len - 1] == 0)) {
        --len;
    }

    GooString convertedStr;
    for (int i = 0; i < len; ++i) {
        char buf[8];
        const int n = utf8Map->mapUnicode(u[i], buf, sizeof(buf));
        convertedStr.append(buf, n);
    }

    return QString::fromUtf8(convertedStr.c_str(), convertedStr.size());
}

QString unicodeToQString(const std::vector<Unicode> &u)
{
    return unicodeToQString(u.data(), u.size());
}

QString UnicodeParsedString(const GooString *s1)
{
    return s1 ? UnicodeParsedString(s1->toStr()) : QString();
}

QString UnicodeParsedString(const std::string &s1)
{
    if (s1.empty()) {
        return QString();
    }

    if (hasUnicodeByteOrderMark(s1) || hasUnicodeByteOrderMarkLE(s1)) {
        return QString::fromUtf16(reinterpret_cast<const ushort *>(s1.c_str()), s1.size() / 2);
    }
    std::string cString = pdfDocEncodingToUTF16(s1);
    auto result = QString::fromUtf16(reinterpret_cast<const ushort *>(cString.c_str()), cString.size() / 2);
    return result;
}

std::unique_ptr<GooString> QStringToUnicodeGooString(const QString &s)
{
    if (s.isEmpty()) {
        return std::make_unique<GooString>();
    }
    int len = s.length() * 2 + 2;
    std::string string;
    string.reserve(len);
    string.push_back(static_cast<char>(0xfe));
    string.push_back(static_cast<char>(0xff));
    for (auto element : s) {
        string.push_back(element.row());
        string.push_back(element.cell());
    }
    return std::make_unique<GooString>(std::move(string));
}

std::unique_ptr<GooString> QStringToGooString(const QString &s)
{
    int len = s.length();
    char *cstring = static_cast<char *>(gmallocn(s.length(), sizeof(char)));
    for (int i = 0; i < len; ++i) {
        cstring[i] = s.at(i).unicode();
    }
    std::unique_ptr<GooString> ret = std::make_unique<GooString>(cstring, len);
    gfree(cstring);
    return ret;
}

std::unique_ptr<GooString> QDateTimeToUnicodeGooString(const QDateTime &dt)
{
    if (!dt.isValid()) {
        return nullptr;
    }

    return QStringToUnicodeGooString(dt.toUTC().toString(QStringLiteral("yyyyMMddhhmmss+00'00'")));
}

Annot::AdditionalActionsType toPopplerAdditionalActionType(Annotation::AdditionalActionType type)
{
    switch (type) {
    case Annotation::CursorEnteringAction:
        return Annot::actionCursorEntering;
    case Annotation::CursorLeavingAction:
        return Annot::actionCursorLeaving;
    case Annotation::MousePressedAction:
        return Annot::actionMousePressed;
    case Annotation::MouseReleasedAction:
        return Annot::actionMouseReleased;
    case Annotation::FocusInAction:
        return Annot::actionFocusIn;
    case Annotation::FocusOutAction:
        return Annot::actionFocusOut;
    case Annotation::PageOpeningAction:
        return Annot::actionPageOpening;
    case Annotation::PageClosingAction:
        return Annot::actionPageClosing;
    case Annotation::PageVisibleAction:
        return Annot::actionPageVisible;
    case Annotation::PageInvisibleAction:
        return Annot::actionPageInvisible;
    }

    return Annot::actionCursorEntering;
}

static void linkActionToTocItem(const ::LinkAction *a, DocumentData *doc, QDomElement *e)
{
    if (!a || !e) {
        return;
    }

    switch (a->getKind()) {
    case actionGoTo: {
        // page number is contained/referenced in a LinkGoTo
        const auto *g = static_cast<const LinkGoTo *>(a);
        const LinkDest *destination = g->getDest();
        if (!destination && g->getNamedDest()) {
            // no 'destination' but an internal 'named reference'. we could
            // get the destination for the page now, but it's VERY time consuming,
            // so better storing the reference and provide the viewport on demand
            const GooString *s = g->getNamedDest();
            auto *charArray = new QChar[s->size()];
            for (size_t i = 0; i < s->size(); ++i) {
                charArray[i] = QChar::fromLatin1(s->c_str()[i]);
            }
            QString aux(charArray, s->size());
            e->setAttribute(QStringLiteral("DestinationName"), aux);
            delete[] charArray;
        } else if (destination && destination->isOk()) {
            LinkDestinationData ldd(destination, nullptr, doc, false);
            e->setAttribute(QStringLiteral("Destination"), LinkDestination(ldd).toString());
        }
        break;
    }
    case actionGoToR: {
        // page number is contained/referenced in a LinkGoToR
        const auto *g = static_cast<const LinkGoToR *>(a);
        const LinkDest *destination = g->getDest();
        if (!destination && g->getNamedDest()) {
            // no 'destination' but an internal 'named reference'. we could
            // get the destination for the page now, but it's VERY time consuming,
            // so better storing the reference and provide the viewport on demand
            const GooString *s = g->getNamedDest();
            auto *charArray = new QChar[s->size()];
            for (size_t i = 0; i < s->size(); ++i) {
                charArray[i] = QChar::fromLatin1(s->c_str()[i]);
            }
            QString aux(charArray, s->size());
            e->setAttribute(QStringLiteral("DestinationName"), aux);
            delete[] charArray;
        } else if (destination && destination->isOk()) {
            LinkDestinationData ldd(destination, nullptr, doc, g->getFileName() != nullptr);
            e->setAttribute(QStringLiteral("Destination"), LinkDestination(ldd).toString());
        }
        e->setAttribute(QStringLiteral("ExternalFileName"), QString::fromStdString(g->getFileName()->toStr()));
        break;
    }
    case actionURI: {
        const auto *u = static_cast<const LinkURI *>(a);
        e->setAttribute(QStringLiteral("DestinationURI"), QString::fromStdString(u->getURI()));
    }
    default:;
    }
}

DocumentData::~DocumentData()
{
    qDeleteAll(m_embeddedFiles);
    delete m_optContentModel.data();
}

void DocumentData::init()
{
    m_backend = Document::SplashBackend;
    paperColor = Qt::white;
    m_hints = 0;
    m_optContentModel = nullptr;
    xrefReconstructed = false;
    xrefReconstructedCallback = {};

#ifdef ANDROID
    // Copy fonts from android apk to the app's storage dir, and
    // set the font directory path
    QString assetsFontDir = QStringLiteral("assets:/share/fonts");
    QString fontsdir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/fonts");
    QDir fontPath = QDir(fontsdir);

    if (fontPath.mkpath(fontPath.absolutePath())) {
        GlobalParams::setFontDir(fontPath.absolutePath().toStdString());
        QDirIterator iterator(assetsFontDir, QDir::NoFilter, QDirIterator::Subdirectories);

        while (iterator.hasNext()) {
            iterator.next();
            QFileInfo fontFileInfo = iterator.fileInfo();
            QString fontFilePath = assetsFontDir + QStringLiteral("/") + fontFileInfo.fileName();
            QString destPath = fontPath.absolutePath() + QStringLiteral("/") + fontFileInfo.fileName();
            QFile::copy(fontFilePath, destPath);
        }
    } else {
        GlobalParams::setFontDir("");
    }
#endif
}

void DocumentData::addTocChildren(QDomDocument *docSyn, QDomNode *parent, const std::vector<::OutlineItem *> *items)
{
    for (::OutlineItem *outlineItem : *items) {
        // iterate over every object in 'items'

        // 1. create element using outlineItem's title as tagName
        QString name = unicodeToQString(outlineItem->getTitle());

        QDomElement item = docSyn->createElement(name);
        parent->appendChild(item);

        // 2. find the page the link refers to
        const ::LinkAction *a = outlineItem->getAction();
        linkActionToTocItem(a, this, &item);

        item.setAttribute(QStringLiteral("Open"), QVariant(outlineItem->isOpen()).toString());

        // 3. recursively descend over children
        outlineItem->open();
        const std::vector<::OutlineItem *> *children = outlineItem->getKids();
        if (children) {
            addTocChildren(docSyn, &item, children);
        }
    }
}

void DocumentData::notifyXRefReconstructed()
{
    if (!xrefReconstructed) {
        xrefReconstructed = true;
    }

    if (xrefReconstructedCallback) {
        xrefReconstructedCallback();
    }
}

FormWidget *FormFieldData::getFormWidget(const FormField *f)
{
    return f->m_formData->fm;
}

FormFieldIconData *FormFieldIconData::getData(const FormFieldIcon &f)
{
    return f.d_ptr;
}
}
