/*============================================================================

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center (DKFZ)
All rights reserved.

Use of this source code is governed by a 3-clause BSD license that can be
found in the LICENSE file.

============================================================================*/

#ifndef mitkPlanarFigure_h
#define mitkPlanarFigure_h

#include <mitkBaseData.h>
#include <mitkCommon.h>
#include <MitkPlanarFigureExports.h>

#include <deque>

namespace mitk
{
  class PlaneGeometry;

  /**
   * \brief Base-class for geometric planar (2D) figures, such as
   * lines, circles, rectangles, polygons, etc.
   *
   * \warning Currently does not support time-resolved data handling
   *
   * Behavior and appearance of PlanarFigures are controlled by various properties; for a detailed
   * list of appearance properties see mitk::PlanarFigureMapper2D
   *
   * The following properties control general PlanarFigure behavior:
   *
   * <ul>
   *   <li>"selected": true if the planar figure is selected
   *   <li>"planarfigure.ishovering": true if the mouse "hovers" over the planar figure
   *   <li>"planarfigure.iseditable": true if the planar figure can be edited (otherwise,
   *       it can only be picked/selected, but its control points cannot be edited); default is true
   *   <li>"planarfigure.isextendable": true if new control points can be inserted into the list of control points;
   *        default is false
   * </ul>
   *
   *
   * TODO: Implement local 2D transform (including center of rotation...)
   *
   */
  class MITKPLANARFIGURE_EXPORT PlanarFigure : public BaseData
  {
  public:
    mitkClassMacro(PlanarFigure, BaseData);
    itkCloneMacro(Self);

      /** \brief Type for individual elements of a polyline (2D points). */
      typedef Point2D PolyLineElement;

    /** \brief Container type for boolean values, used for helper polyline paint flags. */
    typedef itk::VectorContainer<unsigned long, bool> BoolContainerType;

    /** \brief Type for the list of control points defining the figure. */
    typedef std::deque<Point2D> ControlPointListType;
    /** \brief Type for a polyline (vector of 2D points). */
    typedef std::vector<PolyLineElement> PolyLineType;

    /** \brief Sets the 2D geometry on which this figure will be placed.
     *
     * In most cases, this is a Geometry already owned by another object, e.g.
     * describing the slice of the image on which measurements will be
     * performed.
     */
    virtual void SetPlaneGeometry(mitk::PlaneGeometry *geometry);

    /** \brief Returns (previously set) 2D geometry of this figure. */
    virtual const PlaneGeometry *GetPlaneGeometry() const;

    /** \brief True if the planar figure is closed.
     *
     * Default is false. The "closed" boolean property must be set in sub-classes. */
    virtual bool IsClosed() const;

    /** \brief True if the planar figure has been placed (and can be
     * displayed/interacted with). */
    virtual bool IsPlaced() const { return m_FigurePlaced; };
    /** \brief Place figure at the given point (in 2D index coordinates) onto
     * the given 2D geometry.
     *
     * By default, the first two control points of the figure are set to the
     * passed point. Further points can be set via AddControlPoint(), if the
     * current number of control points is below the maximum number of control
     * points.
     *
     * Can be re-implemented in sub-classes as needed.
     */
    virtual void PlaceFigure(const Point2D &point);

    /**
    * \brief Adds / inserts new control-points
    *
    * This method adds a new control-point with the coordinates defined by point at the given index.
    * If 'index' == -1 or index is greater than the number of control-points the new point is appended
    * to the back of the list of control points.
    * If a control-point already exists for 'index', an additional point is inserted at that position.
    * It is not possible to add more points if the maximum number of control-points (GetMaximumNumberOfControlPoints())
    * has been reached.
    */
    virtual bool AddControlPoint(const Point2D &point, int index = -1);

    /**
     * \brief Sets the position of an existing control point, optionally creating it if missing.
     *
     * \param[in] index               Zero-based index of the control point.
     * \param[in] point               New 2D coordinates for the control point.
     * \param[in] createIfDoesNotExist If true, creates the control point at the given index
     *                                if it does not already exist.
     * \return True if the control point was set successfully.
     */
    virtual bool SetControlPoint(unsigned int index, const Point2D &point, bool createIfDoesNotExist = false);

    /**
     * \brief Sets the position of the currently selected control point.
     *
     * \param[in] point New 2D coordinates for the currently selected control point.
     * \return True if a control point is selected and was updated successfully.
     */
    virtual bool SetCurrentControlPoint(const Point2D &point);

    /** \brief Returns the current number of 2D control points defining this figure. */
    unsigned int GetNumberOfControlPoints() const;

    /** \brief Returns the minimum number of control points needed to represent
     * this figure.
     *
     * Must be implemented in sub-classes.
     */
    virtual unsigned int GetMinimumNumberOfControlPoints() const = 0;

    /** \brief Returns the maximum number of control points allowed for
    * this figure (e.g. 3 for triangles).
    *
    * Must be implemented in sub-classes.
    */
    virtual unsigned int GetMaximumNumberOfControlPoints() const = 0;

    /** \brief Selects currently active control points. */
    virtual bool SelectControlPoint(unsigned int index);

    /** \brief Deselect control point; no control point active. */
    virtual bool DeselectControlPoint();

    /** \brief Return currently selected control point. */
    virtual int GetSelectedControlPoint() const { return m_SelectedControlPoint; }
    /** \brief Returns specified control point in 2D world coordinates. */
    Point2D GetControlPoint(unsigned int index) const;

    /**
    * \brief Returns the id of the control-point that corresponds to the given
    * polyline-point.
    */
    virtual int GetControlPointForPolylinePoint(int indexOfPolylinePoint, int polyLineIndex) const;

    /** \brief Returns specified control point in world coordinates. */
    Point3D GetWorldControlPoint(unsigned int index) const;

    /**
     * \brief Returns the polyline representing the planar figure (mutable version).
     *
     * The polyline is regenerated if it is out of date.
     *
     * \param[in] index Zero-based index of the polyline to retrieve.
     * \return Reference to the polyline at the given index.
     */
    PolyLineType& GetPolyLine(unsigned int index);

    /**
     * \brief Returns the polyline representing the planar figure (const version).
     *
     * \param[in] index Zero-based index of the polyline to retrieve.
     * \return Const reference to the polyline at the given index.
     */
    const PolyLineType& GetPolyLine(unsigned int index) const;

    /**
     * \brief Returns a helper polyline that should be drawn at a fixed size regardless of zoom.
     *
     * Helper polylines are used for annotations such as angle arcs, arrow tips,
     * or other decorations that should maintain a constant display size.
     *
     * \param[in] index            Zero-based index of the helper polyline.
     * \param[in] mmPerDisplayUnit Scale factor (mm per display pixel).
     * \param[in] displayHeight    Height of the display in pixels.
     * \return Const reference to the helper polyline.
     */
    const PolyLineType& GetHelperPolyLine(unsigned int index, double mmPerDisplayUnit, unsigned int displayHeight);

    /** \brief Sets the position of the PreviewControlPoint. Automatically sets it visible.*/
    void SetPreviewControlPoint(const Point2D &point);

    /** \brief Marks the PreviewControlPoint as invisible.*/
    void ResetPreviewContolPoint();

    /** \brief Returns whether or not the PreviewControlPoint is visible.*/
    bool IsPreviewControlPointVisible() const;

    /** \brief Returns the coordinates of the PreviewControlPoint. */
    Point2D GetPreviewControlPoint() const;

    /**
     * \brief Returns the number of features available for this PlanarFigure.
     *
     * Features are measurable quantities such as radius, length, area, angle, etc.
     *
     * \return The number of available features.
     */
    virtual unsigned int GetNumberOfFeatures() const;

    /**
     * \brief Returns the name (identifier) of the specified feature.
     *
     * \param[in] index Zero-based index of the feature.
     * \return The feature name string (e.g., "Radius", "Length", "Area").
     */
    const char *GetFeatureName(unsigned int index) const;

    /**
     * \brief Returns the physical unit of the specified feature.
     *
     * \param[in] index Zero-based index of the feature.
     * \return The unit string (e.g., "mm", "mm2", "deg").
     */
    const char *GetFeatureUnit(unsigned int index) const;

    /**
     * \brief Returns the quantity of the specified feature.
     *
     * \param[in] index Zero-based index of the feature.
     * \return The computed quantity (e.g., length in mm, area in mm2).
     */
    double GetQuantity(unsigned int index) const;

    /**
     * \brief Returns whether the specified feature is active.
     *
     * An inactive feature is one that is currently not applicable, for example
     * the area of a non-closed polygon.
     *
     * \param[in] index Zero-based index of the feature.
     * \return True if the feature exists and is active.
     */
    bool IsFeatureActive(unsigned int index) const;

    /**
     * \brief Returns whether the specified feature is set to visible.
     *
     * \param[in] index Zero-based index of the feature.
     * \return True if the feature exists and is visible.
     */
    bool IsFeatureVisible(unsigned int index) const;

    /**
     * \brief Sets the visibility of the specified feature for annotation display.
     *
     * \param[in] index   Zero-based index of the feature.
     * \param[in] visible If true, the feature will be displayed as an annotation
     *                    in the RenderWindow.
     */
    void SetFeatureVisible(unsigned int index, bool visible);

    /** \brief Calculates quantities of all features of this planar figure. */
    virtual void EvaluateFeatures();

    /** \brief Inherited from BaseData. Updates geometry information. */
    void UpdateOutputInformation() override;

    /** \brief Inherited from BaseData. Sets the requested region to the largest possible. */
    void SetRequestedRegionToLargestPossibleRegion() override;

    /** \brief Inherited from BaseData. Checks if the requested region is outside the buffered region. */
    bool RequestedRegionIsOutsideOfTheBufferedRegion() override;

    /** \brief Inherited from BaseData. Verifies the requested region. */
    bool VerifyRequestedRegion() override;

    /**
     * \brief Inherited from BaseData. Sets the requested region from another data object.
     * \param[in] data The data object from which to copy the requested region.
     */
    void SetRequestedRegion(const itk::DataObject *data) override;

    /** \brief  Returns the current number of polylines  */
    virtual unsigned short GetPolyLinesSize();

    /** \brief  Returns the current number of helperpolylines  */
    virtual unsigned short GetHelperPolyLinesSize() const;

    /** \brief Returns whether a helper polyline should be painted or not */
    virtual bool IsHelperToBePainted(unsigned int index) const;

    /** \brief Returns true if the planar figure is reset to "add points" mode
     * when a point is selected.
     *
     * Default return value is false. Subclasses can overwrite this method and
     * execute any reset / initialization statements required. */
    virtual bool ResetOnPointSelect();
    virtual bool ResetOnPointSelectNeeded() const;

    /**
     * \brief Removes the control point at the given index.
     *
     * \param[in] index Zero-based index of the control point to remove.
     */
    virtual void RemoveControlPoint(unsigned int index);

    /** \brief Removes the last control point from the figure. */
    virtual void RemoveLastControlPoint();

    /**
     * \brief Applies spatial constraints to a control point position.
     *
     * Sub-classes can override this method to enforce spatial constraints on
     * specific control points (e.g., keeping them within image bounds or
     * enforcing orthogonality). The default implementation constrains points
     * to the image bounds.
     *
     * \param[in] index The zero-based index of the control point.
     * \param[in] point The proposed 2D coordinates for the control point.
     * \return The constrained 2D coordinates.
     */
    virtual Point2D ApplyControlPointConstraints(unsigned int index, const Point2D &point);

    /**
     * \brief Compares this PlanarFigure with another for equality.
     *
     * Checks whether control points, features, and figure properties match.
     * All subclasses should implement this method for their specific comparison logic.
     *
     * \param[in] other The PlanarFigure to compare with.
     * \return True if both figures are considered equal.
     */
    virtual bool Equals(const mitk::PlanarFigure &other) const;

    /**
     * \brief Resets the number of control points to the specified count.
     *
     * \param[in] numberOfControlPoints The new number of control points.
     */
    void ResetNumberOfControlPoints(int numberOfControlPoints);

  protected:
    PlanarFigure();

    PlanarFigure(const Self &other);

    /** Adds feature (e.g., circumference, radius, angle, ...) to feature vector
     * of a planar figure object and returns integer ID for the feature element.
     * Should be called in sub-class constructors. */
    virtual unsigned int AddFeature(const char *featureName, const char *unitName);

    /** Sets the name of the specified feature. INTERNAL METHOD. */
    void SetFeatureName(unsigned int index, const char *featureName);

    /** Sets the physical unit of the specified feature. INTERNAL METHOD. */
    void SetFeatureUnit(unsigned int index, const char *unitName);

    /** Sets quantity of the specified feature. INTERNAL METHOD. */
    void SetQuantity(unsigned int index, double quantity);

    /** Sets the specified feature as active. INTERAL METHOD. */
    void ActivateFeature(unsigned int index);

    /** Sets the specified feature as active. INTERAL METHOD. */
    void DeactivateFeature(unsigned int index);

    /** \brief Generates the poly-line representation of the planar figure.
     * Must be implemented in sub-classes. */
    virtual void GeneratePolyLine() = 0;

    /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.
   * Must be implemented in sub-classes. */
    virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight) = 0;

    /** \brief Calculates quantities of all features of this planar figure.
     * Must be implemented in sub-classes. */
    virtual void EvaluateFeaturesInternal() = 0;

    /** \brief Initializes the TimeGeometry describing the (time-resolved)
     * geometry of this figure. Note that each time step holds one PlaneGeometry.
     */
    void InitializeTimeGeometry(unsigned int timeSteps = 1) override;

    /** \brief defines the number of PolyLines that will be available */
    void SetNumberOfPolyLines(unsigned int numberOfPolyLines);

    /** \brief Append a point to the PolyLine # index */
    void AppendPointToPolyLine(unsigned int index, PolyLineElement element);

    /** \brief clears the list of PolyLines. Call before re-calculating a new Polyline. */
    void ClearPolyLines();

    /** \brief defines the number of HelperPolyLines that will be available */
    void SetNumberOfHelperPolyLines(unsigned int numberOfHelperPolyLines);

    /** \brief Append a point to the HelperPolyLine # index */
    void AppendPointToHelperPolyLine(unsigned int index, PolyLineElement element);

    /** \brief clears the list of HelperPolyLines. Call before re-calculating a new HelperPolyline. */
    void ClearHelperPolyLines();

    void PrintSelf(std::ostream &os, itk::Indent indent) const override;

    ControlPointListType m_ControlPoints;
    unsigned int m_NumberOfControlPoints;

    // Currently selected control point; -1 means no point selected
    int m_SelectedControlPoint;

    std::vector<PolyLineType> m_PolyLines;
    std::vector<PolyLineType> m_HelperPolyLines;
    BoolContainerType::Pointer m_HelperPolyLinesToBePainted;

    // this point is used to store the coordinates an additional 'ControlPoint' that is rendered
    // when the mouse cursor is above the figure (and not a control-point) and when the
    // property 'planarfigure.isextendable' is set to true
    Point2D m_PreviewControlPoint;
    bool m_PreviewControlPointVisible;

    bool m_FigurePlaced;

  private:
    // not implemented to prevent PlanarFigure::New() calls which would create an itk::Object.
    static Pointer New();

    struct Feature
    {
      Feature(const char *name, const char *unit) : Name(name), Unit(unit), Quantity(0.0), Active(true), Visible(true)
      {
      }

      std::string Name;
      std::string Unit;
      double Quantity;
      bool Active;
      bool Visible;
    };

    bool m_PolyLineUpToDate;
    bool m_HelperLinesUpToDate;
    bool m_FeaturesUpToDate;

    // Vector of features available for this geometric figure
    typedef std::vector<Feature> FeatureVectorType;
    FeatureVectorType m_Features;

    unsigned long m_FeaturesMTime;

    // this pair is used to store the mmInDisplayUnits (m_DisplaySize.first) and the displayHeight
    // (m_DisplaySize.second)
    // that the helperPolyLines have been calculated for.
    // It's used to determine whether or not GetHelperPolyLine() needs to recalculate the HelperPolyLines.
    std::pair<double, unsigned int> m_DisplaySize;
  };

  /**
   * \brief Compares two PlanarFigure objects with a tolerance for floating-point values.
   *
   * \param[in] leftHandSide  The first PlanarFigure.
   * \param[in] rightHandSide The second PlanarFigure.
   * \param[in] eps           Tolerance for floating-point comparisons.
   * \param[in] verbose       If true, logs details about differences.
   * \return True if both figures are considered equal within the given tolerance.
   */
  MITKPLANARFIGURE_EXPORT bool Equal(const mitk::PlanarFigure &leftHandSide,
                                     const mitk::PlanarFigure &rightHandSide,
                                     ScalarType eps,
                                     bool verbose);

} // namespace mitk

#endif
