/***************************************************************************
                              qgswmsserver.h
                              -------------------
  begin                : May 14, 2006
  copyright            : (C) 2006 by Marco Hugentobler
  email                : marco dot hugentobler at karto dot baug dot ethz dot ch
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 QGSWMSSERVER_H
#define QGSWMSSERVER_H

#include "qgsowsserver.h"
#include "qgswmsconfigparser.h"
#include <QDomDocument>
#include <QMap>
#include <QPair>
#include <QString>
#include <map>

class QgsCapabilitiesCache;
class QgsCoordinateReferenceSystem;
class QgsComposerLayerItem;
class QgsComposerLegendItem;
class QgsComposition;
class QgsConfigParser;
class QgsFeature;
class QgsFeatureRendererV2;
class QgsMapLayer;
class QgsMapRenderer;
class QgsPoint;
class QgsRasterLayer;
class QgsRasterRenderer;
class QgsRectangle;
class QgsRenderContext;
class QgsVectorLayer;
class QgsSymbol;
class QgsSymbolV2;
class QgsAccessControl;

class QColor;
class QFile;
class QFont;
class QImage;
class QPaintDevice;
class QPainter;
class QStandardItem;

/** This class handles all the wms server requests. The parameters and values have to be passed in the form of
a map<QString, QString>. This map is usually generated by a subclass of QgsWMSRequestHandler, which makes QgsWMSServer
independent from any server side technology*/

class QgsWMSServer: public QgsOWSServer
{
  public:
    /** Constructor. Does _NOT_ take ownership of
        QgsConfigParser, QgsCapabilitiesCache and QgsMapRenderer*/
    QgsWMSServer(
      const QString& configFilePath
      , QMap<QString, QString> &parameters
      , QgsWMSConfigParser* cp
      , QgsRequestHandler* rh
      , QgsMapRenderer* renderer
      , QgsCapabilitiesCache* capCache
#ifdef HAVE_SERVER_PYTHON_PLUGINS
      , const QgsAccessControl* accessControl
#endif
    );
    ~QgsWMSServer();

    void executeRequest() override;

    /** Returns an XML file with the capabilities description (as described in the WMS specs)
        @param version WMS version (1.1.1 or 1.3.0)
        @param fullProjectInformation If true: add extended project information (does not validate against WMS schema)*/
    QDomDocument getCapabilities( QString version = "1.3.0", bool fullProjectInformation = false );

    QDomDocument getContext();
    /** Returns the map legend as an image (or a null pointer in case of error). The caller takes ownership
    of the image object*/
    QImage* getLegendGraphics();

    typedef QSet<QgsSymbolV2*> SymbolV2Set;
    typedef QMap<QgsVectorLayer*, SymbolV2Set> HitTest;

    /** Returns the map as an image (or a null pointer in case of error). The caller takes ownership
    of the image object). If an instance to existing hit test structure is passed, instead of rendering
    it will fill the structure with symbols that would be used for rendering */
    QImage* getMap( HitTest* hitTest = nullptr );
    /** GetMap request with vector format output. This output is usually symbolized (difference to WFS GetFeature)*/
    void getMapAsDxf();
    /** Returns an SLD file with the style of the requested layer. Exception is raised in case of troubles :-)*/
    QDomDocument getStyle();
    /** Returns an SLD file with the styles of the requested layers. Exception is raised in case of troubles :-)*/
    QDomDocument getStyles();
    /** Returns a describeLayer file with the onlineResource of the requested layers. Exception is raised in case of troubles :-)*/
    QDomDocument describeLayer();

    /** Returns printed page as binary
      @param formatString out: format of the print output (e.g. pdf, svg, png, ...)
      @return printed page as binary or 0 in case of error*/
    QByteArray* getPrint( const QString& formatString );

    /** Creates an xml document that describes the result of the getFeatureInfo request.
       @return 0 in case of success*/
    int getFeatureInfo( QDomDocument& result, const QString& version = "1.3.0" );

    /** Sets configuration parser for administration settings. Does not take ownership*/
    void setAdminConfigParser( QgsWMSConfigParser* parser ) { mConfigParser = parser; }

    /** Returns the schemaExtension for WMS 1.3.0 capabilities*/
    QDomDocument getSchemaExtension();

  private:
    /** Don't use the default constructor*/
    QgsWMSServer();

    /** Initializes WMS layers and configures mMapRendering.
      @param layersList out: list with WMS layer names
      @param stylesList out: list with WMS style names
      @param layerIdList out: list with QGIS layer ids
      @return image configured together with mMapRenderer (or 0 in case of error). The calling function takes ownership of the image*/
    QImage* initializeRendering( QStringList& layersList, QStringList& stylesList, QStringList& layerIdList );

    /** Creates a QImage from the HEIGHT and WIDTH parameters
     @param width image width (or -1 if width should be taken from WIDTH wms parameter)
     @param height image height (or -1 if height should be taken from HEIGHT wms parameter)
     @param useBbox flag to indicate if the BBOX has to be used to adapt aspect ratio
     @return 0 in case of error*/
    QImage* createImage( int width = -1, int height = -1, bool useBbox = true ) const;

    /** Configures mapSettings to the parameters
     HEIGHT, WIDTH, BBOX, CRS.
     @param paintDevice the device that is used for painting (for dpi)
     @return 0 in case of success*/
    int configureMapRender( const QPaintDevice* paintDevice ) const;
    /** Reads the layers and style lists from the parameters LAYERS and STYLES
     @return 0 in case of success*/
    int readLayersAndStyles( QStringList& layersList, QStringList& stylesList ) const;
    /** If the parameter SLD exists, mSLDParser is configured appropriately. The lists are
    set to the layer and style names according to the SLD
    @return 0 in case of success*/
    int initializeSLDParser( QStringList& layersList, QStringList& stylesList );
    static bool infoPointToMapCoordinates( int i, int j, QgsPoint* infoPoint, QgsMapRenderer* mapRenderer );
    /** Appends feature info xml for the layer to the layer element of the feature info dom document
    @param featureBBox the bounding box of the selected features in output CRS
    @return 0 in case of success*/
    int featureInfoFromVectorLayer( QgsVectorLayer* layer,
                                    const QgsPoint* infoPoint,
                                    int nFeatures,
                                    QDomDocument& infoDocument,
                                    QDomElement& layerElement,
                                    QgsMapRenderer* mapRender,
                                    QgsRenderContext& renderContext,
                                    const QString& version,
                                    const QString& infoFormat,
                                    QgsRectangle* featureBBox = nullptr ) const;
    /** Appends feature info xml for the layer to the layer element of the dom document*/
    int featureInfoFromRasterLayer( QgsRasterLayer* layer,
                                    const QgsPoint* infoPoint,
                                    QDomDocument& infoDocument,
                                    QDomElement& layerElement,
                                    const QString& version,
                                    const QString& infoFormat ) const;

    /** Creates a layer set and returns a stringlist with layer ids that can be passed to a QgsMapRenderer. Usually used in conjunction with readLayersAndStyles
       @param scaleDenominator Filter out layer if scale based visibility does not match (or use -1 if no scale restriction)*/
    QStringList layerSet( const QStringList& layersList, const QStringList& stylesList, const QgsCoordinateReferenceSystem& destCRS, double scaleDenominator = -1 ) const;

    /** Record which symbols would be used if the map was in the current configuration of mMapRenderer. This is useful for content-based legend*/
    void runHitTest( QPainter* painter, HitTest& hitTest );
    /** Record which symbols within one layer would be rendered with the given renderer context*/
    void runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, QgsRenderContext& context );

    /** Read legend parameter from the request or from the first print composer in the project*/
    void legendParameters( double& boxSpace, double& layerSpace, double& layerTitleSpace,
                           double& symbolSpace, double& iconLabelSpace, double& symbolWidth, double& symbolHeight, QFont& layerFont, QFont& itemFont, QColor& layerFontColor, QColor& itemFontColor );

#if 0
    QImage* printCompositionToImage( QgsComposition* c ) const;
#endif

    /** Apply filter (subset) strings from the request to the layers. Example: '&FILTER=<layer1>:"AND property > 100",<layer2>:"AND bla = 'hallo!'" '
     * @param layerList list of layer IDs to filter
     * @param originalFilters hash of layer ID to original filter string
     * @note It is strongly recommended that this method be called alongside use of QgsOWSServerFilterRestorer
     * to ensure that the original filters are always correctly restored, regardless of whether exceptions
     * are thrown or functions are terminated early.
     */
    void applyRequestedLayerFilters( const QStringList& layerList, QHash<QgsMapLayer*, QString>& originalFilters ) const;

#ifdef HAVE_SERVER_PYTHON_PLUGINS
    /** Apply filter strings from the access control to the layers.
     * @param layerList layers to filter
     * @param originalLayerFilters the original layers filter dictionary
     */
    void applyAccessControlLayersFilters( const QStringList& layerList, QHash<QgsMapLayer*, QString>& originalLayerFilters ) const;
#endif

    /** Tests if a filter sql string is allowed (safe)
      @return true in case of success, false if string seems unsafe*/
    bool testFilterStringSafety( const QString& filter ) const;
    /** Helper function for filter safety test. Groups stringlist to merge entries starting/ending with quotes*/
    static void groupStringList( QStringList& list, const QString& groupString );

    /** Select vector features with ids specified in parameter SELECTED, e.g. ...&SELECTED=layer1:1,2,9;layer2:3,5,10&...
      @return list with layer ids where selections have been created*/
    QStringList applyFeatureSelections( const QStringList& layerList ) const;
    /** Clear all feature selections in the given layers*/
    void clearFeatureSelections( const QStringList& layerIds ) const;

    /** Applies opacity on layer/group level*/
    void applyOpacities( const QStringList& layerList, QList< QPair< QgsVectorLayer*, QgsFeatureRendererV2*> >& vectorRenderers,
                         QList< QPair< QgsRasterLayer*, QgsRasterRenderer* > >& rasterRenderers,
                         QList< QPair< QgsVectorLayer*, double > >& labelTransparencies,
                         QList< QPair< QgsVectorLayer*, double > >& labelBufferTransparencies
                       );

    /** Restore original opacities*/
    void restoreOpacities( QList< QPair <QgsVectorLayer*, QgsFeatureRendererV2*> >& vectorRenderers,
                           QList< QPair < QgsRasterLayer*, QgsRasterRenderer* > >& rasterRenderers,
                           QList< QPair< QgsVectorLayer*, double > >& labelTransparencies,
                           QList< QPair< QgsVectorLayer*, double > >& labelBufferTransparencies );

    void appendFormats( QDomDocument &doc, QDomElement &elem, const QStringList &formats );

    /** Checks WIDTH/HEIGHT values agains MaxWidth and MaxHeight
      @return true if width/height values are okay*/
    bool checkMaximumWidthHeight() const;

    /** Get service address from REQUEST_URI if not specified in the configuration*/
    QString serviceUrl() const;

    /** Add '<?xml version="1.0" ?>'. Some clients need an xml declaration (though it is not strictly required)*/
    void addXMLDeclaration( QDomDocument& doc ) const;

    /** Converts a feature info xml document to SIA2045 norm*/
    void convertFeatureInfoToSIA2045( QDomDocument& doc );

    /** Cleanup temporary objects (e.g. SLD parser objects or temporary files) after request*/
    void cleanupAfterRequest();

    /** Map containing the WMS parameters*/
    QgsMapRenderer* mMapRenderer;

    QgsCapabilitiesCache* mCapabilitiesCache;

    QgsWMSConfigParser* mConfigParser;

    bool mOwnsConfigParser; //delete config parser after request (e.g. sent SLD)

    // speficy if layer or rule item labels should be drawn in the legend graphic with GetLegendGraphics
    bool mDrawLegendLayerLabel;
    bool mDrawLegendItemLabel;

    QDomElement createFeatureGML(
      QgsFeature* feat,
      QgsVectorLayer* layer,
      QDomDocument& doc,
      QgsCoordinateReferenceSystem& crs,
      const QString& typeName,
      bool withGeom,
      int version,
      QStringList* attributes = nullptr ) const;

    /** Replaces attribute value with ValueRelation or ValueRelation if defined. Otherwise returns the original value*/
    static QString replaceValueMapAndRelation( QgsVectorLayer* vl, int idx, const QString& attributeVal );

    /** Return the image quality to use for getMap request */
    int getImageQuality() const;

    /** Return precision to use for GetFeatureInfo request */
    int getWMSPrecision( int defaultValue ) const;

    /** Gets layer search rectangle (depending on request parameter, layer type, map and layer crs)*/
    QgsRectangle featureInfoSearchRect( QgsVectorLayer* ml, QgsMapRenderer* mr, const QgsRenderContext& rct, const QgsPoint& infoPoint ) const;

    /** Reads and extracts the different options in the FORMAT_OPTIONS parameter*/
    void readFormatOptions( QMap<QString, QString>& formatOptions ) const;
    void readDxfLayerSettings( QList< QPair<QgsVectorLayer *, int > >& layers, const QMap<QString, QString>& formatOptionsMap ) const;
};

#endif
