/***************************************************************************
                              qgsabstractgeopdfexporter.h
                             --------------------------
    begin                : August 2019
    copyright            : (C) 2019 by Nyall Dawson
    email                : nyall dot dawson at gmail dot com
 ***************************************************************************/
/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
#ifndef QGSABSTRACTGEOPDFEXPORTER_H
#define QGSABSTRACTGEOPDFEXPORTER_H

#include "qgis_core.h"
#include "qgsabstractmetadatabase.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsfeature.h"
#include "qgspolygon.h"

#include <QDateTime>
#include <QList>
#include <QMutex>
#include <QPainter>
#include <QTemporaryDir>

#define SIP_NO_FILE

class QgsGeospatialPdfRenderedFeatureHandler;

/**
 * \class QgsAbstractGeospatialPdfExporter
 * \ingroup core
 *
 * \brief Abstract base class for Geospatial PDF exporters.
 *
 * The base class handles generic Geospatial PDF export setup, cleanup and processing steps.
 *
 * This class is a low level implementation detail only. Generally, you should use the high level interface exposed by
 * classes like QgsLayoutExporter instead.
 *
 * \warning QgsAbstractGeospatialPdfExporter is designed to be a short lived object. It should be created for a
 * single export operation only, and then immediately destroyed. Failure to correctly
 * destroy the object after exporting a PDF will leave the application in an inconsistent, unstable state.
 *
 * \note Not available in Python bindings
 *
 * \since QGIS 3.10
 */
class CORE_EXPORT QgsAbstractGeospatialPdfExporter
{
  public:
    /**
     * Returns TRUE if the current QGIS build is capable of Geospatial PDF support.
     *
     * If FALSE is returned, a user-friendly explanation why can be retrieved via
     * geospatialPDFAvailabilityExplanation().
     */
    static bool geospatialPDFCreationAvailable();

    /**
     * Returns a user-friendly, translated string explaining why Geospatial PDF export
     * support is not available on the current QGIS build (or an empty string if
     * Geospatial PDF support IS available).
     * \see geospatialPDFCreationAvailable()
     */
    static QString geospatialPDFAvailabilityExplanation();

    QgsAbstractGeospatialPdfExporter() = default;

    virtual ~QgsAbstractGeospatialPdfExporter() = default;

    /**
     * Contains information about a feature rendered inside the PDF.
     */
    struct RenderedFeature
    {
        RenderedFeature() = default;

        /**
       * Constructor for RenderedFeature.
       */
        RenderedFeature( const QgsFeature &feature, const QgsGeometry &renderedBounds )
          : feature( feature )
          , renderedBounds( renderedBounds )
        {}

        /**
       * Rendered feature.
       */
        QgsFeature feature;

        /**
       * Bounds, in PDF units, of rendered feature. (Multi)LineString or Polygon types only.
       */
        QgsGeometry renderedBounds;
    };

    /**
     * \brief Contains details of a particular input component to be used during PDF composition.
     * \ingroup core
     * \since QGIS 3.10
     */
    struct CORE_EXPORT ComponentLayerDetail
    {
        //! User-friendly name for the generated PDF layer
        QString name;

        //! Associated map layer ID, or an empty string if this component layer is not associated with a map layer
        QString mapLayerId;

        //! Optional group name, for arranging layers in top-level groups
        QString group;

        //! File path to the (already created) PDF to use as the source for this component layer
        QString sourcePdfPath;

        //! Component composition mode
        QPainter::CompositionMode compositionMode = QPainter::CompositionMode_SourceOver;

        //! Component opacity
        double opacity = 1.0;
    };

    /**
     * \brief Contains details of a control point used during georeferencing Geospatial PDF outputs.
     * \ingroup core
     * \since QGIS 3.10
     */
    struct ControlPoint
    {
        /**
       * Constructor for ControlPoint, at the specified \a pagePoint (in millimeters)
       * and \a geoPoint (in CRS units).
       */
        ControlPoint( const QgsPointXY &pagePoint, const QgsPointXY &geoPoint )
          : pagePoint( pagePoint )
          , geoPoint( geoPoint )
        {}

        //! Coordinate on the page of the control point, in millimeters
        QgsPointXY pagePoint;

        //! Georeferenced coordinate of the control point, in CRS units
        QgsPointXY geoPoint;
    };

    struct GeoReferencedSection
    {
        /**
       * Bounds of the georeferenced section on the page, in millimeters.
       *
       * \note if pageBoundsPolygon is specified then this setting is ignored.
       */
        QgsRectangle pageBoundsMm;

        /**
       * Bounds of the georeferenced section on the page, in millimeters, as a free-form polygon.
       *
       * If specified, this will be used instead of pageBoundsMm.
       */
        QgsPolygon pageBoundsPolygon;

        //! Coordinate reference system for georeferenced section
        QgsCoordinateReferenceSystem crs;

        //! List of control points corresponding to this georeferenced section
        QList< QgsAbstractGeospatialPdfExporter::ControlPoint > controlPoints;
    };

    /**
     * Called multiple times during the rendering operation, whenever a \a feature associated with the specified
     * \a layerId is rendered.
     *
     * The optional \a group argument can be used to differentiate features from the same layer exported
     * multiple times as part of different layer groups.
     */
    void pushRenderedFeature( const QString &layerId, const QgsAbstractGeospatialPdfExporter::RenderedFeature &feature, const QString &group = QString() );

    struct ExportDetails
    {
        //! Page size, in millimeters
        QSizeF pageSizeMm;

        //! Output DPI
        double dpi = 300;

        //! List of georeferenced sections
        QList< QgsAbstractGeospatialPdfExporter::GeoReferencedSection > georeferencedSections;

        //! Metadata author tag
        QString author;

        //! Metadata producer tag
        QString producer;

        //! Metadata creator tag
        QString creator;

        //! Metadata creation datetime
        QDateTime creationDateTime;

        //! Metadata subject tag
        QString subject;

        //! Metadata title tag
        QString title;

        //! Metadata keyword map
        QgsAbstractMetadataBase::KeywordMap keywords;

        /**
       * TRUE if ISO32000 extension format georeferencing should be used.
       *
       * This is a recommended setting which results in Geospatial PDF files
       * that use PDF 2.0 Geospatial Features (ISO 32000-2:2017).
       * Compatible with the built-in Acrobat geospatial tools
       */
        bool useIso32000ExtensionFormatGeoreferencing = true;

        /**
       * TRUE if OGC "best practice" format georeferencing should be used.
       *
       * \warning This results in Geospatial PDF files compatible with a unnamed suite of tools starting with Terra and ending with Go, but
       * can break compatibility with the built-in Acrobat geospatial tools (yes, Geospatial PDF
       * format is a mess!).
       *
       * \deprecated QGIS 3.42. This parameter has no longer any effect. Only ISO 32000 georeferencing is handled.
      */
        bool useOgcBestPracticeFormatGeoreferencing = false;

        /**
       * TRUE if feature vector information (such as attributes) should be exported.
       */
        bool includeFeatures = true;

        /**
       * Optional map of map layer ID to custom logical layer tree group in created PDF file.
       *
       * E.g. if the map contains "layer1": "Environment", "layer2": "Environment", "layer3": "Transport"
       * then the created PDF file will have entries in its layer tree for "Environment" and "Transport",
       * and toggling "Environment" will toggle BOTH layer1 and layer2.
       *
       * Layers which are not included in this group will always have their own individual layer tree entry
       * created for them automatically.
       *
       * \see layerTreeGroupOrder
       */
        QMap< QString, QString > customLayerTreeGroups;

        /**
       * Optional map of map layer ID to custom layer tree name to show in the created PDF file.
       *
       * \since QGIS 3.14
       */
        QMap< QString, QString > layerIdToPdfLayerTreeNameMap;

        /**
       * Optional map of map layer ID to initial visibility state. If a layer ID is not present in this,
       * it will default to being initially visible when opening the PDF.
       *
       * \since QGIS 3.14
       */
        QMap< QString, bool > initialLayerVisibility;

        /**
       * Optional list of layer IDs, in the order desired to appear in the generated Geospatial PDF file.
       *
       * Layers appearing earlier in the list will show earlier in the Geospatial PDF layer tree list.
       *
       * \see layerTreeGroupOrder
       *
       * \since QGIS 3.14
       */
        QStringList layerOrder;

        /**
       * Specifies the ordering of layer tree groups in the generated Geospatial PDF file.
       *
       * Groups appearing earlier in the list will show earlier in the Geospatial PDF layer tree list.
       *
       * \see layerOrder
       * \see customLayerTreeGroups
       *
       * \since QGIS 3.38
       */
        QStringList layerTreeGroupOrder;

        /**
       * Contains a list of group names which should be considered as mutually exclusive.
       *
       * \since QGIS 3.40
       */
        QSet< QString > mutuallyExclusiveGroups;
    };

    /**
     * To be called after the rendering operation is complete.
     *
     * Will export the list of PDF layer \a components to a new PDF file at \a destinationFile. The \a components
     * argument gives a list of additional layers to include in the generated PDF file. These must have already
     * been created, e.g. as a result of rendering layers to separate PDF source files.
     *
     * Any features previously collected by calls to pushRenderedFeature() will be included automatically in the Geospatial PDF
     * export.
     *
     * Returns TRUE if the operation was successful, or FALSE if an error occurred. If an error occurred, it
     * can be retrieved by calling errorMessage().
     */
    bool finalize( const QList< QgsAbstractGeospatialPdfExporter::ComponentLayerDetail > &components, const QString &destinationFile, const ExportDetails &details );

    /**
     * Returns the last error message encountered during the export.
     */
    QString errorMessage() const { return mErrorMessage; }

    /**
     * Returns a file path to use for temporary files required for Geospatial PDF creation.
     */
    QString generateTemporaryFilepath( const QString &filename ) const;

    /**
     * Returns TRUE if the specified composition \a mode is supported for layers
     * during Geospatial PDF exports.
     *
     * \since QGIS 3.14
     */
    static bool compositionModeSupported( QPainter::CompositionMode mode );

  protected:
    /**
     * Contains information relating to a single PDF layer in the Geospatial PDF export.
     */
    struct VectorComponentDetail
    {
        //! User-friendly name for the generated PDF layer
        QString name;

        //! Associated map layer ID
        QString mapLayerId;

        //! Optional layer group name
        QString group;

        //! Field name for display
        QString displayAttribute;

        //! File path to the (already created) vector dataset to use as the source for this component layer
        QString sourceVectorPath;

        //! Layer name in vector dataset to use as the source
        QString sourceVectorLayer;
    };

  private:
    QMutex mMutex;
    QMap< QString, QMap< QString, QgsFeatureList > > mCollatedFeatures;

    /**
     * Returns the PDF output component details for the layer with given \a layerId.
     */
    virtual VectorComponentDetail componentDetailForLayerId( const QString &layerId ) = 0;

    QList< VectorComponentDetail > mVectorComponents;

    QString mErrorMessage;
    QTemporaryDir mTemporaryDir;


    bool saveTemporaryLayers();

    QString createCompositionXml( const QList< QgsAbstractGeospatialPdfExporter::ComponentLayerDetail > &components, const ExportDetails &details );

    /**
     * Returns the GDAL string representation of the specified QPainter composition \a mode.
     */
    static QString compositionModeToString( QPainter::CompositionMode mode );

    friend class TestQgsLayoutGeospatialPdfExport;
    friend class TestQgsGeospatialPdfExport;
};

#endif //QGSABSTRACTGEOPDFEXPORTER_H
