/* 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/ticxicchromtraceplotwidget.h>


/////////////////////// Local includes
#include "TicXicChromTracePlotCompositeWidget.hpp"
#include "TicXicChromTracePlotWidget.hpp"
#include "TicXicChromTracePlotWnd.hpp"


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

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

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

  connect(mp_plotWidget,
          &pappso::BaseTracePlotWidget::integrationRequestedSignal,
          this,
          &TicXicChromTracePlotCompositeWidget::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.

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


void
TicXicChromTracePlotCompositeWidget::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);

  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::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 = 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 = context;

  // Check if the selection polygon contain meaningful data. We are in trace
  // plot widget, so the selection polygon might be in the form of a
  // mono-dimensional polygon, although this is not mandatory. But we rely on
  // the selection polygon to be of 1 or 2 dimension for some optimizations. So,
  // if the polygon is 2D, convert it to 1D.

  if(local_context.m_selectionPolygon.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.m_selectionPolygon.convertTo1D();

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

  //qDebug() << "selection polygon:" << local_context.m_selectionPolygon.toString();
  //qDebug() << "X axis range:" << 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 = 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.

  //qDebug();

  ProcessingFlow local_processing_flow =
    getProcessingFlowForPlottable(plottable_p);

  //qDebug() << local_processing_flow.getMsRunDataSetCstSPtr()->getMsRunId()->getFileName();

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

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

  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:"
      //<< ms_fragmentation_spec.toString();
    }

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

  //qDebug();

  processing_step_p->setSelectionPolygon(local_context.m_selectionPolygon);

  //qDebug();

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

  // It is essential that we document the relation between the axes and the
  // corresponding data type.
  processing_step_p->setDataKind(pappso::Axis::x, pappso::DataKind::rt);
  processing_step_p->setDataKind(pappso::Axis::y, pappso::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_p->setDestProcessingType("RT");

      local_processing_flow.push_back(processing_step_p);

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

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

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

          const pappso::MzIntegrationParams *mz_integration_params_p =
            local_processing_flow.mostRecentMzIntegrationParams();

          if(mz_integration_params_p != nullptr)
            mz_integration_params = *mz_integration_params_p;
        }

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

      // 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, local_processing_flow);
        }
      else if(m_ui.integrateToDtMzPushButton->isChecked())
        {
          static_cast<TicXicChromTracePlotWnd *>(mp_parentWnd)
            ->integrateToDtMz(plottable_p, local_processing_flow);
        }
      else if(m_ui.integrateToMzRtPushButton->isChecked())
        {
          static_cast<TicXicChromTracePlotWnd *>(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<TicXicChromTracePlotWnd *>(mp_parentWnd)
        ->integrateToDt(plottable_p, local_processing_flow);
    }
  else if(m_ui.integrateToDtRtPushButton->isChecked())
    {
      //qDebug() << "Integrating to dt/rt colormap.";

      processing_step_p->setDestProcessingType("DT_RT");

      local_processing_flow.push_back(processing_step_p);

      static_cast<TicXicChromTracePlotWnd *>(mp_parentWnd)
        ->integrateToDtRt(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);
    }

  //qDebug();

  return;
}


QString
TicXicChromTracePlotCompositeWidget::craftAnalysisStanza(
  const pappso::BasePlotContext &context)
{
  // qDebug();

  QCPAbstractPlottable *plottable_p = plottableToBeUsedAsIntegrationSource();

  if(plottable_p == nullptr)
    {
      // 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.");
        }

      m_analysisStanza = "";
      return QString();
    }

  ProcessingFlow processing_flow = getProcessingFlowForPlottable(plottable_p);

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow.getMsRunDataSetCstSPtr();

  QString sample_name = ms_run_data_set_csp->getMsRunId()->getSampleName();

  // Craft the analysis stanza by copying the m_analysisStanza that was
  // partially filled-in by the base classes and go on filling-in fields still
  // marked with "%<char>" format patterns.

  QString orig_stanza = m_analysisStanza;
  QString stanza;

  QChar prevChar = ' ';

  for(int iter = 0; iter < orig_stanza.size(); ++iter)
    {
      QChar curChar = orig_stanza.at(iter);

      // qDebug() << "Current char:" << curChar;

      if(curChar == '\\')
        {
          if(prevChar == '\\')
            {
              stanza += '\\';
              prevChar = ' ';
              continue;
            }
          else
            {
              prevChar = '\\';
              continue;
            }
        }

      if(curChar == '%')
        {
          if(prevChar == '\\')
            {
              stanza += '%';
              prevChar = ' ';
              continue;
            }
          else
            {
              prevChar = '%';
              continue;
            }
        }

      if(curChar == 'n')
        {
          if(prevChar == '\\')
            {
              stanza += QString("\n");

              // Because a newline only works if it is followed by something,
              // and
              // if the user wants a termination newline, then we need to
              // duplicate that new line char if we are at the end of the
              // string.

              if(iter == orig_stanza.size() - 1)
                {
                  // This is the last character of the line, then, duplicate
                  // the
                  // newline so that it actually creates a new line in the
                  // text.

                  stanza += QString("\n");
                }

              prevChar = ' ';
              continue;
            }
        }

      if(prevChar == '%')
        {
          // The current character might have a specific signification.
          if(curChar == 'f')
            {
              QFileInfo fileInfo(sample_name);
              if(fileInfo.exists())
                stanza += fileInfo.fileName();
              else
                stanza += "Untitled";
              prevChar = ' ';
              continue;
            }
          if(curChar == 's')
            {
              QFileInfo fileInfo(sample_name);
              if(fileInfo.exists())
                stanza += fileInfo.fileName();
              else
                stanza += "Untitled";
              prevChar = ' ';
              continue;
            }
          if(curChar == 'X')
            {
              stanza += QString("%1").arg(
                context.m_lastCursorHoveredPoint.x(), 0, 'g', 6);
              prevChar = ' ';
              continue;
            }
          if(curChar == 'Y')
            {
              // We need to get the value of the intensity for the graph, but
              // not as an integration to INT, but simply as the y coordinate
              // of the point at X.
              stanza += QString("%1").arg(
                static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                  ->getYatX(context.m_lastCursorHoveredPoint.x(),
                            static_cast<QCPGraph *>(plottable_p)),
                0,
                'g',
                6);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'x')
            {
              stanza += QString("%1").arg(context.m_xDelta, 0, 'g', 6);
              prevChar = ' ';
              continue;
            }
          if(curChar == 'y')
            {
              stanza += QString("%1").arg(context.m_yDelta, 0, 'g', 6);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'I')
            {
              stanza += QString("%1").arg(m_lastTicIntensity, 0, 'g', 3);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'b')
            {
              stanza +=
                QString("%1").arg(context.m_xRegionRangeStart, 0, 'f', 3);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'e')
            {
              stanza += QString("%1").arg(context.m_xRegionRangeEnd, 0, 'f', 3);

              prevChar = ' ';
              continue;
            }
          // At this point the '%' is not followed by any special character
          // above, so we skip them both from the text. If the '%' is to be
          // printed, then it needs to be escaped. We do this because we do not
          // expect that derived classes will do the format-based field-filling
          // work later.

          continue;
        }
      // End of
      // if(prevChar == '%')

      // The character prior this current one was not '%' so we just append
      // the current character.
      stanza += curChar;
    }
  // End of
  // for (int iter = 0; iter < pattern.size(); ++iter)

  // Finally copy the result into the member datum.
  m_analysisStanza = stanza;
  return stanza;
}


} // namespace minexpert

} // namespace msxps
