/* 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>

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


/////////////////////// pappsomspp includes
#include <pappsomspp/widget/plotwidget/driftspecmassspeccolormapplotwidget.h>


/////////////////////// Local includes
#include "DriftSpecMassSpecColorMapPlotCompositeWidget.hpp"
#include "BaseColorMapPlotWnd.hpp"


namespace msxps
{
namespace minexpert
{


DriftSpecMassSpecColorMapPlotCompositeWidget::
  DriftSpecMassSpecColorMapPlotCompositeWidget(QWidget *parent,
                                               const QString &x_axis_label,
                                               const QString &y_axis_label)
  : BaseColorMapPlotCompositeWidget(parent, x_axis_label, y_axis_label)
{
  // qDebug();

  setupWidget();
}


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


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

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

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

  connect(
    mp_plotWidget,
    &pappso::DriftSpecMassSpecColorMapPlotWidget::lastCursorHoveredPointSignal,
    this,
    &DriftSpecMassSpecColorMapPlotCompositeWidget::lastCursorHoveredPoint);

  connect(mp_plotWidget,
          &pappso::DriftSpecMassSpecColorMapPlotWidget::plotRangesChangedSignal,
          mp_parentWnd,
          &BaseColorMapPlotWnd::plotRangesChanged);

  connect(mp_plotWidget,
          &pappso::DriftSpecMassSpecColorMapPlotWidget::xAxisMeasurementSignal,
          this,
          &DriftSpecMassSpecColorMapPlotCompositeWidget::xAxisMeasurement);

  connect(
    mp_plotWidget,
    &pappso::DriftSpecMassSpecColorMapPlotWidget::keyPressEventSignal,
    this,
    &DriftSpecMassSpecColorMapPlotCompositeWidget::plotWidgetKeyPressEvent);

  connect(
    mp_plotWidget,
    &pappso::DriftSpecMassSpecColorMapPlotWidget::keyReleaseEventSignal,
    this,
    &DriftSpecMassSpecColorMapPlotCompositeWidget::plotWidgetKeyReleaseEvent);

  connect(
    mp_plotWidget,
    &pappso::BaseTracePlotWidget::mouseReleaseEventSignal,
    this,
    &DriftSpecMassSpecColorMapPlotCompositeWidget::plotWidgetMouseReleaseEvent);

  // 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::DriftSpecMassSpecColorMapPlotWidget
  // *>(mp_plotWidget),
  //&pappso::DriftSpecMassSpecColorMapPlotWidget::mouseReleaseEventSignal,
  // this,
  //&DriftSpecMassSpecColorMapPlotCompositeWidget::plotWidgetMouseReleaseEvent);

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

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


  /************ 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. We do not do that for color maps yet.

  m_ui.duplicateTracePushButton->hide();
}


void
DriftSpecMassSpecColorMapPlotCompositeWidget::lastCursorHoveredPoint(
  const QPointF &pointf)

{
  BaseColorMapPlotCompositeWidget::lastCursorHoveredPoint(pointf);

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


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

  // We are getting that signal from a plot widget that operates as a
  // drift spec / mass spec color map plot widget.

  // Remember that the xregion start and end are not sorted.
  double x_range_start =
    std::min<double>(context.xRegionRangeStart, context.xRegionRangeEnd);
  double x_range_end =
    std::max<double>(context.xRegionRangeStart, context.xRegionRangeEnd);

  if(x_range_start < 0 && x_range_end < 0)
    return;

  double y_range_start =
    std::min<double>(context.yRegionRangeStart, context.yRegionRangeEnd);
  double y_range_end =
    std::max<double>(context.yRegionRangeStart, context.yRegionRangeEnd);

  if(y_range_start < 0 && y_range_end < 0)
    return;


  // 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 = plottableToBeUsedAsIntegrationSource();

  if(plottable_p == nullptr)
    {
      QMessageBox::information(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 local copy of the processing flow.

  ProcessingFlow local_processing_flow =
    getProcessingFlowForPlottable(plottable_p);

  // qDebug().noquote() << "Processing flow for graph:"
  //<< local_processing_flow.toString();

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    getMsRunDataSetCstSPtrForPlottable(plottable_p);

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

  // Now we need to add a new step in that processing flow so that we document
  // this new integration that we are performing.

  ProcessingStep *processing_step_p = new ProcessingStep();

  // The step has a multi-map of pairs that each relate a processing type with a
  // processing spec. So we need to document a new pair type/spec, let's start
  // with the spec that documents the range of the data we are willing to
  // integrate, that is, the selected region of the graph.

  // The ProcessingSpec has the range start/end values, an optional ms
  // fragmentation specification and a date time that is set automatically at
  // construction time.

  // Allocate a new spec that will hold the DT dimension range.
  ProcessingSpec *dt_processing_spec_p = new ProcessingSpec();

  // And another one for the MZ dimension range.
  ProcessingSpec *mz_processing_spec_p = new ProcessingSpec();

  // 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 =
    local_processing_flow.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();

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

  // Now define the graph ranges for the integration operation.

  // Remember that the region start and end are not sorted. Also, the colormap
  // can have its axes transposed, so we need to ensure that we use the proper
  // axis for mz and the proper axis for the other dimension (rt or dt).

  if(static_cast<pappso::BaseColorMapPlotWidget *>(mp_plotWidget)
       ->xAxisDataKind() == pappso::DataKind::dt)
    {
      dt_processing_spec_p->setRange(x_range_start, x_range_end);
      mz_processing_spec_p->setRange(y_range_start, y_range_end);

      qDebug() << "x axis data kind is dt"
               << "dt range:" << x_range_start << "-" << x_range_end
               << "mz range:" << y_range_start << "-" << y_range_end;
    }
  else
    {
      dt_processing_spec_p->setRange(y_range_start, y_range_end);
      mz_processing_spec_p->setRange(x_range_start, x_range_end);

      qDebug() << "x axis data kind is mz"
               << "dt range:" << y_range_start << "-" << y_range_end
               << "mz range:" << x_range_start << "-" << x_range_end;
    }

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

      // We need to set two specs to the step.

      processing_step_p->newSpec(ProcessingType("DT_MZ_DT_TO_RT"),
                                 dt_processing_spec_p);
      processing_step_p->newSpec(ProcessingType("DT_MZ_MZ_TO_RT"),
                                 mz_processing_spec_p);

      local_processing_flow.push_back(processing_step_p);

      static_cast<BaseColorMapPlotWnd *>(mp_parentWnd)
        ->integrateToRt(plottable_p, local_processing_flow);
    }
  else if(m_ui.integrateToRtDtPushButton->isChecked())
    {
      // qDebug() << "Integration to XIC chromatogram. / drift spectrum color
      // map";

      // We need to set two specs to the step.

      processing_step_p->newSpec(ProcessingType("DT_MZ_DT_TO_RT_DT"),
                                 dt_processing_spec_p);
      processing_step_p->newSpec(ProcessingType("DT_MZ_MZ_TO_RT_DT"),
                                 mz_processing_spec_p);

      local_processing_flow.push_back(processing_step_p);

      static_cast<BaseColorMapPlotWnd *>(mp_parentWnd)
        ->integrateToRtDt(plottable_p, local_processing_flow);
    }
  else if(m_ui.integrateToMzPushButton->isChecked() ||
          m_ui.integrateToDtMzPushButton->isChecked() ||
          m_ui.integrateToRtMzPushButton->isChecked())
    {
      if(m_ui.integrateToMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to mass spectrum.";

          // We need to set two specs to the step.

          processing_step_p->newSpec(ProcessingType("DT_MZ_DT_TO_MZ"),
                                     dt_processing_spec_p);
          processing_step_p->newSpec(ProcessingType("DT_MZ_MZ_TO_MZ"),
                                     mz_processing_spec_p);
        }
      else if(m_ui.integrateToRtMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to rt / m/z color map.";

          // We need to set two specs to the step.

          processing_step_p->newSpec(ProcessingType("DT_MZ_DT_TO_RT_MZ"),
                                     dt_processing_spec_p);
          processing_step_p->newSpec(ProcessingType("DT_MZ_MZ_TO_RT_MZ"),
                                     mz_processing_spec_p);
        }
      else if(m_ui.integrateToDtMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to dt / m/z color map.";

          // We need to set two specs to the step.

          processing_step_p->newSpec(ProcessingType("DT_MZ_DT_TO_DT_MZ"),
                                     dt_processing_spec_p);
          processing_step_p->newSpec(ProcessingType("DT_MZ_MZ_TO_DT_MZ"),
                                     mz_processing_spec_p);
        }

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

      // This is a multi-layer work: the graph has a ProcessingFlow associated
      // to it via the m_graphProcessingFlowMap. We have gotten it above.

      // Extract from it the default pappso::MzIntegrationParams:

      pappso::MzIntegrationParams mz_integration_params =
        local_processing_flow.getDefaultMzIntegrationParams();

      // If it is not valid, then try mz integration params from the various
      // steps of the flow. Preferentially the most recent ones.
      if(!mz_integration_params.isValid())
        {
          // qDebug()
          //<< "The processing flow's default mz integ params are not valid.";

          mz_integration_params =
            *(local_processing_flow.mostRecentMzIntegrationParams());
        }

      // If it is not valid, finally resort to the parameters that can be
      // crafted on the basis of the statistical analysis of the ms run data set
      // while it was loaded from file.
      if(!mz_integration_params.isValid())
        {
          // qDebug() << "The most recent mz integ param are not valid.";

          mz_integration_params =
            ms_run_data_set_csp->craftInitialMzIntegrationParams();
        }

      if(!mz_integration_params.isValid())
        qFatal(
          "The mz integ params from the ms run data set stats are not valid.");

      // Note that we cannot integrate mass spectra with a high resolution
      // here. We need to reduce the resolution to integer resolution.
      if(m_ui.integrateToDtMzPushButton->isChecked() ||
         m_ui.integrateToRtMzPushButton->isChecked())
        {
          mz_integration_params.setBinningType(pappso::BinningType::NONE);
          mz_integration_params.setDecimalPlaces(0);
        }

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

      // 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.
      local_processing_flow.push_back(processing_step_p);

      // qDebug().noquote() << "Pushed back new processing step:"
      //<< processing_step_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<BaseColorMapPlotWnd *>(mp_parentWnd)
            ->integrateToMz(plottable_p, local_processing_flow);
        }
      else if(m_ui.integrateToDtMzPushButton->isChecked())
        {
          static_cast<BaseColorMapPlotWnd *>(mp_parentWnd)
            ->integrateToDtMz(plottable_p, local_processing_flow);
        }
      else if(m_ui.integrateToRtMzPushButton->isChecked())
        {
          static_cast<BaseColorMapPlotWnd *>(mp_parentWnd)
            ->integrateToRtMz(plottable_p, local_processing_flow);
        }
    }
  else if(m_ui.integrateToDtPushButton->isChecked())
    {
      // qDebug() << "Integrating to drift spectrum.";

      // We need to set two specs to the step.

      processing_step_p->newSpec(ProcessingType("DT_MZ_DT_TO_DT"),
                                 dt_processing_spec_p);
      processing_step_p->newSpec(ProcessingType("DT_MZ_MZ_TO_DT"),
                                 mz_processing_spec_p);

      local_processing_flow.push_back(processing_step_p);

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

      // We need to set two specs to the step.

      processing_step_p->newSpec(ProcessingType("DT_MZ_DT_TO_INT"),
                                 dt_processing_spec_p);
      processing_step_p->newSpec(ProcessingType("DT_MZ_MZ_TO_INT"),
                                 mz_processing_spec_p);

      local_processing_flow.push_back(processing_step_p);

      static_cast<BasePlotWnd *>(mp_parentWnd)
        ->integrateToTicIntensity(plottable_p, nullptr, local_processing_flow);
    }
  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);
    }
}

} // namespace minexpert

} // namespace msxps
