// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

/**
 * @class   vtkAxis
 * @brief   takes care of drawing 2D axes
 *
 *
 * The vtkAxis is drawn in screen coordinates. It is usually one of the last
 * elements of a chart to be drawn. It renders the axis label, tick marks and
 * tick labels.
 * The tick marks and labels span the range of values between
 * \a Minimum and \a Maximum.
 * The \a Minimum and \a Maximum values are not allowed to extend beyond the
 * \a MinimumLimit and \a MaximumLimit values, respectively.
 *
 * Note that many other chart elements (e.g., vtkPlotPoints) refer to
 * vtkAxis instances to determine how to scale raw data for presentation.
 * In particular, care must be taken with logarithmic scaling.
 * The axis Minimum, Maximum, and Limit values are stored both unscaled
 * and scaled (with log(x) applied when GetLogScaleActive() returns true).
 * User interfaces will most likely present the unscaled values as they
 * represent the values provided by the user.
 * Other chart elements may need the scaled values in order to draw
 * in the same coordinate system.
 *
 * Just because LogScale is set to true does not guarantee that the axis
 * will use logarithmic scaling -- the Minimum and Maximum values for the
 * axis must both lie to the same side of origin (and not include the origin).
 * Also, this switch from linear- to log-scaling may occur during a rendering
 * pass if autoscaling is enabled.
 * Because the log and pow functions are not invertible and the axis itself
 * decides when to switch between them without offering any external class
 * managing the axis a chance to save the old values, it saves
 * old Limit values in NonLogUnscaled{Min,Max}Limit so that behavior is
 * consistent when LogScale is changed from false to true and back again.
 */

#ifndef vtkAxis_h
#define vtkAxis_h

#include "vtkChartsCoreModule.h" // For export macro
#include "vtkContextItem.h"
#include "vtkPen.h"          // For vtkPen
#include "vtkRect.h"         // For bounding rect
#include "vtkSmartPointer.h" // For vtkSmartPointer
#include "vtkStdString.h"    // For vtkStdString ivars
#include "vtkVector.h"       // For position variables

VTK_ABI_NAMESPACE_BEGIN
class vtkContext2D;
class vtkFloatArray;
class vtkDoubleArray;
class vtkStringArray;
class vtkTextProperty;

class VTKCHARTSCORE_EXPORT vtkAxis : public vtkContextItem
{
public:
  vtkTypeMacro(vtkAxis, vtkContextItem);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /**
   * Enumeration of the axis locations in a conventional XY chart. Other
   * layouts are possible.
   */
  enum Location
  {
    LEFT = 0,
    BOTTOM,
    RIGHT,
    TOP,
    PARALLEL
  };

  enum
  {
    TICK_SIMPLE = 0,
    TICK_WILKINSON_EXTENDED
  };

  /**
   * Creates a 2D Chart object.
   */
  static vtkAxis* New();

  ///@{
  /**
   * Get/set the position of the axis (LEFT, BOTTOM, RIGHT, TOP, PARALLEL).
   */
  virtual void SetPosition(int position);
  vtkGetMacro(Position, int);
  ///@}

  ///@{
  /**
   * Set point 1 of the axis (in pixels), this is usually the origin.
   */
  void SetPoint1(const vtkVector2f& pos);
  void SetPoint1(float x, float y);
  ///@}

  ///@{
  /**
   * Get point 1 of the axis (in pixels), this is usually the origin.
   */
  vtkGetVector2Macro(Point1, float);
  vtkVector2f GetPosition1();
  ///@}

  ///@{
  /**
   * Set point 2 of the axis (in pixels), this is usually the terminus.
   */
  void SetPoint2(const vtkVector2f& pos);
  void SetPoint2(float x, float y);
  ///@}

  ///@{
  /**
   * Get point 2 of the axis (in pixels), this is usually the terminus.
   */
  vtkGetVector2Macro(Point2, float);
  vtkVector2f GetPosition2();
  ///@}

  /**
   * Set the number of tick marks for this axis. Default is -1, which leads to
   * automatic calculation of nicely spaced tick marks.
   */
  virtual void SetNumberOfTicks(int numberOfTicks);

  ///@{
  /**
   * Get the number of tick marks for this axis.
   */
  vtkGetMacro(NumberOfTicks, int);
  ///@}

  ///@{
  /**
   * Get/set the length of tick marks (in pixels).
   */
  vtkSetMacro(TickLength, float);
  vtkGetMacro(TickLength, float);
  ///@}

  ///@{
  /**
   * Get the vtkTextProperty that governs how the axis labels are displayed.
   * Note that the alignment properties are not used.
   */
  vtkGetObjectMacro(LabelProperties, vtkTextProperty);
  ///@}

  /**
   * Set the logical minimum value of the axis, in plot coordinates.
   * If LogScaleActive is true (not just LogScale), then this
   * sets the minimum base-10 <b>exponent</b>.
   */
  virtual void SetMinimum(double minimum);

  ///@{
  /**
   * Get the logical minimum value of the axis, in plot coordinates.
   * If LogScaleActive is true (not just LogScale), then this
   * returns the minimum base-10 <b>exponent</b>.
   */
  vtkGetMacro(Minimum, double);
  ///@}

  /**
   * Set the logical maximum value of the axis, in plot coordinates.
   * If LogScaleActive is true (not just LogScale), then this
   * sets the maximum base-10 <b>exponent</b>.
   */
  virtual void SetMaximum(double maximum);

  ///@{
  /**
   * Get the logical maximum value of the axis, in plot coordinates.
   * If LogScaleActive is true (not just LogScale), then this
   * returns the maximum base-10 <b>exponent</b>.
   */
  vtkGetMacro(Maximum, double);
  ///@}

  /**
   * Set the logical, unscaled minimum value of the axis, in plot coordinates.
   * Use this instead of SetMinimum() if you wish to provide the actual minimum
   * instead of log10(the minimum) as part of the axis scale.
   */
  virtual void SetUnscaledMinimum(double minimum);

  ///@{
  /**
   * Get the logical minimum value of the axis, in plot coordinates.
   */
  vtkGetMacro(UnscaledMinimum, double);
  ///@}

  /**
   * Set the logical maximum value of the axis, in plot coordinates.
   */
  virtual void SetUnscaledMaximum(double maximum);

  ///@{
  /**
   * Get the logical maximum value of the axis, in plot coordinates.
   */
  vtkGetMacro(UnscaledMaximum, double);
  ///@}

  ///@{
  /**
   * Set the logical range of the axis, in plot coordinates.

   * The unscaled range will always be in the same coordinate system of
   * the data being plotted, regardless of whether LogScale is true or false.
   * When calling SetRange() and LogScale is true, the range must be specified
   * in logarithmic coordinates.
   * Using SetUnscaledRange(), you may ignore the value of LogScale.
   */
  virtual void SetRange(double minimum, double maximum);
  virtual void SetRange(double range[2]);
  virtual void SetUnscaledRange(double minimum, double maximum);
  virtual void SetUnscaledRange(double range[2]);
  ///@}

  ///@{
  /**
   * Get the logical range of the axis, in plot coordinates.

   * The unscaled range will always be in the same coordinate system of
   * the data being plotted, regardless of whether LogScale is true or false.
   * Calling GetRange() when LogScale is true will return the log10({min, max}).
   */
  virtual void GetRange(double* range);
  virtual void GetUnscaledRange(double* range);
  ///@}

  /**
   * Set the logical lowest possible value for \a Minimum, in plot coordinates.
   */
  virtual void SetMinimumLimit(double lowest);

  ///@{
  /**
   * Get the logical lowest possible value for \a Minimum, in plot coordinates.
   */
  vtkGetMacro(MinimumLimit, double);
  ///@}

  /**
   * Set the logical highest possible value for \a Maximum, in plot coordinates.
   */
  virtual void SetMaximumLimit(double highest);

  ///@{
  /**
   * Get the logical highest possible value for \a Maximum, in plot coordinates.
   */
  vtkGetMacro(MaximumLimit, double);
  ///@}

  /**
   * Set the logical lowest possible value for \a Minimum, in plot coordinates.
   */
  virtual void SetUnscaledMinimumLimit(double lowest);

  ///@{
  /**
   * Get the logical lowest possible value for \a Minimum, in plot coordinates.
   */
  vtkGetMacro(UnscaledMinimumLimit, double);
  ///@}

  /**
   * Set the logical highest possible value for \a Maximum, in plot coordinates.
   */
  virtual void SetUnscaledMaximumLimit(double highest);

  ///@{
  /**
   * Get the logical highest possible value for \a Maximum, in plot coordinates.
   */
  vtkGetMacro(UnscaledMaximumLimit, double);
  ///@}

  ///@{
  /**
   * Get the margins of the axis, in pixels.
   */
  vtkGetVector2Macro(Margins, int);
  ///@}

  ///@{
  /**
   * Set the margins of the axis, in pixels.
   */
  vtkSetVector2Macro(Margins, int);
  ///@}

  ///@{
  /**
   * Get/set the title text of the axis.
   */
  virtual void SetTitle(const vtkStdString& title);
  virtual vtkStdString GetTitle();
  ///@}

  ///@{
  /**
   * Get the vtkTextProperty that governs how the axis title is displayed.
   */
  vtkGetObjectMacro(TitleProperties, vtkTextProperty);
  ///@}

  ///@{
  /**
   * Get whether the axis is using a log scale.
   * This will always be false when LogScale is false.
   * It is only true when LogScale is true <b>and</b> the \a UnscaledRange
   * does not cross or include the origin (zero).

   * The limits (\a MinimumLimit, \a MaximumLimit, and their
   * unscaled counterparts) do not prevent LogScaleActive from becoming
   * true; they are adjusted if they cross or include the origin
   * and the original limits are preserved for when LogScaleActive
   * becomes false again.
   */
  vtkGetMacro(LogScaleActive, bool);
  ///@}

  ///@{
  /**
   * Get/set whether the axis should <b>attempt</b> to use a log scale.

   * The default is false.
   * \sa{LogScaleActive}.
   */
  vtkGetMacro(LogScale, bool);
  virtual void SetLogScale(bool logScale);
  vtkBooleanMacro(LogScale, bool);
  ///@}

  ///@{
  /**
   * Get/set whether the axis grid lines should be drawn, default is true.
   */
  vtkSetMacro(GridVisible, bool);
  vtkGetMacro(GridVisible, bool);
  ///@}

  ///@{
  /**
   * Get/set whether the axis labels should be visible.
   */
  vtkSetMacro(LabelsVisible, bool);
  vtkGetMacro(LabelsVisible, bool);
  ///@}

  ///@{
  /**
   * Get/set whether the labels for the range should be visible.
   */
  vtkSetMacro(RangeLabelsVisible, bool);
  vtkGetMacro(RangeLabelsVisible, bool);
  ///@}

  ///@{
  /**
   * Get/set the offset (in pixels) of the label text position from the axis
   */
  vtkSetMacro(LabelOffset, float);
  vtkGetMacro(LabelOffset, float);
  ///@}

  ///@{
  /**
   * Get/set whether the tick marks should be visible.
   */
  vtkSetMacro(TicksVisible, bool);
  vtkGetMacro(TicksVisible, bool);
  ///@}

  ///@{
  /**
   * Get/set whether the axis line should be visible.
   */
  vtkSetMacro(AxisVisible, bool);
  vtkGetMacro(AxisVisible, bool);
  ///@}

  ///@{
  /**
   * Get/set whether the axis title should be visible.
   */
  vtkSetMacro(TitleVisible, bool);
  vtkGetMacro(TitleVisible, bool);
  ///@}

  ///@{
  /**
   * Get/set the numerical precision to use, default is 2. This is ignored
   * when Notation is STANDARD_NOTATION or PRINTF_NOTATION.
   */
  virtual void SetPrecision(int precision);
  vtkGetMacro(Precision, int);
  ///@}

  /**
   * Enumeration of the axis notations available.
   */
  enum
  {
    STANDARD_NOTATION = 0,
    SCIENTIFIC_NOTATION,
    FIXED_NOTATION,
    PRINTF_NOTATION
  };

  ///@{
  /**
   * Get/Set the printf-style format string used when TickLabelAlgorithm is
   * TICK_SIMPLE and Notation is PRINTF_NOTATION. The default is "%g".
   */
  virtual void SetLabelFormat(const std::string& fmt);
  vtkGetMacro(LabelFormat, std::string);
  ///@}

  ///@{
  /**
   * Get/Set the printf-style format string used for range labels.
   * This format is always used regardless of TickLabelAlgorithm and
   * Notation. Default is "%g".
   */
  vtkSetMacro(RangeLabelFormat, std::string);
  vtkGetMacro(RangeLabelFormat, std::string);
  ///@}

  ///@{
  /**
   * Get/set the numerical notation, standard, scientific, fixed, or a
   * printf-style format string.
   * \sa SetPrecision SetLabelFormat
   */
  virtual void SetNotation(int notation);
  vtkGetMacro(Notation, int);
  ///@}

  /**
   * Enumeration of the axis behaviors.
   */
  enum
  {
    AUTO = 0, // Automatically scale the axis to view all data that is visible.
    FIXED,    // Use a fixed axis range and make no attempt to rescale.
    CUSTOM    // Deprecated, use the tick label settings instead.
  };

  ///@{
  /**
   * Get/set the behavior of the axis (auto or fixed). The default is 0 (auto).
   */
  vtkSetMacro(Behavior, int);
  vtkGetMacro(Behavior, int);
  ///@}

  ///@{
  /**
   * Set/get the vtkPen object that controls the way this axis is drawn.
   */
  vtkSetSmartPointerMacro(Pen, vtkPen);
  vtkGetObjectMacro(Pen, vtkPen);
  ///@}

  ///@{
  /**
   * Set/get the vtkPen object that controls the way this axis is drawn.
   */
  vtkSetSmartPointerMacro(GridPen, vtkPen);
  vtkGetObjectMacro(GridPen, vtkPen);
  ///@}

  ///@{
  /**
   * Get/set the tick label algorithm that is used to calculate the min, max
   * and tick spacing. There are currently two algorithms, vtkAxis::TICK_SIMPLE
   * is the default and uses a simple algorithm. The second option is
   * vtkAxis::TICK_WILKINSON which uses an extended Wilkinson algorithm to find
   * the optimal range, spacing and font parameters.
   */
  vtkSetMacro(TickLabelAlgorithm, int);
  vtkGetMacro(TickLabelAlgorithm, int);
  ///@}

  ///@{
  /**
   * Get/set the scaling factor used for the axis, this defaults to 1.0 (no
   * scaling), and is used to coordinate scaling with the plots, charts, etc.
   */
  vtkSetMacro(ScalingFactor, double);
  vtkGetMacro(ScalingFactor, double);
  vtkSetMacro(Shift, double);
  vtkGetMacro(Shift, double);
  ///@}

  /**
   * Update the geometry of the axis. Takes care of setting up the tick mark
   * locations etc. Should be called by the scene before rendering.
   */
  void Update() override;

  /**
   * Paint event for the axis, called whenever the axis needs to be drawn.
   */
  bool Paint(vtkContext2D* painter) override;

  /**
   * Use this function to autoscale the axes after setting the minimum and
   * maximum values. This will cause the axes to select the nicest numbers
   * that enclose the minimum and maximum values, and to select an appropriate
   * number of tick marks.
   */
  virtual void AutoScale();

  /**
   * Recalculate the spacing of the tick marks - typically useful to do after
   * scaling the axis.
   */
  virtual void RecalculateTickSpacing();

  /**
   * An array with the positions of the tick marks along the axis line.
   * The positions are specified in the plot coordinates of the axis.
   */
  virtual vtkDoubleArray* GetTickPositions();

  /**
   * An array with the positions of the tick marks along the axis line.
   * The positions are specified in scene coordinates.
   */
  virtual vtkFloatArray* GetTickScenePositions();

  /**
   * A string array containing the tick labels for the axis.
   */
  virtual vtkStringArray* GetTickLabels();

  /**
   * Set the tick positions, and optionally custom tick labels. If the labels
   * and positions are null then automatic tick labels will be assigned. If
   * only positions are supplied then appropriate labels will be generated
   * according to the axis settings. If positions and labels are supplied they
   * must be of the same length. Returns true on success, false on failure.
   */
  virtual bool SetCustomTickPositions(vtkDoubleArray* positions, vtkStringArray* labels = nullptr);

  /**
   * Request the space the axes require to be drawn. This is returned as a
   * vtkRectf, with the corner being the offset from Point1, and the width/
   * height being the total width/height required by the axis. In order to
   * ensure the numbers are correct, Update() should be called on the axis.
   */
  vtkRectf GetBoundingRect(vtkContext2D* painter);

  /**
   * Return a "nice number", often defined as 1, 2 or 5. If roundUp is true then
   * the nice number will be rounded up, false it is rounded down. The supplied
   * number should be between 0.0 and 9.9.
   */
  static double NiceNumber(double number, bool roundUp);

  /**
   * Static function to calculate "nice" minimum, maximum, and tick spacing
   * values.
   */
  static double NiceMinMax(double& min, double& max, float pixelRange, float tickPixelSpacing);

  /**
   * Generate a single label using the current settings when TickLabelAlgorithm
   * is TICK_SIMPLE.
   */
  virtual vtkStdString GenerateSimpleLabel(double val);

  /**
   * Return true if the supplied x, y coordinate is inside the item.
   */
  bool Hit(const vtkContextMouseEvent& mouse) override;

protected:
  vtkAxis();
  ~vtkAxis() override;

  /**
   * Update whether log scaling will be used for layout and rendering.

   * Log scaling is only active when LogScaling is true <b>and</b> the closed,
   * unscaled range does not contain the origin.
   * The boolean parameter determines whether the minimum and maximum values
   * are set from their unscaled counterparts.
   */
  void UpdateLogScaleActive(bool updateMinMaxFromUnscaled);

  /**
   * Calculate and assign nice labels/logical label positions.
   */
  virtual void GenerateTickLabels(double min, double max);

  /**
   * Generate tick labels from the supplied double array of tick positions.
   */
  virtual void GenerateTickLabels();

  virtual void GenerateLabelFormat(int notation, double n);

  /**
   * Generate label using a printf-style format string.
   */
  virtual vtkStdString GenerateSprintfLabel(double value, const std::string& format);

  /**
   * Calculate the next "nicest" numbers above and below the current minimum.
   * \return the "nice" spacing of the numbers.
   */
  double CalculateNiceMinMax(double& min, double& max);

  /**
   * Return a tick mark for a logarithmic axis.
   * If roundUp is true then the upper tick mark is returned.
   * Otherwise the lower tick mark is returned.
   * Tick marks will be: ... 0.1 0.2 .. 0.9 1 2 .. 9 10 20 .. 90 100 ...
   * Parameter nicevalue will be set to true if tick mark is in:
   * ... 0.1 0.2 0.5 1 2 5 10 20 50 100 ...
   * Parameter order is set to the detected order of magnitude of the number.
   */
  double LogScaleTickMark(double number, bool roundUp, bool& niceValue, int& order);

  /**
   * Generate logarithmically-spaced tick marks with linear-style labels.

   * This is for the case when log scaling is active, but the axis min and max
   * span less than an order of magnitude.
   * In this case, the most significant digit that varies is identified and
   * ticks generated for each value that digit may take on. If that results
   * in only 2 tick marks, the next-most-significant digit is varied.
   * If more than 20 tick marks would result, the stride for the varying digit
   * is increased.
   */
  virtual void GenerateLogSpacedLinearTicks(int order, double min, double max);

  /**
   * Generate tick marks for logarithmic scale for specific order of magnitude.
   * Mark generation is limited by parameters min and max.
   * Tick marks will be: ... 0.1 0.2 .. 0.9 1 2 .. 9 10 20 .. 90 100 ...
   * Tick labels will be: ... 0.1 0.2 0.5 1 2 5 10 20 50 100 ...
   * If Parameter detaillabels is disabled tick labels will be:
   * ... 0.1 1 10 100 ...
   * If min/max is not in between 1.0 and 9.0 defaults will be used.
   * If min and max do not differ 1 defaults will be used.
   */
  void GenerateLogScaleTickMarks(
    int order, double min = 1.0, double max = 9.0, bool detailLabels = true);

  /**
   * Calculate the position where the title of the axis would be drawn.
   */
  void CalculateTitlePosition(vtkVector2f& out);

  int Position;  // The position of the axis (LEFT, BOTTOM, RIGHT, TOP)
  float* Point1; // The position of point 1 (usually the origin)
  float* Point2; // The position of point 2 (usually the terminus)
  vtkVector2f Position1, Position2;
  double TickInterval;              // Interval between tick marks in plot space
  int NumberOfTicks;                // The number of tick marks to draw
  float TickLength;                 // The length of the tick marks
  vtkTextProperty* LabelProperties; // Text properties for the labels.
  double Minimum;                   // Minimum value of the axis
  double Maximum;                   // Maximum values of the axis
  double MinimumLimit;              // Lowest possible value for Minimum
  double MaximumLimit;              // Highest possible value for Maximum
  double UnscaledMinimum;           // UnscaledMinimum value of the axis
  double UnscaledMaximum;           // UnscaledMaximum values of the axis
  double UnscaledMinimumLimit;      // Lowest possible value for UnscaledMinimum
  double UnscaledMaximumLimit;      // Highest possible value for UnscaledMaximum
  double NonLogUnscaledMinLimit;    // Saved UnscaledMinimumLimit (when !LogActive)
  double NonLogUnscaledMaxLimit;    // Saved UnscaledMinimumLimit (when !LogActive)
  int Margins[2];                   // Horizontal/vertical margins for the axis
  vtkStdString Title;               // The text label drawn on the axis
  vtkTextProperty* TitleProperties; // Text properties for the axis title
  bool LogScale;                    // *Should* the axis use a log scale?
  bool LogScaleActive;              // *Is* the axis using a log scale?
  bool GridVisible;                 // Whether the grid for the axis should be drawn
  bool LabelsVisible;               // Should the axis labels be visible
  bool RangeLabelsVisible;          // Should range labels be visible?
  float LabelOffset;                // Offset of label from the tick mark
  bool TicksVisible;                // Should the tick marks be visible.
  bool AxisVisible;                 // Should the axis line be visible.
  bool TitleVisible;                // Should the title be visible.
  int Precision;                    // Numerical precision to use, defaults to 2.
  int Notation;                     // The notation to use (standard, scientific, mixed)
  std::string LabelFormat;          // The printf-style format string used for labels.
  std::string RangeLabelFormat;     // The printf-style format string used for range labels.
  int Behavior;                     // The behaviour of the axis (auto, fixed, custom).
  float MaxLabel[2];                // The widest/tallest axis label.
  bool TitleAppended;               // Track if the title is updated when the label formats
                                    // are changed in the Extended Axis Labeling algorithm

  ///@{
  /**
   * Scaling factor used on this axis, this is used to accurately render very
   * small/large numbers accurately by converting the underlying range by the
   * scaling factor.
   */
  double ScalingFactor;
  double Shift;
  ///@}

  /**
   * Are we using custom tick labels, or should the axis generate them?
   */
  bool CustomTickLabels;

  /**
   * This object stores the vtkPen that controls how the axis is drawn.
   */
  vtkSmartPointer<vtkPen> Pen;

  /**
   * This object stores the vtkPen that controls how the grid lines are drawn.
   */
  vtkSmartPointer<vtkPen> GridPen;

  /**
   * Position of tick marks in screen coordinates
   */
  vtkSmartPointer<vtkDoubleArray> TickPositions;

  /**
   * Position of tick marks in screen coordinates
   */
  vtkSmartPointer<vtkFloatArray> TickScenePositions;

  /**
   * The labels for the tick marks
   */
  vtkSmartPointer<vtkStringArray> TickLabels;

  /**
   * Hint as to whether a nice min/max was set, otherwise labels may not be
   * present at the top/bottom of the axis.
   */
  bool UsingNiceMinMax;

  /**
   * Mark the tick labels as dirty when the min/max value is changed.
   */
  bool TickMarksDirty;

  /**
   * Flag to indicate that the axis has been resized.
   */
  bool Resized;

  /**
   * The algorithm being used to tick label placement.
   */
  int TickLabelAlgorithm;

  /**
   * The point cache is marked dirty until it has been initialized.
   */
  vtkTimeStamp BuildTime;

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

  /**
   * Return true if the value is in range, false otherwise.
   */
  bool InRange(double value);
};

VTK_ABI_NAMESPACE_END
#endif // vtkAxis_h
