/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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
 */


/////////////////////// StdLib includes


/////////////////////// Qt includes
#include <QSettings>
#include <QMenuBar>
#include <QMenu>
#include <QDebug>


/////////////////////// pappsomspp includes


/////////////////////// Local includes
#include "BaseTracePlotWnd.hpp"
#include "ProgramWindow.hpp"
#include <MsXpS/libXpertMassGui/ColorSelector.hpp>

namespace MsXpS
{
namespace MineXpert
{


//! Construct an BaseTracePlotWnd instance.
BaseTracePlotWnd::BaseTracePlotWnd(QWidget *parent,
                                   const QString &title,
                                   const QString &settingsTitle,
                                   const QString &description)
  : BasePlotWnd(parent, title, settingsTitle, description)
{
}

//! Destruct \c this BaseTracePlotWnd instance.
BaseTracePlotWnd::~BaseTracePlotWnd()
{
  // qDebug();

  if(mpa_noiseSampleTrace)
    delete mpa_noiseSampleTrace;
}

void
BaseTracePlotWnd::setNoiseSampleTrace(const pappso::Trace &trace)
{
  if(mpa_noiseSampleTrace != nullptr)
    delete mpa_noiseSampleTrace;

  mpa_noiseSampleTrace = new pappso::Trace(trace);
}

void
BaseTracePlotWnd::resetNoiseSampleTrace()
{
  if(mpa_noiseSampleTrace != nullptr)
    delete mpa_noiseSampleTrace;
}

bool
BaseTracePlotWnd::getNoiseSampleStats(std::size_t &point_count,
                                      double &mean,
                                      double &std_dev)
{
  if(mpa_noiseSampleTrace == nullptr)
    return false;

  if(!dynamic_cast<std::vector<pappso::DataPoint> *>(mpa_noiseSampleTrace)
        ->size())
    return false;

  // We need to compute the mean value of the intensities, but without taking
  // into account any value that is 0 (because the noise sample trace might have
  // been made by combining disjointed stretches of an original trace, and in
  // between, the values might be 0).

  QString trace_as_string = mpa_noiseSampleTrace->toString();
  // qDebug().noquote() << "Trace as string\n" << trace_as_string;

  std::vector<double> intensities = mpa_noiseSampleTrace->yValues();

  using Iterator = std::vector<double>::const_iterator;

  double y_sum = 0;
  point_count  = 0;

  for(Iterator iter = intensities.cbegin(); iter < intensities.cend(); ++iter)
    {
      if(*iter != 0)
        {
          y_sum += *iter;

          point_count += 1;
        }
    }

  mean = y_sum / point_count;

  // Calculate the std dev value

  double variance = 0;
  for(std::vector<double>::const_iterator iter = intensities.cbegin();
      iter < intensities.cend();
      ++iter)
    {
      double diff  = *iter - mean;
      variance    += diff * diff;
    }

  if(!mean)
    return false;

  variance /= mean;
  std_dev   = sqrt(variance);

  // qDebug() << "The noise point count:" << point_count
  //<< " -- the noise mean:" << mean
  //<< " -- the noise std deviation: " << std_dev;

  return true;
}

void
BaseTracePlotWnd::newTrace(const pappso::Trace &trace,
                           MsRunDataSetCstSPtr ms_run_data_set_csp,
                           const ProcessingFlow &processing_flow,
                           const QColor &color,
                           QCPAbstractPlottable *parent_plottable_p)
{
  // We get a new trace, but we cannot know before where to put it. We first
  // need to check if there are pinned-down widgets or not.

  // qDebug() << "Adding new trace with parent plottable:"
  //          << parent_plottable_p;


  // We need to establish what widget is the destination of the integration. If
  // one or more than a widget is/are pinned-down, then that/these should be the
  // destinations. If no widget is pinned-down, then a new plot widget needs to
  // be created.

  // qDebug() << "The processing flow:" << processing_flow.toString();

  std::vector<BasePlotCompositeWidget *> pinned_down_widgets =
    pinnedDownWidgets();

  if(pinned_down_widgets.size())
    {
      // qDebug() << "There is at least one pinned composite plot widget.";

      for(auto &&pinned_down_widget : pinned_down_widgets)
        {
          // qDebug() << "Iterating in one pinned-down composite widget.";

          static_cast<BaseTracePlotCompositeWidget *>(pinned_down_widget)
            ->addTrace(trace,
                       static_cast<QCPGraph *>(parent_plottable_p),
                       ms_run_data_set_csp,
                       ms_run_data_set_csp->getMsRunId()->getSampleName(),
                       processing_flow,
                       color);
        }
    }
  else
    {
      // qDebug() << "There is no pinned composite plot widget.";
      addTracePlot(trace,
                   ms_run_data_set_csp,
                   processing_flow,
                   color,
                   static_cast<QCPGraph *>(parent_plottable_p));
    }
}

QCPGraph *
BaseTracePlotWnd::finalNewTracePlotConfiguration(
  BaseTracePlotCompositeWidget *composite_widget_p,
  const pappso::Trace &trace,
  const ProcessingFlow &processing_flow,
  const QColor &color,
  QCPAbstractPlottable *parent_plottable_p)
{

  // qDebug() << "Final configuration for addTracePlot for parent plottable:"
  //<< parent_plottable_p;

  //////// CAUTION ///////
  //////// CAUTION ///////

  // The single graph destruction signal. We do one single connection for the
  // plot widget and not for each graph that is in the plot widget. Otherwise,
  // we get signals multiple times for the same graph. In previous buggy code,
  // we had this connection performed in the BaseTracePlotCompositeWidget class
  // qDebug().noquote()
  //<< "Making the connection:"
  //<< "pappso::BaseTracePlotWidget::graphDestructionRequestedSignal to "
  //"mp_parentWnd->getProgramWindow()->graphDestructionRequested.";

  connect(composite_widget_p->getPlotWidget(),
          &pappso::BasePlotWidget::plottableDestructionRequestedSignal,
          this,
          &BaseTracePlotWnd::requestPlottableDestruction);

  //////// CAUTION ///////
  //////// CAUTION ///////

  QColor local_color(color);

  // Get color from the available colors, or if none is available, create one
  // randomly without requesting the user to select one from QColorDialog.
  if(!local_color.isValid())
    local_color = libXpertMassGui::ColorSelector::getRandomColor();

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow.getMsRunDataSetCstSPtr();

  // qDebug() << ms_run_data_set_csp.get();

  QString sample_name = ms_run_data_set_csp->getMsRunId()->getSampleName();

  QCPGraph *graph_p = composite_widget_p->addTrace(trace,
                                                   parent_plottable_p,
                                                   ms_run_data_set_csp,
                                                   sample_name,
                                                   processing_flow,
                                                   local_color);

  composite_widget_p->setMinimumSize(400, 200);
  mp_splitter->addWidget(composite_widget_p);

  // qDebug() << "Added the plottable to the splitter, returning plottable:"
  //<< (QCPAbstractPlottable *)graph_p;

  // We now need to inform the parent window that the size of the scroll area
  // might have changed.

  BasePlotWnd::resizeEvent(nullptr);

  return graph_p;
}

void
BaseTracePlotWnd::requestPlottableDestruction(
  const pappso::BasePlotWidget *base_plot_widget_p,
  QCPAbstractPlottable *plottable_p,
  const pappso::BasePlotContext &context)
{
  // We first need to get the pointer to the composite plot widget
  // that contains the pappso::BasePlotWidget.

  BaseTracePlotCompositeWidget *composite_widget_p =
    dynamic_cast<BaseTracePlotCompositeWidget *>(base_plot_widget_p->parent());

  mp_programWindow->plottableDestructionRequested(
    composite_widget_p, plottable_p, context);
}

QCPGraph *
BaseTracePlotWnd::duplicateTracePlot(
  const BaseTracePlotCompositeWidget *composite_widget_p)
{
  // qDebug();

  // The user wants to duplicate traces from this widget to a receiving widget.
  // The receiving widget cannot be in another window.

  // There are a number of different situations for the source widget:

  // 1. No trace is selected but there is a single trace in the source
  // widget: that trace is going to be duplicated.

  // 2. No trace is selected and there are more than one trace: nothing happens.

  // 3. Some traces are selected: these are copied.

  int graph_count = composite_widget_p->getPlotWidget()->plottableCount();

  if(!graph_count)
    return nullptr;

  QList<QCPGraph *> selected_graph_list;

  if(graph_count == 1)
    {
      // qDebug() << "Only a graph is plotted, this is going to be the source";

      QCPGraph *parent_graph_p = composite_widget_p->getPlotWidget()->graph();
      selected_graph_list.push_back(parent_graph_p);
    }
  else
    {
      selected_graph_list =
        composite_widget_p->getPlotWidget()->selectedGraphs();

      int selected_graph_list_size = selected_graph_list.size();

      if(!selected_graph_list_size)
        {
          // qDebug() << "Not a single graph is selected. Doing nothing.";

          return nullptr;
        }
      else
        {
          // qDebug() << "There are" << selected_graph_list_size
          //<< "graphs selected. These are going to be duplicated.";
        }
    }

  // There are a number of different situations for the destination widget:

  // 1. One widget is currently pinned-down: it is going to be the
  // destination widget.

  // 2. More than one widget is pinned-down: a dialog windows asks that the
  // operation be confirmed. If so the trace is duplicated in all the
  // destination widgets.

  // 3. No widget is pinned-down: a new widget is created and the source
  // traces are duplicated in it.

  // Handle the various cases in a loop iterating over all the graph to be
  // duplicate (or at least one that was set in the list).

  std::vector<BasePlotCompositeWidget *> destination_widget_list =
    pinnedDownWidgets();
  std::size_t destination_widget_list_size = destination_widget_list.size();

  // qDebug() << "The number of pinned-down widgets:"
  //<< destination_widget_list_size;

  QCPGraph *new_graph_p = nullptr;

  // For each parent graph to be duplicated, duplicate it to the destination
  // widget(s).
  for(auto &&parent_graph_p : selected_graph_list)
    {
      // No need to make a copy since we are not modifying it. We'll pass
      // it by reference and downstream code will allocate a new copy
      // properly parented if necessary.

      const ProcessingFlow *processing_flow_p =
        composite_widget_p->getCstProcessingFlowPtrForPlottable(parent_graph_p);

      // The processing flow always documents the ms run data set from which
      // the data plotted and being working on are emanating.
      MsRunDataSetCstSPtr ms_run_data_set_csp =
        processing_flow_p->getMsRunDataSetCstSPtr();

      if(ms_run_data_set_csp == nullptr)
        qFatal() << "The pointer cannot be nullptr.";

      // Sanity check
      MsRunDataSetCstSPtr check_ms_run_data_set_csp =
        composite_widget_p->getMsRunDataSetCstSPtrForPlottable(parent_graph_p);

      if(check_ms_run_data_set_csp != ms_run_data_set_csp)
        qFatal() << "Cannot be that the pointers be different.";

      if(!destination_widget_list_size)
        {
          // qDebug()
          //<< "Not a single widget is pinned-down. Create a new plot widget.";

          pappso::Trace trace =
            static_cast<const pappso::BaseTracePlotWidget *>(
              composite_widget_p->getPlotWidget())
              ->toTrace(parent_graph_p);

          // qDebug() << "the source trace has size:" << trace.size();

          new_graph_p = addTracePlot(trace,
                                     ms_run_data_set_csp,
                                     *processing_flow_p,
                                     parent_graph_p->pen().color(),
                                     parent_graph_p);

          // qDebug() << "Done adding trace plot:" << new_graph_p
          //<< "with parent graph:" << parent_graph_p;
        }
      else
        {
          for(auto &&destination_widget_p : destination_widget_list)
            {

              if(destination_widget_p == composite_widget_p)
                {
                  // qDebug()
                  //<< "The only pinned-down widget is the one asking for "
                  //"duplication. Will need to create a new widget.";

                  new_graph_p = addTracePlot(
                    static_cast<const pappso::BaseTracePlotWidget *>(
                      composite_widget_p->getPlotWidget())
                      ->toTrace(parent_graph_p),
                    composite_widget_p->getMsRunDataSetCstSPtrForPlottable(
                      parent_graph_p),
                    *processing_flow_p,
                    parent_graph_p->pen().color(),
                    parent_graph_p);
                }
              else
                {
                  // qDebug()
                  //<< "One widget pinned-down and that is not the one asking "
                  //"for duplication.";

                  new_graph_p =
                    static_cast<BaseTracePlotCompositeWidget *>(
                      destination_widget_p)
                      ->addTrace(
                        static_cast<const pappso::BaseTracePlotWidget *>(
                          composite_widget_p->getPlotWidget())
                          ->toTrace(parent_graph_p),
                        parent_graph_p,
                        composite_widget_p->getMsRunDataSetCstSPtrForPlottable(
                          parent_graph_p),
                        "Un-named sample",
                        *processing_flow_p,
                        parent_graph_p->pen().color());
                }
            }
        }
    }

  return new_graph_p;
}

} // namespace MineXpert

} // namespace MsXpS
