/***************************************************************************
  qgslabelingenginev2.h
  --------------------------------------
  Date                 : September 2015
  Copyright            : (C) 2015 by Martin Dobias
  Email                : wonder dot sk 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 QGSLABELINGENGINEV2_H
#define QGSLABELINGENGINEV2_H

#include "qgsgeometry.h"

#include "qgsmapsettings.h"

#include "qgspallabeling.h"

#include <QFlags>

class QgsAbstractLabelProvider;
class QgsRenderContext;
class QgsGeometry;

namespace pal
{
  class LabelInfo;
}

/**
 * @brief The QgsLabelFeature class describes a feature that
 * should be used within the labeling engine. Those may be the usual textual labels,
 * diagrams, or any other custom type of map annotations (generated by custom
 * label providers).
 *
 * Instances only contain data relevant to the labeling engine (geometry, label size etc.)
 * necessary for the layout. Rendering of labels is done by label providers.
 *
 * Individual label providers may create subclasses of QgsLabelFeature in order to add
 * more data to the instances that will be later used for drawing of labels.
 *
 * @note this class is not a part of public API yet. See notes in QgsLabelingEngineV2
 * @note added in QGIS 2.12
 */
class CORE_EXPORT QgsLabelFeature
{
  public:
    //! Create label feature, takes ownership of the geometry instance
    QgsLabelFeature( QgsFeatureId id, GEOSGeometry* geometry, const QSizeF& size );
    //! Clean up geometry and curved label info (if present)
    virtual ~QgsLabelFeature();

    //! Identifier of the label (unique within the parent label provider)
    QgsFeatureId id() const { return mId; }

    //! Get access to the associated geometry
    GEOSGeometry* geometry() const { return mGeometry; }

    //! Size of the label (in map units)
    QSizeF size() const { return mSize; }

    /** Returns the feature's labeling priority.
     * @returns feature's priority, as a value between 0 (highest priority)
     * and 1 (lowest priority). Returns -1.0 if feature will use the layer's default priority.
     * @see setPriority
     */
    double priority() const { return mPriority; }
    /** Sets the priority for labeling the feature.
     * @param priority feature's priority, as a value between 0 (highest priority)
     * and 1 (lowest priority). Set to -1.0 to use the layer's default priority
     * for this feature.
     * @see priority
     */
    void setPriority( double priority ) { mPriority = priority; }

    //! Whether the label should use a fixed position instead of being automatically placed
    bool hasFixedPosition() const { return mHasFixedPosition; }
    //! Set whether the label should use a fixed position instead of being automatically placed
    void setHasFixedPosition( bool enabled ) { mHasFixedPosition = enabled; }
    //! Coordinates of the fixed position (relevant only if hasFixedPosition() returns true)
    QgsPoint fixedPosition() const { return mFixedPosition; }
    //! Set coordinates of the fixed position (relevant only if hasFixedPosition() returns true)
    void setFixedPosition( const QgsPoint& point ) { mFixedPosition = point; }

    //! Whether the label should use a fixed angle instead of using angle from automatic placement
    bool hasFixedAngle() const { return mHasFixedAngle; }
    //! Set whether the label should use a fixed angle instead of using angle from automatic placement
    void setHasFixedAngle( bool enabled ) { mHasFixedAngle = enabled; }
    //! Angle in degrees of the fixed angle (relevant only if hasFixedAngle() returns true)
    double fixedAngle() const { return mFixedAngle; }
    //! Set angle in degrees of the fixed angle (relevant only if hasFixedAngle() returns true)
    void setFixedAngle( double angle ) { mFixedAngle = angle; }

    /** Returns whether the quadrant for the label is fixed.
     * Applies to "around point" placement strategy.
     * @see setFixedQuadrant
     * @see quadOffset
     */
    bool hasFixedQuadrant() const { return mHasFixedQuadrant; }
    /** Sets whether the quadrant for the label must be respected. This can be used
     * to fix the quadrant for specific features when using an "around point" placement.
     * @see fixedQuadrant
     * @see quadOffset
     */
    void setHasFixedQuadrant( bool enabled ) { mHasFixedQuadrant = enabled; }
    //! Applies to "offset from point" placement strategy and "around point" (in case hasFixedQuadrant() returns true).
    //! Determines which side of the point to use.
    //! For X coordinate, values -1, 0, 1 mean left, center, right.
    //! For Y coordinate, values -1, 0, 1 mean above, center, below.
    QPointF quadOffset() const { return mQuadOffset; }
    //! Set which side of the point to use
    //! @see quadOffset
    void setQuadOffset( const QPointF& quadOffset ) { mQuadOffset = quadOffset; }
    //! Applies only to "offset from point" placement strategy.
    //! What offset (in map units) to use from the point
    QgsPoint positionOffset() const { return mPositionOffset; }
    //! Applies only to "offset from point" placement strategy.
    //! Set what offset (in map units) to use from the point
    void setPositionOffset( const QgsPoint& offset ) { mPositionOffset = offset; }
    //! Applies to "around point" placement strategy or linestring features.
    //! Distance of the label from the feature (in map units)
    double distLabel() const { return mDistLabel; }
    //! Applies to "around point" placement strategy or linestring features.
    //! Set distance of the label from the feature (in map units)
    void setDistLabel( double dist ) { mDistLabel = dist; }

    //! Applies only to linestring features - after what distance (in map units)
    //! the labels should be repeated (0 = no repetitions)
    double repeatDistance() const { return mRepeatDistance; }
    //! Applies only to linestring features - set after what distance (in map units)
    //! the labels should be repeated (0 = no repetitions)
    void setRepeatDistance( double dist ) { mRepeatDistance = dist; }

    //! Whether label should be always shown (sets very high label priority)
    bool alwaysShow() const { return mAlwaysShow; }
    //! Set whether label should be always shown (sets very high label priority)
    void setAlwaysShow( bool enabled ) { mAlwaysShow = enabled; }

    /** Returns whether the feature will act as an obstacle for labels.
     * @returns true if feature is an obstacle
     * @see setIsObstacle
     */
    bool isObstacle() const { return mIsObstacle; }
    /** Sets whether the feature will act as an obstacle for labels.
     * @param enabled whether feature will act as an obstacle
     * @see isObstacle
     */
    void setIsObstacle( bool enabled ) { mIsObstacle = enabled; }
    /** Returns the obstacle factor for the feature. The factor controls the penalty
     * for labels overlapping this feature.
     * @see setObstacleFactor
     */
    double obstacleFactor() const { return mObstacleFactor; }
    /** Sets the obstacle factor for the feature. The factor controls the penalty
     * for labels overlapping this feature.
     * @param factor larger factors ( > 1.0 ) will result in labels
     * which are less likely to cover this feature, smaller factors ( < 1.0 ) mean labels
     * are more likely to cover this feature (where required)
     * @see obstacleFactor
     */
    void setObstacleFactor( double factor ) { mObstacleFactor = factor; }

    /** Text of the label
     *
     * Used also if "merge connected lines to avoid duplicate labels" is enabled
     * to identify which features may be merged.
     */
    QString labelText() const { return mLabelText; }
    //! Set text of the label
    void setLabelText( const QString& text ) { mLabelText = text; }

    //! Get additional infor required for curved label placement. Returns null if not set
    pal::LabelInfo* curvedLabelInfo() const { return mInfo; }
    //! takes ownership of the instance
    void setCurvedLabelInfo( pal::LabelInfo* info ) { mInfo = info; }

    //! Get PAL layer of the label feature. Should be only used internally in PAL
    pal::Layer* layer() const { return mLayer; }
    //! Assign PAL layer to the label feature. Should be only used internally in PAL
    void setLayer( pal::Layer* layer ) { mLayer = layer; }

    //! Return provider of this instance
    QgsAbstractLabelProvider* provider() const;

  protected:
    //! Pointer to PAL layer (assigned when registered to PAL)
    pal::Layer* mLayer;

    //! Associated ID unique within the parent label provider
    QgsFeatureId mId;
    //! Geometry of the feature to be labelled
    GEOSGeometry* mGeometry;
    //! Width and height of the label
    QSizeF mSize;
    //! Priority of the label
    double mPriority;
    //! whether mFixedPosition should be respected
    bool mHasFixedPosition;
    //! fixed position for the label (instead of automatic placement)
    QgsPoint mFixedPosition;
    //! whether mFixedAngle should be respected
    bool mHasFixedAngle;
    //! fixed rotation for the label (instead of automatic choice)
    double mFixedAngle;
    //! whether mQuadOffset should be respected (only for "around point" placement)
    bool mHasFixedQuadrant;
    //! whether the side of the label is fixed (only for "around point" placement)
    QPointF mQuadOffset;
    //! offset of label from the feature (only for "offset from point" placement)
    QgsPoint mPositionOffset;
    //! distance of label from the feature (only for "around point" placement or linestrings)
    double mDistLabel;
    //! distance after which label should be repeated (only for linestrings)
    double mRepeatDistance;
    //! whether to always show label - even in case of collisions
    bool mAlwaysShow;
    //! whether the feature geometry acts as an obstacle for labels
    bool mIsObstacle;
    //! how strong is the geometry acting as obstacle
    double mObstacleFactor;
    //! text of the label
    QString mLabelText;
    //! extra information for curved labels (may be null)
    pal::LabelInfo* mInfo;
};



class QgsLabelingEngineV2;


/**
 * @brief The QgsAbstractLabelProvider class is an interface class. Implementations
 * return list of labels and their associated geometries - these are used by
 * QgsLabelingEngineV2 to compute the final layout of labels.
 *
 * Implementations also take care of drawing the returned final label positions.
 *
 * @note this class is not a part of public API yet. See notes in QgsLabelingEngineV2
 * @note added in QGIS 2.12
 */
class CORE_EXPORT QgsAbstractLabelProvider
{

  public:
    //! Construct the provider with default values
    QgsAbstractLabelProvider();
    //! Vritual destructor
    virtual ~QgsAbstractLabelProvider() {}

    //! Associate provider with a labeling engine (should be only called internally from QgsLabelingEngineV2)
    void setEngine( const QgsLabelingEngineV2* engine ) { mEngine = engine; }

    enum Flag
    {
      DrawLabels              = 1 << 1,  //!< whether the labels should be rendered
      DrawAllLabels           = 1 << 2,  //!< whether all features will be labelled even though overlaps occur
      MergeConnectedLines     = 1 << 3,  //!< whether adjacent lines (with the same label text) should be merged
      CentroidMustBeInside    = 1 << 4,  //!< whether location of centroid must be inside of polygons
      FitInPolygonOnly        = 1 << 5,  //!< whether labels must fall completely within the polygon
      LabelPerFeaturePart     = 1 << 6,  //!< whether to label each part of multi-part features separately
    };
    Q_DECLARE_FLAGS( Flags, Flag )

    //! Return list of label features (they are owned by the provider and thus deleted on its destruction)
    virtual QList<QgsLabelFeature*> labelFeatures( QgsRenderContext& context ) = 0;

    //! draw this label at the position determined by the labeling engine
    virtual void drawLabel( QgsRenderContext& context, pal::LabelPosition* label ) const = 0;

    //! Return list of child providers - useful if the provider needs to put labels into more layers with different configuration
    virtual QList<QgsAbstractLabelProvider*> subProviders() { return QList<QgsAbstractLabelProvider*>(); }

    //! Name of the layer (for statistics, debugging etc.) - does not need to be unique
    QString name() const { return mName; }

    //! Flags associated with the provider
    Flags flags() const { return mFlags; }

    //! What placement strategy to use for the labels
    QgsPalLayerSettings::Placement placement() const { return mPlacement; }

    //! For layers with linestring geometries - extra placement flags (or-ed combination of QgsPalLayerSettings::LinePlacementFlags)
    unsigned int linePlacementFlags() const { return mLinePlacementFlags; }

    //! Default priority of labels (may be overridden by individual labels)
    double priority() const { return mPriority; }

    //! How the feature geometries will work as obstacles
    QgsPalLayerSettings::ObstacleType obstacleType() const { return mObstacleType; }

    //! How to handle labels that would be upside down
    QgsPalLayerSettings::UpsideDownLabels upsidedownLabels() const { return mUpsidedownLabels; }

  protected:
    //! Associated labeling engine
    const QgsLabelingEngineV2* mEngine;

    //! Name of the layer
    QString mName;
    //! Flags altering drawing and registration of features
    Flags mFlags;
    //! Placement strategy
    QgsPalLayerSettings::Placement mPlacement;
    //! Extra placement flags for linestring geometries
    unsigned int mLinePlacementFlags;
    //! Default priority of labels
    double mPriority;
    //! Type of the obstacle of feature geometries
    QgsPalLayerSettings::ObstacleType mObstacleType;
    //! How to handle labels that would be upside down
    QgsPalLayerSettings::UpsideDownLabels mUpsidedownLabels;
};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsAbstractLabelProvider::Flags )



/**
 * @brief The QgsLabelingEngineV2 class provides map labeling functionality.
 * The input for the engine is a list of label provider objects and map settings.
 * Based on the input, the engine computes layout of labels for the given map view
 * with no collisions between the labels. Drawing of resulting labels is done
 * again by label providers.
 *
 * The labeling engine is used for the map rendering in QgsMapRendererJob instances,
 * individual map layer renderers may add label providers - for example,
 * QgsVectorLayerRenderer may add text label provider and diagram provider
 * (if labeling / diagrams were configured for such vector layer).
 *
 * The labeling engine may also be used independently from map rendering loop:
 * \code
 *   QgsLabelingEngineV2 engine;
 *   engine.setMapSettings( mapSettings );
 *   // add one or more providers
 *   engine.addProvider( ... );
 *   // compute the labeling and draw labels (using painter from the context)
 *   engine.run( context );
 * \endcode
 *
 * @note this class is not a part of public API yet. The provider's interface still
 * uses pal::LabelPosition as an argument in drawLabels() method - this should be
 * sorted out first (a class common to API and pal?). Also, the API may need more
 * polishing to be easy to use - e.g. use concept of labeling layers in API
 * (equivalent of pal::Layer) instead of subProviders(), label providers integrated
 * into feature loop vs providers with independent feature loop), split labeling
 * computation from drawing of labels, improved results class with label iterator).
 * @note added in QGIS 2.12
 */
class CORE_EXPORT QgsLabelingEngineV2
{
  public:
    //! Construct the labeling engine with default settings
    QgsLabelingEngineV2();
    //! Clean up everything (especially the registered providers)
    ~QgsLabelingEngineV2();

    enum Flag
    {
      UseAllLabels          = 1 << 1,  //!< Whether to draw all labels even if there would be collisions
      UsePartialCandidates  = 1 << 2,  //!< Whether to use also label candidates that are partially outside of the map view
      RenderOutlineLabels   = 1 << 3,  //!< Whether to render labels as text or outlines
      DrawLabelRectOnly     = 1 << 4,  //!< Whether to only draw the label rect and not the actual label text (used for unit tests)
      DrawCandidates        = 1 << 5,  //!< Whether to draw rectangles of generated candidates (good for debugging)
      DrawShadowRects       = 1 << 6,  //!< Whether to show debugging rectangles for drop shadows
    };
    Q_DECLARE_FLAGS( Flags, Flag )

    //! Associate map settings instance
    void setMapSettings( const QgsMapSettings& mapSettings ) { mMapSettings = mapSettings; }
    //! Get associated map settings
    const QgsMapSettings& mapSettings() const { return mMapSettings; }

    //! Add provider of label features. Takes ownership of the provider
    void addProvider( QgsAbstractLabelProvider* provider );

    //! Remove provider if the provider's initialization failed. Provider instance is deleted.
    void removeProvider( QgsAbstractLabelProvider* provider );

    //! compute the labeling with given map settings and providers
    void run( QgsRenderContext& context );

    //! Return pointer to recently computed results and pass the ownership of results to the caller
    QgsLabelingResults* takeResults();

    //! For internal use by the providers
    QgsLabelingResults* results() const { return mResults; }

    //! Set flags of the labeling engine
    void setFlags( const Flags& flags ) { mFlags = flags; }
    //! Get flags of the labeling engine
    Flags flags() const { return mFlags; }
    //! Test whether a particular flag is enabled
    bool testFlag( Flag f ) const { return mFlags.testFlag( f ); }
    //! Set whether a particual flag is enabled
    void setFlag( Flag f, bool enabled = true ) { if ( enabled ) mFlags |= f; else mFlags &= ~f; }

    //! Get number of candidate positions that will be generated for each label feature (default to 8)
    void numCandidatePositions( int& candPoint, int& candLine, int& candPolygon ) { candPoint = mCandPoint; candLine = mCandLine; candPolygon = mCandPolygon; }
    //! Set number of candidate positions that will be generated for each label feature
    void setNumCandidatePositions( int candPoint, int candLine, int candPolygon ) { mCandPoint = candPoint; mCandLine = candLine; mCandPolygon = candPolygon; }

    //! Set which search method to use for removal collisions between labels
    void setSearchMethod( QgsPalLabeling::Search s ) { mSearchMethod = s; }
    //! Which search method to use for removal collisions between labels
    QgsPalLabeling::Search searchMethod() const { return mSearchMethod; }

    //! Read configuration of the labeling engine from the current project file
    void readSettingsFromProject();
    //! Write configuration of the labeling engine to the current project file
    void writeSettingsToProject();

  protected:
    void processProvider( QgsAbstractLabelProvider* provider, QgsRenderContext& context, pal::Pal& p );

  protected:
    //! Associated map settings instance
    QgsMapSettings mMapSettings;
    //! List of providers (the are owned by the labeling engine)
    QList<QgsAbstractLabelProvider*> mProviders;
    QList<QgsAbstractLabelProvider*> mSubProviders;
    //! Flags
    Flags mFlags;
    //! search method to use for removal collisions between labels
    QgsPalLabeling::Search mSearchMethod;
    //! Number of candedate positions that will be generated for features
    int mCandPoint, mCandLine, mCandPolygon;

    //! Resulting labeling layout
    QgsLabelingResults* mResults;
};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsLabelingEngineV2::Flags )

#endif // QGSLABELINGENGINEV2_H
