
// SPDX-License-Identifier: CC-BY-NC-SA-4.0
//
// Copyright (C) 2026 Bit by Bit Signal Processing LLC (https://bxbsp.com)
//
// This work is placed under the "Creative Commons Attribution
// NonCommercial ShareAlike 4.0 International" license, known
// by the shortened acronym "CC-BY-NC-SA-4.0".
//
// This work is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// A CC-BY-NC-SA-4.0 license allows you to use this work for
// noncommercial purposes so long as attribution is made to the
// original author.  Modified versions of this work may be distributed,
// but only under the same license.  For further details, see the
// Creative Commons License "CC-BY-NC-SA-4.0".
//
// You should have received a copy of the CC-BY-NC-SA-4.0 license
// along with this work. If not, see
// <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
//

//

#ifndef GRAPH_HH
#define GRAPH_HH

#include "panel.hh"
#include "subpanel.hh"
#include "color.hh"
#include "point.hh"
#include "multiwindow.hh"
#include "draw_text.hh"
#include "hotspot.hh"
#include "graph_data.hh"
#include "multilingua.hh"


class graph : public multiwindow
{
public:

  //
  // set_xmin_xmax and set_ymin_ymax
  //
  // These functions encourage setting the xmin and xmax value of the graph.
  // However, they won't force the graph of the current graph data to be off
  // of the screen.  If the graph would be off of the screen and force is true,
  // then the closest values are used to those requested that would put the graph
  // on the screen.  If that happens and force is false, then the old values
  // are used and the change is rejected.  The values may always be adjusted
  // slightly to align the graph with a nice tic layout.
  //
  // If the values used aren't close to what was requested, the functions
  // return false.  Otherwise they return true.
  //
  // If called without arguments, these functions autoscale the axis.  For
  // the x-axis, they scale to the full extent of the data, with perhaps some
  // modification to match it with good tics.  For the y-axis, only 90% of
  // the screen area is used (to allow for higher or lower data values with
  // later data updates.)
  //
  bool set_xmin_xmax(double new_xmin=0, double new_xmax=0, bool force=true);
  bool set_ymin_ymax(double new_ymin=0, double new_ymax=0, bool force=true);

  //
  // These functions are for updating graph data, from a different thread than
  // the thread that's actually performing the graphing, in a thread-safe manner.
  //
  // First, ready_for_new_data() is called.  If the graph has finished processing
  // the last batch of data given to it, this will return true.  Otherwise, the
  // graph is not yet ready, and new data can't yet be supplied.
  //
  // If the graph is ready for new data, a pointer to the old (now unused) data
  // can be retrieved with get_old_data().  This is useful, for example, if the
  // old data structure is to be reused to supply new data or if it needs to
  // be deleted.  It is also optional, depending on circumstances.
  //
  // Then accept_data() is called supplying the new data structure.  This sits in
  // the graph until it can be picked up and be swapped out for the old data.
  // This happens in another thread, so it can only happen when the other thread
  // isn't using the data.
  //
  void         accept_data(graph_data* gd);
  bool         ready_for_new_data();
  graph_data*  get_old_data();

  // T is char, wchar_t, char16_t, or char32_t
  //
  // For the title only, it is printed as multicolor text.
  // Values of 10+dataset_number in the text will not print but
  // instead will switch color to the color of that dataset.
  // Printing starts with the title color.  To switch back to
  // the title color, put in a character with value
  // 10+graph_data::MAX_DATASETS.
  //
  // After changing titles or labels, call mark_dirty()
  // to redraw.  If changing from no title to a title or
  // vice-versa, call mark_layout_dirty() instead, since
  // this changes the layout.
  //
  template<class T> void set_title(const T* title, int language=0);
  template<class T> void set_x_axis_title(const T* title, int language=0);
  template<class T> void set_y_axis_title(const T* title, int language=0);
  template<class T> void set_label(int dataset_number, const T* label, int language=0);

  //
  // After changing colors, call mark_dirty() to redraw
  //
  void set_dataset_color      (int dataset, color c);
  void set_title_color        (color c);
  void set_axis_title_color   (color c);
  void set_tic_label_color    (color c);
  void set_tic_color          (color c);
  void set_axis_color         (color c);
  void set_background_color   (color c);
  void set_panel_color        (color c);
  void set_grid_major_color   (color c);
  void set_grid_minor_color   (color c);
  void set_error_color        (color c);

  //
  // Font heights are set as percent of graph height, but with
  // specified max and min values.  After changing font heights,
  // call mark_layout_dirty() to resize things.
  //
  // Note that the title and axis_title and error sizes may be 
  // overridden to smaller values if the titles are so long that 
  // they'd extend beyond the available space.
  //
  void set_title_font_height         (int percent, int min_pixels, int max_pixels);
  void set_axis_title_font_height    (int percent, int min_pixels, int max_pixels);
  void set_tic_label_font_height     (int percent, int min_pixels, int max_pixels);
  void set_dataset_label_font_height (int percent, int min_pixels, int max_pixels);
  void set_error_text_font_height    (int percent, int min_pixels, int max_pixels);

  void allow_user_resize_x(bool allow);
  void allow_user_resize_y(bool allow);
  
  graph();
  ~graph();

private:

  bool user_resize_x_ok;
  bool user_resize_y_ok;
  
  graph_data*           gd;
  graph_data* volatile  new_gd;
  graph_data* volatile  old_gd;

  multilingua      labels[graph_data::MAX_DATASETS];
  dt_colors        GRAPH_COLORS;

  void         update_data_being_graphed();
  void         plot_data(graph_data* gd);

  virtual void   layout();
  virtual void   draw_dirty();
  virtual void   draw_dynamic();


  color TITLE_COLOR;
  color AXIS_TITLE_COLOR;
  color TIC_LABEL_COLOR;
  color AXIS_COLOR;
  color TIC_COLOR;
  color BG_COLOR;
  color PANEL_COLOR;
  color GRID_MINOR_COLOR;
  color GRID_MAJOR_COLOR;
  color ERROR_COLOR;
  

  void calculate_x_tic_labels();
  void calculate_y_tic_labels();

  static bool move_zoom_function(hotspot* hs, my_event&me, void* data);
  static bool xaxis_move_zoom_function(hotspot* hs, my_event&me, void* data);
  static bool yaxis_move_zoom_function(hotspot* hs, my_event&me, void* data);
  
  bool x_zoom_function(my_event& me);
  bool y_zoom_function(my_event& me);
  bool zoom_function(my_event& me);
  bool zoom_function_wheel(my_event& me);


  // graphpanel is a panel inside the main panel where graphs are done.
  // This is separate so the entire thing doesn't need to be redrawn with a data update.
  subpanel* graphpanel;  
  
  hotspot*  zoom_hotspot;
  hotspot*  xaxis_hotspot;
  hotspot*  yaxis_hotspot;


  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //
  //  Metadata
  //
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  
  multilingua title;
  multilingua x_axis_title;
  multilingua y_axis_title;

  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //
  //  Data
  //
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  
  // These are the current values displayed on the graph
  double             xmin;
  double             xmax;
  double             ymin;
  double             ymax;

  // These are the values seen in the provided data
  double             ymax_in_last_dataset;
  double             ymin_in_last_dataset;

  // This is the last value for the number of datasets.  If this changes and we're drawing
  // labels, need to redraw the labels.
  int last_num_datasets;

  
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //
  //  Calculated Tic Labels
  //
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////

  const static int MAX_TIC_LABELS   = 22;
  const static int MAX_LABEL_LENGTH = 50;
  
  int    num_x_tic_labels;
  double x_tic_label_values[MAX_TIC_LABELS];
  bool   x_tic_label_drawn[MAX_TIC_LABELS];
  char   x_tic_label_text[MAX_TIC_LABELS][MAX_LABEL_LENGTH];
  double x_tic_unlabeled_start;
  double x_tic_unlabeled_step;
  int    x_tic_label_greatest_width;

  int    num_y_tic_labels;
  double y_tic_label_values[MAX_TIC_LABELS];
  bool   y_tic_label_drawn[MAX_TIC_LABELS];
  char   y_tic_label_text[MAX_TIC_LABELS][MAX_LABEL_LENGTH];
  double y_tic_unlabeled_start;
  double y_tic_unlabeled_step;
  int    y_tic_label_greatest_width;

  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //
  //  Geometry
  //
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  
  // Width and height of the graph
  //int width;   // Must be the same as p->width, or geometry needs to be recalculated
  //int height;  // Must be the same as p->height, or geometry needs to be recalculated

  bool draw_title;
  bool draw_x_title;
  bool draw_y_title;

  bool draw_x_tic_labels;
  bool draw_y_tic_labels;

  // font sizes
  int title_font_height;
  int axis_title_font_height;
  int axis_tic_label_font_height;
  int dataset_label_font_height;
  int error_text_font_height;

  int title_font_percent;
  int axis_title_font_percent;
  int axis_tic_label_font_percent;
  int dataset_label_font_percent;
  int error_text_font_percent;

  int title_font_min;
  int axis_title_font_min;
  int axis_tic_label_font_min;
  int dataset_label_font_min;
  int error_text_font_min;

  int title_font_max;
  int axis_title_font_max;
  int axis_tic_label_font_max;
  int dataset_label_font_max;
  int error_text_font_max;

  // Size of drawn tics
  
  int minor_tic_length;
  int major_tic_length;
  
  // Widths, from left to right.  "width" or "margin" indicate how wide an item is.  "offset" indicates position from left edge of panel (0).

  int graph_left_margin;
  int y_title_width;           // The same value as axis_title_font_height
  int y_title_offset;          // Offset of y-axis title text, which is specified from a bottom center point, then rotated 90 degrees.
  int y_title_right_margin;
  int y_tic_label_width;       // Calculated carefully based on the length of the selected y-axis tic labels
  int y_tic_label_offset;      // Offset of y-axis tic text, which is specified from a right center point.
  int y_tic_label_margin;
  int graph_left_offset;

  // Widths, from right to left.

  int graph_right_margin;
  int graph_x_label_margin;
  int graph_right_offset;
  

  // Heights, from top to bottom.  "height" indicates how tall an item is.  "offset' indicates position from top (0)

  int graph_top_margin;
  int title_height;            // Same as title_font_height
  int title_offset;            // Offset of title text, specified from the bottom center point.
  int title_below_margin;
  int graph_top_offset;

  // Heights, from bottom to top.

  int graph_bottom_margin;
  int x_title_offset;          // Offset of x-axis title text, which is specified from a top center point.
  int x_title_height;          // Same as axis_title_font_height
  int x_title_above_margin;
  int x_tic_label_offset;
  int x_tic_label_height;      // Same as axis_tic_label_font_height
  int x_tic_above_margin;
  int graph_bottom_offset;

  // Centers
  int graph_center_x;
  int graph_center_y;

  
  int delayed_redraw_count;

  
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //
  //  Functions
  //
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  float  graph_x_value_to_pixel(double value);
  float  graph_y_value_to_pixel(double value);
  int    graph_x_value_to_datapoint(double value);
};


template<class T> void graph::set_title(const T* title, int language)
{
  this->title.set_text(title, language);
}

template<class T> void graph::set_x_axis_title(const T* title, int language)
{
  this->x_axis_title.set_text(title, language);
}

template<class T> void graph::set_y_axis_title(const T* title, int language)
{
  this->y_axis_title.set_text(title, language);
}


template<class T> void graph::set_label(int dataset_number, const T* label, int language)
{
  this->labels[dataset_number].set_text(label, language);
}


#endif
