/***************************************************************************
    qgsmaptooladvanceddigitizing.h  - map tool with event in map coordinates
    ----------------------
    begin                : October 2014
    copyright            : (C) Denis Rouzaud
    email                : denis.rouzaud@gmail.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 QGSMAPTOOLADVANCEDDIGITIZE_H
#define QGSMAPTOOLADVANCEDDIGITIZE_H

#include <memory>

#include "qgis_gui.h"
#include "qgsmapmouseevent.h"
#include "qgsmaptooledit.h"
#include "qgsreferencedgeometry.h"

class QgsAdvancedDigitizingDockWidget;
class QgsSnapToGridCanvasItem;
class QgsSnapIndicator;

/**
 * \ingroup gui
 * \brief A QgsMapTool which gives events directly in map coordinates and allows filtering of events.
 *
 * Events from QgsMapTool are caught and their QMouseEvent are transformed into QgsMapMouseEvent (with map coordinates).
 * Events are then forwarded to corresponding virtual methods which can be reimplemented in subclasses.
 * An event filter can be set on the map tool to filter and modify the events in map coordinates (\see QgsMapToolMapEventFilter).
 * \note at the moment, the event filter is used by the CAD tools (\see QgsCadDocWidget).
 * \note the event filter definition is not exposed in Python API to avoid any unexpected behavior.
 */
class GUI_EXPORT QgsMapToolAdvancedDigitizing : public QgsMapToolEdit
{
    Q_OBJECT
  public:
    /**
     * Creates an advanced digitizing maptool
     * \param canvas         The map canvas on which the tool works
     * \param cadDockWidget  The cad dock widget which will be used to adjust mouse events
     */
    explicit QgsMapToolAdvancedDigitizing( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget );
    ~QgsMapToolAdvancedDigitizing() override;

    //! Catch the mouse press event, filters it, transforms it to map coordinates and send it to virtual method
    void canvasPressEvent( QgsMapMouseEvent *e ) override;
    //! Catch the mouse release event, filters it, transforms it to map coordinates and send it to virtual method
    void canvasReleaseEvent( QgsMapMouseEvent *e ) override;
    //! Catch the mouse move event, filters it, transforms it to map coordinates and send it to virtual method
    void canvasMoveEvent( QgsMapMouseEvent *e ) override;

    /**
     * Registers this maptool with the cad dock widget
     */
    void activate() override;

    /**
     * Unregisters this maptool from the cad dock widget
     */
    void deactivate() override;

    QgsAdvancedDigitizingDockWidget *cadDockWidget() const { return mCadDockWidget; }

    /**
     * Returns the layer associated with the map tool.
     *
     * By default this returns the map canvas' QgsMapCanvas::currentLayer().
     *
     * \since QGIS 3.22
     */
    virtual QgsMapLayer *layer() const;

    /**
     * Returns whether functionality of advanced digitizing dock widget is currently allowed.
     *
     * Tools may decide to switch this support on/off based on the current state of the map tool.
     * For example, in vertex tool before user picks a vertex to move, advanced digitizing dock
     * widget should be disabled and only enabled once a vertex is being moved. Other map tools
     * may keep advanced digitizing allowed all the time.
     *
     * If TRUE is returned, that does not mean that advanced digitizing is actually active,
     * because it is up to the user to enable/disable it when it is allowed.
     *
     * The default is that advanced digitizing is allowed.
     *
     * \see setAdvancedDigitizingAllowed()
     */
    bool isAdvancedDigitizingAllowed() const { return mAdvancedDigitizingAllowed; }

    /**
     * Returns whether mouse events (press/move/release) should automatically try to snap mouse position
     * (according to the snapping configuration of map canvas) before passing the mouse coordinates
     * to the tool. This may be desirable default behavior for some map tools, but not for other map tools.
     * It is therefore possible to configure the behavior by the map tool.
     *
     * The default is that auto snapping is enabled.
     *
     * \see isAutoSnapEnabled()
     */
    bool isAutoSnapEnabled() const { return mAutoSnapEnabled; }

    /**
     * Returns whether the snapping indicator should automatically be used.
     *
     * The default is that a snap indicator is not used.
     *
     * \see setUseSnappingIndicator()
     * \since QGIS 3.40
     */
    bool useSnappingIndicator() const;

    /**
     * Calculates geometry measures for a \a geometry, including area and total length (or perimeter).
     *
     * \note Not available in Python bindings.
     * \since QGIS 4.0
     */
    SIP_SKIP static void calculateGeometryMeasures(
      const QgsReferencedGeometry &geometry,
      const QgsCoordinateReferenceSystem &destinationCrs,
      Qgis::CadMeasurementDisplayType areaType,
      Qgis::CadMeasurementDisplayType totalLengthType,
      QString &areaString,
      QString &totalLengthString
    );

  protected:
    /**
     * Sets whether functionality of advanced digitizing dock widget is currently allowed.
     * This method is protected because it should be a decision of the map tool and not from elsewhere.
     *
     * The default is that advanced digitizing is allowed.
     *
     * \see isAdvancedDigitizingAllowed()
     */
    void setAdvancedDigitizingAllowed( bool allowed ) { mAdvancedDigitizingAllowed = allowed; }

    /**
     * Sets whether mouse events (press/move/release) should automatically try to snap mouse position
     * This method is protected because it should be a decision of the map tool and not from elsewhere.
     *
     * The default is that auto snapping is enabled.
     *
     * \see isAutoSnapEnabled()
     */
    void setAutoSnapEnabled( bool enabled ) { mAutoSnapEnabled = enabled; }

    /**
     * Sets whether a snapping indicator should automatically be used.
     *
     * The default is that a snap indicator is not used.
     *
     * \see useSnappingIndicator()
     * \since QGIS 3.40
     */
    void setUseSnappingIndicator( bool enabled );


    QgsAdvancedDigitizingDockWidget *mCadDockWidget = nullptr;

  public:
    /**
     * Override this method when subclassing this class.
     * This will receive adapted events from the cad system whenever a
     * canvasPressEvent is triggered and it's not hidden by the cad's
     * construction mode.
     *
     * \param e Mouse events prepared by the cad system
     */
    virtual void cadCanvasPressEvent( QgsMapMouseEvent *e ) { Q_UNUSED( e ) }


    /**
     * Override this method when subclassing this class.
     * This will receive adapted events from the cad system whenever a
     * canvasReleaseEvent is triggered and it's not hidden by the cad's
     * construction mode.
     *
     * \param e Mouse events prepared by the cad system
     */
    virtual void cadCanvasReleaseEvent( QgsMapMouseEvent *e ) { Q_UNUSED( e ) }


    /**
     * Override this method when subclassing this class.
     * This will receive adapted events from the cad system whenever a
     * canvasMoveEvent is triggered and it's not hidden by the cad's
     * construction mode.
     *
     * \param e Mouse events prepared by the cad system
     */
    virtual void cadCanvasMoveEvent( QgsMapMouseEvent *e ) { Q_UNUSED( e ) }

    /**
     * Enables or disables snap to grid of mouse events.
     * The snapping will occur in the layer's CRS.
     *
     * \since QGIS 3.4
     */
    bool snapToLayerGridEnabled() const;

    /**
     * Enables or disables snap to grid of mouse events.
     * The snapping will occur in the layer's CRS.
     *
     * \since QGIS 3.4
     */
    void setSnapToLayerGridEnabled( bool snapToLayerGridEnabled );

  signals:

    //NOTE -- we use QgsReferencedGeometry here, as we'd like to defer the transformation of geometry to a particular
    //desired destination CRS (eg layer CRS or map canvas CRS) as the caller's responsibility. That's because we don't want
    //to waste cycles doing that transformation with every mouse move, when potentially NOTHING is even connected to this signal!
    //By using QgsReferencedGeometry we allows the emitters to just use the geometries they've already calculated for the rubber
    //bands, regardless of what CRS they are in
    /**
     * Emitted whenever the \a geometry associated with the tool is changed, including transient (i.e. non-finalized, hover state) changes.
     *
     * Connections to this signal should take care to check the CRS of \a geometry, as it may be either in the
     * canvas CRS or an associated layer's CRS.
     *
     * \since QGIS 4.0
     */
    void transientGeometryChanged( const QgsReferencedGeometry &geometry );

  private slots:

    /**
     * Is to be called by the cad system whenever a point changes outside of a
     * mouse event. E.g. when additional constraints are toggled.
     * The specified point will be used to generate a fake mouse event which will
     * be sent as move event to cadCanvasMoveEvent.
     *
     * \param point The last point known to the cad system.
     */
    void cadPointChanged( const QgsPointXY &point );

    void onCurrentLayerChanged();

    void onTransientGeometryChanged( const QgsReferencedGeometry &geometry );

  private:
    //! Whether to allow use of advanced digitizing dock at this point
    bool mAdvancedDigitizingAllowed = true;
    //! Whether to snap mouse cursor to map before passing coordinates to cadCanvas*Event()
    bool mAutoSnapEnabled = true;
    //! Whether to snap to grid before passing coordinates to cadCanvas*Event()
    bool mSnapToLayerGridEnabled = true;
    QgsSnapToGridCanvasItem *mSnapToGridCanvasItem = nullptr;

    std::unique_ptr<QgsSnapIndicator> mSnapIndicator;
};

#endif // QGSMAPTOOLADVANCEDDIGITIZE_H
