/* 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/ticxicchromdriftspeccolormapplotwidget.h>
#include <pappsomspp/core/processing/combiners/integrationscoperect.h>
#include <pappsomspp/core/processing/combiners/integrationscoperhomb.h>


/////////////////////// Local includes
#include "TicXicChromDriftSpecColorMapPlotCompositeWidget.hpp"
#include "BaseColorMapPlotWnd.hpp"
#include "ui_BasePlotCompositeWidget.h"

namespace MsXpS
{
namespace MineXpert
{


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

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

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

  setWindowIcon(qApp->windowIcon());

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

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

  connect(
    mp_plotWidget,
    &pappso::TicXicChromDriftSpecColorMapPlotWidget::
      lastCursorHoveredPointSignal,
    this,
    &TicXicChromDriftSpecColorMapPlotCompositeWidget::lastCursorHoveredPoint);

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

  connect(
    mp_plotWidget,
    &pappso::TicXicChromDriftSpecColorMapPlotWidget::xAxisMeasurementSignal,
    this,
    &TicXicChromDriftSpecColorMapPlotCompositeWidget::xAxisMeasurement);

  connect(
    mp_plotWidget,
    &pappso::TicXicChromDriftSpecColorMapPlotWidget::keyPressEventSignal,
    this,
    &TicXicChromDriftSpecColorMapPlotCompositeWidget::plotWidgetKeyPressEvent);

  connect(
    mp_plotWidget,
    &pappso::TicXicChromDriftSpecColorMapPlotWidget::keyReleaseEventSignal,
    this,
    &TicXicChromDriftSpecColorMapPlotCompositeWidget::
      plotWidgetKeyReleaseEvent);

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

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

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

{
  BaseColorMapPlotCompositeWidget::lastCursorHoveredPoint(pointf);

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

void
TicXicChromDriftSpecColorMapPlotCompositeWidget::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 / drift spec color map plot widget. By essence, the selected
  // region needs to be two-dimensional, so the integration scope needs to be
  // so.

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

  //**if(!context.m_selectionPolygon.is2D())
  if(!context.msp_integrationScope->is2D())
    {
      qDebug() << "The integration scope cannot be mono-dimensional when "
                  "selecting for integration from a color map";
      return;
    }
  else
    {
      //**if(context.m_selectionPolygon.isRectangle())
      if(context.msp_integrationScope->isRectangle())
        {
          // qDebug() << "The integration scope is rectangle.";
        }
      else
        {
          if(!context.msp_integrationScope->isRhomboid())
            qFatal(
              "Cannot be that 2D integration scope is neither rectangle nor "
              "rhomboid.");

          qDebug() << "The integration scope is rhomboid (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);
  context.msp_integrationScope->range(
    pappso::Enums::Axis::x, 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);
  context.msp_integrationScope->range(
    pappso::Enums::Axis::y, 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 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 =
    getCstProcessingFlowPtrForPlottable(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>();

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

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

  // Sanity check.

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

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

  if(y_axis_data_kind == pappso::Enums::DataKind::dt)
    {
      // qDebug() << "Y axis: dt";
    }
  else if(y_axis_data_kind == pappso::Enums::DataKind::rt)
    {
      // qDebug() << "Y axis: rt";
    }

  if(x_axis_data_kind == pappso::Enums::DataKind::dt &&
     y_axis_data_kind != pappso::Enums::DataKind::rt)
    {
      qFatal() << "Programming error.";
    }
  else if(x_axis_data_kind == pappso::Enums::DataKind::rt &&
          y_axis_data_kind != pappso::Enums::DataKind::dt)
    {
      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;

  // We know the integration scope at this point is 2D and is either Rect or
  // Rhomb.

  pappso::IntegrationScopeBaseSPtr local_integration_scope_base_sp = nullptr;

  if(context.msp_integrationScope->isRectangle())
    {
      // const pappso::IntegrationScopeBase *integration_scope_base_p =
      //   context.msp_integrationScope.get();
      // const pappso::IntegrationScopeRect *integration_scope_rect_p =
      //   dynamic_cast<const pappso::IntegrationScopeRect *>(
      //     integration_scope_base_p);
      // const pappso::IntegrationScopeRect *integration_scope_rect_new_p =
      //   integration_scope_rect_p->clone();
      // pappso::IntegrationScopeRectCstSPtr integration_scope_rect_new_csp =
      //   std::make_shared<const pappso::IntegrationScopeRect>(
      //     *integration_scope_rect_new_p);
      // pappso::IntegrationScopeRectSPtr integration_scope_rect_new_sp =
      //   std::make_shared<pappso::IntegrationScopeRect>(
      //     *integration_scope_rect_new_p);
      // local_integration_scope_base_sp =
      //   std::static_pointer_cast<pappso::IntegrationScopeBase>(
      //     integration_scope_rect_new_sp);

      pappso::IntegrationScopeRectSPtr integration_scope_rect_new_sp =
        std::make_shared<pappso::IntegrationScopeRect>(
          *dynamic_cast<const pappso::IntegrationScopeRect *>(
            context.msp_integrationScope.get()));

      local_integration_scope_base_sp =
        std::static_pointer_cast<pappso::IntegrationScopeBase>(
          integration_scope_rect_new_sp);
    }
  else if(context.msp_integrationScope->isRhomboid())
    {
      // const pappso::IntegrationScopeBase *integration_scope_base_p =
      //   context.msp_integrationScope.get();
      // const pappso::IntegrationScopeRhomb *integration_scope_rhomb_p =
      //   dynamic_cast<const pappso::IntegrationScopeRhomb *>(
      //     integration_scope_base_p);
      // const pappso::IntegrationScopeRhomb *integration_scope_rhomb_new_p =
      //   integration_scope_rhomb_p->clone();
      // pappso::IntegrationScopeRhombCstSPtr integration_scope_rhomb_new_csp =
      //   std::make_shared<const pappso::IntegrationScopeRhomb>(
      //     *integration_scope_rhomb_new_p);
      // pappso::IntegrationScopeRhombSPtr integration_scope_rhomb_new_sp =
      //   std::make_shared<pappso::IntegrationScopeRhomb>(
      //     *integration_scope_rhomb_new_p);
      // local_integration_scope_base_sp =
      //   std::static_pointer_cast<pappso::IntegrationScopeBase>(
      //     integration_scope_rhomb_new_sp);

      pappso::IntegrationScopeRhombSPtr integration_scope_rhomb_new_sp =
        std::make_shared<pappso::IntegrationScopeRhomb>(
          *dynamic_cast<const pappso::IntegrationScopeRhomb *>(
            context.msp_integrationScope.get()));

      local_integration_scope_base_sp =
        std::static_pointer_cast<pappso::IntegrationScopeBase>(
          integration_scope_rhomb_new_sp);
    }
  else
    qFatal(
      "Cannot be that 2D integration scope is neither rectangle nor "
      "rhomboid.");

  // qDebug() << "New local integration scope is Rect:"
  //          << local_integration_scope_base_sp->isRectangle();
  // qDebug() << "New local integration scope is Rhomb:"
  //          << local_integration_scope_base_sp->isRhomboid();

  if(x_axis_data_kind == pappso::Enums::DataKind::rt)
    {
      // We have to transpose the selection rectangle so that x:DT and y:RT.
      std::const_pointer_cast<pappso::IntegrationScopeBase>(
        local_integration_scope_base_sp)
        ->transpose();
    }
  // else
  // No need to tranpose, we deal with data in the right x:DT y:RT.

  // Now we know we have the right integration scope for x:DT and y:RT.

  processing_step_sp->setDataKind(pappso::Enums::Axis::x,
                                  pappso::Enums::DataKind::dt);
  processing_step_sp->setDataKind(pappso::Enums::Axis::y,
                                  pappso::Enums::DataKind::rt);

  // And now actually set the integration scope to the step.

  // processing_step_sp->setSelectionPolygon(local_selection_polygon);
  processing_step_sp->setIntegrationScope(local_integration_scope_base_sp);

  // qDebug() << "Local integration scope copy:"
  //          << local_integration_scope_base_sp->toString();

  // We must say what is the data source processing type.
  processing_step_sp->setSrcProcessingType(pappso::Enums::Axis::x, "DT");
  processing_step_sp->setSrcProcessingType(pappso::Enums::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_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<BaseColorMapPlotWnd *>(mp_parentWnd)
        ->integrateToRt(plottable_p, *temp_processing_flow_p);
    }
  else if(m_ui->integrateToDtRtPushButton->isChecked())
    {
      // qDebug() << "Integrating to dt / rt color map.";

      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<BaseColorMapPlotWnd *>(mp_parentWnd)
        ->integrateToDtRt(plottable_p, *temp_processing_flow_p);
    }
  // 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_sp->setDestProcessingType("MZ");
        }
      else if(m_ui->integrateToMzRtPushButton->isChecked())
        {
          // qDebug() << "Integrating to m/z / rt 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.

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


      // 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 rt while the mz is the y axis.

      // bool res = local_selection_polygon.rangeY(mz_range_lower,
      // mz_range_upper);
      bool res = local_integration_scope_base_sp->range(
        pappso::Enums::Axis::y, 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_p->setSmallestMz(mz_range_lower);
      mz_integration_params_p->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::MzIntegrationParams::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.
      // The step does not take ownership.
      processing_step_sp->setMzIntegrationParams(mz_integration_params_p);
      delete 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<BaseColorMapPlotWnd *>(mp_parentWnd)
            ->integrateToMz(plottable_p, *temp_processing_flow_p);
        }
      else if(m_ui->integrateToDtMzPushButton->isChecked())
        {
          static_cast<BaseColorMapPlotWnd *>(mp_parentWnd)
            ->integrateToDtMz(plottable_p, *temp_processing_flow_p);
        }
      else if(m_ui->integrateToMzRtPushButton->isChecked())
        {
          static_cast<BaseColorMapPlotWnd *>(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<BaseColorMapPlotWnd *>(mp_parentWnd)
        ->integrateToDt(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;
}

} // namespace MineXpert

} // namespace MsXpS
