/* 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/types.h>
#include <pappsomspp/widget/plotwidget/ticxicchrommassspeccolormapplotwidget.h>


/////////////////////// Local includes
#include "TicXicChromMassSpecColorMapPlotCompositeWidget.hpp"
#include "TicXicChromTracePlotWidget.hpp"
#include "TicXicChromMassSpecColorMapWnd.hpp"


namespace msxps
{
namespace minexpert
{


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


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


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

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

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

  connect(
    mp_plotWidget,
    &pappso::TicXicChromMassSpecColorMapPlotWidget::
      lastCursorHoveredPointSignal,
    this,
    &TicXicChromMassSpecColorMapPlotCompositeWidget::lastCursorHoveredPoint);

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

  connect(
    mp_plotWidget,
    &pappso::TicXicChromMassSpecColorMapPlotWidget::xAxisMeasurementSignal,
    this,
    &TicXicChromMassSpecColorMapPlotCompositeWidget::xAxisMeasurement);

  connect(
    mp_plotWidget,
    &pappso::TicXicChromMassSpecColorMapPlotWidget::keyPressEventSignal,
    this,
    &TicXicChromMassSpecColorMapPlotCompositeWidget::plotWidgetKeyPressEvent);

  connect(
    mp_plotWidget,
    &pappso::TicXicChromMassSpecColorMapPlotWidget::keyReleaseEventSignal,
    this,
    &TicXicChromMassSpecColorMapPlotCompositeWidget::plotWidgetKeyReleaseEvent);

  connect(mp_plotWidget,
          &pappso::BaseTracePlotWidget::mouseReleaseEventSignal,
          this,
          &TicXicChromMassSpecColorMapPlotCompositeWidget::
            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::TicXicChromMassSpecColorMapPlotWidget
  // *>(mp_plotWidget),
  //&pappso::TicXicChromMassSpecColorMapPlotWidget::mouseReleaseEventSignal,
  // this,
  //&TicXicChromMassSpecColorMapPlotCompositeWidget::plotWidgetMouseReleaseEvent);

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

  connect(
    mp_plotWidget,
    &pappso::BaseTracePlotWidget::integrationRequestedSignal,
    this,
    &TicXicChromMassSpecColorMapPlotCompositeWidget::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
TicXicChromMassSpecColorMapPlotCompositeWidget::lastCursorHoveredPoint(
  const QPointF &pointf)

{
  BaseColorMapPlotCompositeWidget::lastCursorHoveredPoint(pointf);

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


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

  // We are getting that signal from a plot widget that operates as a
  // tic xic chrom / mass spec color map plot widget. By essence, the selected
  // region needs to be two-dimensional, so the selection polygon needs to be
  // so.

  // Check if the selection polygon contain meaninful data. We are in a colormap
  // plot widget, so the selection polygon cannot be in the form of a
  // mono-dimensional polygon.

  if(!context.m_selectionPolygon.is2D())
    {
      // qDebug() << "The selection polygon cannot be mono-dimensional when "
      //"selecting for integration from color map";
      return;
    }
  else
    {
      if(context.m_selectionPolygon.isRectangle())
        {
          // qDebug() << "The selection polygon is rectangle.";
        }
      else
        {
          // qDebug() << "The selection polygon is oblique (skewed).";
        }
    }

  // Sanity check.

  // Immediately check if the selection range makes sense. For example, having
  // both the start/end values in the negative values does not mean anything
  // with respect to tic|xic chrom vs mass spectrum data!

  double x_range_start;
  double x_range_end;

  context.m_selectionPolygon.rangeX(x_range_start, x_range_end);

  // qDebug() << "X axis range:" << x_range_start << "-" << x_range_end;

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

  double y_range_start;
  double y_range_end;

  context.m_selectionPolygon.rangeY(y_range_start, y_range_end);

  // qDebug() << "Y axis range:" << y_range_start << "-" << y_range_end;

  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 so that we can modify it by
  // adding the new processing step that describes what we are doing now.

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

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

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

  // Now define which selection polygon sides have which data kind.

  // Sanity check.

  pappso::DataKind x_axis_data_kind =
    static_cast<pappso::BaseColorMapPlotWidget *>(mp_plotWidget)
      ->xAxisDataKind();
  pappso::DataKind y_axis_data_kind =
    static_cast<pappso::BaseColorMapPlotWidget *>(mp_plotWidget)
      ->yAxisDataKind();

  if(x_axis_data_kind == pappso::DataKind::mz)
    {
      // qDebug() << "X axis: mz";
    }
  else if(x_axis_data_kind == pappso::DataKind::rt)
    {
      // qDebug() << "X axis: rt";
    }

  if(x_axis_data_kind == pappso::DataKind::mz &&
     y_axis_data_kind != pappso::DataKind::rt)
    {
      qFatal("Programming error.");
    }
  else if(x_axis_data_kind == pappso::DataKind::rt &&
          y_axis_data_kind != pappso::DataKind::mz)
    {
      qFatal("Programming error.");
    }

  // The colormap can have its axes transposed, so we need to ensure that we
  // document the axis/data-kind relation properly. Note that we want the
  // processing step to be documented according to the alpha numerical ordering.
  // So, if we are working in a DT_RT colormap, then, x axis needs to be DT and
  // y axis needs to be RT.

  // Make a local copy of the selection polygon because we might need to
  // transpose it.

  pappso::SelectionPolygon local_selection_polygon = context.m_selectionPolygon;

  if(x_axis_data_kind == pappso::DataKind::rt)
    {
      // We have to transpose the selection rectangle so that x:DT and y:RT.
      local_selection_polygon = local_selection_polygon.transpose();
    }
  // else
  // No need to tranpose, we deal with data in the right x:MZ y:RT.

  // qDebug() << "The selection polygon:" << local_selection_polygon.toString();

  // Now we know we have the right selection polygon for x:MZ and y:RT.

  processing_step_p->setDataKind(pappso::Axis::x, pappso::DataKind::mz);
  processing_step_p->setDataKind(pappso::Axis::y, pappso::DataKind::rt);


  // And now actually set the selection polygon to the step.

  processing_step_p->setSelectionPolygon(local_selection_polygon);

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

  // Now we need to document not the source but the destination. What kind of
  // integration are we about to perform?

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

      processing_step_p->setDestProcessingType("RT");

      local_processing_flow.push_back(processing_step_p);

      static_cast<BaseColorMapPlotWnd *>(mp_parentWnd)
        ->integrateToRt(plottable_p, local_processing_flow);
    }
  else if(m_ui.integrateToDtRtPushButton->isChecked())
    {
      // qDebug() << "Integrating to dt / rt color map.";

      processing_step_p->setDestProcessingType("DT_RT");

      local_processing_flow.push_back(processing_step_p);

      static_cast<BaseColorMapPlotWnd *>(mp_parentWnd)
        ->integrateToDtRt(plottable_p, local_processing_flow);
    }
  // Handle all the integrations to MZ in one place
  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_p->setDestProcessingType("MZ");
        }
      else if(m_ui.integrateToMzRtPushButton->isChecked())
        {
          // qDebug() << "Integrating to m/z / rt color map.";

          processing_step_p->setDestProcessingType("MZ_RT");
        }
      else if(m_ui.integrateToDtMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to dt / m/z color map.";

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

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

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

      // qDebug()
      //<< "The graph's processing flow has default mz integration params:"
      //<< mz_integration_params.toString();

      // 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. Trying to get the most recent mz integ params "
          //"from the processing flow. If valid, we may tune them later.";

          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. We use "
                      //"the getMsRunDataSetStats to craft new ones that we may "
                      //"tune later.";

          mz_integration_params =
            ms_run_data_set_csp->craftInitialMzIntegrationParams();
        }
      else
        {
          //qDebug() << "The most recent mz integ params are valid. Use them, "
                      //"with potential tuning.";
        }

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

      // At this point, let's see if we can optimize the integration
      // computation. Indeed, if we know we have an mz range in the selection
      // polygon, document that range to lower computing time.

      double mz_range_lower = std::numeric_limits<double>::max();
      double mz_range_upper = std::numeric_limits<double>::min();

      // We know we have the x axis being the dt while the mz is the y axis.

      bool res = local_selection_polygon.rangeY(mz_range_lower, mz_range_upper);

      if(!res)
        {
          qFatal("Failed to get the m/z range out of the selection polygon");
        }

      // At this point, change the values in the mz_integration_params so
      // that the min and max values match the range, which will ensure that
      // the integration is the quickest possible as the bins are created in
      // this range and not in another one that might be larger (if the
      // integration comes right from the intial TIC, then the range is the
      // whole range of the mass data in the dataset.

      mz_integration_params.setSmallestMz(mz_range_lower);
      mz_integration_params.setGreatestMz(mz_range_upper);

      //qDebug() << "After tuning the mz range, mz integration params:"
               //<< mz_integration_params.toString();


#if 0

      // This is no more true: we do not force a low resolution computation for
      // the color maps. The mz integration parameters are now entirely
      // satisfied.


      // 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.integrateToMzRtPushButton->isChecked())
        {
          mz_integration_params.setBinningType(pappso::BinningType::NONE);
          mz_integration_params.setDecimalPlaces(0);
        }

#endif

      // 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.integrateToMzRtPushButton->isChecked())
        {
          static_cast<BaseColorMapPlotWnd *>(mp_parentWnd)
            ->integrateToMzRt(plottable_p, local_processing_flow);
        }
    }
  else if(m_ui.integrateToDtPushButton->isChecked())
    {
      //qDebug() << "Integrating to drift spectrum.";

      processing_step_p->setDestProcessingType("DT");

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

      processing_step_p->setDestProcessingType("INT");

      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
