// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
/**
 * @class   vtkDendrogramItem
 * @brief   A 2D graphics item for rendering a tree as
 * a dendrogram
 *
 *
 * Draw a tree as a dendrogram
 * The input tree's vertex data must contain at least two arrays.
 * The first required array is a vtkStringArray called "node name".
 * This array is used to label the leaf nodes of the tree.
 * The second required array is a scalar array called "node weight".
 * This array is used by vtkTreeLayoutStrategy to set any particular
 * node's distance from the root of the tree.
 *
 * The vtkNewickTreeReader automatically initializes both of these
 * required arrays in its output tree.
 *
 * .SEE ALSO
 * vtkTree vtkNewickTreeReader
 */

#ifndef vtkDendrogramItem_h
#define vtkDendrogramItem_h

#include "vtkContextItem.h"
#include "vtkViewsInfovisModule.h" // For export macro

#include "vtkNew.h"           // For vtkNew ivars
#include "vtkSmartPointer.h"  // For vtkSmartPointer ivars
#include "vtkStdString.h"     // For SetGet ivars
#include "vtkVector.h"        // For vtkVector2f ivar
#include "vtkWrappingHints.h" // For VTK_MARSHALAUTO

VTK_ABI_NAMESPACE_BEGIN
class vtkColorLegend;
class vtkDoubleArray;
class vtkGraphLayout;
class vtkLookupTable;
class vtkPruneTreeFilter;
class vtkTree;

class VTKVIEWSINFOVIS_EXPORT VTK_MARSHALAUTO vtkDendrogramItem : public vtkContextItem
{
public:
  static vtkDendrogramItem* New();
  vtkTypeMacro(vtkDendrogramItem, vtkContextItem);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /**
   * Set the tree that this item draws.  Note that this tree's vertex data
   * must contain a vtkStringArray called "node name".  The vtkNewickTreeReader
   * automatically creates this required array for you.
   */
  virtual void SetTree(vtkTree* tree);

  /**
   * Get the tree that this item draws.
   */
  vtkTree* GetTree();

  /**
   * Collapse subtrees until there are only n leaf nodes left in the tree.
   * The leaf nodes that remain are those that are closest to the root.
   * Any subtrees that were collapsed prior to this function being called
   * may be re-expanded.
   */
  void CollapseToNumberOfLeafNodes(unsigned int n);

  /**
   * Get the collapsed tree
   */
  vtkTree* GetPrunedTree();

  /**
   * Indicate which array within the Tree's VertexData should be used to
   * color the tree.  The specified array must be a vtkDoubleArray.
   * By default, the tree will be drawn in black.
   */
  void SetColorArray(const char* arrayName);

  ///@{
  /**
   * Get/set whether or not leaf nodes should be extended so that they all line
   * up vertically.  The default is to NOT extend leaf nodes.  When extending
   * leaf nodes, the extra length is drawn in grey so as to distinguish it from
   * the actual length of the leaf node.
   */
  vtkSetMacro(ExtendLeafNodes, bool);
  vtkGetMacro(ExtendLeafNodes, bool);
  vtkBooleanMacro(ExtendLeafNodes, bool);
  ///@}

  /**
   * Set which way the tree should face within the visualization.  The default
   * is for the tree to be drawn left to right.
   */
  void SetOrientation(int orientation);

  /**
   * Get the current tree orientation.
   */
  int GetOrientation();

  /**
   * Get the rotation angle (in degrees) that corresponds to the given
   * tree orientation.  For the default orientation (LEFT_TO_RIGHT), this
   * is 90 degrees.
   */
  double GetAngleForOrientation(int orientation);

  /**
   * Get the angle that vertex labels should be rotated for the corresponding
   * tree orientation.  For the default orientation (LEFT_TO_RIGHT), this
   * is 0 degrees.
   */
  double GetTextAngleForOrientation(int orientation);

  ///@{
  /**
   * Get/Set whether or not leaf nodes should be labeled by this class.
   * Default is true.
   */
  vtkSetMacro(DrawLabels, bool);
  vtkGetMacro(DrawLabels, bool);
  vtkBooleanMacro(DrawLabels, bool);
  ///@}

  ///@{
  /**
   * Set the position of the dendrogram.
   */
  vtkSetVector2Macro(Position, float);
  void SetPosition(const vtkVector2f& pos);
  ///@}

  ///@{
  /**
   * Get position of the dendrogram.
   */
  vtkGetVector2Macro(Position, float);
  vtkVector2f GetPositionVector();
  ///@}

  ///@{
  /**
   * Get/Set the spacing between the leaf nodes in our dendrogram.
   * Default is 18 pixels.
   */
  vtkGetMacro(LeafSpacing, double);
  vtkSetMacro(LeafSpacing, double);
  ///@}

  /**
   * This function calls RebuildBuffers() if necessary.
   * Once PrepareToPaint() has been called, GetBounds() is guaranteed
   * to provide useful information.
   */
  void PrepareToPaint(vtkContext2D* painter);

  /**
   * Get the bounds for this item as (Xmin,Xmax,Ymin,Ymax).
   * These bounds are only guaranteed to be accurate after Paint() or
   * PrepareToPaint() has been called.
   */
  virtual void GetBounds(double bounds[4]);

  /**
   * Compute the width of the longest leaf node label.
   */
  void ComputeLabelWidth(vtkContext2D* painter);

  /**
   * Get the width of the longest leaf node label.
   */
  float GetLabelWidth();

  /**
   * Find the position of the vertex with the specified name.  Store
   * this information in the passed array.  Returns true if the vertex
   * was found, false otherwise.
   */
  bool GetPositionOfVertex(const std::string& vertexName, double position[2]);

  /**
   * Paints the input tree as a dendrogram.
   */
  bool Paint(vtkContext2D* painter) override;

  ///@{
  /**
   * Get/Set how wide the edges of this dendrogram should be.  Default is one pixel.
   */
  vtkGetMacro(LineWidth, float);
  vtkSetMacro(LineWidth, float);
  ///@}

  ///@{
  /**
   * Get/set whether or not the number of collapsed leaf nodes should be written
   * inside the triangle representing a collapsed subtree.  Default is true.
   */
  vtkSetMacro(DisplayNumberOfCollapsedLeafNodes, bool);
  vtkGetMacro(DisplayNumberOfCollapsedLeafNodes, bool);
  vtkBooleanMacro(DisplayNumberOfCollapsedLeafNodes, bool);
  ///@}

  ///@{
  /**
   * Get/Set the name of the array that specifies the distance of each vertex
   * from the root (NOT the vertex's parent).  This array should be a part of
   * the input tree's VertexData.  By default, this value is "node weight",
   * which is the name of the array created by vtkNewickTreeReader.
   */
  vtkGetMacro(DistanceArrayName, vtkStdString);
  vtkSetMacro(DistanceArrayName, vtkStdString);
  ///@}

  ///@{
  /**
   * Get/Set the name of a vtkStringArray that specifies the names of the
   * vertices of the input tree.  This array should be a part of the input
   * tree's VertexData.  By default, this value is "node name", which is the
   * name of the array created by vtkNewickTreeReader.
   */
  vtkGetMacro(VertexNameArrayName, vtkStdString);
  vtkSetMacro(VertexNameArrayName, vtkStdString);
  ///@}

  // this struct & class allow us to generate a priority queue of vertices.
  struct WeightedVertex
  {
    vtkIdType ID;
    double weight;
  };
  class CompareWeightedVertices
  {
  public:
    // Returns true if v2 is higher priority than v1
    bool operator()(WeightedVertex& v1, WeightedVertex& v2)
    {
      if (v1.weight <= v2.weight)
      {
        return false;
      }
      return true;
    }
  };

  /**
   * Enum for Orientation.
   */
  enum
  {
    LEFT_TO_RIGHT,
    UP_TO_DOWN,
    RIGHT_TO_LEFT,
    DOWN_TO_UP
  };

  /**
   * Returns true if the transform is interactive, false otherwise.
   */
  bool Hit(const vtkContextMouseEvent& mouse) override;

  /**
   * Collapse or expand a subtree when the user double clicks on an
   * internal node.
   */
  bool MouseDoubleClickEvent(const vtkContextMouseEvent& event) override;

protected:
  vtkDendrogramItem();
  ~vtkDendrogramItem() override;

  vtkVector2f PositionVector;
  float* Position;

  /**
   * Generate some data needed for painting.  We cache this information as
   * it only needs to be generated when the input data changes.
   */
  virtual void RebuildBuffers();

  /**
   * This function does the bulk of the actual work in rendering our dendrogram
   */
  virtual void PaintBuffers(vtkContext2D* painter);

  /**
   * This function returns a bool indicating whether or not we need to rebuild
   * our cached data before painting.
   */
  virtual bool IsDirty();

  /**
   * Compute how to scale our data so that text labels will fit within the
   * bounds determined by the spacing between the leaf nodes of the tree.
   */
  void ComputeMultipliers();

  /**
   * Compute the bounds of our tree in pixel coordinates.
   */
  void ComputeBounds();

  /**
   * Count the number of leaf nodes in the tree
   */
  void CountLeafNodes();

  /**
   * Count the number of leaf nodes that descend from a given vertex.
   */
  int CountLeafNodes(vtkIdType vertex);

  /**
   * Get the tree vertex closest to the specified coordinates.
   */
  vtkIdType GetClosestVertex(double x, double y);

  /**
   * Collapse the subtree rooted at vertex.
   */
  void CollapseSubTree(vtkIdType vertex);

  /**
   * Expand the previously collapsed subtree rooted at vertex.
   */
  void ExpandSubTree(vtkIdType vertex);

  /**
   * Look up the original ID of a vertex in the pruned tree.
   */
  vtkIdType GetOriginalId(vtkIdType vertex);

  /**
   * Look up the ID of a vertex in the pruned tree from a vertex ID
   * of the input tree.
   */
  vtkIdType GetPrunedIdForOriginalId(vtkIdType originalId);

  /**
   * Check if the click at (x, y) should be considered as a click on
   * a collapsed subtree.  Returns the vtkIdType of the pruned subtree
   * if so, -1 otherwise.
   */
  vtkIdType GetClickedCollapsedSubTree(double x, double y);

  /**
   * Calculate the extent of the data that is visible within the window.
   * This information is used to ensure that we only draw details that
   * will be seen by the user.  This improves rendering speed, particularly
   * for larger data.
   */
  void UpdateVisibleSceneExtent(vtkContext2D* painter);

  /**
   * Returns true if any part of the line segment defined by endpoints
   * (x0, y0), (x1, y1) falls within the extent of the currently
   * visible scene.  Returns false otherwise.
   */
  bool LineIsVisible(double x0, double y0, double x1, double y1);

  /**
   * Internal function.  Use SetOrientation(int orientation) instead.
   */
  void SetOrientation(vtkTree* tree, int orientation);

  // Setup the position, size, and orientation of this dendrogram's color
  // legend based on the dendrogram's current orientation.
  void PositionColorLegend();

  vtkSmartPointer<vtkTree> Tree;
  vtkSmartPointer<vtkTree> LayoutTree;

private:
  vtkDendrogramItem(const vtkDendrogramItem&) = delete;
  void operator=(const vtkDendrogramItem&) = delete;

  vtkSmartPointer<vtkTree> PrunedTree;
  vtkMTimeType DendrogramBuildTime;
  vtkNew<vtkGraphLayout> Layout;
  vtkNew<vtkPruneTreeFilter> PruneFilter;
  vtkNew<vtkLookupTable> TriangleLookupTable;
  vtkNew<vtkLookupTable> TreeLookupTable;
  vtkNew<vtkColorLegend> ColorLegend;
  vtkDoubleArray* ColorArray;
  double MultiplierX;
  double MultiplierY;
  int NumberOfLeafNodes;
  double LeafSpacing;

  double MinX;
  double MinY;
  double MaxX;
  double MaxY;
  double SceneBottomLeft[3];
  double SceneTopRight[3];
  float LabelWidth;
  float LineWidth;
  bool ColorTree;
  bool ExtendLeafNodes;
  bool DrawLabels;
  bool DisplayNumberOfCollapsedLeafNodes;
  bool LegendPositionSet;
  vtkStdString DistanceArrayName;
  vtkStdString VertexNameArrayName;
};

VTK_ABI_NAMESPACE_END
#endif
