/* This code comes right from the msXpertSuite software project.
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * 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
 */


/////////////////////// StdLib includes
#include <vector>


/////////////////////// Qt includes
#include <QVector>


/////////////////////// Local includes
#include "basetraceplotwidget.h"
#include "pappsomspp/core/exception/exceptionnotpossible.h"
#include "pappsomspp/core/pappsoexception.h"

namespace pappso
{


BaseTracePlotWidget::BaseTracePlotWidget(QWidget *parent)
  : BasePlotWidget(parent)
{
  // We can afford to call createAllAncillaryItems() in this derived class
  // because all the items will have been created *before* the addition of plots
  // and then the rendering order will hide them to the viewer, since the
  // rendering order is according to the order in which the items have been
  // created.
  //
  // The fact that the ancillary items are created before trace plots is not a
  // problem because the trace plots are sparse and do not effectively hide the
  // data.
  //
  // But, in the color map plot widgets, we cannot afford to create the
  // ancillary items *before* the plot itself because then, the rendering of the
  // plot (created after) would screen off the ancillary items (created before).
  //
  // So, the createAllAncillaryItems() function needs to be called in the
  // derived classes at the most appropriate moment in the setting up of the
  // widget.
  createAllAncillaryItems();
}

BaseTracePlotWidget::BaseTracePlotWidget(QWidget *parent,
                                         const QString &x_axis_label,
                                         const QString &y_axis_label)
  : BasePlotWidget(parent, x_axis_label, y_axis_label)
{
  // We can afford to call createAllAncillaryItems() in this derived class
  // because all the items will have been created *before* the addition of plots
  // and then the rendering order will hide them to the viewer, since the
  // rendering order is according to the order in which the items have been
  // created.
  //
  // The fact that the ancillary items are created before trace plots is not a
  // problem because the trace plots are sparse and do not effectively hide the
  // data.
  //
  // But, in the color map plot widgets, we cannot afford to create the
  // ancillary items *before* the plot itself because then, the rendering of the
  // plot (created after) would screen off the ancillary items (created before).
  //
  // So, the createAllAncillaryItems() function needs to be called in the
  // derived classes at the most appropriate moment in the setting up of the
  // widget.
  createAllAncillaryItems();
}

//! Destruct \c this BaseTracePlotWidget instance.
/*!

  The destruction involves clearing the history, deleting all the axis range
  history items for x and y axes.

*/
BaseTracePlotWidget::~BaseTracePlotWidget()
{
}

void
BaseTracePlotWidget::setGraphData(int graph_index,
                                  const std::vector<double> &keys,
                                  const std::vector<double> &values)
{
  QCPGraph *graph_p = graph(graph_index);

  if(graph_p == nullptr)
    qFatal("Programming error.");

  return setGraphData(graph_p, keys, values);
}

void
BaseTracePlotWidget::setGraphData(QCPGraph *graph_p,
                                  const std::vector<double> &keys,
                                  const std::vector<double> &values)
{
  if(graph_p == nullptr)
    qFatal("Pointer cannot be nullptr.");

  // Version that is now deprecated (20200924)
  // graph_p->setData(QVector<double>::fromStdVector(keys),
  // QVector<double>::fromStdVector(values));

  QVector<double> key_qvector;
  QVector<double> value_qvector;


#if 0
                  // Now replace the graph's data. Note that the data are
                  // inherently sorted (true below).

                  // The begin() -- end() ranges constructor did not work as of
                  // Qt 5.14.2 this day: 20200721

                  key_qvector =
                    QVector(keys.begin(),
                            keys.end());
                  value_qvector =
                    QVector(values.begin(),
                            values.end());
#endif

  for(auto &value : keys)
    key_qvector.push_back(value);

  for(auto &value : values)
    value_qvector.push_back(value);

  graph_p->setData(key_qvector, value_qvector, true);

  graph_p->setPen(m_pen);

  rescaleAxes();
  resetAxesRangeHistory();
  replot();
}

void
BaseTracePlotWidget::clearGraphData(int graph_index)
{
  QCPGraph *graph_p = graph(graph_index);

  if(graph_p == nullptr)
    qFatal("Programming error.");

  graph_p->data().clear();

  rescaleAxes();
  resetAxesRangeHistory();
  replot();
}

QCPGraph *
BaseTracePlotWidget::addTrace(const pappso::Trace &trace, const QColor &color)
{
  // qDebug();

  if(!color.isValid())
    throw PappsoException(
      QString("The color to be used for the plot graph is invalid."));

  // This seems to be unpleasant.
  // setFocus();

  QCPGraph *graph_p = addGraph();

  graph_p->setLayer("plotsLayer");

  // Now depracated as of 20200924
  // graph_p->setData(QVector<double>::fromStdVector(trace.xValues()),
  // QVector<double>::fromStdVector(trace.yValues()));

  QVector<double> key_qvector;
  QVector<double> value_qvector;

#if 0
                  // Now replace the graph's data. Note that the data are
                  // inherently sorted (true below).

                  // The begin() -- end() ranges constructor did not work as of
                  // Qt 5.14.2 this day: 20200721

                  key_qvector =
                    QVector(trace.xValues().begin(),
                            .trace.xValues()end());
                  value_qvector =
                    QVector(trace.yValues().begin(),
                            trace.yValues().end());
#endif

  for(auto &value : trace.xValues())
    {
      key_qvector.push_back(value);
    }

  for(auto &value : trace.yValues())
    {
      value_qvector.push_back(value);
    }

#if 0

  qDebug() << "The size of the x values for trace is:" << key_qvector.size()
           << "and for y values is:" << value_qvector.size();

  QString text;

  for(qsizetype iter = 0; iter < key_qvector.size(); ++iter)
    text += QString("(%1,%2)\n")
              .arg(key_qvector.at(iter), 0, 'f', 6)
              .arg(value_qvector.at(iter), 0, 'f', 6);

  qDebug().noquote() << text;

#endif


  graph_p->setData(key_qvector, value_qvector, true);

  QPen pen = graph()->pen();
  pen.setColor(color);
  graph()->setPen(pen);

  // Connect the signal of selection change so that we can re-emit it for the
  // widget that is using *this widget.

  connect(graph_p,
          static_cast<void (QCPAbstractPlottable::*)(bool)>(
            &QCPAbstractPlottable::selectionChanged),
          [this, graph_p]() {
            emit plottableSelectionChangedSignal(graph_p, graph_p->selected());
          });

  // Rescaling the axes is actually unpleasant if there are more than one
  // graph in the plot widget and that we are adding one. So only, rescale if
  // the number of graphs is == 1, that is we are adding the first one.

  if(graphCount() == 1)
    {
      rescaleAxes();
      resetAxesRangeHistory();
    }

  replot();

  return graph_p;
}

QCPBars *
BaseTracePlotWidget::addBars(const pappso::Trace &bars, const QColor &color)
{
  qDebug();

  if(!color.isValid())
    throw PappsoException(
      QString("The color to be used for the plot graph is invalid."));

  // This seems to be unpleasant.
  // setFocus();

  QCPBars *bars_p = new QCPBars(xAxis, yAxis);
  bars_p->setWidthType(QCPBars::WidthType::wtPlotCoords);
  bars_p->setWidth(0.0005);
  bars_p->setLayer("plotsLayer");

  // Now depracated as of 20200924
  // graph_p->setData(QVector<double>::fromStdVector(trace.xValues()),
  // QVector<double>::fromStdVector(trace.yValues()));

  std::vector<double> x_values = bars.xValues();
  QVector<double> key_qvector;
  key_qvector.assign(x_values.begin(), x_values.end());

  std::vector<double> y_values = bars.yValues();
  QVector<double> value_qvector;
  value_qvector.assign(y_values.begin(), y_values.end());

  qDebug() << "The size of the x values for trace is:" << key_qvector.size()
           << "and for y values is:" << value_qvector.size();

#if 0

  qDebug() << "The size of the x values for trace is:" << key_qvector.size()
           << "and for y values is:" << value_qvector.size();

  QString text;

  for(qsizetype iter = 0; iter < key_qvector.size(); ++iter)
    text += QString("(%1,%2)\n")
              .arg(key_qvector.at(iter), 0, 'f', 6)
              .arg(value_qvector.at(iter), 0, 'f', 6);

  qDebug().noquote() << text;

#endif


  bars_p->setData(key_qvector, value_qvector, true);

  QPen pen = graph()->pen();
  pen.setColor(color);
  graph()->setPen(pen);

  // Connect the signal of selection change so that we can re-emit it for the
  // widget that is using *this widget.

  connect(bars_p,
          static_cast<void (QCPAbstractPlottable::*)(bool)>(
            &QCPAbstractPlottable::selectionChanged),
          [this, bars_p]() {
            emit plottableSelectionChangedSignal(bars_p, bars_p->selected());
          });

  // Rescaling the axes is actually unpleasant if there are more than one
  // graph in the plot widget and that we are adding one. So only, rescale if
  // the number of graphs is == 1, that is we are adding the first one.

  if(graphCount() == 1)
    {
      rescaleAxes();
      resetAxesRangeHistory();
    }

  replot();

  return bars_p;
}

//! Find a minimal integration range starting at an existing data point
/*!

  If the user clicks onto a plot at a location that is not a true data point,
  get a data range that begins at the preceding data point and that ends at
  the clicked location point.

*/
bool
BaseTracePlotWidget::findIntegrationLowerRangeForKey(int index,
                                                     double key,
                                                     QCPRange &range)
{

  // Given a key double value, we want to know what is the range that will
  // frame correctly the key double value if that key value is not exactly
  // the one of a point of the trace.

  // First of all get the keys of the graph.

  QCPGraph *theGraph = graph(index);

  if(theGraph == nullptr)
    throw ExceptionNotPossible(
      "basetraceplotwidget.cpp @ indIntegrationLowerRangeForKey() -- ERROR "
      "theGraph cannot be nullptr.");

  // QCPGraphDataContainer is a typedef QCPDataContainer<QCPGraphData> and
  // QCPDataContainer< DataType > is a Class Template. So in this context,
  // DataType is QCPGraphData.
  // QCPGraphData is the data point, that is the (key,value) pair.
  QSharedPointer<QCPGraphDataContainer> graph_data_container_p =
    theGraph->data();

  QCPDataRange dataRange = graph_data_container_p->dataRange();

  if(!dataRange.isValid())
    return false;

  if(!dataRange.size())
    return false;

  if(dataRange.size() > 1)
    {
      double firstKey = graph_data_container_p->at(dataRange.begin())->key;
      double lastKey  = graph_data_container_p->at(dataRange.end())->key;

      // There is one check to be done: the user might erroneously set the mouse
      // cursor beyond the last point of the graph. If that is the case, then
      // upper key needs to be that very point. All we need to do is return the
      // lower key, that is the pre-last key of the keys list. No need to
      // iterate in the keys list.

      if(key > lastKey)
        {
          // No need to search for the key in the keys, just get the lower key
          // immediately, that is, the key that is one slot left the last key.
          range.lower = graph_data_container_p->at(dataRange.end() - 2)->key;
          range.upper = graph_data_container_p->at(dataRange.end() - 1)->key;

          return true;
        }

      // Likewise, if the cursor is set left of the first plot point, then that
      // will be the lower range point. All we need is to provide the upper
      // range point as the second point of the plot.

      if(key < firstKey)
        {
          range.lower = firstKey;
          range.upper = graph_data_container_p->at(dataRange.begin() + 1)->key;

          return true;
        }

      // Finally the generic case where the user point to any point *in* the
      // graph.

      range.lower =
        graph_data_container_p->findBegin(key, /*expandedRange*/ true)->key;
      range.upper =
        std::prev(graph_data_container_p->findEnd(key, /*expandedRange*/ true))
          ->key;

      return true;
    }

  return false;
}

std::vector<double>
BaseTracePlotWidget::getValuesX(int graph_index) const
{
  std::vector<double> keys;

  QCPGraph *graph_p = graph(graph_index);

  if(graph_p == nullptr)
    qFatal("Programming error.");

  QSharedPointer<QCPGraphDataContainer> graph_data_container_p =
    graph_p->data();

  // Iterate in the keys
  auto beginIt = graph_data_container_p->begin();
  auto endIt   = graph_data_container_p->end();

  for(auto iter = beginIt; iter != endIt; ++iter)
    keys.push_back(iter->key);

  return keys;
}

std::vector<double>
BaseTracePlotWidget::getValuesY(int graph_index) const
{
  std::vector<double> values;

  QCPGraph *graph_p = graph(graph_index);

  if(graph_p == nullptr)
    qFatal("Programming error.");

  QSharedPointer<QCPGraphDataContainer> graph_data_container_p =
    graph_p->data();

  // Iterate in the values
  auto beginIt = graph_data_container_p->begin();
  auto endIt   = graph_data_container_p->end();

  for(auto iter = beginIt; iter != endIt; ++iter)
    values.push_back(iter->key);

  return values;
}

QCPRange
BaseTracePlotWidget::getValueRangeOnKeyRange(QCPAbstractPlottable *plottable_p,
                                             bool &ok)
{

  // The X axis range is set. But we want to find for that X axis range the
  // min and max Y values. This function is useful when the user asks that
  // while changing the X axis range, the trace be always in full scale on the
  // Y axis.

  QCPRange key_range(xAxis->range().lower, xAxis->range().upper);

  if(plottable_p != nullptr)
    {

      return plottable_p->getValueRange(ok, QCP::SignDomain::sdBoth, key_range);
    }
  else
    {

      // How many graphs are currently plotted in this plot widget ?
      int graph_count = graphCount();

      // Iterate in each graph and get the y max value. Then compare with the
      // largest one and update if necessary. Store the pointer to the graph
      // that has a larger y value. At the end of the iteration, it will be
      // the winner.

      double temp_min_value = std::numeric_limits<double>::max();
      double temp_max_value = std::numeric_limits<double>::min();

      bool found_range = false;

      for(int iter = 0; iter < graph_count; ++iter)
        {
          QCPGraph *plottable_p = graph(iter);

          QCPRange value_range =
            plottable_p->getValueRange(ok, QCP::SignDomain::sdBoth, key_range);

          if(ok)
            found_range = true;

          if(value_range.lower < temp_min_value)
            temp_min_value = value_range.lower;
          if(value_range.upper > temp_max_value)
            temp_max_value = value_range.upper;
        }

      // At this point return the range.

      ok = found_range;
      return QCPRange(temp_min_value, temp_max_value);
    }
}

QCPRange
BaseTracePlotWidget::getValueRangeOnKeyRange(int index, bool &ok)
{

  // The X axis range is set. But we want to find for that X axis range the
  // min and max Y values. This function is useful when the user asks that
  // while changing the X axis range, the trace be always in full scale on the
  // Y axis.

  QCPAbstractPlottable *plottable_p = plottable(index);

  if(plottable_p == nullptr)
    qFatal("Programming error.");

  return getValueRangeOnKeyRange(plottable_p, ok);
}

double
BaseTracePlotWidget::getYatX(double x, QCPGraph *graph_p)
{
  if(graph_p == nullptr)
    qFatal("Programming error.");

  QCPItemTracer *tracer_p = new QCPItemTracer(this);
  tracer_p->setGraph(graph_p);
  tracer_p->setInterpolating(true);
  tracer_p->setGraphKey(x);
  tracer_p->updatePosition();

  double value = tracer_p->position->value();

  tracer_p->setGraph(nullptr);

  // Essential to do this because otherwise crash when closing the app.
  removeItem(tracer_p);

  return value;
}

double
BaseTracePlotWidget::getYatX(double x, int index)
{
  QCPGraph *graph_p = graph(index);

  if(graph_p == nullptr)
    qFatal("Programming error.");

  return getYatX(x, graph_p);
}

void
BaseTracePlotWidget::axisDoubleClickHandler(
  QCPAxis *axis,
  [[maybe_unused]] QCPAxis::SelectablePart part,
  QMouseEvent *event)
{
  // qDebug();

  m_context.m_keyboardModifiers = QGuiApplication::queryKeyboardModifiers();

  if(m_context.m_keyboardModifiers & Qt::ControlModifier)
    {
      // qDebug();

      // If the Ctrl modifiers is active, then both axes are to be reset. Also
      // the histories are reset also.

      rescaleAxes();
      resetAxesRangeHistory();
    }
  else
    {
      // qDebug();

      // Only the axis passed as parameter is to be rescaled.
      // Reset the range of that axis to the max view possible, but for the y
      // axis check if the Shift keyboard key is pressed. If so the full scale
      // should be calculated only on the data in the current x range.

      if(axis->orientation() == Qt::Vertical)
        {
          if(m_context.m_keyboardModifiers & Qt::ShiftModifier)
            {

              // In this case, we want to make a rescale of the Y axis such
              // that it displays full scale the data in the current X axis
              // range only.

              bool ok = false;

              QCPRange value_range = getValueRangeOnKeyRange(nullptr, ok);

              yAxis->setRange(value_range);
            }
          else
            axis->rescale();
        }
      else
        axis->rescale();

      updateAxesRangeHistory();

      event->accept();
    }

  // The double-click event does not cancel the mouse press event. That is, if
  // left-double-clicking, at the end of the operation the button still
  // "pressed". We need to remove manually the button from the pressed buttons
  // context member.

  m_context.m_pressedMouseButtons ^= event->button();

  updateContextXandYAxisRanges();

  emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);

  replot();
}

void
BaseTracePlotWidget::axisRescale()
{
  double xLower = xAxis->range().lower;
  double xUpper = xAxis->range().upper;

  // Get the current y lower/upper range.
  double yLower = yAxis->range().lower;
  double yUpper = yAxis->range().upper;

  // This function is called only when the user has clicked on the x/y axis or
  // when the user has dragged the left mouse button with the Ctrl key
  // modifier. The m_context.m_wasClickOnXAxis is then simulated in the mouse
  // move handler. So we need to test which axis was clicked-on.

  if(m_context.m_wasClickOnXAxis)
    {

      // We are changing the range of the X axis.

      // What is the x delta ?
      double xDelta =
        m_context.m_currentDragPoint.x() - m_context.m_startDragPoint.x();

      // If xDelta is < 0, the  we were dragging from right to left, we are
      // compressing the view on the x axis, by adding new data to the right
      // hand size of the graph. So we add xDelta to the upper bound of the
      // range. Otherwise we are uncompressing the view on the x axis and
      // remove the xDelta from the upper bound of the range. This is why we
      // have the
      // '-'
      // and not '+' below;

      // qDebug() << "Setting xaxis:" << xLower << "--" << xUpper - xDelta;

      xAxis->setRange(xLower, xUpper - xDelta);


      // Old version
      // if(xDelta < 0)
      //{
      //// The dragging operation was from right to left, we are enlarging
      //// the range (thus, we are unzooming the view, since the widget
      //// always has the same size).

      // xAxis->setRange(xLower, xUpper + fabs(xDelta));
      //}
      // else
      //{
      //// The dragging operation was from left to right, we are reducing
      //// the range (thus, we are zooming the view, since the widget
      //// always has the same size).

      // xAxis->setRange(xLower, xUpper - fabs(xDelta));
      //}

      // We may either leave the scale of the Y axis as is (default) or
      // the user may want an automatic scale of the Y axis such that the
      // data displayed in the new X axis range are full scale on the Y
      // axis. For this, the Shift modifier key should be pressed.

      if(m_context.m_keyboardModifiers & Qt::ShiftModifier)
        {

          // In this case, we want to make a rescale of the Y axis such that
          // it displays full scale the data in the current X axis range only.

          bool ok = false;

          QCPRange value_range = getValueRangeOnKeyRange(nullptr, ok);

          yAxis->setRange(value_range);
        }
      // else, do leave the Y axis range unchanged.
    }
  // End of
  // if(m_context.m_wasClickOnXAxis)
  else // that is, if(m_context.m_wasClickOnYAxis)
    {
      // We are changing the range of the Y axis.

      // What is the y delta ?
      double yDelta =
        m_context.m_currentDragPoint.y() - m_context.m_startDragPoint.y();

      // See above for an explanation of the computation.

      yAxis->setRange(yLower, yUpper - yDelta);

      // Old version
      // if(yDelta < 0)
      //{
      //// The dragging operation was from top to bottom, we are enlarging
      //// the range (thus, we are unzooming the view, since the widget
      //// always has the same size).

      // yAxis->setRange(yLower, yUpper + fabs(yDelta));
      //}
      // else
      //{
      //// The dragging operation was from bottom to top, we are reducing
      //// the range (thus, we are zooming the view, since the widget
      //// always has the same size).

      // yAxis->setRange(yLower, yUpper - fabs(yDelta));
      //}
    }
  // End of
  // else // that is, if(m_context.m_wasClickOnYAxis)

  // Update the context with the current axes ranges

  updateContextXandYAxisRanges();

  emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);

  replot();
}

void
BaseTracePlotWidget::axisReframe()
{
  // qDebug();

  // double sorted_start_drag_point_x =
  // std::min(m_context.m_startDragPoint.x(), m_context.m_currentDragPoint.x());

  // xAxis->setRange(sorted_start_drag_point_x,
  // sorted_start_drag_point_x + fabs(m_context.m_xDelta));

  xAxis->setRange(
    QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeStop));

  // Note that the y axis should be rescaled from current lower value to new
  // upper value matching the y-axis position of the cursor when the mouse
  // button was released.

  yAxis->setRange(xAxis->range().lower,
                  std::max<double>(m_context.m_yRegionRangeStart,
                                   m_context.m_yRegionRangeStop));

  // qDebug() << "xaxis:" << xAxis->range().lower << "-" <<
  // xAxis->range().upper
  //<< "yaxis:" << yAxis->range().lower << "-" << yAxis->range().upper;

  // If the shift modifier key is pressed, then the user want the y axis
  // to be full scale.
  if(m_context.m_keyboardModifiers & Qt::ShiftModifier)
    {

      bool ok = false;

      QCPRange value_range = getValueRangeOnKeyRange(nullptr, ok);

      yAxis->setRange(value_range);
    }
  // else do nothing, let the y axis range as is.

  updateContextXandYAxisRanges();

  updateAxesRangeHistory();
  emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);

  replot();
}

void
BaseTracePlotWidget::axisZoom()
{

  // Use the m_context.m_xRegionRangeStart/End values, but we need to sort the
  // values before using them, because now we want to really have the lower x
  // value. Simply craft a QCPRange that will swap the values if lower is not
  // < than upper QCustomPlot calls this normalization).

  xAxis->setRange(
    QCPRange(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeStop));

  // If the shift modifier key is pressed, then the user want the y axis
  // to be full scale.
  if(m_context.m_keyboardModifiers & Qt::ShiftModifier)
    {

      bool ok = false;

      QCPRange value_range = getValueRangeOnKeyRange(nullptr, ok);

      yAxis->setRange(value_range);
    }
  else
    yAxis->setRange(
      QCPRange(m_context.m_yRegionRangeStart, m_context.m_yRegionRangeStop));

  updateContextXandYAxisRanges();

  updateAxesRangeHistory();
  emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);

  replot();
}

void
BaseTracePlotWidget::axisPan()
{
  // qDebug();

  // Sanity check
  if(!m_context.m_wasClickOnXAxis && !m_context.m_wasClickOnYAxis)
    qFatal(
      "This function can only be called if the mouse click was on one of the "
      "axes");

  if(m_context.m_wasClickOnXAxis)
    {
      xAxis->setRange(m_context.m_xRange.lower - m_context.m_xDelta,
                      m_context.m_xRange.upper - m_context.m_xDelta);

      // If the shift modifier key is pressed, then the user want the y axis
      // to be full scale.
      if(m_context.m_keyboardModifiers & Qt::ShiftModifier)
        {

          bool ok = false;

          QCPRange value_range = getValueRangeOnKeyRange(nullptr, ok);

          yAxis->setRange(value_range);
        }
      // else nothing to do we do not change the y axis scale.
    }

  if(m_context.m_wasClickOnYAxis)
    {
      yAxis->setRange(m_context.m_yRange.lower - m_context.m_yDelta,
                      m_context.m_yRange.upper - m_context.m_yDelta);
    }

  updateContextXandYAxisRanges();

  // qDebug() << "The updated context:" << m_context.toString();

  // We cannot store the new ranges in the history, because the pan operation
  // involved a huge quantity of micro-movements elicited upon each mouse move
  // cursor event so we would have a huge history.
  // updateAxesRangeHistory();

  // Now that the contex has the right range values, we can emit the
  // signal that will be used by this plot widget users, typically to
  // abide by the x/y range lock required by the user.

  emit plotRangesChangedSignal((QMouseEvent *)nullptr, m_context);

  replot();
}

pappso::Trace
BaseTracePlotWidget::toTrace(int index, bool only_visible_range) const
{
  QCPGraph *graph_p = graph(index);

  return toTrace(graph_p, only_visible_range);
}

pappso::Trace
BaseTracePlotWidget::toTrace(const QCPGraph *graph_p, bool only_visible_range) const
{
  if(graph_p == nullptr)
    qFatal("Programming error. Pointer cannot be nullptr.");

  pappso::Trace trace;

  if(only_visible_range)
    {
      // qDebug() << "Only visible range.";

      QCPRange x_axis_range = xAxis->range();

      // qDebug() << "Returning for visible range:" << x_axis_range;

      return toTrace(x_axis_range, graph_p);
    }
  else
    {
      qDebug() << "Failed to get key range. Will return full key range.";
    }

  QSharedPointer<QCPGraphDataContainer> graph_data_container_p = graph_p->data();

  // Iterate in the *all* the trace keys
  auto beginIt = graph_data_container_p->begin();
  auto endIt   = graph_data_container_p->end();

  for(auto iter = beginIt; iter != endIt; ++iter)
    trace.push_back(pappso::DataPoint(iter->key, iter->value));

  return trace;
}

pappso::Trace
BaseTracePlotWidget::toTrace(const QCPRange &x_axis_range, int index) const
{
  QCPGraph *graph_p = graph(index);

  if(graph_p == nullptr)
    qFatal("Programming error.");

  return toTrace(x_axis_range, graph_p);
}

pappso::Trace
BaseTracePlotWidget::toTrace(const QCPRange &x_axis_range,
                             const QCPGraph *graph_p) const
{
  // qDebug() << "to Trace only key visible range:" << x_axis_range;

  // Make a Trace with the data in the range.
  Trace data_trace;

  QSharedPointer<QCPGraphDataContainer> graph_data_container_sp;

  graph_data_container_sp = graph_p->data();

  // Grab the iterator to the start to the x axis range
  // If expandedRange is true, the data point just
  // below sortKey will be considered, otherwise the one just above.
  auto beginIt = graph_data_container_sp->findBegin(x_axis_range.lower,
                                                    /*expandedRange*/ false);
  // Grab the iterator to the end of the axis range
  // If expandedRange is true, the data point just
  // above sortKey will be considered, otherwise the
  // one just below.
  auto endIt = graph_data_container_sp->findEnd(x_axis_range.upper,
                                                /*expandedRange*/ false);

  for(auto iter = beginIt; iter != endIt; ++iter)
    data_trace.push_back(DataPoint(iter->key, iter->value));

  return data_trace;
}


pappso::Trace
BaseTracePlotWidget::toTrace(const QCPRange &x_axis_range, const QCPBars *bars_p) const
{
  // qDebug() << "to Trace only key visible range:" << x_axis_range;

  // Make a Trace with the data in the range.
  Trace data_trace;

  QSharedPointer<QCPBarsDataContainer> bars_data_container_sp;

  bars_data_container_sp = bars_p->data();

  // Grab the iterator to the start to the x axis range
  // If expandedRange is true, the data point just
  // below sortKey will be considered, otherwise the one just above.
  auto beginIt = bars_data_container_sp->findBegin(x_axis_range.lower,
                                                   /*expandedRange*/ false);
  // Grab the iterator to the end of the axis range
  // If expandedRange is true, the data point just
  // above sortKey will be considered, otherwise the
  // one just below.
  auto endIt = bars_data_container_sp->findEnd(x_axis_range.upper,
                                               /*expandedRange*/ false);

  for(auto iter = beginIt; iter != endIt; ++iter)
    data_trace.push_back(DataPoint(iter->key, iter->value));

  return data_trace;
}

} // namespace pappso
