/*
    SPDX-FileCopyrightText: 2004-2005 Enrico Ros <eros.kde@email.it>
    SPDX-FileCopyrightText: 2004-2007 Albert Astals Cid <aacid@kde.org>

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

#ifndef _OKULAR_DOCUMENT_P_H_
#define _OKULAR_DOCUMENT_P_H_

#include "document.h"
#include "script/event_p.h"

#include "synctex/synctex_parser.h"
#include <memory>

// qt/kde/system includes
#include <KConfigDialog>
#include <KPluginMetaData>
#include <QHash>
#include <QMap>
#include <QMutex>
#include <QPointer>
#include <QUrl>

// local includes
#include "fontinfo.h"
#include "generator.h"

class QUndoStack;
class QEventLoop;
class QFile;
class QTimer;
class QTemporaryFile;
class KPluginMetaData;

struct AllocatedPixmap;
struct ArchiveData;
struct RunningSearch;

namespace Okular
{
class ScriptAction;
class ConfigInterface;
class PageController;
class SaveInterface;
class Scripter;
class View;
}

struct GeneratorInfo {
    explicit GeneratorInfo(Okular::Generator *g, const KPluginMetaData &data)
        : generator(g)
        , metadata(data)
        , config(nullptr)
        , save(nullptr)
        , configChecked(false)
        , saveChecked(false)
    {
    }

    Okular::Generator *generator;
    KPluginMetaData metadata;
    Okular::ConfigInterface *config;
    Okular::SaveInterface *save;
    bool configChecked : 1;
    bool saveChecked : 1;
};

namespace Okular
{
class OKULARCORE_EXPORT BackendConfigDialog : public KConfigDialog
{
    Q_OBJECT
public:
    BackendConfigDialog(QWidget *parent, const QString &name, KCoreConfigSkeleton *config)
        : KConfigDialog(parent, name, config)
    {
    }

    KPageWidget *thePageWidget()
    {
        return pageWidget();
    }
};

class FontExtractionThread;

struct DoContinueDirectionMatchSearchStruct {
    QSet<int> *pagesToNotify;
    RegularAreaRect *match;
    int currentPage;
    int searchID;
};

enum LoadDocumentInfoFlag {
    LoadNone = 0,
    LoadPageInfo = 1,    // Load annotations and forms
    LoadGeneralInfo = 2, // History, rotation, ...
    LoadAllInfo = 0xff
};
Q_DECLARE_FLAGS(LoadDocumentInfoFlags, LoadDocumentInfoFlag)

class DocumentPrivate
{
public:
    explicit DocumentPrivate(Document *parent)
        : m_parent(parent)
        , m_searchCancelled(false)
        , m_tempFile(nullptr)
        , m_docSize(-1)
        , m_allocatedPixmapsTotalMemory(0)
        , m_maxAllocatedTextPages(0)
        , m_warnedOutOfMemory(false)
        , m_rotation(Rotation0)
        , m_exportCached(false)
        , m_bookmarkManager(nullptr)
        , m_memCheckTimer(nullptr)
        , m_saveBookmarksTimer(nullptr)
        , m_generator(nullptr)
        , m_walletGenerator(nullptr)
        , m_generatorsLoaded(false)
        , m_pageController(nullptr)
        , m_closingLoop(nullptr)
        , m_scripter(nullptr)
        , m_archiveData(nullptr)
        , m_fontsCached(false)
        , m_annotationEditingEnabled(true)
        , m_annotationBeingModified(false)
        , m_undoStack(nullptr)
        , m_docdataMigrationNeeded(false)
        , m_synctex_scanner(nullptr)
    {
        calculateMaxTextPages();
    }

    // private methods
    bool updateMetadataXmlNameAndDocSize();
    QString pagesSizeString() const;
    QString namePaperSize(double inchesWidth, double inchesHeight) const;
    QString localizedSize(const QSizeF size) const;
    qulonglong calculateMemoryToFree();
    void cleanupPixmapMemory();
    void cleanupPixmapMemory(qulonglong memoryToFree);
    AllocatedPixmap *searchLowestPriorityPixmap(bool unloadableOnly = false, bool thenRemoveIt = false, DocumentObserver *observer = nullptr /* any */);
    void calculateMaxTextPages();
    qulonglong getTotalMemory();
    qulonglong getFreeMemory(qulonglong *freeSwap = nullptr);
    bool loadDocumentInfo(LoadDocumentInfoFlags loadWhat);
    bool loadDocumentInfo(QFile &infoFile, LoadDocumentInfoFlags loadWhat);
    void loadViewsInfo(View *view, const QDomElement &e);

    /**
     * @since 24.12
     */
    void setDefaultViewMode(View *view, Generator::PageLayout defaultViewMode);

    void saveViewsInfo(View *view, QDomElement &e) const;
    QUrl giveAbsoluteUrl(const QString &fileName) const;
    bool openRelativeFile(const QString &fileName);
    Generator *loadGeneratorLibrary(const KPluginMetaData &service);
    void loadAllGeneratorLibraries();
    void loadServiceList(const QVector<KPluginMetaData> &offers);
    void unloadGenerator(const GeneratorInfo &info);
    void cacheExportFormats();
    void setRotationInternal(int r, bool notify);
    ConfigInterface *generatorConfig(GeneratorInfo &info);
    SaveInterface *generatorSave(GeneratorInfo &info);
    Document::OpenResult openDocumentInternal(const KPluginMetaData &offer, bool isstdin, const QString &docFile, const QByteArray &filedata, const QString &password);
    static ArchiveData *unpackDocumentArchive(const QString &archivePath);
    bool savePageDocumentInfo(QTemporaryFile *infoFile, int what) const;
    DocumentViewport nextDocumentViewport() const;
    void notifyAnnotationChanges(int page);
    void notifyFormChanges(int page);
    bool canAddAnnotationsNatively() const;
    bool canModifyExternalAnnotations() const;
    bool canRemoveExternalAnnotations() const;
    OKULARCORE_EXPORT static QString docDataFileName(const QUrl &url, qint64 document_size);
    bool cancelRenderingBecauseOf(PixmapRequest *executingRequest, PixmapRequest *newRequest);

    // Methods that implement functionality needed by undo commands
    void performAddPageAnnotation(int page, Annotation *annotation);
    void performRemovePageAnnotation(int page, Annotation *annotation);
    void performModifyPageAnnotation(int page, Annotation *annotation, bool appearanceChanged);
    void performSetAnnotationContents(const QString &newContents, Annotation *annot, int pageNumber);

    void recalculateForms();

    // private slots
    void saveDocumentInfo() const;
    void slotTimedMemoryCheck();
    void sendGeneratorPixmapRequest();
    void rotationFinished(int page, Okular::Page *okularPage);
    void slotFontReadingProgress(int page);
    void fontReadingGotFont(const Okular::FontInfo &font);
    void slotGeneratorConfigChanged();
    void refreshPixmaps(int);
    void _o_configChanged();
    void doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct);
    void doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID);
    void doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList &words);

    void doProcessSearchMatch(RegularAreaRect *match, RunningSearch *search, QSet<int> *pagesToNotify, int currentPage, int searchID, bool moveViewport, const QColor &color);

    /**
     * Executes a JavaScript script from the setInterval function.
     *
     * @since 1.9
     */
    void executeScript(const QString &function);

    // generators stuff
    /**
     * This method is used by the generators to signal the finish of
     * the pixmap generation @p request.
     */
    void requestDone(PixmapRequest *request);
    void textGenerationDone(Page *page);
    /**
     * Sets the bounding box of the given @p page (in terms of upright orientation, i.e., Rotation0).
     */
    void setPageBoundingBox(int page, const NormalizedRect &boundingBox);

    /**
     * Request a particular metadata of the Document itself (ie, not something
     * depending on the document type/backend).
     */
    QVariant documentMetaData(const Generator::DocumentMetaDataKey key, const QVariant &option) const;

    /**
     * Return whether the normalized rectangle @p rectOfInterest on page number @p rectPage
     * is fully visible.
     */
    bool isNormalizedRectangleFullyVisible(const Okular::NormalizedRect &rectOfInterest, int rectPage);

    // For sync files
    void loadSyncFile(const QString &filePath);

    void clearAndWaitForRequests();

    OKULARCORE_EXPORT static QString evaluateKeystrokeEventChange(const QString &oldVal, const QString &newVal, int selStart, int selEnd);

    /*
     * Executes a ScriptAction with the event passed as parameter.
     */
    void executeScriptEvent(const std::shared_ptr<Event> &event, const Okular::ScriptAction *linkscript);

    /*
     * Find the corresponding page number for the form field passed as parameter.
     */
    int findFieldPageNumber(Okular::FormField *field);

    // member variables
    Document *m_parent;
    QPointer<QWidget> m_widget;

    // find descriptors, mapped by ID (we handle multiple searches)
    QMap<int, RunningSearch *> m_searches;
    bool m_searchCancelled;

    // needed because for remote documents docFileName is a local file and
    // we want the remote url when the document refers to relativeNames
    QUrl m_url;

    // cached stuff
    QString m_docFileName;
    QString m_xmlFileName;
    QTemporaryFile *m_tempFile;
    qint64 m_docSize;

    // viewport stuff
    std::list<DocumentViewport> m_viewportHistory;
    std::list<DocumentViewport>::iterator m_viewportIterator;
    DocumentViewport m_nextDocumentViewport; // see Link::Goto for an explanation
    QString m_nextDocumentDestination;

    // observers / requests / allocator stuff
    QSet<DocumentObserver *> m_observers;
    std::list<PixmapRequest *> m_pixmapRequestsStack;
    std::list<PixmapRequest *> m_executingPixmapRequests;
    QMutex m_pixmapRequestsMutex;
    std::list<AllocatedPixmap *> m_allocatedPixmaps;
    qulonglong m_allocatedPixmapsTotalMemory;
    QList<int> m_allocatedTextPagesFifo;
    int m_maxAllocatedTextPages;
    bool m_warnedOutOfMemory;

    // the rotation applied to the document
    Rotation m_rotation;

    // the current size of the pages (if available), and the cache of the
    // available page sizes
    PageSize m_pageSize;
    PageSize::List m_pageSizes;

    // cache of the export formats
    bool m_exportCached;
    ExportFormat::List m_exportFormats;
    ExportFormat m_exportToText;

    // our bookmark manager
    BookmarkManager *m_bookmarkManager;

    // timers (memory checking / info saver)
    QTimer *m_memCheckTimer;
    QTimer *m_saveBookmarksTimer;

    QHash<QString, GeneratorInfo> m_loadedGenerators;
    Generator *m_generator;
    QString m_generatorName;
    Generator *m_walletGenerator;
    bool m_generatorsLoaded;
    QVector<Page *> m_pagesVector;
    QVector<VisiblePageRect *> m_pageRects;

    // cache of the mimetype we support
    QStringList m_supportedMimeTypes;

    PageController *m_pageController;
    QEventLoop *m_closingLoop;

    Scripter *m_scripter;

    ArchiveData *m_archiveData;
    QString m_archivedFileName;

    QPointer<FontExtractionThread> m_fontThread;
    bool m_fontsCached;
    QSet<DocumentInfo::Key> m_documentInfoAskedKeys;
    DocumentInfo m_documentInfo;
    FontInfo::List m_fontsCache;

    QSet<View *> m_views;

    bool m_annotationEditingEnabled;
    bool m_annotationBeingModified; // is an annotation currently being moved or resized?

    QUndoStack *m_undoStack;
    QDomNode m_prevPropsOfAnnotBeingModified;

    // Since 0.21, we no longer support saving annotations and form data in
    // the docdata/ directory and we ask the user to migrate them to an
    // external file as soon as possible, otherwise the document will be
    // shown in read-only mode. This flag is set if the docdata/ XML file
    // for the current document contains any annotation or form.
    bool m_docdataMigrationNeeded;

    synctex_scanner_p m_synctex_scanner;

    QString m_openError;

    // generator selection
    static QVector<KPluginMetaData> availableGenerators();
    static QVector<KPluginMetaData> configurableGenerators();
    static KPluginMetaData generatorForMimeType(const QMimeType &type, QWidget *widget, const QVector<KPluginMetaData> &triedOffers = QVector<KPluginMetaData>());

    // overrides the editor command (for example with a command from the command line)
    QString editorCommandOverride;
};

class DocumentInfoPrivate
{
public:
    QMap<QString, QString> values; // key -> value
    QMap<QString, QString> titles; // key -> title For the custom keys
};

}

#endif

/* kate: replace-tabs on; indent-width 4; */
