//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================
#ifndef vtk_aeva_ext_vtkProportionalEditRepresentation_h
#define vtk_aeva_ext_vtkProportionalEditRepresentation_h

#include "vtk/aeva/ext/AEVAExtModule.h" // For export macro

#include "vtkAlgorithmOutput.h"
#include "vtkNew.h"
// #include "vtkPolyData.h"
#include "vtkVector.h"
#include "vtkWidgetRepresentation.h"

#include <array>

class vtkActor;
class vtkPolyDataMapper;
class vtkCellPicker;
class vtkGlyph3DMapper;
class vtkSphereSource;
class vtkTubeFilter;
class vtkProportionalEditElements;
class vtkProportionalEditFilter;
class vtkProperty;
class vtkPolyDataAlgorithm;
class vtkTransform;
class vtkBox;
class vtkLookupTable;

#define VTK_MAX_CONE_RESOLUTION 2048

/**
 * @class   vtkProportionalEditRepresentation
 * @brief   defining the representation for a vtkProportionalEditElements
 *
 * This class is a concrete representation for the
 * vtkProportionalEditWidget.
 *
 * @sa
 * vtkProportionalEditWidget, vtkProportionalEditElements
*/
class AEVAEXT_EXPORT vtkProportionalEditRepresentation : public vtkWidgetRepresentation
{
public:
  /**
   * Instantiate the class.
   */
  static vtkProportionalEditRepresentation* New();

  vtkProportionalEditRepresentation(const vtkProportionalEditRepresentation&) = delete;
  vtkProportionalEditRepresentation& operator=(const vtkProportionalEditRepresentation&) = delete;

  //@{
  /**
   * Standard methods for the class.
   */
  vtkTypeMacro(vtkProportionalEditRepresentation, vtkWidgetRepresentation);
  void PrintSelf(ostream& os, vtkIndent indent) override;
  //@}

  //@{
  /**
   * Set/get an endpoint.
   */
  bool SetEndpoint(bool isBottom, double x, double y, double z);
  bool SetEndpoint(bool isBottom, const vtkVector3d& pt);
  vtkVector3d GetEndpoint(bool isBottom) const;

  void SetBottomPoint(double x, double y, double z) { this->SetEndpoint(true, x, y, z); }
  void SetBottomPoint(double* x) VTK_SIZEHINT(3) { this->SetEndpoint(true, x[0], x[1], x[2]); }
  double* GetBottomPoint() VTK_SIZEHINT(3);

  void SetTopPoint(double x, double y, double z) { this->SetEndpoint(false, x, y, z); }
  void SetTopPoint(double* x) VTK_SIZEHINT(3) { this->SetEndpoint(false, x[0], x[1], x[2]); }
  double* GetTopPoint() VTK_SIZEHINT(3);
  //@}

  //@{
  /**
   * Set/get the displacement vector.
   */
  void SetDisplacement(double x, double y, double z);
  void SetDisplacement(double* disp) VTK_SIZEHINT(3);
  double* GetDisplacement() VTK_SIZEHINT(3);
  //@}

  //@{
  /**
   * Set/get the projection vector.
   */
  void SetProjectionVector(double x, double y, double z);
  void SetProjectionVector(double* proj) VTK_SIZEHINT(3);
  double* GetProjectionVector() VTK_SIZEHINT(3);
  //@}

  //@{
  /**
   * Set/get the radius of influence.
   *
   * Negative values are generally a bad idea but not prohibited at this point.
   * They will result in bad surface normals, though.
   */
  bool SetRadius(double r);
  double GetRadius() const;
  //@}

  //@{
  /**
   * Use a cylinder rather than a sphere for indicating influence region.
   */
  bool SetCylindrical(int isCylindrical);
  vtkGetMacro(Cylindrical, int);
  vtkBooleanMacro(Cylindrical, int);
  //@}

  //@{
  /**
   * Edge color for preview geometry
   */
  vtkSetVector3Macro(EdgeColor, double);
  vtkGetVector3Macro(EdgeColor, double);
  //@}

  //@{
  /**
   * Force the displacement vector to be aligned with one of the x-y-z axes.
   * If one axis is set on, the other two will be set off.
   * Remember that when the state changes, a ModifiedEvent is invoked.
   * This can be used to snap to the axes if it is originally
   * not aligned.
   */
  void SetAlongXAxis(vtkTypeBool var);
  vtkGetMacro(AlongXAxis, vtkTypeBool);
  vtkBooleanMacro(AlongXAxis, vtkTypeBool);
  void SetAlongYAxis(vtkTypeBool var);
  vtkGetMacro(AlongYAxis, vtkTypeBool);
  vtkBooleanMacro(AlongYAxis, vtkTypeBool);
  void SetAlongZAxis(vtkTypeBool var);
  vtkGetMacro(AlongZAxis, vtkTypeBool);
  vtkBooleanMacro(AlongZAxis, vtkTypeBool);
  //@}

  //@{
  /**
   * Enable/disable the drawing of the influence region. In some cases the sphere/cylinder
   * interferes with the object that it is operating on.
   */
  void SetDrawRegion(vtkTypeBool drawCyl);
  vtkGetMacro(DrawRegion, vtkTypeBool);
  vtkBooleanMacro(DrawRegion, vtkTypeBool);
  //@}

  //@{
  /**
   * Set/Get the resolution of the spherer/cylinder. This is the number of
   * polygonal facets used to approximate the curved
   * surface (for rendering purposes).
   */
  vtkSetClampMacro(Resolution, int, 8, VTK_MAX_CONE_RESOLUTION);
  vtkGetMacro(Resolution, int);
  //@}

  //@{
  /**
   * Set/Get the tolerance.
   *
   * This how close the endpoints are allowed to be.
   * It is initially set to 1e-8.
   *
   * Note that this is an absolute distance in world coordinates,
   * so if your scene is very small, you may need to adjust this.
   */
  vtkSetClampMacro(Tolerance, double, 0.0, VTK_DOUBLE_MAX);
  vtkGetMacro(Tolerance, double);
  //@}

  //@{
  /**
   * Turn on/off tubing of the wire outline of the cylinder
   * intersection (against the bounding box). The tube thickens the
   * line by wrapping with a vtkTubeFilter.
   */
  vtkSetMacro(Tubing, vtkTypeBool);
  vtkGetMacro(Tubing, vtkTypeBool);
  vtkBooleanMacro(Tubing, vtkTypeBool);
  //@}

  //@{
  /**
   * Turn on/off the ability to scale the widget with the mouse.
   */
  vtkSetMacro(ScaleEnabled, vtkTypeBool);
  vtkGetMacro(ScaleEnabled, vtkTypeBool);
  vtkBooleanMacro(ScaleEnabled, vtkTypeBool);
  //@}

  //@{
  /**
   * Set the geometry to be deformed as a preview.
   * UUID identifies the piece of the multiblock surface.
   */
  vtkSetObjectMacro(Surface, vtkAlgorithmOutput);
  vtkGetObjectMacro(Surface, vtkAlgorithmOutput);
  vtkGetStringMacro(SurfaceID);
  vtkSetStringMacro(SurfaceID);
  //@}

  /**
   * Satisfies the superclass API.  This will change the state of the widget
   * to match changes that have been made to the underlying PolyDataSource.
   */
  void UpdatePlacement();

  //@{
  /**
   * Get the properties on the axis (line and endpoints).
   */
  vtkGetObjectMacro(HandleProperty, vtkProperty);
  vtkGetObjectMacro(SelectedHandleProperty, vtkProperty);
  //@}

  //@{
  /**
   * Get the sphere/cylinder region properties. The properties of the cylinder when selected
   * and unselected can be manipulated.
   */
  vtkGetObjectMacro(RegionProperty, vtkProperty);
  vtkGetObjectMacro(SelectedRegionProperty, vtkProperty);
  //@}

  //@{
  /**
   * Get the property of the axes. (This property also
   * applies to the axes when tubed.)
   */
  vtkGetObjectMacro(AxisProperty, vtkProperty);
  //@}

  //@{
  /**
   * Methods to interface with the vtkWidget.
   */
  int ComputeInteractionState(int X, int Y, int modify = 0) override;
  void PlaceWidget(double bounds[6]) override;
  void BuildRepresentation() override;
  void StartWidgetInteraction(double eventPos[2]) override;
  void WidgetInteraction(double newEventPos[2]) override;
  void EndWidgetInteraction(double newEventPos[2]) override;
  //@}

  //@{
  /**
   * Methods supporting the rendering process.
   */
  double* GetBounds() override;
  void GetActors(vtkPropCollection* pc) override;
  void ReleaseGraphicsResources(vtkWindow* window) override;
  int RenderOpaqueGeometry(vtkViewport* viewport) override;
  int RenderTranslucentPolygonalGeometry(vtkViewport* viewport) override;
  vtkTypeBool HasTranslucentPolygonalGeometry() override;
  //@}

  //@{
  /**
   * Specify a translation distance used by the BumpCone() method. Note that the
   * distance is normalized; it is the fraction of the length of the bounding
   * box of the wire outline.
   */
  vtkSetClampMacro(BumpDistance, double, 0.000001, 1);
  vtkGetMacro(BumpDistance, double);
  //@}

  /**
   * Translate the cylinder in the direction of the view vector by the
   * specified BumpDistance. The dir parameter controls which
   * direction the pushing occurs, either in the same direction as the
   * view vector, or when negative, in the opposite direction.  The factor
   * controls what percentage of the bump is used.
   */
  void BumpCone(int dir, double factor);

  /**
   * Push the cylinder the distance specified along the view
   * vector. Positive values are in the direction of the view vector;
   * negative values are in the opposite direction. The distance value
   * is expressed in world coordinates.
   */
  void PushCone(double distance);

  // Manage the state of the widget
  enum _InteractionState
  {
    Outside = 0,
    Moving,
    AdjustingRadius,
    MovingBottomHandle,
    MovingTopHandle,
    MovingProjectionHandle,
    MovingWhole,
    RotatingAxis,
    TranslatingCenter,
    Scaling
  };

  static std::string InteractionStateToString(int state);

  //@{
  /**
   * The interaction state may be set from a widget (e.g.,
   * vtkImplicitCylinderWidget) or other object. This controls how the
   * interaction with the widget proceeds. Normally this method is used as
   * part of a handshaking process with the widget: First
   * ComputeInteractionState() is invoked that returns a state based on
   * geometric considerations (i.e., cursor near a widget feature), then
   * based on events, the widget may modify this further.
   */
  vtkSetClampMacro(InteractionState, int, Outside, TranslatingCenter);
  //@}

  //@{
  /**
   * Sets the visual appearance of the representation based on the
   * state it is in. This state is usually the same as InteractionState.
   */
  virtual void SetRepresentationState(int state);
  vtkGetMacro(RepresentationState, int);
  //@}

  /*
  * Register internal Pickers within PickingManager
  */
  void RegisterPickers() override;

protected:
  vtkProportionalEditRepresentation();
  ~vtkProportionalEditRepresentation() override;

  /// Visual elements of the representation.
  enum ElementType
  {
    BottomHandle = 0,
    TopHandle,
    ProjectionHandle,
    DisplacementAxis,
    Sphere,
    Cylinder,
    Preview,
    NumberOfElements
  };

  void HighlightElement(ElementType elem, int highlight);

  void HighlightRegion(int highlight);
  void HighlightAxis(int highlight);
  void HighlightHandle(bool isBottom, int highlight);
  void HighlightProjectionHandle(int highlight);

  // Methods to manipulate the cylinder
  void Rotate(double X, double Y, const double* p1, const double* p2, double* vpn);
  void AdjustRadius(double X, double Y, const double* p1, const double* p2);
  void TranslateCenter(const double* p1, const double* p2);
  void TranslateCenterOnAxis(const double* p1, const double* p2);
  void TranslateHandle(bool isBottom, const double* p1, const double* p2);
  void TranslateProjectionHandle(const double* p1, const double* p2);
  void Scale(const double* p1, const double* p2, double X, double Y);
  void SizeHandles();

  void CreateDefaultProperties();
  void BuildPreviewGeometry();

  struct Element
  {
    vtkNew<vtkActor> Actor;
    vtkNew<vtkPolyDataMapper> Mapper;
  };

  /// Actors and mappers for all visual elements of the representation.
  std::array<Element, NumberOfElements> Elements;

  int RepresentationState;
  /// Keep track of event positions
  double LastEventPosition[3];
  /// Controlling the push operation
  double BumpDistance{ 0.01 };
  /// Controlling ivars
  vtkTypeBool AlongXAxis{ false };
  vtkTypeBool AlongYAxis{ false };
  vtkTypeBool AlongZAxis{ false };
  /// The actual geometry which is being manipulated
  vtkNew<vtkProportionalEditElements> Geometry;

  /// The filter we use to generate temporary changes
  vtkNew<vtkProportionalEditFilter> Filter;

  /// The facet resolution for rendering purposes.
  int Resolution{ 16 };
  double Tolerance{ 1e-8 };      ///< How close are endpoints allowed to be?
  vtkTypeBool ScaleEnabled{ 1 }; ///< whether the widget can be scaled
  vtkTypeBool DrawRegion{ 0 };
  vtkNew<vtkTubeFilter> AxisTuber; ///< Used to style edges.
  vtkTypeBool Tubing{ true };      /// <control whether tubing is on
  int Cylindrical{ 0 };            ///< control whether sphere or cylinder
  double EdgeColor[3] = { 0., 0., 0. };
  vtkVector3d DisplacementVector;
  vtkVector3d ProjectionVector;
  /// the preview geometry input
  vtkAlgorithmOutput* Surface{ nullptr };
  char* SurfaceID{ nullptr };

  /// Source of endpoint handle geometry
  vtkNew<vtkSphereSource> HandleSphere;

  /// Do the picking
  vtkNew<vtkCellPicker> Picker;
  vtkNew<vtkCellPicker> RegionPicker;

  /// Properties used to control the appearance of selected objects and
  /// the manipulator in general.
  vtkNew<vtkProperty> HandleProperty;
  vtkNew<vtkProperty> SelectedHandleProperty;
  vtkNew<vtkProperty> RegionProperty;
  vtkNew<vtkProperty> SelectedRegionProperty;
  vtkNew<vtkProperty> AxisProperty;
  vtkNew<vtkProperty> SelectedAxisProperty;

  /// Support GetBounds() method
  vtkNew<vtkBox> BoundingBox;

  /// Overrides for the point-handle polydata mappers
  vtkNew<vtkGlyph3DMapper> BottomHandleMapper;
  vtkNew<vtkGlyph3DMapper> TopHandleMapper;
  vtkNew<vtkGlyph3DMapper> ProjectionHandleMapper;

  vtkNew<vtkTransform> Transform;
};

#endif
