#ifndef QGSLABELFEATURE_H
#define QGSLABELFEATURE_H

#include "qgsgeometry.h"

#include "qgspallabeling.h"

namespace pal
{
  class LabelInfo;
}

class QgsAbstractLabelProvider;
class QgsRenderContext;
class QgsGeometry;


/** \ingroup core
 * @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
 * @note not available in Python bindings
 */
class CORE_EXPORT QgsLabelFeature
{
  public:

    //! Stores visual margins for labels (left, right, top and bottom)
    //! @note not available in Python bindings
    struct VisualMargin
    {
      /** Default constructor, all margins are set to 0.0
       */
      VisualMargin()
          : left( 0.0 )
          , right( 0.0 )
          , top( 0.0 )
          , bottom( 0.0 )
      {}

      /** Constructor allowing margins to be specified
       * @param top top margin
       * @param right right margin
       * @param bottom bottom margin
       * @param left left margin
       */
      VisualMargin( double top, double right, double bottom, double left )
          : left( left )
          , right( right )
          , top( top )
          , bottom( bottom )
      {}

      //! Left margin
      double left;
      //! Right margin
      double right;
      //! Top margin
      double top;
      //! Bottom margin
      double bottom;

      VisualMargin& operator *=( double value )
      {
        left *= value;
        right *= value;
        top *= value;
        bottom *= value;
        return *this;
      }
    };

    //! Create label feature, takes ownership of the geometry instance
    QgsLabelFeature( QgsFeatureId id, GEOSGeometry* geometry, 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; }

    /** Sets the label's obstacle geometry, if different to the feature geometry.
     * This can be used to override the shape of the feature for obstacle detection, eg to
     * buffer around a point geometry to prevent labels being placed too close to the
     * point itself. It not set, the feature's geometry is used for obstacle detection.
     * Ownership of obstacle geometry is transferred.
     * @note added in QGIS 2.14
     * @see obstacleGeometry()
     */
    void setObstacleGeometry( GEOSGeometry* obstacleGeom );

    /** Returns the label's obstacle geometry, if different to the feature geometry.
     * @note added in QGIS 2.14
     * @see setObstacleGeometry()
     */
    GEOSGeometry* obstacleGeometry() const { return mObstacleGeometry; }

    /** Sets the label's permissible zone geometry. If set, the feature's label MUST be fully contained
     * within this zone, and the feature will not be labeled if no candidates can be generated which
     * are not contained within the zone.
     * @param geometry permissible zone geometry. If an invalid QgsGeometry is passed then no zone limit
     * will be applied to the label candidates (this is the default behaviour).
     * @note added in QGIS 2.18
     * @see permissibleZone()
     */
    void setPermissibleZone( const QgsGeometry& geometry );

    /** Returns the label's permissible zone geometry. If a valid geometry is returned, the feature's label
     * MUST be fully contained within this zone, and the feature will not be labeled if no candidates can be
     * generated which are not contained within the zone.
     * @note added in QGIS 2.18
     * @see setPermissibleZone()
     * @see permissibleZonePrepared()
     */
    QgsGeometry permissibleZone() const { return mPermissibleZone; }

    /** Returns a GEOS prepared geometry representing the label's permissibleZone().
     * @see permissibleZone()
     * @note added in QGIS 2.18
     */
    //TODO - remove when QgsGeometry caches GEOS preparedness
    const GEOSPreparedGeometry* permissibleZonePrepared() const { return mPermissibleZoneGeosPrepared; }

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

    /** Sets the visual margin for the label feature. The visual margin represents a margin
     * within the label which should not be considered when calculating the positions of candidates
     * for the label feature. It is used in certain label placement modes to adjust the position
     * of candidates so that they all appear to be at visually equal distances from a point feature.
     * For instance, this can be used to place labels which sit above a point so that their baseline
     * rather then the descender of the label is at a preset distance from the point.
     * @param margin visual margins for label
     * @see visualMargin()
     */
    void setVisualMargin( const VisualMargin& margin ) { mVisualMargin = margin; }

    /** Returns the visual margin for the label feature.
     * @see setVisualMargin() for details
     */
    const VisualMargin& visualMargin() const { return mVisualMargin; }

    /** Sets the size of the rendered symbol associated with this feature. This size is taken into
     * account in certain label placement modes to avoid placing labels over the rendered
     * symbol for this feature.
     * @see symbolSize()
     */
    void setSymbolSize( QSizeF size ) { mSymbolSize = size; }

    /** Returns the size of the rendered symbol associated with this feature, if applicable.
     * This size is taken into account in certain label placement modes to avoid placing labels over
     * the rendered symbol for this feature. The size will only be set for labels associated
     * with a point feature.
     * @see symbolSize()
     */
    const QSizeF& symbolSize() const { return mSymbolSize; }

    /** 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; }

    /** Returns the label's z-index. Higher z-index labels are rendered on top of lower
     * z-index labels.
     * @see setZIndex()
     * @note added in QGIS 2.14
     */
    double zIndex() const { return mZIndex; }

    /** Sets the label's z-index. Higher z-index labels are rendered on top of lower
     * z-index labels.
     * @param zIndex z-index for label
     * @see zIndex()
     * @note added in QGIS 2.14
     */
    void setZIndex( double zIndex ) { mZIndex = zIndex; }

    //! 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( 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; }

    /** Returns the offset type, which determines how offsets and distance to label
     * behaves. Support depends on which placement mode is used for generating
     * label candidates.
     * @see setOffsetType()
     */
    QgsPalLayerSettings::OffsetType offsetType() const { return mOffsetType; }

    /** Sets the offset type, which determines how offsets and distance to label
     * behaves. Support depends on which placement mode is used for generating
     * label candidates.
     * @see offsetType()
     */
    void setOffsetType( QgsPalLayerSettings::OffsetType type ) { mOffsetType = type; }

    //! 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; }

    /** Returns the priority ordered list of predefined positions for label candidates. This property
     * is only used for OrderedPositionsAroundPoint placements.
     * @see setPredefinedPositionOrder()
     */
    QVector< QgsPalLayerSettings::PredefinedPointPosition > predefinedPositionOrder() const { return mPredefinedPositionOrder; }

    /** Sets the priority ordered list of predefined positions for label candidates. This property
     * is only used for OrderedPositionsAroundPoint placements.
     * @see predefinedPositionOrder()
     */
    void setPredefinedPositionOrder( const QVector< QgsPalLayerSettings::PredefinedPointPosition >& order ) { mPredefinedPositionOrder = order; }

    //! 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;
    //! Optional geometry to use for label obstacles, if different to mGeometry
    GEOSGeometry* mObstacleGeometry;
    //! Optional geometry to use for label's permissible zone
    QgsGeometry mPermissibleZone;
    //! Width and height of the label
    QSizeF mSize;
    //! Visual margin of label contents
    VisualMargin mVisualMargin;
    //! Size of associated rendered symbol, if applicable
    QSizeF mSymbolSize;
    //! Priority of the label
    double mPriority;
    //! Z-index of label (higher z-index labels are rendered on top of lower z-index labels)
    double mZIndex;
    //! 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;
    //! Offset type for certain placement modes
    QgsPalLayerSettings::OffsetType mOffsetType;
    //! Ordered list of predefined positions for label (only for OrderedPositionsAroundPoint placement)
    QVector< QgsPalLayerSettings::PredefinedPointPosition > mPredefinedPositionOrder;
    //! 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;

  private:

    // TODO - not required when QgsGeometry caches geos preparedness
    const GEOSPreparedGeometry* mPermissibleZoneGeosPrepared;

};

#endif // QGSLABELFEATURE_H
