// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-FileCopyrightText: Copyright (c) Sandia Corporation
// SPDX-License-Identifier: BSD-3-Clause
/**
 * @class   vtkCosmicTreeLayoutStrategy
 * @brief   tree layout strategy reminiscent of astronomical systems
 *
 *
 * This layout strategy takes an input tree and places all the children of a
 * node into a containing circle. The placement is such that each child
 * placed can be represented with a circle tangent to the containing circle
 * and (usually) 2 other children. The interior of the circle is left empty
 * so that graph edges drawn on top of the tree will not obfuscate the tree.
 * However, when one child is much larger than all the others, it may
 * encroach on the center of the containing circle; that's OK, because it's
 * large enough not to be obscured by edges drawn atop it.
 *
 * @par Thanks:
 * Thanks to the galaxy and David Thompson hierarchically nested inside it
 * for inspiring this layout strategy.
 */

#ifndef vtkCosmicTreeLayoutStrategy_h
#define vtkCosmicTreeLayoutStrategy_h

#include "vtkGraphLayoutStrategy.h"
#include "vtkInfovisLayoutModule.h" // For export macro

VTK_ABI_NAMESPACE_BEGIN
class vtkDoubleArray;
class vtkDataArray;
class vtkPoints;
class vtkTree;

class VTKINFOVISLAYOUT_EXPORT vtkCosmicTreeLayoutStrategy : public vtkGraphLayoutStrategy
{
public:
  static vtkCosmicTreeLayoutStrategy* New();
  void PrintSelf(ostream& os, vtkIndent indent) override;
  vtkTypeMacro(vtkCosmicTreeLayoutStrategy, vtkGraphLayoutStrategy);

  /**
   * Perform the layout.
   */
  void Layout() override;

  ///@{
  /**
   * Should node size specifications be obeyed at leaf nodes only or
   * (with scaling as required to meet constraints) at every node in
   * the tree?
   * This defaults to true, so that leaf nodes are scaled according to
   * the size specification provided, and the parent node sizes are
   * calculated by the algorithm.
   */
  vtkSetMacro(SizeLeafNodesOnly, vtkTypeBool);
  vtkGetMacro(SizeLeafNodesOnly, vtkTypeBool);
  vtkBooleanMacro(SizeLeafNodesOnly, vtkTypeBool);
  ///@}

  ///@{
  /**
   * How many levels of the tree should be laid out?
   * For large trees, you may wish to set the root and maximum depth
   * in order to retrieve the layout for the visible portion of the tree.
   * When this value is zero or negative, all nodes below and including
   * the LayoutRoot will be presented.
   * This defaults to 0.
   */
  vtkSetMacro(LayoutDepth, int);
  vtkGetMacro(LayoutDepth, int);
  ///@}

  ///@{
  /**
   * What is the top-most tree node to lay out?
   * This node will become the largest containing circle in the layout.
   * Use this in combination with SetLayoutDepth to retrieve the
   * layout of a subtree of interest for rendering.
   * Setting LayoutRoot to a negative number signals that the root node
   * of the tree should be used as the root node of the layout.
   * This defaults to -1.
   */
  vtkSetMacro(LayoutRoot, vtkIdType);
  vtkGetMacro(LayoutRoot, vtkIdType);
  ///@}

  ///@{
  /**
   * Set the array to be used for sizing nodes.
   * If this is set to an empty string or nullptr (the default),
   * then all leaf nodes (or all nodes, when SizeLeafNodesOnly is false)
   * will be assigned a unit size.
   */
  vtkSetStringMacro(NodeSizeArrayName);
  vtkGetStringMacro(NodeSizeArrayName);
  ///@}

protected:
  /// How are node sizes specified?
  enum RadiusMode
  {
    NONE,   //!< No node sizes specified... unit radius is assumed.
    LEAVES, //!< Only leaf node sizes specified... parents are calculated during layout.
    ALL     //!< All node sizes specified (overconstrained, so a scale factor for each parent is
            //!< calculated during layout).
  };

  vtkCosmicTreeLayoutStrategy();
  ~vtkCosmicTreeLayoutStrategy() override;

  /**
   * Recursive routine used to lay out tree nodes. Called from Layout().
   */
  void LayoutChildren(vtkTree* tree, vtkPoints* newPoints, vtkDoubleArray* radii,
    vtkDoubleArray* scale, vtkIdType root, int depth, RadiusMode mode);

  /**
   * Recursive routine that adds each parent node's (x,y) position to its children.
   * This must be done only after all the children have been laid out at the origin
   * since we will not know the parent's position until after the child radii have
   * been determined.
   */
  void OffsetChildren(vtkTree* tree, vtkPoints* pts, vtkDoubleArray* radii, vtkDoubleArray* scale,
    double parent[4], vtkIdType root, int depth, RadiusMode mode);

  /**
   * Create an array to hold radii, named appropriately (depends on \a NodeSizeArrayName)
   * and initialized to either (a) -1.0 for each node or (b) a deep copy of an existing array.
   * @param numVertices  The number of vertices on the tree.
   * @param initialValue The starting value of each node's radius. Only used when \a inputRadii is
   * nullptr.
   * @param inputRadii   Either nullptr or the address of another array to be copied into the output
   * array
   * @retval             The array of node radii to be set on the output
   */
  vtkDoubleArray* CreateRadii(vtkIdType numVertices, double initialValue, vtkDataArray* inputRadii);

  /**
   * Create an array to hold scale factors, named appropriately (depends on \a NodeSizeArrayName)
   * and initialized to -1.0.
   * @param numVertices The number of vertices on the tree.
   * @retval            The array of node scale factors to be set on the output
   */
  vtkDoubleArray* CreateScaleFactors(vtkIdType numVertices);

  vtkTypeBool SizeLeafNodesOnly;
  int LayoutDepth;
  vtkIdType LayoutRoot;
  char* NodeSizeArrayName;

private:
  vtkCosmicTreeLayoutStrategy(const vtkCosmicTreeLayoutStrategy&) = delete;
  void operator=(const vtkCosmicTreeLayoutStrategy&) = delete;
};

VTK_ABI_NAMESPACE_END
#endif // vtkCosmicTreeLayoutStrategy_h
