
//=========================================================================
//  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_vtkVolumeInspectionRepresentation_h
#define vtk_aeva_ext_vtkVolumeInspectionRepresentation_h

#include "vtk/aeva/ext/AEVAExtModule.h" // For export macro
#include "vtkAlgorithmOutput.h"
#include "vtkMapper.h"
#include "vtkNew.h"
#include "vtkScalarsToColors.h"
#include "vtkVector.h"
#include "vtkWidgetRepresentation.h"

#include <array>

class vtkActor;
// class vtkPolyDataMapper;
class vtkMapper;
class vtkConeSource;
class vtkGlyph3DMapper;
class vtkSphereSource;
class vtkProperty;
class vtkImageData;
class vtkOutlineFilter;
class vtkFeatureEdges;
class vtkPolyData;
class vtkPolyDataAlgorithm;
class vtkPropPicker;
class vtkSMTransferFunctionManager;
class vtkTransform;
class vtkBox;
class vtkPlane;
class vtkPlaneSource;
class vtkCutter;

#define VTK_MAX_CONE_RESOLUTION 2048

/**
 * @class   vtkVolumeInspectionRepresentation
 * @brief   defining the representation for a vtkConeFrustum
 *
 * This class is a concrete representation for the
 * vtkVolumeInspectionWidget. It represents 3 axis-aligned planes
 * defined by a center point in the context of a bounding box.
 * This representation can be manipulated by using the
 * vtkVolumeInspectionWidget to adjust the radii and endpoints.
 *
 * @sa
 * vtkVolumeInspectionWidget
*/
class AEVAEXT_EXPORT vtkVolumeInspectionRepresentation : public vtkWidgetRepresentation
{
public:
  /**
   * Instantiate the class.
   */
  static vtkVolumeInspectionRepresentation* New();

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

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

  //@{
  /**
   * Set/get an center point of the widget where 3 plane intersect.
   */
  void SetOrigin(double x, double y, double z);
  void SetOrigin(double xyz[3]);
  // bool SetOrigin(const vtkVector3d& pt) { return SetOrigin(pt.GetData()); }
  void GetOrigin(double xyz[3]) const;
  double* GetOrigin() VTK_SIZEHINT(3);
  //@}

  //@{
  /**
   * Set/get slice indices
   */
  void SetSlice(int i, int j, int k, bool rebuild);
  void SetSlice(int slice[3], bool rebuild);
  void SetSlice(int i, int j, int k) { this->SetSlice(i, j, k, true); }
  void SetSlice(int slice[3]) { this->SetSlice(slice, true); }
  void GetSlice(int slice[3]) const;
  const int* GetSlice() VTK_SIZEHINT(3);
  //@}

  //@{
  /**
   * Enable/disable the drawing of 3 planes.
   */
  void SetDrawPlane(vtkTypeBool drawPlane);
  vtkGetMacro(DrawPlane, vtkTypeBool);
  vtkBooleanMacro(DrawPlane, vtkTypeBool);
  //@}

  //@{
  /**
   * Enable/disable the drawing of 3 planes.
   */
  void SetDrawOrigin(vtkTypeBool drawOrigin);
  vtkGetMacro(DrawOrigin, vtkTypeBool);
  vtkBooleanMacro(DrawOrigin, vtkTypeBool);
  //@}

  //@{
  /**
   * Enable/disable the drawing of the outline.
   */
  void SetDrawOutline(vtkTypeBool val);
  vtkGetMacro(DrawOutline, vtkTypeBool);
  vtkBooleanMacro(DrawOutline, vtkTypeBool);
  //@}

  //@{
  /**
   * Turn on/off the ability to move the widget outside of the bounds
   * specified in the initial PlaceWidget() invocation.
   */
  vtkSetMacro(OutsideBounds, vtkTypeBool);
  vtkGetMacro(OutsideBounds, vtkTypeBool);
  vtkBooleanMacro(OutsideBounds, vtkTypeBool);
  //@}

  //@{
  /**
   * Set/Get the bounds of the widget representation. PlaceWidget can also be
   * used to set the bounds of the widget but it may also have other effects
   * on the internal state of the representation. Use this function when only
   * the widget bounds are needs to be modified.
   */
  vtkSetVector6Macro(WidgetBounds, double);
  vtkGetVector6Macro(WidgetBounds, double);
  //@}

  //@{
  /**
   * Turn on/off whether the plane should be constrained to the widget bounds.
   * If on, the origin will not be allowed to move outside the set widget bounds.
   * This is the default behaviour.
   * If off, the origin can be freely moved and the widget outline will change
   * accordingly.
   */
  vtkSetMacro(ConstrainToWidgetBounds, vtkTypeBool);
  vtkGetMacro(ConstrainToWidgetBounds, vtkTypeBool);
  vtkBooleanMacro(ConstrainToWidgetBounds, vtkTypeBool);
  //@}

  /**
   * 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 plane properties. The properties of the plane when selected
   * and unselected can be manipulated.
   */
  vtkGetObjectMacro(PlaneProperty, vtkProperty);
  vtkGetObjectMacro(SelectedPlaneProperty, vtkProperty);
  //@}

  //@{
  /**
   * Set the image data to be renderered
   * UUID identifies the piece of the multiblock surface.
   */
  // void SetImage(vtkAlgorithmOutput*);
  // vtkAlgorithmOutput* GetImage();
  vtkSetObjectMacro(Image, vtkAlgorithmOutput);
  vtkGetObjectMacro(Image, vtkAlgorithmOutput);
  vtkGetStringMacro(ImageID);
  vtkSetStringMacro(ImageID);
  void UpdateMapper();
  //@}

  //@{
  /**
   * Update the plane-mappers to use the representation's lookup table.
   */
  vtkGetObjectMacro(LookupTable, vtkScalarsToColors);
  virtual void SetLookupTable(vtkScalarsToColors* lkup);
  //@}

  //@{
  /**
   * Methods to interface with the vtkVolumeInspectionWidget.
   */
  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 to set/get visual properties of the widget.
   *
   * When the mouse hovers over a plane, it is selected and rendered
   * with a different ambient color and opacity.
   *
   * All component values must be in [0,1].
   */
  virtual double* GetPlaneColor() VTK_SIZEHINT(4);
  virtual VTK_WRAPEXCLUDE void GetPlaneColor(double& red,
    double& green,
    double& blue,
    double& alpha);
  virtual VTK_WRAPEXCLUDE void GetPlaneColor(double* rgba)
  {
    this->GetPlaneColor(rgba[0], rgba[1], rgba[2], rgba[3]);
  }

  virtual void SetPlaneColor(const double* rgba) VTK_SIZEHINT(4);
  virtual void SetPlaneColor(double red, double green, double blue, double alpha)
  {
    double tmp[4] = { red, green, blue, alpha };
    this->SetPlaneColor(tmp);
  }

  virtual double* GetSelectedPlaneColor() VTK_SIZEHINT(4);
  virtual VTK_WRAPEXCLUDE void GetSelectedPlaneColor(double& red,
    double& green,
    double& blue,
    double& alpha);
  virtual VTK_WRAPEXCLUDE void GetSelectedPlaneColor(double* rgba)
  {
    this->GetSelectedPlaneColor(rgba[0], rgba[1], rgba[2], rgba[3]);
  }

  virtual void SetSelectedPlaneColor(const double* rgba) VTK_SIZEHINT(4);
  virtual void SetSelectedPlaneColor(double red, double green, double blue, double alpha)
  {
    double tmp[4] = { red, green, blue, alpha };
    this->SetSelectedPlaneColor(tmp);
  }
  //@}

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

  // Manage the state of the widget
  enum _InteractionState
  {
    Outside = 0,
    Moving,
    MovingOrigin,
    PushingX,
    PushingY,
    PushingZ
  };

  static std::string InteractionStateToString(int state);

  //@{
  /**
   * The interaction state may be set from a widget (e.g.,
   * vtkVolumeInspectionWidget) 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, PushingZ);
  //@}

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

  void SetInputData(vtkImageData* in);

protected:
  vtkVolumeInspectionRepresentation();
  ~vtkVolumeInspectionRepresentation() override;

  enum ElementType
  {
    Origin = 0,
    PlaneX,
    PlaneY,
    PlaneZ,
    Outline,
    NumberOfElements
  };

  struct Element
  {
    vtkSmartPointer<vtkActor> Actor;
    vtkSmartPointer<vtkMapper> Mapper;
  };

  std::array<Element, NumberOfElements> Elements;

  void HighlightElement(ElementType elem, int highlight);

  int RepresentationState;
  int TranslationAxis;

  // Keep track of event positions
  double LastEventPosition[3];
  double LastEventOrientation[4];
  double StartEventOrientation[4];

  double SnappedEventOrientation[4];

  // The bounding box is represented by a single voxel image data
  vtkNew<vtkImageData> Box;
  vtkNew<vtkOutlineFilter> OutlineBox;
  vtkTypeBool OutsideBounds; // whether the widget can be moved outside input's bounds
  double WidgetBounds[6];
  vtkTypeBool ConstrainToWidgetBounds;
  void HighlightOutline(int highlight);

  // The cut plane is produced with a vtkCutter
  vtkTypeBool DrawPlane;
  vtkTypeBool DrawOutline;
  vtkTypeBool DrawOrigin;
  void HighlightPlane(ElementType plane, int highlight);
  int Slice[3];
  vtkAlgorithmOutput* Image;
  char* ImageID;
  double origin[3];
  int planePerm[3];

  // The origin positioning handle
  vtkNew<vtkSphereSource> OriginSphere;
  void HighlightOrigin(int highlight);

  // Do the picking
  vtkNew<vtkPropPicker> Picker;
  double LastPickPosition[3];

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

  // Methods to manipulate the plane
  void TranslateOrigin(const double* p1, const double* p2);
  void PushSlice(int iPlane, double d);
  void PushSlice(int iPlane, const double* p1, const double* p2);
  void SizeOrigin();

  // Properties used to control the appearance of selected objects and
  // the manipulator in general.
  vtkProperty* PlaneProperty;
  vtkProperty* SelectedPlaneProperty;
  vtkProperty* OutlineProperty;
  vtkProperty* SelectedOutlineProperty;
  vtkProperty* SphereProperty;
  vtkProperty* SelectedSphereProperty;
  void CreateDefaultProperties();

  void GeneratePlane();

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

  vtkScalarsToColors* LookupTable{ nullptr };

private:
  void SliceToOrigin(const int slice[3], double origin[3]) const;
  void OriginToSlice(const double org[3], int slice[3]) const;
  bool InBuildRepresentation{ false };
};

#endif
