/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 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 <QWidget>
#include <QApplication>

/////////////////////// QCustomPlot


/////////////////////// pappsomspp includes
#include <pappsomspp/gui/plotwidget/ticxicchromtraceplotwidget.h>
#include <pappsomspp/core/processing/combiners/integrationscope.h>


/////////////////////// Local includes
#include "Application.hpp"
#include "TicXicChromTracePlotCompositeWidget.hpp"
#include "TicXicChromTracePlotWnd.hpp"
#include "ui_BasePlotCompositeWidget.h"

namespace MsXpS
{
namespace MineXpert
{


TicXicChromTracePlotCompositeWidget::TicXicChromTracePlotCompositeWidget(
  QWidget *parent, const QString &x_axis_label, const QString &y_axis_label)
  : BaseTracePlotCompositeWidget(parent, x_axis_label, y_axis_label)
{
  setupWidget();
}

TicXicChromTracePlotCompositeWidget::~TicXicChromTracePlotCompositeWidget()
{
  // qDebug();
}

void
TicXicChromTracePlotCompositeWidget::setupWidget()
{
  // qDebug();

  static_assert(
    std::is_base_of_v<QWidget, TicXicChromTracePlotCompositeWidget>);

  dynamic_cast<QWidget *>(this)->setWindowIcon(qApp->windowIcon());

  /************  The QCustomPlot widget *************/
  /************  The QCustomPlot widget *************/
  mp_plotWidget =
    new pappso::TicXicChromTracePlotWidget(this, m_axisLabelX, m_axisLabelY);
  m_ui->qcpTracePlotWidgetHorizontalLayout->addWidget(mp_plotWidget);

  connect(mp_plotWidget,
          &pappso::TicXicChromTracePlotWidget::setFocusSignal,
          this,
          [&]() {
            BaseTracePlotCompositeWidget::mp_parentWnd->plotWidgetGotFocus(
              this);
          });

  connect(mp_plotWidget,
          &pappso::TicXicChromTracePlotWidget::lastCursorHoveredPointSignal,
          this,
          &TicXicChromTracePlotCompositeWidget::lastCursorHoveredPoint);

  connect(mp_plotWidget,
          &pappso::TicXicChromTracePlotWidget::plotRangesChangedSignal,
          mp_parentWnd,
          &BaseTracePlotWnd::plotRangesChanged);

  connect(mp_plotWidget,
          &pappso::TicXicChromTracePlotWidget::xAxisMeasurementSignal,
          this,
          &TicXicChromTracePlotCompositeWidget::xAxisMeasurement);

  connect(static_cast<pappso::TicXicChromTracePlotWidget *>(mp_plotWidget),
          &pappso::TicXicChromTracePlotWidget::keyPressEventSignal,
          this,
          &TicXicChromTracePlotCompositeWidget::plotWidgetKeyPressEvent);

  connect(mp_plotWidget,
          &pappso::TicXicChromTracePlotWidget::keyReleaseEventSignal,
          this,
          &TicXicChromTracePlotCompositeWidget::plotWidgetKeyReleaseEvent);

  connect(static_cast<pappso::TicXicChromTracePlotWidget *>(mp_plotWidget),
          static_cast<void (pappso::TicXicChromTracePlotWidget::*)(
            const pappso::BasePlotContext &)>(
            &pappso::TicXicChromTracePlotWidget::mousePressEventSignal),
          this,
          static_cast<void (TicXicChromTracePlotCompositeWidget::*)(
            const pappso::BasePlotContext &)>(
            &TicXicChromTracePlotCompositeWidget::plotWidgetMousePressEvent));

  // This connection might be required if the specialized tic/xic chrom plot
  // widget has some specific data to provide in the specialized context.
  connect(static_cast<pappso::TicXicChromTracePlotWidget *>(mp_plotWidget),
          static_cast<void (pappso::TicXicChromTracePlotWidget::*)(
            const pappso::BasePlotContext &)>(
            &pappso::TicXicChromTracePlotWidget::mouseReleaseEventSignal),
          this,
          static_cast<void (TicXicChromTracePlotCompositeWidget::*)(
            const pappso::BasePlotContext &)>(
            &TicXicChromTracePlotCompositeWidget::plotWidgetMouseReleaseEvent));


  connect(mp_plotWidget,
          &pappso::TicXicChromTracePlotWidget::plottableSelectionChangedSignal,
          this,
          &BasePlotCompositeWidget::plottableSelectionChanged);

  connect(mp_plotWidget,
          &pappso::BaseTracePlotWidget::integrationRequestedSignal,
          this,
          &TicXicChromTracePlotCompositeWidget::integrationRequested);

  // createMainMenu();

  /************ The QCustomPlot widget *************/

  /************ The various widgets in this composite widget ***************/
  /************ The various widgets in this composite widget ***************/

  // The button that triggers the duplication of the trace into another trace
  // plot widget.

  connect(m_ui->duplicateTracePushButton, &QPushButton::clicked, [this]() {
    // qDebug();
    static_cast<BaseTracePlotWnd *>(getParentWnd())->duplicateTracePlot(this);
  });

  // Create the list of peak labels for the labelling session.
  // We have to do this initialization in the derived class, because
  // the base class has a nullptr mp_plotWidget. FIXME.
  TracePlotLabelListQSPtr peak_label_list_qsp =
    QSharedPointer<TracePlotLabelList>::create(
      /*name*/ "Peak labels",
      /*plot_layer_name*/ "labelsLayer",
      mp_plotWidget,
      /*should_manage_layout*/ false,
      /*x_axis_delta_trigger*/ 0,
      /*label_vertical_shift_count_factor*/ 0,
      mp_plotWidget->xAxis->labelFont(),
      mp_plotWidget->xAxis->labelColor());

  m_nameLabelListMap[peak_label_list_qsp->getName()] = peak_label_list_qsp;
}

void
TicXicChromTracePlotCompositeWidget::createMainMenu()
{
  // qDebug() << "Completing the main menu:" << mp_mainMenu;

  // Update the menu tooltip.
  m_ui->mainMenuPushButton->setToolTip(
    "TIC/XIC chrom. trace plot widget menu (Ctrl+P, M)");
}

void
TicXicChromTracePlotCompositeWidget::plotWidgetKeyPressEvent(
  const pappso::BasePlotContext &context)
{
  // qDebug() << "context:" << context.toString();

  // First call the base class that will fill-in the generic fields if the space
  // char was pressed.
  BaseTracePlotCompositeWidget::plotWidgetKeyPressEvent(context);

  m_plotWidgetPressedKeyCode = context.m_pressedKeyCode;

  if(context.m_pressedKeyCode == Qt::Key_Space)
    {
      QCPAbstractPlottable *plottable_p =
        plottableToBeUsedAsIntegrationSource();

      if(plottable_p == nullptr)
        {
#if 0
                                        // We actually do not do this because that part is handled by the base
                                        // class.

          // We should inform the user if there are more than one plottable.

          int plottable_count = mp_plotWidget->plottableCount();

          if(plottable_count > 1)
            {
              QMessageBox::information(
                this,
                "Crafting an analysis stanza for a graph",
                "Please select a single trace and try again.");
            }
#endif

          // No plottable available, nothing to do.
          return;
        }

      QString stanza = craftAnalysisStanza(context);

      // qDebug() << "analysis stanza:" << stanza;

      // At this point, the analysis stanza can be effectively recorded.

      // qDebug() << "Going to call record.";

      mp_parentWnd->recordAnalysisStanza(stanza, plottable_p->pen().color());
    }
  // End of
  // if(context.m_pressedKeyCode == Qt::Key_Space)
}

void
TicXicChromTracePlotCompositeWidget::plotWidgetMousePressEvent(
  const pappso::BasePlotContext &context)
{
  // qDebug() << "The MassSpecTracePlotContext:" << context.toString();

  BaseTracePlotCompositeWidget::plotWidgetMousePressEvent(context);
}

void
TicXicChromTracePlotCompositeWidget::plotWidgetMouseReleaseEvent(
  const pappso::BasePlotContext &context)
{
  // qDebug() << "The BasePlotContext:" << context.toString();

  BaseTracePlotCompositeWidget::plotWidgetMouseReleaseEvent(context);
}

void
TicXicChromTracePlotCompositeWidget::lastCursorHoveredPoint(
  const QPointF &pointf)

{
  BaseTracePlotCompositeWidget::lastCursorHoveredPoint(pointf);


  // At this point we can start doing tic/xic chromatogram-specific
  // calculations.
}

void
TicXicChromTracePlotCompositeWidget::moveMouseCursorToNextGraphPoint(
  const pappso::BasePlotContext &context)
{
  BaseTracePlotCompositeWidget::moveMouseCursorToNextGraphPoint(context);

  // Modify the context to conform to the integration range that was computed
  // in the base class function.

  pappso::BasePlotContext local_context;
  local_context.initialize(context);
  local_context.m_xRegionRangeStart = m_integrationRange.lower;
  local_context.m_xRegionRangeEnd   = m_integrationRange.upper;

  if(m_isSinglePointIntegrationMode)
    integrationRequested(local_context);
}

void
TicXicChromTracePlotCompositeWidget::integrationRequested(
  const pappso::BasePlotContext &context)
{
  // qDebug().noquote() << "context:" << context.toString();

  // Make a local copy of the context because we may need to modify it.
  pappso::BasePlotContext local_context;
  local_context.initialize(context);

  // Check if the integration scope contains meaningful data. We are in trace
  // plot widget, so the scope might be in the form of a
  // mono-dimensional scope, although this is not mandatory.

  if(local_context.msp_integrationScope->is2D())
    {
      // qDebug()
      //   << "Odd situation where the selection polygon is two-dimensional "
      //      "and the trace plot selection is mono-dimensional. Converting "
      //      "it to a 1D selection polygon.";

      double width;
      if(!local_context.msp_integrationScope->getWidth(width))
        qFatal() << "Failed to get width.";

      QPointF point;
      if(!local_context.msp_integrationScope->getPoint(point))
        qFatal() << "Failed to get point.";

      local_context.msp_integrationScope =
        std::make_shared<const pappso::IntegrationScope>(
          pappso::IntegrationScope(point, width));
    }

  std::const_pointer_cast<pappso::IntegrationScopeBase>(
    local_context.msp_integrationScope)
    ->setDataKindX(pappso::Enums::DataKind::rt);

  // Sanity check.

  // The user may select at the left of the x=0, but that does not make sense if
  // the whole selected region is in the x axis negative space.
  double range_start;
  double range_end;

  local_context.msp_integrationScope->range(
    pappso::Enums::Axis::x, range_start, range_end);

  // qDebug().noquote() << "Local copy of context:" << local_context.toString();

  // qDebug() << "X axis range extracted from integration scope:" << range_start
  //          << "-" << range_end;

  // Having negative retention times does not make any sense.
  if(range_start < 0 && range_end < 0)
    return;

  // We are getting that signal from a plot widget that operates as a tic/xic
  // chrom plot widget.

  // We need check which destination button is currently checked.

  // The widget does not handle the integrations. There are the
  // responsibility of the parent window of the right type.

  // Note also that the widgets are all multi-graph, in the sense that if the
  // user acted such that there are more than one graph in the plot widget, then
  // we need to know what is the trace the user is willing to integrate new data
  // from. If there is only one graph (trace) in the plot widget, there is no
  // doubt. If there are more than one traces there, then the one that is
  // selected is the right one. If there are more than one traces selected, then
  // we cannot do anything. Display an error message.


  // The QCustomPlot-based widget might contain more than one graph, and we
  // need to know on what graph the user is willing to perform the
  // integration.

  QCPAbstractPlottable *plottable_p =
    dynamic_cast<const BasePlotCompositeWidget *>(this)
      ->plottableToBeUsedAsIntegrationSource();

  if(plottable_p == nullptr)
    {
      QMessageBox::information(dynamic_cast<QWidget *>(this),
                               "Please select *one* trace",
                               "In order to perform an integration, the source "
                               "graph needs to be selected.",
                               QMessageBox::StandardButton::Ok,
                               QMessageBox::StandardButton::NoButton);

      return;
    }

  // Create a temporary copy of the ProcessingFlow (with no parentship) so that
  // we can modify it with new steps and then pass it as reference to downstream
  // code that will allocate a fully parented instance.
  // clone() returns heap-allocated instance.
  ProcessingFlow *temp_processing_flow_p =
    getCstProcessingFlowPtrForPlottable(plottable_p)->clone(/*parent*/ nullptr);

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    getMsRunDataSetCstSPtrForPlottable(plottable_p);

  // qDebug() << ms_run_data_set_csp->getMsRunDataSetStats().toString();

  // Create a processing step to document the specifics of this new integration.

  ProcessingStepSPtr processing_step_sp = std::make_shared<ProcessingStep>();

  // If the user had set ms fragmentation specifications, all these are listed
  // in the various ProcessingSpec instances, so we do not loose any. However,
  // the user is entitled to define any new ms fragmentation specifications
  // using the corresponding dialog box that it can open from the composite
  // widget's menu. If that was done, the specification is stored as the default
  // ProcessingFlow's ms fragmentation specification.

  MsFragmentationSpec ms_fragmentation_spec =
    temp_processing_flow_p->getDefaultMsFragmentationSpec();

  // Only set the ms frag spec to the processing spec if it is valid.
  if(ms_fragmentation_spec.isValid())
    {
      // qDebug().noquote() << "The default ms frag spec from the processing
      // flow " "is valid, using it:"
      //<< ms_fragmentation_spec.toString();

      processing_step_sp->setMsFragmentationSpec(ms_fragmentation_spec);
    }
  else
    {
      // qDebug() << "The default ms frag spec from the processing flow is not "
      //"valid. Not using it:"
      //<< ms_fragmentation_spec.toString();
    }

  // Now make sure we document the selection polygon.
  // Now define the graph range for the integration operation.

  processing_step_sp->setIntegrationScope(local_context.msp_integrationScope);

  // We must say what is the data source processing type.
  processing_step_sp->setSrcProcessingType(pappso::Enums::Axis::x, "RT");

  // It is essential that we document the relation between the axes and the
  // corresponding data type.
  processing_step_sp->setDataKind(pappso::Enums::Axis::x,
                                  pappso::Enums::DataKind::rt);
  processing_step_sp->setDataKind(pappso::Enums::Axis::y,
                                  pappso::Enums::DataKind::unset);

  // Now handle the specific part of the ProcessingStep, that part that depends
  // on the kind of integration the user is willing to perform. Note below that
  // we gather all the integrations to destinations that involve integration
  // mass spectra. This is because we factorize the requriement to have proper
  // mz integation params set for these integrations.

  if(m_ui->integrateToRtPushButton->isChecked())
    {
      // qDebug() << "Integration to XIC chromatogram.";

      processing_step_sp->setDestProcessingType("RT");

      temp_processing_flow_p->getStepsRef().push_back(processing_step_sp);

      static_cast<TicXicChromTracePlotWnd *>(mp_parentWnd)
        ->integrateToRt(plottable_p, nullptr, *temp_processing_flow_p);
    }
  else if(m_ui->integrateToMzPushButton->isChecked() ||
          m_ui->integrateToDtMzPushButton->isChecked() ||
          m_ui->integrateToMzRtPushButton->isChecked())
    {
      if(m_ui->integrateToMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to mass spectrum.";
          processing_step_sp->setDestProcessingType("MZ");

          // qDebug() << "Currently elaborating processing_step_p: "
          //<< processing_step_p->toString();
        }
      else if(m_ui->integrateToMzRtPushButton->isChecked())
        {
          // qDebug() << "Integrating to rt|m/z color map.";
          processing_step_sp->setDestProcessingType("MZ_RT");
        }
      else if(m_ui->integrateToDtMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to dt|m/z color map.";
          processing_step_sp->setDestProcessingType("DT_MZ");
        }

      // qDebug() << "The configured step:"
      //          << processing_step_sp->toString();

      // At this point we need to define how the mass data integrator will
      // combine spectra, since we are integrating to a mass spectrum.

      // The function below will first get the potential settings that might
      // have been stored by the user when "Set as default" is checked in the
      // MzIntegrationParamsDlg window. If no integration settings were found,
      // then other possibilities are tested (in particular by looking into
      // the ProcessingFlow).

      /* Heap-allocated*/
      pappso::MzIntegrationParams *mz_integration_params_p =
        BasePlotCompositeWidget::getSensibleMzIntegrationParams(plottable_p);

      // We finally have mz integ params that we can set to the processing step
      // that we are creating to document this current integration.
      // Takes ownership.
      processing_step_sp->setMzIntegrationParams(mz_integration_params_p);

      // And nwo add the new processing step to the local copy of the processing
      // flow, so that we document on top of all the previous steps, also this
      // last one.

      // Should perform automatically the conversion from non-const to const.
      temp_processing_flow_p->getStepsRef().push_back(processing_step_sp);

      // qDebug() << "The temp processing flow with the new step:"
      //          << temp_processing_flow_p->toString();

      // The local processing flow contains all the previous steps and the last
      // one that documents this current integration.

      if(m_ui->integrateToMzPushButton->isChecked())
        {
          static_cast<TicXicChromTracePlotWnd *>(mp_parentWnd)
            ->integrateToMz(plottable_p, *temp_processing_flow_p);
        }
      else if(m_ui->integrateToDtMzPushButton->isChecked())
        {
          static_cast<TicXicChromTracePlotWnd *>(mp_parentWnd)
            ->integrateToDtMz(plottable_p, *temp_processing_flow_p);
        }
      else if(m_ui->integrateToMzRtPushButton->isChecked())
        {
          static_cast<TicXicChromTracePlotWnd *>(mp_parentWnd)
            ->integrateToMzRt(plottable_p, *temp_processing_flow_p);
        }
    }
  else if(m_ui->integrateToDtPushButton->isChecked())
    {
      // qDebug() << "Integrating to drift spectrum.";

      processing_step_sp->setDestProcessingType("DT");

      // Should perform automatically the conversion from non-const to const.
      temp_processing_flow_p->getStepsRef().push_back(processing_step_sp);

      static_cast<TicXicChromTracePlotWnd *>(mp_parentWnd)
        ->integrateToDt(plottable_p, *temp_processing_flow_p);
    }
  else if(m_ui->integrateToDtRtPushButton->isChecked())
    {
      // qDebug() << "Integrating to dt/rt colormap.";

      processing_step_sp->setDestProcessingType("DT_RT");

      // Should perform automatically the conversion from non-const to const.
      temp_processing_flow_p->getStepsRef().push_back(processing_step_sp);

      static_cast<TicXicChromTracePlotWnd *>(mp_parentWnd)
        ->integrateToDtRt(plottable_p, *temp_processing_flow_p);
    }
  else if(m_ui->integrateToIntPushButton->isChecked())
    {
      // qDebug() << "Integrating to TIC intensity.";

      processing_step_sp->setDestProcessingType("INT");

      // Should perform automatically the conversion from non-const to const.
      temp_processing_flow_p->getStepsRef().push_back(processing_step_sp);

      static_cast<BasePlotWnd *>(mp_parentWnd)
        ->integrateToTicIntensity(
          plottable_p, nullptr, *temp_processing_flow_p);
    }
  else
    {
      QMessageBox::information(
        this,
        "Please select a destination for the integration",
        "In order to perform an integration, select a destination "
        "like a mass spectrum or a drift spectrum by clicking one "
        "of the buttons on the right margin of the plot widget.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);
    }

  delete temp_processing_flow_p;

  return;
}

QString
TicXicChromTracePlotCompositeWidget::craftAnalysisStanza(
  const pappso::BasePlotContext &context)
{
  Q_UNUSED(context)

  // qDebug().noquote() << "With context:" << context.toString();
  // qDebug().noquote()
  // << "We get from the base class stanza work the following text:"
  // << m_analysisStanza;

  // Since this class has no specialized data, apart those already handled
  // in the base class function, all we need to do is return the stanza that
  // has already been crafted by the base class.

  return m_analysisStanza;
}

} // namespace MineXpert
} // namespace MsXpS
