/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


#pragma once

/////////////////////// Qt includes
#include <QObject>
#include <QString>
#include <QWidget>
#include <QBrush>
#include <QColor>


/////////////////////// QCustomPlot
#include <qcustomplot.h>


///////////////////////////// Local includes
#include <minexpert/nongui/History.hpp>
#include <libmass/Trace.hpp>
#include <minexpert/nongui/MassSpecDataSet.hpp>
#include <minexpert/nongui/MzIntegrationParams.hpp>


namespace msXpSmineXpert
{


//! The AbstractPlotWidget class provides the basis for specialized plotting
//! widget classes.
/*!

  All the plotting in mineXpert occurs in classes derived from this one. This
  class in turn derives from QCustomPlot, a plotting library for the Qt
  framework.

*/
class AbstractPlotWidget : public QCustomPlot
{
  Q_OBJECT;

  protected:
  //! Pointer to the parent object.
  QWidget *mp_parentWnd = Q_NULLPTR;

  //! Name of the plot widget.
  QString m_name = "NOT_SET";

  //! Description of the plot widget.
  QString m_desc = "NOT_SET";

  std::vector<std::shared_ptr<msXpSlibmass::Trace>> m_plotTraces;
  std::size_t m_currentPlotTraceIndex = 0;

  //! Last intensity value that was calculated.
  /*!

    This value is used when a Tic integration is asked in the various
    derived classes. While the functions need to be defined in the derived
    classes, this member datum can lie here. It is initialized to NaN so
    that it is easy to check if it received a value already.

    \sa m_keyRangeStart <br> m_keyRangeEnd.
    */
  double m_lastTicIntensity = qSNaN();

  //! Left border of the x-axis (abscissae) range selected for TIC intensity
  //! calculation.
  /*!

    It is always possible in any of the different types of derived classes
    to compute a TIC intensity single value integration. To perform that
    operation, it is necessary to specify the x-axis range over which to
    integrate the data. This is the left border of that range.

    \sa m_keyRangeEnd.
    */
  // This pair of values are used to qualify the key-axis (X axis) range
  // selected for the the TIC intensity integration calculation.
  double m_keyRangeStart = qSNaN();

  //! Right border of the x-axis (abscissae) range selected for TIC intensity
  //! calculation.
  double m_keyRangeEnd = qSNaN();

  //! Pointer to the MassSpecDataSet instance.
  /*!

    We always need to refer to the MassSpecDataSet-contained mass spectral
    data. This pointer allows us to taccess that MassSpecDataSet instance.

    Note that we do now systematically own the data in MassSpecDataSet. The
    processe leading to that ownership is as follows:

    When a data file is loaded in full (that is, the mass data are stored in
    the MassSpecDataSet instance) the first operation that is performed on
    the loaded data is to compute the TIC chromatogram from these data. To
    display that TIC chromatogram, a TicChromPlotWidget object is
    instanciated. <em> Only at that moment</em> the TicChromPlotWidget
    instance that gets the pointer to the MassSpecDataSet instance calls
    setParent() on that pointer so that it becomes the parent of the data.

    This process is designed so that only when a TIC chromatogram plot
    widget is destroyed the corresponding mass data get destroyed all along.
    Indeed, the paradigm for using the mineXpert mining program is that the
    TIC chromatogram is the visually exploitable root of the data from where
    the mining can start. When the root data plotting widget disappears the
    user has no more "handle" over the data and thus these data are
    unusable. If unusable, the data must be freed. That happens when the
    user deletes the plot from the TIC chromatogram window.

*/
  const MassSpecDataSet *mp_massSpecDataSet = Q_NULLPTR;


  //! The name of the data file from which the mass data were read.
  QString m_fileName;

  //! Tells if the plot widget is designed to contain multiple graphs.
  /*!

    There are two kinds of plot widgets in mineXpert:

    - plot widgets that show only one graph. For example, a
    MassSpecPlogWiget may show one mass spectrum and only one. This is
    typically the case of the plot widgets that are located at the bottom
    part of the corresponding plot window.

    - plot widgets that show multiple graphs. In a window that displays plot
    widget, like an instance of the MassSpecWnd class, there are two plot
    widget parts. The upper part contains a plot widget in which multiple
    graphs can be displayed. In fact, in this plot widget all the graphs
    plotted in the plot widget located in the bottom part of the window are
    replicated in such a manner that they all get plotted in the same
    widget. This is what is called a multi-graph plot widget.

*/
  bool m_isMultiGraph = false;

  // If this widget is not a multiGraph plot widget, then this pointer points
  // to its multiGraph plot widget counterpart.
  QCPGraph *mp_multiGraph = nullptr;

  //! Pointer to the mz=f(dt) color map...
  /*!

    That color map instance is only allocated when drift time mass
    spectrometry data are handled.

*/
  QCPColorMap *mp_colorMap = Q_NULLPTR;

  //! History member in which to store all the events...
  /*!

    that contributed to the shaping of the data as plotted in this plot widget.

    History is a concept that aims at describing all the various integration
    steps that were performed on the initial mass data set (as loaded from a
    file) and that contributed to the selection of a unique subset of these
    initial data.

    For example, starting from the TIC chromatogram of the initially loaded
    data, one may ask that an integration be performed on [start -- end]
    range of the retention time axis in order to produce a mass spectrum.
    Then, a feature in that mass spectrum is queried for it drift time. That
    is performed by asking an integration of the [start -- end] m/z range in
    the spectrum in order to produce a drift spectrum. And on and on.

    The \c m_history member records all these integration steps.

*/
  History m_history;

  //! Pointer to the contextual menu.
  QMenu *mpa_contextMenu = Q_NULLPTR;

  QAction *mpa_exportDataAction = Q_NULLPTR;
  QAction *mpa_plotXyFileAction = Q_NULLPTR;
  QAction *mpa_savePlotGraphics = Q_NULLPTR;

  //! Rectangle defining the borders of zoomed-in/out data.
  QCPItemRect *m_zoomRect = Q_NULLPTR;

  //! Line that is printed when the user selects a range.
  QCPItemLine *m_selectLine = Q_NULLPTR;

  //! Text that is displayed on the plotted graph to document it.
  /*!

    That text is typically the file path of the data file.
*/
  QCPItemText *m_descText = Q_NULLPTR;

  //! Text describing the x-axis delta value during a drag operation.
  QCPItemText *m_xDeltaText = Q_NULLPTR;

  //! Tells if the tracers should be visible.
  bool m_tracersVisible = true;

  //! Horizontal tracer
  QCPItemLine *m_hStartTracer;

  //! Vertical selection start tracer (typically in green).
  QCPItemLine *m_vStartTracer;

  //! Vertical selection end tracer (typically in red).
  QCPItemLine *m_endTracer /*only vertical*/;

  //! Tells if the mouse click occurred on the x axis.
  bool m_clickWasOnXAxis = false;

  //! Tells if the mouse click occurred on the y axis.
  bool m_clickWasOnYAxis = false;

  //! Index of the last axis range history item.
  /*!

    Each time the user modifies the ranges (x/y axis) during panning or
    zooming of the graph, the new axis ranges are stored in a axis ranges
    history list. This index allows to point to the last range of that
    history.

*/
  int m_lastAxisRangeHistoryIndex = -1;

  //! List of x axis ranges occurring during the panning zooming actions.
  QList<QCPRange *> m_xAxisRangeHistory;

  //! List of y axis ranges occurring during the panning zooming actions.
  QList<QCPRange *> m_yAxisRangeHistory;

  //! Pen used to draw the graph and textual elements in the plot widget.
  QPen m_pen;

  //! How many mouse move events must be skipped */
  /*!

    when the data are so massive that the graph panning becomes sluggish. By
    default, the value is 10 events to be skipped before accounting one. The
    "fat data" mouse movement handler mechanism is actuated by using a
    keyboard key combination. There is no automatic shift between normal
    processing and "fat data" processing.

*/
  int m_mouseMoveHandlerSkipAmount = 10;

  //! Counter to handle the "fat data" mouse move event handling.
  /*!

    \sa m_mouseMoveHandlerSkipAmount.

*/
  int m_mouseMoveHandlerSkipCount = 0;

  //! Map of keyboard codes, registered for having a special function, with
  // textual helper string.
  QMap<int, QString> m_regQtKeyCodeMap;

  //! Key code of the last pressed keyboard key.
  int m_pressedKeyCode;

  //! Tells at each instant if the user is dragging the mouse.
  bool m_isDragging = false;

  //! When dragging, the mouse draws a path starting at that point.
  QPointF m_startDragPoint;

  //! When dragging, the mouse draws a path ending at that point.
  QPointF m_currentDragPoint;

  //! Last point that the mouse cursor went over.
  QPointF m_lastMousedPlotPoint;

  //! When selecting a plot region, the left (smallest value, x-axis).
  double m_xRangeMin = 0;

  //! When selecting a plot region, the left (smallest value, y-axis).
  double m_yRangeMin = 0;

  //! When selecting a plot region, the right (greatest value, x-axis).
  double m_xRangeMax = 0;
  //! When selecting a plot region, the right (greatest value, y-axis).
  double m_yRangeMax = 0;

  //! When dragging the mouse over a plot, the spanned region in x axis.
  double m_xDelta = 0;
  //! When dragging the mouse over a plot, the spanned region in x axis.
  double m_yDelta = 0;

  // QColor m_unfocusedColor = QColor(Qt::lightGray);
  // QColor m_unfocusedColor = QColor(230, 230, 230, 255);

  //! Color used for the background of unfocused plot.
  QColor m_unfocusedColor = QColor("lightgray");
  //! Color used for the background of unfocused plot.
  QBrush m_unfocusedBrush = QBrush(m_unfocusedColor);

  //! Color used for the background of focused plot.
  QColor m_focusedColor = QColor(Qt::transparent);
  //! Color used for the background of focused plot.
  QBrush m_focusedBrush = QBrush(m_focusedColor);

  QColor m_plottingColor = QColor("black");

  bool findIntegrationLowerRangeForKey(double key, QCPRange *p_range);

  public:
  explicit AbstractPlotWidget(QWidget *parent,
                              const QString &name,
                              const QString &desc,
                              const MassSpecDataSet *massSpecDataSet,
                              const QString &fileName = QString(),
                              bool isMultiGraph       = false);

  virtual ~AbstractPlotWidget();

  QWidget *parentWnd();

  Q_INVOKABLE void setGraphData(QVector<double> keys, QVector<double> values);
  Q_INVOKABLE void showGraphData(std::size_t plotTraceIndex);

  Q_INVOKABLE void setName(QString name);
  Q_INVOKABLE QString name() const;

  Q_INVOKABLE void setDesc(QString desc);
  Q_INVOKABLE QString desc() const;

  void setMzIntegrationParams(MzIntegrationParams params);
  MzIntegrationParams mzIntegrationParams() const;

  void setMultiGraph(QCPGraph *graph);

  Q_INVOKABLE double
  xRangeMin() const
  {
    return m_xRangeMin;
  }
  Q_INVOKABLE double
  xRangeMax() const
  {
    return m_xRangeMax;
  }

  Q_INVOKABLE double
  yRangeMin() const
  {
    return m_yRangeMin;
  }
  Q_INVOKABLE double
  yRangeMax() const
  {
    return m_yRangeMax;
  }

  QCPColorMap *
  colorMap() const
  {
    return mp_colorMap;
  }

  const MassSpecDataSet *
  massSpecDataSet() const
  {
    return mp_massSpecDataSet;
  }

  const History &
  history() const
  {
    return m_history;
  }
  History &
  rhistory()
  {
    return m_history;
  }

  virtual bool setupWidget();
  virtual QMenu *createContextMenu();

  Q_INVOKABLE void showHelpSummary();

  void clearHistory();
  void addHistoryItem(HistoryItem *item);
  Q_INVOKABLE void showHistory();
  Q_INVOKABLE QString historyAsText();

  void clearAxisRangeHistory();
  void updateAxisRangeHistory();
  void backOneAxisHistoryStep();
  void setAxisRangesWithHistoryIndex(int index);

  void hideAllPlotItems();
  void showTracers();
  void hideTracers();

  bool isSelectionARectangle();
  void drawRectangleAndZoom();
  void drawXDeltaLineAndMeasure();
  void drawXDeltaLineForZoomOrIntegration();
  bool isClickOntoAnyAxis(const QPointF &mousePoint);
  bool isClickOntoXAxis(const QPointF &mousePoint);
  bool isClickOntoYAxis(const QPointF &mousePoint);
  void calculateSortedDragDeltas();
  void
  yMinMaxOnXAxisCurrentRange(double &min, double &max, QCPGraph *g = Q_NULLPTR);
  void axisRescale();

  virtual void registerQtKeyCode(int qtKeyCode, const QString &helpText);
  virtual void clearPlot();

  virtual void mouseMoveHandler(QMouseEvent *event);
  virtual void selectionChangedHandler();
  virtual void mousePressHandler(QMouseEvent *event);
  virtual void mouseReleaseHandler(QMouseEvent *event);
  virtual void mouseReleaseHandledEvent(QMouseEvent *event) = 0;
  virtual void axisDoubleClickHandler(QCPAxis *axis,
                                      QCPAxis::SelectablePart part,
                                      QMouseEvent *event);

  virtual void setFocus();
  virtual void keyPressEvent(QKeyEvent *event);
  virtual void keyReleaseEvent(QKeyEvent *event);

  void
  replotWithAxisRange(QCPRange xAxisRange, QCPRange yAxisRange, int whichAxis);
  Q_INVOKABLE void replotWithAxisRangeX(double lower, double upper);
  Q_INVOKABLE void replotWithAxisRangeY(double lower, double upper);


  void applySavitzkyGolay();
  void backFilteringStep();
  void forwardFilteringStep();

  double getYatX(double k, QCPGraph *g);
  Q_INVOKABLE double getYatX(double x);

  void redrawPlotBackground(QWidget *focusedPlotWidget);

  Q_INVOKABLE void shouldDestroyPlotWidget();

  Q_INVOKABLE void toggleMultiGraph();
  Q_INVOKABLE void showMultiGraph();
  Q_INVOKABLE void hideMultiGraph();

  QColor color() const;
  void setPlottingColor(const QColor &newColor);
  void changePlottingColor(const QColor &newColor);

  Q_INVOKABLE QString asXyText();

  Q_INVOKABLE virtual void exportData();
  Q_INVOKABLE virtual void exportPlot();
  Q_INVOKABLE virtual void exportPlotToFile(const QString &fileName,
                                            double start = qSNaN(),
                                            double end   = qSNaN());

  Q_INVOKABLE virtual bool savePlotToGraphicsFile();

  Q_INVOKABLE virtual bool savePlotToPdfFile(const QString &fileName,
                                             bool noCosmeticPen,
                                             int width,
                                             int height,
                                             const QString &pdfCreator,
                                             const QString &title);


  //! Return the arbitrary integration type that this plot widget can handle
  /*!
    This integration type is useful to craft HistoryItem objects reporting the
    ranges at any given time in the plot widget, be it of any of the derived
    classes.
    */
  virtual int arbitraryIntegrationType() = 0;

  Q_INVOKABLE QList<double> keys();
  Q_INVOKABLE QList<double> values();

  void setLastTicIntensity(double value);
  Q_INVOKABLE double lastTicIntensity(void);

  Q_INVOKABLE msXpSlibmass::Trace trace();

  signals:
  void updateStatusBarSignal(QString msg);

  void destroyMatchingMultiGraphGraph(AbstractPlotWidget *widget);

  void aboutToDestroyPlotWidget(AbstractPlotWidget *widget = Q_NULLPTR);
  void destroyPlotWidget(AbstractPlotWidget *widget = Q_NULLPTR);

  void toggleMultiGraphSignal(AbstractPlotWidget *widget, bool recursive);

  // This integration is particular and sits in the abstract class because it
  // can be elicited from any plot widget type (mass, drift, tic).

  void newTicChromatogram(const MassSpecDataSet *massSpecDataSet,
                          const QString &msg,
                          const History &history,
                          QColor color);
};


} // namespace msXpSmineXpert
