/* 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/gui/plotwidget/driftspectraceplotwidget.h>
#include <pappsomspp/core/processing/combiners/mzintegrationparams.h>
#include <pappsomspp/core/processing/combiners/integrationscope.h>


/////////////////////// Local includes
#include "DriftSpecTracePlotCompositeWidget.hpp"
#include "DriftSpecTracePlotWnd.hpp"
#include "ui_BasePlotCompositeWidget.h"

namespace MsXpS
{
namespace MineXpert
{


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

  setupWidget();
}

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

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

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

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

  connect(mp_plotWidget,
          &pappso::DriftSpecTracePlotWidget::lastCursorHoveredPointSignal,
          this,
          &DriftSpecTracePlotCompositeWidget::lastCursorHoveredPoint);

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

  connect(mp_plotWidget,
          &pappso::DriftSpecTracePlotWidget::xAxisMeasurementSignal,
          this,
          &DriftSpecTracePlotCompositeWidget::xAxisMeasurement);

  connect(mp_plotWidget,
          &pappso::DriftSpecTracePlotWidget::keyPressEventSignal,
          this,
          &DriftSpecTracePlotCompositeWidget::plotWidgetKeyPressEvent);

  connect(mp_plotWidget,
          &pappso::DriftSpecTracePlotWidget::keyReleaseEventSignal,
          this,
          &DriftSpecTracePlotCompositeWidget::plotWidgetKeyReleaseEvent);

  connect(mp_plotWidget,
          &pappso::DriftSpecTracePlotWidget::mouseReleaseEventSignal,
          this,
          &DriftSpecTracePlotCompositeWidget::plotWidgetMouseReleaseEvent);

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

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


  // connect(mp_plotWidget,
  //&pappso::DriftSpecTracePlotWidget,
  // this,
  //&DriftSpecTracePlotCompositeWidget::);

  /************ 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
DriftSpecTracePlotCompositeWidget::lastCursorHoveredPoint(const QPointF &pointf)

{
  BaseTracePlotCompositeWidget::lastCursorHoveredPoint(pointf);

  // At this point we can start doing drift spectrum-specific calculations.
}

void
DriftSpecTracePlotCompositeWidget::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
DriftSpecTracePlotCompositeWidget::integrationRequested(
  const pappso::BasePlotContext &context)
{
  // qDebug().noquote() << "context:" << context.toString();

  // The reference implementation is the TicXicChromTracePlotCompositeWidget
  // one. Look there for all the explanative comments.

  // 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.";

      //**local_context.msp_integrationScope->convertTo1D();
      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::IntegrationScopeBase>(
          pappso::IntegrationScope(point, width));
    }

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

  // 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.m_selectionPolygon.rangeX(range_start, range_end);
  local_context.msp_integrationScope->range(
    pappso::Enums::Axis::x, range_start, range_end);

  // qDebug() << "X axis range:" << range_start << "-" << range_end;

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

  // We are receiving the call from a plot widget that operates a drift time
  // plot widget.

  // What integration destination is currently checked ? Note that the widget
  // does not handle integrations, it delegates them to the parent window of the
  // right type (Tic window for an integration to RT, or mass spec window for an
  // integration to MZ).

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

  ProcessingFlow *temp_processing_flow_p =
    getProcessingFlowPtrForPlottable(plottable_p)->clone(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>();

  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.";
    }

  // Now make sure we document the selection polygon.
  // Now define the graph range for the integration operation.
  //**processing_step_sp->setSelectionPolygon(local_context.m_selectionPolygon);
  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, "DT");

  // 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::dt);
  processing_step_sp->setDataKind(pappso::Enums::Axis::y,
                                  pappso::Enums::DataKind::unset);

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

      processing_step_sp->setDestProcessingType("RT");

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

      static_cast<DriftSpecTracePlotWnd *>(mp_parentWnd)
        ->integrateToRt(plottable_p, *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");
        }
      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");
        }

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

      // 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);

      // This is no more true, we let the user set the binning logic.
      // // 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())
      //   {
      //     mz_integration_params_p->setBinningType(
      //       pappso::MzIntegrationParams::BinningType::NONE);
      //     mz_integration_params_p->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.
      // 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().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<DriftSpecTracePlotWnd *>(mp_parentWnd)
            ->integrateToMz(plottable_p, *temp_processing_flow_p);
        }
      else if(m_ui->integrateToDtMzPushButton->isChecked())
        {
          static_cast<DriftSpecTracePlotWnd *>(mp_parentWnd)
            ->integrateToDtMz(plottable_p, *temp_processing_flow_p);
        }
      else if(m_ui->integrateToMzRtPushButton->isChecked())
        {
          static_cast<DriftSpecTracePlotWnd *>(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<DriftSpecTracePlotWnd *>(mp_parentWnd)
        ->integrateToDt(plottable_p, nullptr, *temp_processing_flow_p);
    }
  else if(m_ui->integrateToDtRtPushButton->isChecked())
    {
      // qDebug() << "Integrating to TIC|XIC chromatogram / drift spectrum
      // 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<DriftSpecTracePlotWnd *>(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;
}

void
DriftSpecTracePlotCompositeWidget::plotWidgetKeyPressEvent(
  const pappso::BasePlotContext &context)
{
  // qDebug() << "ENTER";

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

  qDebug() << "After filling-in by base class, the stanza is:"
           << m_analysisStanza;

  m_plotWidgetPressedKeyCode = context.m_pressedKeyCode;

  // Now handle here the mass specific data, like z, Mr, m/z.
  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.

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

QString
DriftSpecTracePlotCompositeWidget::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
