/* 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 "basecolormapplotwidget.h"
#include "../../core/trace/maptrace.h"
#include "pappsomspp/core/pappsoexception.h"


namespace pappso
{


BaseColorMapPlotWidget::BaseColorMapPlotWidget(QWidget *parent,
                                               const QString &x_axis_label,
                                               const QString &y_axis_label)
  : BasePlotWidget(parent, x_axis_label, y_axis_label)
{
  // Do not call createAllAncillaryItems() in this base 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.
  //
  // In the present case, the function needs to be called right after addition
  // of the color map plot.
}


BaseColorMapPlotWidget::BaseColorMapPlotWidget(QWidget *parent) : BasePlotWidget(parent, "x", "y")
{
  // Do not call createAllAncillaryItems() in this base 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.
  //
  // In the present case, the function needs to be called right after addition
  // of the color map plot.
}


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

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

*/
BaseColorMapPlotWidget::~BaseColorMapPlotWidget()
{
  if(mpa_origColorMapData != nullptr)
    delete mpa_origColorMapData;

  if(mpa_origColorMapPlotConfig != nullptr)
    delete mpa_origColorMapPlotConfig;
}


void
BaseColorMapPlotWidget::setColorMapPlotConfig(const ColorMapPlotConfig &color_map_config)
{
  m_colorMapPlotConfig = color_map_config;
}


const ColorMapPlotConfig *
BaseColorMapPlotWidget::getOrigColorMapPlotConfig()
{
  return mpa_origColorMapPlotConfig;
}


const ColorMapPlotConfig &
BaseColorMapPlotWidget::getColorMapPlotConfig()
{
  return m_colorMapPlotConfig;
}


QCPColorMap *
BaseColorMapPlotWidget::addColorMap(
  std::shared_ptr<std::map<double, MapTrace>> double_map_trace_map_sp,
  const ColorMapPlotConfig color_map_plot_config,
  const QColor &color)
{
  // qDebug() << "Adding color map with config:" <<
  // color_map_plot_config.toString();

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

  QCPColorMap *color_map_p = new QCPColorMap(xAxis, yAxis);

  color_map_p->setLayer("plotsLayer");

  // Do not forget to copy the config!
  m_colorMapPlotConfig = color_map_plot_config;

  // Immediately create a copy of the original data for backup.
  mpa_origColorMapPlotConfig = new ColorMapPlotConfig(color_map_plot_config);

#if 0
      // This is the code on the QCustomPlot documentation and it works fine.
      QCPColorMap *color_map_p = new QCPColorMap(xAxis, yAxis);

      color_map_p->data()->setSize(50, 50);
      color_map_p->data()->setRange(QCPRange(0, 2), QCPRange(0, 2));
      for(int x = 0; x < 50; ++x)
        for(int y = 0; y < 50; ++y)
          color_map_p->data()->setCell(x, y, qCos(x / 10.0) + qSin(y / 10.0));
      color_map_p->setGradient(QCPColorGradient::gpPolar);
      color_map_p->rescaleDataRange(true);
      rescaleAxes();
      replot();
#endif

  // Only now can afford to call createAllAncillaryItems() in this derived class
  // because the color map has been created already. The rendering order will
  // thus not hide the ancillary items, since they have been created after the
  // color map plot (since the rendering order is according to the
  // order in which the items have been created). See contructor note.
  createAllAncillaryItems();

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

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

  // qDebug() << "Configuring the color map with this config:"
  //<< color_map_plot_config.toString();

  color_map_p->data()->setSize(color_map_plot_config.keyCellCount,
                               color_map_plot_config.mzCellCount);

  color_map_p->data()->setRange(
    QCPRange(color_map_plot_config.minKeyValue, color_map_plot_config.maxKeyValue),
    QCPRange(color_map_plot_config.minMzValue, color_map_plot_config.maxMzValue));
  color_map_p->data()->fill(0.0);

  // We have now to fill the color map.

  for(auto &&pair : *double_map_trace_map_sp)
    {

      // The first value is the key and the second value is the MapTrace into
      // which we need to iterated and for each point (double mz, double
      // intensity) create a map cell.

      double dt_or_rt_key = pair.first;
      MapTrace map_trace  = pair.second;

      for(auto &&data_point_pair : map_trace)
        {
          double mz        = data_point_pair.first;
          double intensity = data_point_pair.second;

          // We are filling dynamically the color map. If a cell had already
          // something in, then we need to take that into account. This is
          // because we let QCustomPlot handle the fuzzy transition between
          // color map plot cells.

          double prev_intensity = color_map_p->data()->data(dt_or_rt_key, mz);
          double new_intensity  = prev_intensity + intensity;

          // Record the min/max cell intensity value (origM(in/ax)ZValue). We
          // will need that later. Also update the lastM(in/ax)ZValue because
          // when doing this kind of data conversion it is assume that the user
          // actually changes the data.
          m_colorMapPlotConfig.setOrigAndLastMinZValue(
            std::min(m_colorMapPlotConfig.origMinZValue, new_intensity));

          m_colorMapPlotConfig.setOrigAndLastMaxZValue(
            std::max(m_colorMapPlotConfig.origMaxZValue, new_intensity));

          // qDebug() << "Setting tri-point:" << dt_or_rt_key << "," << mz <<
          // ","
          //<< new_intensity;

          color_map_p->data()->setData(dt_or_rt_key, mz, new_intensity);
        }
    }

  // At this point we have finished filling-up the color map.

  // The gpThermal is certainly one of the best.

  color_map_p->setGradient(QCPColorGradient::gpThermal);

  color_map_p->rescaleDataRange(true);

  color_map_p->rescaleAxes();
  resetAxesRangeHistory();

  // The pen of the color map itself is of no use. Instead the user will see the
  // color of the axes' labels.

  QPen pen = xAxis->basePen();
  pen.setColor(color);

  xAxis->setBasePen(pen);
  xAxis->setLabelColor(color);
  xAxis->setTickLabelColor(color);

  yAxis->setBasePen(pen);
  yAxis->setLabelColor(color);
  yAxis->setTickLabelColor(color);

  // And now set the color map's pen to the same color, even if we do not use
  // it, we need it for coloring the plots that might be integrated from this
  // color map.

  color_map_p->setPen(pen);

  // Copy the original color map's data into a backup copy.

  mpa_origColorMapData = new QCPColorMapData(*(color_map_p->data()));

  replot();

  return color_map_p;
}

QCPColorMap *
BaseColorMapPlotWidget::addColorMap(const TimsFrame &tims_frame,
                                    const ColorMapPlotConfig color_map_plot_config,
                                    const QColor &color)
{
  qDebug();
  if(!color.isValid())
    throw PappsoException(QString("The color to be used for the plot graph is invalid."));

  QCPColorMap *color_map_p = new QCPColorMap(xAxis, yAxis);

  color_map_p->setLayer("plotsLayer");

  // Do not forget to copy the config!
  m_colorMapPlotConfig = color_map_plot_config;

  // Immediately create a copy of the original data for backup.
  mpa_origColorMapPlotConfig = new ColorMapPlotConfig(color_map_plot_config);

  qDebug();
#if 0
      // This is the code on the QCustomPlot documentation and it works fine.
      QCPColorMap *color_map_p = new QCPColorMap(xAxis, yAxis);

      color_map_p->data()->setSize(50, 50);
      color_map_p->data()->setRange(QCPRange(0, 2), QCPRange(0, 2));
      for(int x = 0; x < 50; ++x)
        for(int y = 0; y < 50; ++y)
          color_map_p->data()->setCell(x, y, qCos(x / 10.0) + qSin(y / 10.0));
      color_map_p->setGradient(QCPColorGradient::gpPolar);
      color_map_p->rescaleDataRange(true);
      rescaleAxes();
      replot();
#endif

  // Only now can afford to call createAllAncillaryItems() in this derived class
  // because the color map has been created already. The rendering order will
  // thus not hide the ancillary items, since they have been created after the
  // color map plot (since the rendering order is according to the
  // order in which the items have been created). See contructor note.
  createAllAncillaryItems();

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

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

  // qDebug() << "Configuring the color map with this config:"
  //<< color_map_plot_config.toString();

  color_map_p->data()->setSize(color_map_plot_config.keyCellCount,
                               color_map_plot_config.mzCellCount);

  color_map_p->data()->setRange(
    QCPRange(color_map_plot_config.minKeyValue, color_map_plot_config.maxKeyValue),
    QCPRange(color_map_plot_config.minMzValue, color_map_plot_config.maxMzValue));
  color_map_p->data()->fill(0.0);
  // double max_intensity = 0;
  qDebug();
  // We have now to fill the color map.
  std::size_t number_of_scans = tims_frame.getTotalNumberOfScans();
  for(std::size_t i = 0; i < number_of_scans; i++)
    {
      std::vector<quint32> tof_index_vector       = tims_frame.getScanTofIndexList(i);
      std::vector<quint32> intensity_index_vector = tims_frame.getScanIntensityList(i);


      // The first value is the key and the second value is the MapTrace into
      // which we need to iterated and for each point (double mz, double
      // intensity) create a map cell.

      double dt_or_rt_key      = i;
      std::size_t vector_index = 0;
      for(quint32 mzindex : tof_index_vector)
        {
          double mz        = mzindex;
          double intensity = intensity_index_vector.at(vector_index);
          // max_intensity    = std::max(max_intensity, intensity);
          // We are filling dynamically the color map. If a cell had already
          // something in, then we need to take that into account. This is
          // because we let QCustomPlot handle the fuzzy transition between
          // color map plot cells.

          double prev_intensity = color_map_p->data()->data(dt_or_rt_key, mz);
          double new_intensity  = prev_intensity + intensity;

          // qDebug() << "mz=" << mz << " int=" << intensity;

          // Record the min/max cell intensity value (origM(in/ax)ZValue). We
          // will need that later. Also update the lastM(in/ax)ZValue because
          // when doing this kind of data conversion it is assume that the user
          // actually changes the data.
          m_colorMapPlotConfig.setOrigAndLastMinZValue(
            std::min(m_colorMapPlotConfig.origMinZValue, new_intensity));

          m_colorMapPlotConfig.setOrigAndLastMaxZValue(
            std::max(m_colorMapPlotConfig.origMaxZValue, new_intensity));

          // qDebug() << "Setting tri-point:" << dt_or_rt_key << "," << mz <<
          // ","
          //<< new_intensity;

          color_map_p->data()->setCell(dt_or_rt_key, mz, new_intensity);

          // qDebug() << "dt_or_rt_key=" << dt_or_rt_key << " mz=" << mz
          //         << " new_intensity=" << new_intensity;

          vector_index++;
        }
    }

  // At this point we have finished filling-up the color map.

  // The gpThermal is certainly one of the best.

  color_map_p->setGradient(QCPColorGradient::gpThermal);

  color_map_p->data()->recalculateDataBounds();
  color_map_p->rescaleDataRange(true);

  color_map_p->rescaleAxes();
  resetAxesRangeHistory();

  // The pen of the color map itself is of no use. Instead the user will see the
  // color of the axes' labels.

  qDebug();
  QPen pen = xAxis->basePen();
  pen.setColor(color);

  xAxis->setBasePen(pen);
  xAxis->setLabelColor(color);
  xAxis->setTickLabelColor(color);

  yAxis->setBasePen(pen);
  yAxis->setLabelColor(color);
  yAxis->setTickLabelColor(color);

  // And now set the color map's pen to the same color, even if we do not use
  // it, we need it for coloring the plots that might be integrated from this
  // color map.

  color_map_p->setPen(pen);

  // Copy the original color map's data into a backup copy.

  mpa_origColorMapData = new QCPColorMapData(*(color_map_p->data()));

  color_map_p->setInterpolate(false);
  color_map_p->setTightBoundary(false);

  replot();


  qDebug() << color_map_p->data()->keyRange();
  qDebug() << color_map_p->data()->valueRange();
  qDebug() << color_map_p->data()->dataBounds();
  qDebug();
  return color_map_p;
}

void
BaseColorMapPlotWidget::transposeAxes()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()" ;

  QCPColorMap *color_map_p = static_cast<QCPColorMap *>(plottable(0));

  QCPColorMapData *origData = color_map_p->data();

  int keySize   = origData->keySize();
  int valueSize = origData->valueSize();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Orig data size:" << keySize << valueSize;

  QCPRange keyRange   = origData->keyRange();
  QCPRange valueRange = origData->valueRange();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Value at cell 80,650:" << origData->cell(80,650);

  // Transposed map.
  QCPColorMapData *newData = new QCPColorMapData(valueSize, keySize, valueRange, keyRange);

  for(int iter = 0; iter < keySize; ++iter)
    {
      for(int jter = 0; jter < valueSize; ++jter)
        {
          double cellData = origData->cell(iter, jter);

          newData->setCell(jter, iter, cellData);
        }
    }

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "New data size:" << newData->keySize() << newData->valueSize();

  // At this point the transposition has been done.

  color_map_p->data()->clear();
  color_map_p->rescaleDataRange(true);

  // Now we need to invert the labels and data kinds.

  Enums::DataKind temp_data_kind     = m_colorMapPlotConfig.xAxisDataKind;
  m_colorMapPlotConfig.xAxisDataKind = m_colorMapPlotConfig.yAxisDataKind;
  m_colorMapPlotConfig.yAxisDataKind = temp_data_kind;

  QString temp_axis_label = xAxis->label();
  xAxis->setLabel(yAxis->label());
  yAxis->setLabel(temp_axis_label);

  // Will take ownership of the newData.
  color_map_p->setData(newData);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Value at cell 80,650:" << newData->cell(80,650)
  //<< "Value at cell 650, 80:" << newData->cell(650,80);

  // QCPAxis *p_keyAxis = mp_colorMap->keyAxis();
  // QCPAxis *p_valueAxis = mp_colorMap->valueAxis();

  // mp_colorMap->setKeyAxis(p_valueAxis);
  // mp_colorMap->setValueAxis(p_keyAxis);

  color_map_p->rescaleAxes();

  replot();
}


void
BaseColorMapPlotWidget::zAxisScaleToLog10()
{
  // The user wants to rescale the intensity values of the color map according
  // to: new_int = log10(orig_int).

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()" ;

  if(m_colorMapPlotConfig.zAxisScale == Enums::AxisScale::log10)
    {
      qDebug() << "Asking to change z axis scale to log10 while it is already "
                  "like so.";

      return;
    }

  // qDebug() << "m_colorMapPlotConfig:" << m_colorMapPlotConfig.toString();

  QCPColorMap *color_map_p = static_cast<QCPColorMap *>(plottable(0));

  QCPColorMapData *map_data = color_map_p->data();

  int keySize   = map_data->keySize();
  int valueSize = map_data->valueSize();

  QCPRange keyRange   = map_data->keyRange();
  QCPRange valueRange = map_data->valueRange();

  // Make a copy of the current config so that we can modify
  // the xxxZvalue values.
  ColorMapPlotConfig new_color_map_plot_config(m_colorMapPlotConfig);

  // But we need to reset these two values to be able to update them using
  // std::min() and std::max() below.
  new_color_map_plot_config.setOrigAndLastMinZValue(std::numeric_limits<double>::max());

  new_color_map_plot_config.setOrigAndLastMaxZValue(std::numeric_limits<double>::min());

  // qDebug() << "new_color_map_plot_config"
  //<< new_color_map_plot_config.toString();

  // Log-ified heat map.
  QCPColorMapData *new_map_data = new QCPColorMapData(keySize, valueSize, keyRange, valueRange);

  // qDebug() << "Starting iteration in the color map.";

  for(int iter = 0; iter < keySize; ++iter)
    {
      for(int jter = 0; jter < valueSize; ++jter)
        {
          double cell_data = map_data->cell(iter, jter);

          double new_cell_data = 0;

          if(!cell_data)
            // The log10 would be -inf, but then we'd have a huge data range and
            // the color map would look totally blue... that is like 0 intensity
            // all over.
            new_cell_data = -1;
          else
            new_cell_data = std::log10(cell_data);

          // Store the new values here. Should we change the last or orig or
          // both ?
          new_color_map_plot_config.lastMinZValue =
            //(new_cell_data < new_color_map_plot_config.minZValue
            //? new_cell_data
            //: new_color_map_plot_config.minZValue);
            std::min(new_color_map_plot_config.lastMinZValue, new_cell_data);

          new_color_map_plot_config.lastMaxZValue =
            //(new_cell_data > new_color_map_plot_config.maxZValue
            //? new_cell_data
            //: new_color_map_plot_config.maxZValue);
            std::max(new_color_map_plot_config.lastMaxZValue, new_cell_data);

          // qDebug() << "cell_data:" << cell_data
          //<< "new_cell_data:" << new_cell_data
          //<< "new_color_map_plot_config.minZValue:"
          //<< new_color_map_plot_config.minZValue
          //<< "new_color_map_plot_config.maxZValue:"
          //<< new_color_map_plot_config.maxZValue;

          new_map_data->setCell(iter, jter, new_cell_data);
        }
    }

  // qDebug() << "Finished iteration in the color map.";

  color_map_p->data()->clear();

  // Will take ownership of the new_map_data.
  color_map_p->setData(new_map_data);

  color_map_p->data()->recalculateDataBounds();
  color_map_p->rescaleDataRange(true);

  // At this point the new color map data have taken their place, we can update
  // the config. This, way any new filtering can take advantage of the new
  // values and compute the threshold correctly.
  m_colorMapPlotConfig = new_color_map_plot_config;

  // Now we need to document the change.
  m_colorMapPlotConfig.zAxisScale = Enums::AxisScale::log10;

  // qDebug() << "new_color_map_plot_config"
  //<< new_color_map_plot_config.toString();

  // qDebug() << "m_colorMapPlotConfig:" << m_colorMapPlotConfig.toString();

  // We should not do this, as the user might have zoomed to a region of
  // interest.
  // color_map_p->rescaleAxes();

  replot();
}

void
BaseColorMapPlotWidget::zAxisFilterLowPassPercentage(double threshold_percentage)
{
  // This filter allows all the values smaller than a threshold to remain
  // unchanged. Instead, all the values above the threshold will be reset to
  // that threshold.
  //
  // The effect of this filter is to enhance the high-intensity signal.

  QCPColorMap *color_map_p = static_cast<QCPColorMap *>(plottable(0));

  QCPColorMapData *map_data = color_map_p->data();

  int keySize   = map_data->keySize();
  int valueSize = map_data->valueSize();

  QCPRange keyRange   = map_data->keyRange();
  QCPRange valueRange = map_data->valueRange();

  double minZValue = m_colorMapPlotConfig.lastMinZValue;
  double maxZValue = m_colorMapPlotConfig.lastMaxZValue;

  double amplitude = maxZValue - minZValue;

  double amplitude_fraction = amplitude * threshold_percentage / 100;

  double threshold = minZValue + amplitude_fraction;

  // qDebug() << "Before filtering minZValue:" << minZValue
  //<< "maxZValue:" << maxZValue << "fraction:" << fraction
  //<< "threshold:" << threshold
  //<< "new threshold percentage:" << new_threshold_percentage;

  // Make a copy of the current config so that we can modify
  // the xxxZvalue values.
  ColorMapPlotConfig new_color_map_plot_config(m_colorMapPlotConfig);

  // But we need to reset these two values to be able to update them using
  // std::min() and std::max() below.
  new_color_map_plot_config.lastMinZValue = std::numeric_limits<double>::max();
  new_color_map_plot_config.lastMaxZValue = std::numeric_limits<double>::min();

  // Filtered
  QCPColorMapData *new_map_data = new QCPColorMapData(keySize, valueSize, keyRange, valueRange);

  for(int iter = 0; iter < keySize; ++iter)
    {
      for(int jter = 0; jter < valueSize; ++jter)
        {
          double cell_data = map_data->cell(iter, jter);

          double new_cell_data = 0;

          if(cell_data < threshold)
            // Keep the value, we are in low-pass
            new_cell_data = cell_data;
          else
            new_cell_data = threshold;

          // Store the new values here.
          new_color_map_plot_config.lastMinZValue =
            //(new_cell_data < new_color_map_plot_config.minZValue
            //? new_cell_data
            //: new_color_map_plot_config.minZValue);
            std::min(new_color_map_plot_config.lastMinZValue, new_cell_data);

          new_color_map_plot_config.lastMaxZValue =
            //(new_cell_data > new_color_map_plot_config.maxZValue
            //? new_cell_data
            //: new_color_map_plot_config.maxZValue);
            std::max(new_color_map_plot_config.lastMaxZValue, new_cell_data);

          // qDebug() << "cell_data:" << cell_data
          //<< "new_cell_data:" << new_cell_data
          //<< "new_color_map_plot_config.minZValue:"
          //<< new_color_map_plot_config.minZValue
          //<< "new_color_map_plot_config.maxZValue:"
          //<< new_color_map_plot_config.maxZValue;

          new_map_data->setCell(iter, jter, new_cell_data);
        }
    }

  color_map_p->data()->clear();

  // Will take ownership of the new_map_data.
  color_map_p->setData(new_map_data);

  color_map_p->data()->recalculateDataBounds();
  color_map_p->rescaleDataRange(true);


  // At this point the new color map data have taken their place, we can update
  // the config. This, way any new filtering can take advantage of the new
  // values and compute the threshold correctly.
  m_colorMapPlotConfig = new_color_map_plot_config;

  // qDebug() << "Member colormap plot config is now, after filter was applied:"
  //<< m_colorMapPlotConfig.toString();

  // We should not do this, as the user might have zoomed to a region of
  // interest.
  // color_map_p->rescaleAxes();

  replot();
}


void
BaseColorMapPlotWidget::zAxisFilterLowPassThreshold(double threshold)
{

  // This filter allows all the values smaller than a threshold to remain
  // unchanged. Instead, all the values above the threshold will be reset to
  // that threshold.

  QCPColorMap *color_map_p = static_cast<QCPColorMap *>(plottable(0));

  QCPColorMapData *map_data = color_map_p->data();

  int keySize   = map_data->keySize();
  int valueSize = map_data->valueSize();

  QCPRange keyRange   = map_data->keyRange();
  QCPRange valueRange = map_data->valueRange();

  // qDebug() << "Before filtering minZValue:" << minZValue
  //<< "maxZValue:" << maxZValue << "fraction:" << fraction
  //<< "threshold:" << threshold
  //<< "new threshold percentage:" << new_threshold_percentage;

  // Make a copy of the current config so that we can modify
  // the xxxZvalue values.
  ColorMapPlotConfig new_color_map_plot_config(m_colorMapPlotConfig);

  // But we need to reset these two values to be able to update them using
  // std::min() and std::max() below.
  new_color_map_plot_config.lastMinZValue = std::numeric_limits<double>::max();
  new_color_map_plot_config.lastMaxZValue = std::numeric_limits<double>::min();

  // Filtered
  QCPColorMapData *new_map_data = new QCPColorMapData(keySize, valueSize, keyRange, valueRange);

  for(int iter = 0; iter < keySize; ++iter)
    {
      for(int jter = 0; jter < valueSize; ++jter)
        {
          double cell_data = map_data->cell(iter, jter);

          double new_cell_data = 0;

          if(cell_data < threshold)
            // Keep the value, we are in low-pass
            new_cell_data = cell_data;
          else
            new_cell_data = threshold;

          // Store the new values here.
          new_color_map_plot_config.lastMinZValue =
            //(new_cell_data < new_color_map_plot_config.minZValue
            //? new_cell_data
            //: new_color_map_plot_config.minZValue);
            std::min(new_color_map_plot_config.lastMinZValue, new_cell_data);

          new_color_map_plot_config.lastMaxZValue =
            //(new_cell_data > new_color_map_plot_config.maxZValue
            //? new_cell_data
            //: new_color_map_plot_config.maxZValue);
            std::max(new_color_map_plot_config.lastMaxZValue, new_cell_data);

          // qDebug() << "cell_data:" << cell_data
          //<< "new_cell_data:" << new_cell_data
          //<< "new_color_map_plot_config.minZValue:"
          //<< new_color_map_plot_config.minZValue
          //<< "new_color_map_plot_config.maxZValue:"
          //<< new_color_map_plot_config.maxZValue;

          new_map_data->setCell(iter, jter, new_cell_data);
        }
    }

  color_map_p->data()->clear();

  // Will take ownership of the new_map_data.
  color_map_p->setData(new_map_data);

  color_map_p->data()->recalculateDataBounds();
  color_map_p->rescaleDataRange(true);


  // At this point the new color map data have taken their place, we can update
  // the config. This, way any new filtering can take advantage of the new
  // values and compute the threshold correctly.
  m_colorMapPlotConfig = new_color_map_plot_config;

  // qDebug() << "Member colormap plot config is now, after filter was applied:"
  //<< m_colorMapPlotConfig.toString();

  // We should not do this, as the user might have zoomed to a region of
  // interest.
  // color_map_p->rescaleAxes();

  replot();
}

void
BaseColorMapPlotWidget::zAxisFilterHighPassPercentage(double threshold_percentage)
{
  // This filter allows all the value greater than a threshold to remain
  // unchanged. Instead, all the values below the threshold will be reset to
  // that threshold value.
  //
  // The effect of this filter is to reduce the low-intensity signal: reduce
  // noise.

  QCPColorMap *color_map_p = static_cast<QCPColorMap *>(plottable(0));

  QCPColorMapData *map_data = color_map_p->data();

  int keySize   = map_data->keySize();
  int valueSize = map_data->valueSize();

  QCPRange keyRange   = map_data->keyRange();
  QCPRange valueRange = map_data->valueRange();

  double minZValue = m_colorMapPlotConfig.lastMinZValue;
  double maxZValue = m_colorMapPlotConfig.lastMaxZValue;

  double amplitude = maxZValue - minZValue;

  double amplitude_fraction = amplitude * threshold_percentage / 100;

  double threshold = minZValue + amplitude_fraction;

  // qDebug() << "Before filtering minZValue:" << minZValue
  //<< "maxZValue:" << maxZValue << "fraction:" << fraction
  //<< "threshold:" << threshold
  //<< "new threshold percentage:" << new_threshold_percentage;

  // Make a copy of the current config so that we can modify
  // the xxxZvalue values.
  ColorMapPlotConfig new_color_map_plot_config(m_colorMapPlotConfig);

  // But we need to reset these two values to be able to update them using
  // std::min() and std::max() below.
  new_color_map_plot_config.lastMinZValue = std::numeric_limits<double>::max();
  new_color_map_plot_config.lastMaxZValue = std::numeric_limits<double>::min();

  // Filtered
  QCPColorMapData *new_map_data = new QCPColorMapData(keySize, valueSize, keyRange, valueRange);

  for(int iter = 0; iter < keySize; ++iter)
    {
      for(int jter = 0; jter < valueSize; ++jter)
        {
          double cell_data = map_data->cell(iter, jter);

          double new_cell_data = 0;

          if(cell_data > threshold)
            // Keep the value, we are in high-pass
            new_cell_data = cell_data;
          else
            new_cell_data = threshold;

          // Store the new values here.
          new_color_map_plot_config.lastMinZValue =
            //(new_cell_data < new_color_map_plot_config.minZValue
            //? new_cell_data
            //: new_color_map_plot_config.minZValue);
            std::min(new_color_map_plot_config.lastMinZValue, new_cell_data);

          new_color_map_plot_config.lastMaxZValue =
            //(new_cell_data > new_color_map_plot_config.maxZValue
            //? new_cell_data
            //: new_color_map_plot_config.maxZValue);
            std::max(new_color_map_plot_config.lastMaxZValue, new_cell_data);

          // qDebug() << "cell_data:" << cell_data
          //<< "new_cell_data:" << new_cell_data
          //<< "new_color_map_plot_config.minZValue:"
          //<< new_color_map_plot_config.minZValue
          //<< "new_color_map_plot_config.maxZValue:"
          //<< new_color_map_plot_config.maxZValue;

          new_map_data->setCell(iter, jter, new_cell_data);
        }
    }

  color_map_p->data()->clear();

  // Will take ownership of the new_map_data.
  color_map_p->setData(new_map_data);

  color_map_p->data()->recalculateDataBounds();
  color_map_p->rescaleDataRange(true);


  // At this point the new color map data have taken their place, we can update
  // the config. This, way any new filtering can take advantage of the new
  // values and compute the threshold correctly.
  m_colorMapPlotConfig = new_color_map_plot_config;

  // qDebug() << "Member colormap plot config is now, after filter was applied:"
  //<< m_colorMapPlotConfig.toString();

  // We should not do this, as the user might have zoomed to a region of
  // interest.
  // color_map_p->rescaleAxes();

  replot();
}


void
BaseColorMapPlotWidget::zAxisDataResetToOriginal()
{
  // The user might have changed to the axis scale to log10, for example.
  // While doing this, the original data were still available in
  // mpa_origColorMapData,with mpa_origColorMapPlotConfig. We need to reset the
  // current data to the original data.
  //
  // Same thing for filters that might have been applied to the data.

  QCPColorMap *color_map_p = static_cast<QCPColorMap *>(plottable(0));
  color_map_p->data()->clear();

  if(mpa_origColorMapData == nullptr)
    throw(PappsoException("Not possible that the mpa_origColorMapData pointer be null."));

  // We do no want that the color_map_p takes ownership of the data, because
  // these must remain there always, so pass true, to say that we want to copy
  // the data not transfer the pointer.
  color_map_p->setData(mpa_origColorMapData, true);

  color_map_p->data()->recalculateDataBounds();
  color_map_p->rescaleDataRange(true);

  // We should not do this, as the user might have zoomed to a region of
  // interest.
  // color_map_p->rescaleAxes();

  // Reset the current plot config to what it was originally. The member
  // m_colorMapPlotConfig.zAxisScale  is now Enums::AxisScale::orig.
  m_colorMapPlotConfig = *mpa_origColorMapPlotConfig;

  replot();
}


Enums::DataKind
BaseColorMapPlotWidget::xAxisDataKind() const
{
  return m_colorMapPlotConfig.xAxisDataKind;
}


Enums::DataKind
BaseColorMapPlotWidget::yAxisDataKind() const
{
  return m_colorMapPlotConfig.yAxisDataKind;
}


Enums::AxisScale
BaseColorMapPlotWidget::axisScale(Enums::Axis axis) const
{
  if(axis == Enums::Axis::x)
    return m_colorMapPlotConfig.xAxisScale;
  else if(axis == Enums::Axis::y)
    return m_colorMapPlotConfig.yAxisScale;
  else if(axis == Enums::Axis::z)
    return m_colorMapPlotConfig.zAxisScale;
  else
    throw PappsoException(
      QString("basecolormapplotwidget.cpp: The axis cannot be different than "
              "x, y or z."));

  return Enums::AxisScale::unset;
}


Enums::AxisScale
BaseColorMapPlotWidget::xAxisScale() const
{
  return m_colorMapPlotConfig.xAxisScale;
}


Enums::AxisScale
BaseColorMapPlotWidget::yAxisScale() const
{
  return m_colorMapPlotConfig.yAxisScale;
}


Enums::AxisScale
BaseColorMapPlotWidget::zAxisScale() const
{
  return m_colorMapPlotConfig.zAxisScale;
}


void
BaseColorMapPlotWidget::setPlottingColor(QCPAbstractPlottable *plottable_p, const QColor &new_color)
{
  Q_UNUSED(plottable_p);

  // The pen of the color map itself is of no use. Instead the user will see the
  // color of the axes' labels.

  QPen pen = xAxis->basePen();
  pen.setColor(new_color);

  xAxis->setBasePen(pen);
  xAxis->setLabelColor(new_color);
  xAxis->setTickLabelColor(new_color);

  yAxis->setBasePen(pen);
  yAxis->setLabelColor(new_color);
  yAxis->setTickLabelColor(new_color);

  // And now set the color map's pen to the same color, even if we do not use
  // it, we need it for coloring the plots that might be integrated from this
  // color map.

  QCPColorMap *color_map_p = static_cast<QCPColorMap *>(plottable(0));

  color_map_p->setPen(pen);

  replot();
}


QColor
BaseColorMapPlotWidget::getPlottingColor(int index) const
{
  Q_UNUSED(index);

  QPen pen = xAxis->basePen();
  return pen.color();
}


void
BaseColorMapPlotWidget::currentXaxisRangeIndices(int &lower, int &upper)
{
  // We want to limit the ranges to the visible data range in the plot widget.

  QCPColorMap *color_map_p  = static_cast<QCPColorMap *>(plottable(0));
  QCPColorMapData *map_data = color_map_p->data();

  bool found_range = false;

  // Get the full data set DT values range because if the context contains no
  // values for the currently displayed ranges, then we fall back to them;
  QCPRange full_data_range = color_map_p->getKeyRange(found_range);

  if(!found_range)
    {
      qDebug() << "The range was not found";
      return;
    }

  // qDebug() << "Full key data range:" << full_data_range.lower << "-"
  //<< full_data_range.upper;

  // But what we actually want is the currently visible axes ranges. And these
  // are stored in the context.

  double visible_data_range_lower = m_context.m_xRange.lower;
  double visible_data_range_upper = m_context.m_xRange.upper;

  // qDebug() << "Visible key data range:" << visible_data_range_lower << "-"
  //<< visible_data_range_upper;

  // Note that if there has been *no* panning, rescale, nothing, with the color
  // map, then the context has no idea of the ranges. So we need to check that.
  // If that is the case, then we use the full key range as the full plot is
  // displayed full scale upon its first showing.

  if(!visible_data_range_lower || !visible_data_range_upper)
    {
      visible_data_range_lower = full_data_range.lower;
      visible_data_range_upper = full_data_range.upper;
    }

  // qDebug() << "Visible key range:" << visible_data_range_lower << "-"
  //<< visible_data_range_upper;

  // And now convert the double value ranges into cell indices, which is what we
  // are being asked for.

  map_data->coordToCell(visible_data_range_lower, 0, &lower, nullptr);
  map_data->coordToCell(visible_data_range_upper, 0, &upper, nullptr);

  // qDebug() << "Cell indices for currently visible key range:" << lower << "-"
  //<< upper;
}


void
BaseColorMapPlotWidget::currentYaxisRangeIndices(int &lower, int &upper)
{
  // We want to limit the ranges to the visible data range in the plot widget.

  QCPColorMap *color_map_p  = static_cast<QCPColorMap *>(plottable(0));
  QCPColorMapData *map_data = color_map_p->data();

  bool found_range = false;

  // Get the full data set MZ values range because if the context contains no
  // values for the currently displayed ranges, then we fall back to them;
  QCPRange full_data_range = color_map_p->getValueRange(found_range);

  if(!found_range)
    {
      qDebug() << "The range was not found";
      return;
    }

  // qDebug() << "Full value data range:" << full_data_range.lower << "-"
  //<< full_data_range.upper;

  // But what we actually want is the currently visible axes ranges. And these
  // are stored in the context.

  double visible_data_range_lower = m_context.m_yRange.lower;
  double visible_data_range_upper = m_context.m_yRange.upper;

  // qDebug() << "Visible value data range:" << visible_data_range_lower << "-"
  //<< visible_data_range_upper;

  // Note that if there has been *no* panning, rescale, nothing, with the color
  // map, then the context has no idea of the ranges. So we need to check that.
  // If that is the case, then we use the full key range as the full plot is
  // displayed full scale upon its first showing.

  if(!visible_data_range_lower || !visible_data_range_upper)
    {
      visible_data_range_lower = full_data_range.lower;
      visible_data_range_upper = full_data_range.upper;
    }

  // qDebug() << "Final visible value data range:" << visible_data_range_lower
  //<< "-" << visible_data_range_upper;

  // And now convert the double value ranges into cell indices, which is what we
  // are being asked for.

  map_data->coordToCell(0, visible_data_range_lower, nullptr, &lower);
  map_data->coordToCell(0, visible_data_range_upper, nullptr, &upper);

  // qDebug() << "Cell indices for currently visible value range:" << lower <<
  // "-"
  //<< upper;
}


void
BaseColorMapPlotWidget::dataTo3ColString(QString &data_string)
{
  // We want to export the data to a string in the x y z format, with
  // x=key (cell's x coordinate)
  // y=value (cell's y coordinate)
  // z=intensity (cell value)

  QCPColorMap *color_map_p  = static_cast<QCPColorMap *>(plottable(0));
  QCPColorMapData *map_data = color_map_p->data();

  int key_index_lower_range;
  int key_index_upper_range;
  currentXaxisRangeIndices(key_index_lower_range, key_index_upper_range);

  // qDebug() << "Cell indices for currently visible key range:"
  //<< key_index_lower_range << "-" << key_index_upper_range;

  int value_index_lower_range;
  int value_index_upper_range;
  currentYaxisRangeIndices(value_index_lower_range, value_index_upper_range);

  // qDebug() << "Cell indices for currently visible value range:"
  //<< value_index_lower_range << "-" << value_index_upper_range;

  data_string.clear();
  QString debug_string;

  // Iterate in the matrix' key axis (DT, for example)
  for(int key_iter = key_index_lower_range; key_iter < key_index_upper_range; ++key_iter)
    {
      // Iterate in the matrix' value axis (MZ, for example)
      for(int value_iter = value_index_lower_range; value_iter < value_index_upper_range;
          ++value_iter)
        {
          // This would be the DT value (x axis)
          double key;

          // This would be the MZ value (y axis)
          double value;

          map_data->cellToCoord(key_iter, value_iter, &key, &value);

          data_string += QString("%1 %2 %3\n")
                           .arg(key, 0, 'f', 6, ' ')
                           .arg(value, 0, 'f', 6, ' ')
                           // The intensity without decimals
                           .arg(map_data->cell(key_iter, value_iter), 0, 'f', 0, ' ');
        }
    }

  // qDebug() << "The completed data string has size: " << data_string.size();
}


void
BaseColorMapPlotWidget::dataToMatrixString(QString &data_string, bool detailed)
{
  // We want to export the data in the form of a matrix, exactly as the data
  // appear within the colormap, unless the color is replaced with the intensity
  // value.

  // We want to limit the export to the visible data range in the plot widget.

  QCPColorMap *color_map_p  = static_cast<QCPColorMap *>(plottable(0));
  QCPColorMapData *map_data = color_map_p->data();

  int key_index_lower_range;
  int key_index_upper_range;
  currentXaxisRangeIndices(key_index_lower_range, key_index_upper_range);

  // qDebug() << "Cell indices for currently visible key range:"
  //<< key_index_lower_range << "-" << key_index_upper_range;

  int value_index_lower_range;
  int value_index_upper_range;
  currentYaxisRangeIndices(value_index_lower_range, value_index_upper_range);

  // qDebug() << "Cell indices for currently visible value range:"
  //<< value_index_lower_range << "-" << value_index_upper_range;

  data_string.clear();

  // At this point, we can write the header of the key data (that is the dt
  // key values).

  for(int key_iter = key_index_lower_range; key_iter < key_index_upper_range; ++key_iter)
    {
      double current_key_value;
      map_data->cellToCoord(key_iter, 0, &current_key_value, nullptr);

      data_string += QString("%1 ").arg(current_key_value, 0, 'f', 6, ' ');
    }

  // Finally call the newline
  data_string += "\n";

  // Now fill in the matrix, from top to down, that is from higher m/z values to
  // lower values.

  // The matrix we are exporting looks like this:

  //       |
  //       |
  //       |
  //       |
  //       |
  //       |
  //  m/z  |
  //       |
  //       |
  //       |
  //       |
  //       |______________________________
  //                      dt

  // Because we want the matrix to be presented the same, we need to fill in the
  // matrix from top to bottom starting from higher m/z values.

  for(int value_iter = value_index_upper_range; value_iter >= value_index_lower_range; --value_iter)
    {

      for(int key_iter = key_index_lower_range; key_iter < key_index_upper_range; ++key_iter)
        {
          double intensity = map_data->cell(key_iter, value_iter);

          // Only to report debug messages
          double key_double;
          double value_double;
          map_data->cellToCoord(key_iter, value_iter, &key_double, &value_double);

          // qDebug() << "Currently iterated cell: " << key_iter << ","
          //<< value_iter << "with values:" << key_double << ","
          //<< value_double << "with intensity:" << intensity;

          // The intensity without decimals
          if(detailed)
            data_string += QString("%1/%2/%3 ")
                             .arg(key_double, 0, 'f', 6, ' ')
                             .arg(value_double, 0, 'f', 6, ' ')
                             .arg(intensity);
          else
            data_string += QString("%1 ").arg(intensity, 0, 'f', 0, ' ');
        }

      data_string += "\n";
    }

  // qDebug().noquote() << "The matrix: " << data_string;
  // qDebug() << "The completed data string has size: " <<
  // data_string.size();

  data_string += "\n";
}


} // namespace pappso
