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

  Program:   Visualization Toolkit
  Module:    vtkProportionalEditFilter.h

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm 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.

=========================================================================*/
/**
 * @class   vtkProportionalEditFilter
 * @brief   transform selected vertex and move nearby vertices proportionally
 *
 * There are two instance variables that control the generation of error
 * data. If the ivar GenerateEditDistance is on, then a scalar value indicating
 * the distance of each vertex from its original position is computed. If the
 * ivar GenerateEditVector is on, then a vector representing change in
 * position is computed.
 *
 * @warning
 * The editing operation does not guarrantee the mesh quality
 */

#ifndef vtk_aeva_ext_vtkProportionalEditFilter_h
#define vtk_aeva_ext_vtkProportionalEditFilter_h

#include <map>

#include "vtk/aeva/ext/AEVAExtModule.h"

#include "vtkMath.h"
#include "vtkMinimalStandardRandomSequence.h"
#include "vtkPolyDataAlgorithm.h"
#include "vtkSetGet.h"

class AEVAEXT_EXPORT vtkProportionalEditFilter : public vtkPolyDataAlgorithm
{
public:
  vtkTypeMacro(vtkProportionalEditFilter, vtkPolyDataAlgorithm);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /**
   * Construct object with InfluenceRadius 0.0, Projected false, Smooth Falloff.
   * EditDistance and EditVector are not generated by default.
   */
  static vtkProportionalEditFilter* New();

  //@{
  /**
   * Specify the radius of the influence region for smooth transition
   */
  vtkSetMacro(InfluenceRadius, double);
  vtkGetMacro(InfluenceRadius, double);
  //@}

  //@{
  /**
   * Specify the displacement of the vertex to be edited
   */
  vtkSetVector3Macro(AnchorPointCoords, double);
  vtkGetVector3Macro(AnchorPointCoords, double);
  //@}

  //@{
  /**
   * Specify the displacement of the vertex to be edited
   */
  vtkSetVector3Macro(AnchorPointDisplacement, double);
  vtkGetVector3Macro(AnchorPointDisplacement, double);
  //@}

  //@{
  /**
   * If the editing is projected in 2D, ie, true if the influence region is a infinite-long
   * cylinder; false if a ball
   */
  vtkSetMacro(Projected, vtkTypeBool);
  vtkGetMacro(Projected, vtkTypeBool);
  vtkBooleanMacro(Projected, vtkTypeBool);
  //@}

  //@{
  /**
   * Specify the projection direction; used only if \p Projected is true
   */
  vtkSetVector3Macro(ProjectionDirection, double);
  vtkGetVector3Macro(ProjectionDirection, double);
  //@}
  //
  //@{
  /**
   * Turn on/off the generation of scalar distance values.
   */
  vtkSetMacro(GenerateEditDistance, vtkTypeBool);
  vtkGetMacro(GenerateEditDistance, vtkTypeBool);
  vtkBooleanMacro(GenerateEditDistance, vtkTypeBool);
  //@}

  //@{
  /**
   * Turn on/off the generation of error vectors.
   */
  vtkSetMacro(GenerateEditVector, vtkTypeBool);
  vtkGetMacro(GenerateEditVector, vtkTypeBool);
  vtkBooleanMacro(GenerateEditVector, vtkTypeBool);
  //@}

  //@{
  /**
   * Set/get the desired precision for the output types. See the documentation
   * for the vtkAlgorithm::DesiredOutputPrecision enum for an explanation of
   * the available precision settings.
   */
  vtkSetMacro(OutputPointsPrecision, int);
  vtkGetMacro(OutputPointsPrecision, int);
  //@}

  enum FalloffType
  {
    Linear = 0,
    Smooth,
    Constant,
    Random,
    Sphere,
    InverseSquare,
    Sharp
  };
  void SetFalloff(const std::string& name) { falloff.SetType(name); }
  void SetFalloff(const FalloffType& type) { falloff.SetType(type); }
  FalloffType GetFalloff() const { return falloff.GetType(); }
  std::string GetFalloffTypeName() const { return falloff.GetTypeName(); }

private:
  /*
   * Falloff functor
   */
  class Falloff
  {
  public:
    using FalloffType = vtkProportionalEditFilter::FalloffType;
    Falloff(const char* name = "Smooth") { this->SetType(name); }

    double operator()(const double dist) const { return this->eval(dist); }

    FalloffType GetType() const { return this->type; }
    std::string GetTypeName() const { return Falloff::typeToName.at(this->type); }

    void SetType(const std::string& name);
    void SetType(const FalloffType& type);

  private:
    static double LinearEval(const double dist) { return 1.0 - dist; }
    static double ConstantEval(const double /*dist*/) { return 1.0; }
    static double RandomEval(const double /*dist*/)
    {
      return static_cast<double>(std::rand()) / RAND_MAX;
    }
    static double SmoothEval(const double dist) { return 0.5 * (cos(dist * vtkMath::Pi()) + 1); }
    static double SphereEval(const double dist) { return sqrt(1.0 - dist * dist); }
    static double InverseSquareEval(const double dist) { return 1.0 - dist * dist; }
    static double SharpEval(const double dist) { return (1.0 - dist) * (1.0 - dist); };
    // maps between type and name
    static const std::map<std::string, FalloffType> nameToType;
    static const std::map<FalloffType, std::string> typeToName;

    FalloffType type;
    double (*eval)(const double dist);
  }; // Fallout

protected:
  vtkProportionalEditFilter();
  ~vtkProportionalEditFilter() override = default;

  int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override;
  // int FillInputPortInformation(int port, vtkInformation* info) override;

  Falloff falloff;
  double InfluenceRadius;
  double AnchorPointDisplacement[3];
  double AnchorPointCoords[3];
  vtkTypeBool Projected;
  double ProjectionDirection[3];
  vtkTypeBool GenerateEditDistance;
  vtkTypeBool GenerateEditVector;
  int OutputPointsPrecision;

private:
  vtkProportionalEditFilter(const vtkProportionalEditFilter&) = delete;
  void operator=(const vtkProportionalEditFilter&) = delete;
};
#endif
