/**
 * \file pappsomspp/widget/spectrumwidget/qcpspectrum.cpp
 * \date 31/12/2017
 * \author Olivier Langella
 * \brief Custom plot derivative to plot a spectrum
 */


/*******************************************************************************
 * Copyright (c) 2017 Olivier Langella <Olivier.Langella@u-psud.fr>.
 *
 * This file is part of the PAPPSOms++ library.
 *
 *     PAPPSOms++ 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.
 *
 *     PAPPSOms++ 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 PAPPSOms++.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     Olivier Langella <Olivier.Langella@u-psud.fr> - initial API and
 *implementation
 ******************************************************************************/

#include "qcpspectrum.h"
#include "massspectrumwidget.h"

using namespace pappso;

QCPSpectrum::QCPSpectrum(MassSpectrumWidget *parent, bool visible) : QCustomPlot(parent)
{
  qDebug() << "QCPSpectrum::QCPSpectrum begin";

  setFocusPolicy(Qt::ClickFocus);
  _parent                 = parent;
  _mz_range.lower         = 0;
  _mz_range.upper         = 0;
  _intensity_range.lower  = 0;
  _intensity_range.upper  = 0;
  _mass_delta_range.upper = -100;
  _mass_delta_range.lower = 100;


  // make axis rects' left side line up:
  QCPMarginGroup *group = new QCPMarginGroup(this);
  this->axisRect()->setMarginGroup(QCP::msLeft | QCP::msRight, group);


  _p_peak_bars = new QCPBars(xAxis, yAxis);
  _p_peak_bars->setWidthType(QCPBars::WidthType::wtAbsolute);
  _p_peak_bars->setWidth(_bar_width);
  _p_peak_bars->setPen(QPen(Qt::black, 1));
  _p_peak_bars->setVisible(true);

  _p_peak_bars_isotope = new QCPBars(xAxis, yAxis);
  _p_peak_bars_isotope->setWidthType(QCPBars::WidthType::wtAbsolute);
  _p_peak_bars_isotope->setWidth(_bar_width * 10);
  QColor red(Qt::red);
  red.setAlpha(100);
  _p_peak_bars_isotope->setPen(QPen(red, 1));
  _p_peak_bars_isotope->setBrush(QBrush(red));

  mp_peak_bars_precursor = new QCPBars(xAxis, yAxis);
  mp_peak_bars_precursor->setWidthType(QCPBars::WidthType::wtAbsolute);
  mp_peak_bars_precursor->setWidth(_bar_width * 1.5);
  mp_peak_bars_precursor->setPen(QPen(Qt::cyan, 1));
  mp_peak_bars_precursor->setVisible(true);

  connect(this->xAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(setMzRangeChanged(QCPRange)));


  std::vector<Enums::PeptideIon> all_ion_list = {
    Enums::PeptideIon::b,
    Enums::PeptideIon::bstar,
    Enums::PeptideIon::bo,
    Enums::PeptideIon::a,
    Enums::PeptideIon::astar,
    Enums::PeptideIon::ao,
    Enums::PeptideIon::bp,
    Enums::PeptideIon::c,
    Enums::PeptideIon::y,     ///< Cter amino ions
    Enums::PeptideIon::ystar, ///< Cter amino ions + NH3 loss
    Enums::PeptideIon::yo,    ///< Cter amino ions + H2O loss
    Enums::PeptideIon::z,     ///< Cter carbocations
    Enums::PeptideIon::yp,
    Enums::PeptideIon::x};
  qDebug() << "SpectrumWidget::setVisibleMassDelta 5";

  for(Enums::PeptideIon ion_type : all_ion_list)
    {
      QCPBars *p_peak_bars = new QCPBars(xAxis, yAxis);
      p_peak_bars->setWidthType(QCPBars::WidthType::wtAbsolute);
      p_peak_bars->setWidth(_bar_width);
      p_peak_bars->setPen(QPen(PeptideFragmentIon::getPeptideIonColor(ion_type), 1));

      p_peak_bars->setVisible(true);
      _map_ion_type_bars.insert(std::pair<Enums::PeptideIon, QCPBars *>(ion_type, p_peak_bars));
    }
  qDebug() << "SpectrumWidget::setVisibleMassDelta visible ?";
  setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
  if(visible)
    {

      _p_delta_axis_rect = new QCPAxisRect(this);
      this->plotLayout()->addElement(1, 0, _p_delta_axis_rect);
      _p_delta_axis_rect->axis(QCPAxis::atBottom)->setLayer("axes");
      _p_delta_axis_rect->axis(QCPAxis::atBottom)->grid()->setLayer("grid");
      _p_delta_axis_rect->axis(QCPAxis::atLeft)->setLabel("mass delta");
      // bring bottom and main axis rect closer together:
      this->plotLayout()->setRowSpacing(0);
      _p_delta_axis_rect->setAutoMargins(QCP::msLeft | QCP::msRight | QCP::msBottom);
      _p_delta_axis_rect->setMargins(QMargins(0, 0, 0, 0));
      this->setAutoAddPlottableToLegend(false);
      _p_delta_graph = new QCPGraph(_p_delta_axis_rect->axis(QCPAxis::atBottom),
                                    _p_delta_axis_rect->axis(QCPAxis::atLeft));
      _p_delta_graph->setLineStyle(QCPGraph::LineStyle::lsNone);
      _p_delta_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 4.0));

      _p_delta_axis_rect->setMarginGroup(QCP::msLeft | QCP::msRight, group);
      _p_delta_axis_rect->setMaximumSize(QSize(QWIDGETSIZE_MAX, 100));


      //_p_delta_axis_rect->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom );
      _p_delta_axis_rect->setRangeDrag(Qt::Horizontal | Qt::Vertical);
      _p_delta_axis_rect->setRangeZoom(Qt::Vertical);


      connect(_p_delta_axis_rect->axis(QCPAxis::atBottom),
              SIGNAL(rangeChanged(QCPRange)),
              this,
              SLOT(setMzRangeChanged(QCPRange)));
    }
  else
    {
      _p_delta_axis_rect = nullptr;
    }
  qDebug() << "QCPSpectrum::QCPSpectrum end";
}
QCPSpectrum::~QCPSpectrum()
{
}

void
QCPSpectrum::setSpectrumP(const MassSpectrum *spectrum)
{
  _mass_delta_range.upper = -100;
  _mass_delta_range.lower = 100;
  // generate basic peaks:
  _p_spectrum = spectrum;
  if((spectrum != nullptr) && (spectrum->size() != 0))
    {
      // throw PappsoException(QObject::tr("Error in
      // SpectrumWidget::setSpectrumSp :\n_spectrum_sp.get() == nullptr"));
      //}
      _p_peak_bars->setVisible(true);
      for(const DataPoint &peak : *spectrum)
        {
          _p_peak_bars->addData(peak.x, peak.y);
        }
      _mz_range.lower        = spectrum->front().x - 1;
      _mz_range.upper        = spectrum->back().x + 1;
      _intensity_range.lower = 0;
      _intensity_range.upper = spectrum->maxY();
    }
  else
    {
      _mz_range.lower        = 0;
      _mz_range.upper        = 0;
      _intensity_range.lower = 0;
      _intensity_range.upper = 0;
    }
}

void
QCPSpectrum::rescale()
{
  _p_peak_bars->setVisible(true);

  //_p_peak_bars->rescaleAxes(false);
  // this->rescaleAxes(false);


  if(_mz_range.lower < 0)
    {
      _mz_range.lower = 0;
    }
  if(_mz_range.upper < 0)
    {
      _mz_range.upper = 1000;
    }
  else
    {
      xAxis->setRange(_mz_range);
      yAxis->setRange(_intensity_range);
      if(_p_delta_axis_rect != nullptr)
        {
          _p_delta_axis_rect->axis(QCPAxis::AxisType::atLeft)->setRange(_mass_delta_range);
        }
    }
}

void
QCPSpectrum::setMzRangeChanged(QCPRange range)
{
  qDebug() << "QCPSpectrum::setMzRangeChanged _mz_range.lower" << _mz_range.lower;
  if(_mz_range.lower > 0)
    {
      if(range.lower < _mz_range.lower)
        {
          range.lower = _mz_range.lower;
        }
      if(range.upper > _mz_range.upper)
        {
          range.upper = _mz_range.upper;
        }
    }

  xAxis->setRange(range);

  if(_p_delta_axis_rect != nullptr)
    {
      _p_delta_axis_rect->axis(QCPAxis::atBottom)->setRange(range);
    }
}

void
QCPSpectrum::clearData()
{
  qDebug();
  _p_peak_bars->setData(QVector<double>(), QVector<double>());
  _p_peak_bars->data().clear();
  qDebug();
  _p_peak_bars_isotope->setData(QVector<double>(), QVector<double>());
  _p_peak_bars_isotope->data().clear();
  qDebug();
  mp_peak_bars_precursor->setData(QVector<double>(), QVector<double>());
  mp_peak_bars_precursor->data().clear();
  for(std::pair<Enums::PeptideIon, QCPBars *> pair_ion_bar : _map_ion_type_bars)
    {
      pair_ion_bar.second->setData(QVector<double>(), QVector<double>());
      pair_ion_bar.second->data().clear();
    }
  // this->clearPlottables();
  // this->clearItems();
  if(_p_delta_axis_rect != nullptr)
    {
      _p_delta_graph->setData(QVector<double>(), QVector<double>());
      _p_delta_graph->data().clear();
    }

  _mz_range.lower = 0;
  _mz_range.upper = 0;

  _mass_delta_range.upper = -100;
  _mass_delta_range.lower = 100;
  qDebug();
}

void
QCPSpectrum::keyPressEvent(QKeyEvent *event)
{
  if(event->key() == Qt::Key_Control)
    {
      _control_key = true;
    }
  qDebug() << "QCPSpectrum::keyPressEvent end";
}

void
QCPSpectrum::keyReleaseEvent(QKeyEvent *event)
{
  if(event->key() == Qt::Key_Control)
    {
      _control_key = false;
    }
  qDebug() << "QCPSpectrum::keyReleaseEvent end";
}

void
QCPSpectrum::mousePressEvent(QMouseEvent *event)
{
  /*qDebug() << "QCPSpectrum::mousePressEvent begin "
           << xAxis->pixelToCoord(event->x()) << " "
           << yAxis->pixelToCoord(event->y());*/
  _click = true;

#if QT_VERSION < 0x060000
  _old_x = event->x();
  _old_y = yAxis->pixelToCoord(event->y());
#else
  _old_x = event->position().x();
  _old_y = yAxis->pixelToCoord(event->position().y());
#endif
  if(_old_y < 0)
    _old_y = 0;
  /* qDebug() << "QCPSpectrum::mousePressEvent end";*/
}
void
QCPSpectrum::mouseReleaseEvent(QMouseEvent *event [[maybe_unused]])
{
  /*qDebug() << "QCPSpectrum::mouseReleaseEvent begin "
           << xAxis->pixelToCoord(event->x()) << " "
           << yAxis->pixelToCoord(event->y());*/
  _click = false;
  // qDebug() << "QCPSpectrum::mouseReleaseEvent end";
}
void
QCPSpectrum::mouseMoveEvent(QMouseEvent *event)
{

#if QT_VERSION < 0x060000
  pappso::pappso_double x = xAxis->pixelToCoord(event->x());
#else
  pappso::pappso_double x = xAxis->pixelToCoord(event->position().x());
#endif
  if(_click)
    {
      /* qDebug() << "QCPSpectrum::mouseMoveEvent begin "
                << xAxis->pixelToCoord(event->x()) << " "
                << yAxis->pixelToCoord(event->y());*/

#if QT_VERSION < 0x060000
      pappso::pappso_double y = yAxis->pixelToCoord(event->y());
#else
      pappso::pappso_double y = yAxis->pixelToCoord(event->position().y());
#endif
      if(y < 0)
        {
          y = 0;
        }
      if(_control_key)
        {
          if(y > 0)
            {
              this->yAxis->scaleRange(_old_y / y, 0);
            }
        }
      else
        {

#if QT_VERSION < 0x060000
          this->xAxis->moveRange(xAxis->pixelToCoord(_old_x) - xAxis->pixelToCoord(event->x()));
#else
          this->xAxis->moveRange(xAxis->pixelToCoord(_old_x) -
                                 xAxis->pixelToCoord(event->position().x()));
#endif
        }

#if QT_VERSION < 0x060000
      _old_x = event->x();
#else
      _old_x = event->position().x();
#endif
      _old_y = y;
      replot();
      // qDebug() << "QCPSpectrum::mouseMoveEvent end";
    }
  else
    {
      if(_p_spectrum != nullptr)
        {
          pappso::pappso_double mouse_mz_range = xAxis->pixelToCoord(10) - xAxis->pixelToCoord(8);
          getMostIntensePeakBetween(x, mouse_mz_range);
          _parent->mzChangeEvent(x);
        }
    }
}

void
QCPSpectrum::getNearestPeakBetween(pappso_double mz, pappso_double mouse_mz_range) const
{
  /*qDebug() << "QCPSpectrum::getNearestPeakBetween begin " << mz << " "
           << mouse_mz_range;*/
  const DataPoint *p_peak_match;
  p_peak_match      = nullptr;
  pappso_double min = mz - mouse_mz_range;
  pappso_double max = mz + mouse_mz_range;

  for(const DataPoint &peak : *_p_spectrum)
    {
      if((peak.x > min) && (peak.x < max))
        {
          if(p_peak_match == nullptr)
            {
              p_peak_match = &peak;
            }
          else
            {
              if(fabs(mz - peak.x) < fabs(mz - p_peak_match->x))
                {
                  p_peak_match = &peak;
                }
            }
        }
    }
  _parent->peakChangeEvent(p_peak_match);
  // qDebug() << "QCPSpectrum::getNearestPeakBetween end";
}


void
QCPSpectrum::getMostIntensePeakBetween(pappso_double mz, pappso_double mouse_mz_range) const
{
  /*qDebug() << "QCPSpectrum::getNearestPeakBetween begin " << mz << " "
           << mouse_mz_range;*/
  const DataPoint *p_peak_match;
  p_peak_match      = nullptr;
  pappso_double min = mz - mouse_mz_range;
  pappso_double max = mz + mouse_mz_range;

  for(const DataPoint &peak : *_p_spectrum)
    {
      if((peak.x > min) && (peak.x < max))
        {
          if(p_peak_match == nullptr)
            {
              p_peak_match = &peak;
            }
          else
            {
              if(p_peak_match->y < peak.y)
                {
                  p_peak_match = &peak;
                }
            }
        }
    }
  _parent->peakChangeEvent(p_peak_match);
  // qDebug() << "QCPSpectrum::getNearestPeakBetween end";
}

void
QCPSpectrum::addMassDelta(const PeakIonIsotopeMatch &peak_ion_match)
{
  if(_p_delta_axis_rect != nullptr)
    {
      // observed - theoretical
      double diff = peak_ion_match.getPeak().x -
                    peak_ion_match.getPeptideNaturalIsotopeAverageSp().get()->getMz();
      _p_delta_graph->addData(peak_ion_match.getPeak().x, diff);

      if(diff > _mass_delta_range.upper)
        _mass_delta_range.upper = diff;
      if(diff < _mass_delta_range.lower)
        _mass_delta_range.lower = diff;

      _p_delta_axis_rect->axis(QCPAxis::AxisType::atLeft)->setRange(_mass_delta_range);
    }
}

void
QCPSpectrum::addPeakIonIsotopeMatch(const PeakIonIsotopeMatch &peak_ion_match)
{
  _map_ion_type_bars[peak_ion_match.getPeptideIonType()]->addData(peak_ion_match.getPeak().x,
                                                                  peak_ion_match.getPeak().y);
}

void
QCPSpectrum::addMs1IsotopePattern(
  const std::vector<pappso::PeptideNaturalIsotopeAverageSp> &isotope_mass_list,
  pappso_double intensity)
{
  pappso_double total_intensity =
    ((pappso_double)1.0 / isotope_mass_list.at(0).get()->getIntensityRatio()) * intensity;
  for(PeptideNaturalIsotopeAverageSp peptide : isotope_mass_list)
    {
      _p_peak_bars_isotope->addData(peptide.get()->getMz(),
                                    peptide.get()->getIntensityRatio() * total_intensity);
    }
}

void
pappso::QCPSpectrum::highlightPrecursorPeaks(double precursor_mz,
                                             int charge,
                                             PrecisionPtr ms2_precision)
{
  MzRange range(precursor_mz, ms2_precision);
  double precursor_mz_c13 = precursor_mz + (DIFFC12C13 / charge);
  MzRange range_c13(precursor_mz_c13, ms2_precision);

  for(const DataPoint &peak : *_p_spectrum)
    {
      if(((peak.x > range.lower()) && (peak.x < range.upper())) ||
         ((peak.x > range_c13.lower()) && (peak.x < range_c13.upper())))
        {
          mp_peak_bars_precursor->addData(peak.x, peak.y);
          QCPItemText *text_label = new QCPItemText(this);
          text_label->setVisible(true);
          text_label->setPositionAlignment(Qt::AlignBottom | Qt::AlignHCenter);
          text_label->position->setType(QCPItemPosition::ptPlotCoords);
          text_label->position->setCoords(peak.x, peak.y);
          // place position at center/top of axis rect
          text_label->setFont(QFont(font().family(), 8));
          text_label->setText("precursor");
          text_label->setColor(Qt::cyan);
        }
    }
}
