/**
 * \file pappsomspp/vendors/tims/timsframebase.cpp
 * \date 16/12/2019
 * \author Olivier Langella
 * \brief handle a single Bruker's TimsTof frame without binary data
 */

/*******************************************************************************
 * Copyright (c) 2019 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/>.
 *
 ******************************************************************************/
#include "timsframebase.h"
#include "../../../pappsomspp/pappsoexception.h"
#include "../../../pappsomspp/exception/exceptionoutofrange.h"
#include "mzcalibration/mzcalibrationmodel1.h"
#include <QDebug>
#include <cmath>

namespace pappso
{

TimsFrameBase::TimsFrameBase(std::size_t timsId, quint32 scanNum)
{
  qDebug() << timsId;
  m_timsId = timsId;

  m_scanNumber = scanNum;
}

TimsFrameBase::TimsFrameBase([[maybe_unused]] const TimsFrameBase &other)
{
}

TimsFrameBase::~TimsFrameBase()
{
}

void
TimsFrameBase::setAccumulationTime(double accumulation_time_ms)
{
  m_accumulationTime = accumulation_time_ms;
}


void
TimsFrameBase::setMzCalibration(double T1_frame,
                                double T2_frame,
                                double digitizerTimebase,
                                double digitizerDelay,
                                double C0,
                                double C1,
                                double C2,
                                double C3,
                                double C4,
                                double T1_ref,
                                double T2_ref,
                                double dC1,
                                double dC2)
{

  /*  MzCalibrationModel1 mzCalibration(temperature_correction,
                                 digitizerTimebase,
                                 digitizerDelay,
                                 C0,
                                 C1,
                                 C2,
                                 C3,
                                 C4);
                                 */
  msp_mzCalibration = std::make_shared<MzCalibrationModel1>(T1_frame,
                                                            T2_frame,
                                                            digitizerTimebase,
                                                            digitizerDelay,
                                                            C0,
                                                            C1,
                                                            C2,
                                                            C3,
                                                            C4,
                                                            T1_ref,
                                                            T2_ref,
                                                            dC1,
                                                            dC2);
}

bool
TimsFrameBase::checkScanNum(std::size_t scanNum) const
{
  if(scanNum >= m_scanNumber)
    {
      throw pappso::ExceptionOutOfRange(
        QObject::tr("Invalid scan number : scanNum %1  > m_scanNumber %2")
          .arg(scanNum)
          .arg(m_scanNumber));
    }

  return true;
}

std::size_t
TimsFrameBase::getNbrPeaks(std::size_t scanNum) const
{
  throw PappsoException(
    QObject::tr(
      "ERROR unable to get number of peaks in TimsFrameBase for scan number %1")
      .arg(scanNum));
}

MassSpectrumSPtr
TimsFrameBase::getMassSpectrumSPtr(std::size_t scanNum) const
{
  throw PappsoException(
    QObject::tr(
      "ERROR unable to getMassSpectrumSPtr in TimsFrameBase for scan number %1")
      .arg(scanNum));
}
Trace
TimsFrameBase::cumulateScanToTrace(std::size_t scanNumBegin,
                                   std::size_t scanNumEnd) const
{
  throw PappsoException(
    QObject::tr("ERROR unable to cumulateScanToTrace in TimsFrameBase for scan "
                "number begin %1 end %2")
      .arg(scanNumBegin)
      .arg(scanNumEnd));
}
void
TimsFrameBase::cumulateScansInRawMap(std::map<quint32, quint32> &rawSpectrum
                                     [[maybe_unused]],
                                     std::size_t scanNumBegin,
                                     std::size_t scanNumEnd) const
{
  throw PappsoException(
    QObject::tr(
      "ERROR unable to cumulateScansInRawMap in TimsFrameBase for scan "
      "number begin %1 end %2")
      .arg(scanNumBegin)
      .arg(scanNumEnd));
}
void
TimsFrameBase::setTime(double time)
{
  m_time = time;
}

void
TimsFrameBase::setMsMsType(quint8 type)
{

  qDebug() << " m_msMsType=" << type;
  m_msMsType = type;
}

unsigned int
TimsFrameBase::getMsLevel() const
{
  if(m_msMsType == 0)
    return 1;
  return 2;
}

double
TimsFrameBase::getTime() const
{
  return m_time;
}

std::size_t
TimsFrameBase::getId() const
{
  return m_timsId;
}
void
TimsFrameBase::setTimsCalibration(int tims_model_type,
                                  double C0,
                                  double C1,
                                  double C2,
                                  double C3,
                                  double C4,
                                  [[maybe_unused]] double C5,
                                  double C6,
                                  double C7,
                                  double C8,
                                  double C9)
{
  if(tims_model_type != 2)
    {
      throw pappso::PappsoException(QObject::tr(
        "ERROR in TimsFrame::setTimsCalibration tims_model_type != 2"));
    }
  m_timsDvStart = C2; // C2 from TimsCalibration
  m_timsTtrans  = C4; // C4 from TimsCalibration
  m_timsNdelay  = C0; // C0 from TimsCalibration
  m_timsVmin    = C8; // C8 from TimsCalibration
  m_timsVmax    = C9; // C9 from TimsCalibration
  m_timsC6      = C6;
  m_timsC7      = C7;


  m_timsSlope =
    (C3 - m_timsDvStart) / C1; //  //C3 from TimsCalibration // C2 from
                               //  TimsCalibration // C1 from TimsCalibration
}
double
TimsFrameBase::getVoltageTransformation(std::size_t scanNum) const
{
  double v = m_timsDvStart +
             m_timsSlope * ((double)scanNum - m_timsTtrans - m_timsNdelay);

  if(v < m_timsVmin)
    {
      throw pappso::PappsoException(
        QObject::tr("ERROR in TimsFrame::getVoltageTransformation invalid tims "
                    "calibration, v < m_timsVmin"));
    }


  if(v > m_timsVmax)
    {
      throw pappso::PappsoException(
        QObject::tr("ERROR in TimsFrame::getVoltageTransformation invalid tims "
                    "calibration, v > m_timsVmax"));
    }
  return v;
}
double
TimsFrameBase::getDriftTime(std::size_t scanNum) const
{
  return (m_accumulationTime / (double)m_scanNumber) * ((double)scanNum);
}

double
TimsFrameBase::getOneOverK0Transformation(std::size_t scanNum) const
{
  return 1 / (m_timsC6 + (m_timsC7 / getVoltageTransformation(scanNum)));
}


std::size_t
TimsFrameBase::getScanNumFromOneOverK0(double one_over_k0) const
{
  double temp = 1 / one_over_k0;
  temp        = temp - m_timsC6;
  temp        = temp / m_timsC7;
  temp        = 1 / temp;
  temp        = temp - m_timsDvStart;
  temp        = temp / m_timsSlope + m_timsTtrans + m_timsNdelay;
  return (std::size_t)std::round(temp);
}

bool
TimsFrameBase::hasSameCalibrationData(const TimsFrameBase &other) const
{
  if((m_timsDvStart == other.m_timsDvStart) &&
     (m_timsTtrans == other.m_timsTtrans) &&
     (m_timsNdelay == other.m_timsNdelay) && (m_timsVmin == other.m_timsVmin) &&
     (m_timsVmax == other.m_timsVmax) && (m_timsC6 == other.m_timsC6) &&
     (m_timsC7 == other.m_timsC7) && (m_timsSlope == other.m_timsSlope))
    {
      return true;
    }
  return false;
}


pappso::Trace
TimsFrameBase::getTraceFromCumulatedScans(
  std::map<quint32, quint32> &accumulated_scans) const
{
  qDebug();
  // qDebug();
  // add flanking peaks
  pappso::Trace local_trace;

  MzCalibrationInterface *mz_calibration_p =
    getMzCalibrationInterfaceSPtr().get();


  DataPoint element;
  for(auto &scan_element : accumulated_scans)
    {
      // intensity normalization
      element.y = ((double)scan_element.second) * 100.0 / m_accumulationTime;

      // mz calibration
      element.x = mz_calibration_p->getMzFromTofIndex(scan_element.first);

      local_trace.push_back(element);
    }
  local_trace.sortX();

  qDebug();
  // qDebug();
  return local_trace;
}

pappso::Trace
TimsFrameBase::getTraceFromCumulatedScansBuiltinCentroid(
  std::map<quint32, quint32> &accumulated_scans) const
{
  qDebug();
  // qDebug();
  // add flanking peaks
  std::vector<quint32> keys;
  transform(begin(accumulated_scans),
            end(accumulated_scans),
            back_inserter(keys),
            [](std::map<quint32, quint32>::value_type const &pair) {
              return pair.first;
            });
  std::sort(keys.begin(), keys.end());
  pappso::DataPoint data_point_cumul;
  data_point_cumul.x = 0;
  data_point_cumul.y = 0;

  pappso::Trace local_trace;

  MzCalibrationInterface *mz_calibration_p =
    getMzCalibrationInterfaceSPtr().get();


  quint32 last_key = 0;

  for(quint32 key : keys)
    {
      if(key == last_key + 1)
        {
          // cumulate
          if(accumulated_scans[key] > accumulated_scans[last_key])
            {
              if(data_point_cumul.x == last_key)
                {
                  // growing peak
                  data_point_cumul.x = key;
                  data_point_cumul.y += accumulated_scans[key];
                }
              else
                {
                  // new peak
                  // flush
                  if(data_point_cumul.y > 0)
                    {
                      // intensity normalization
                      data_point_cumul.y *= 100.0 / m_accumulationTime;


                      // mz calibration
                      data_point_cumul.x =
                        mz_calibration_p->getMzFromTofIndex(data_point_cumul.x);
                      local_trace.push_back(data_point_cumul);
                    }

                  // new point
                  data_point_cumul.x = key;
                  data_point_cumul.y = accumulated_scans[key];
                }
            }
          else
            {
              data_point_cumul.y += accumulated_scans[key];
            }
        }
      else
        {
          // flush
          if(data_point_cumul.y > 0)
            {
              // intensity normalization
              data_point_cumul.y *= 100.0 / m_accumulationTime;


              qDebug() << "raw data x=" << data_point_cumul.x;
              // mz calibration
              data_point_cumul.x =
                mz_calibration_p->getMzFromTofIndex(data_point_cumul.x);
              qDebug() << "mz=" << data_point_cumul.x;
              local_trace.push_back(data_point_cumul);
            }

          // new point
          data_point_cumul.x = key;
          data_point_cumul.y = accumulated_scans[key];
        }

      last_key = key;
    }
  // flush
  if(data_point_cumul.y > 0)
    {
      // intensity normalization
      data_point_cumul.y *= 100.0 / m_accumulationTime;


      // mz calibration
      data_point_cumul.x =
        mz_calibration_p->getMzFromTofIndex(data_point_cumul.x);
      local_trace.push_back(data_point_cumul);
    }

  local_trace.sortX();
  qDebug();
  // qDebug();
  return local_trace;
}

const MzCalibrationInterfaceSPtr &
TimsFrameBase::getMzCalibrationInterfaceSPtr() const
{
  if(msp_mzCalibration == nullptr)
    {

      throw pappso::PappsoException(
        QObject::tr("ERROR in %1, %2, %3 msp_mzCalibration is null")
          .arg(__FILE__)
          .arg(__FUNCTION__)
          .arg(__LINE__));
    }
  return msp_mzCalibration;
}

void
TimsFrameBase::setMzCalibrationInterfaceSPtr(
  MzCalibrationInterfaceSPtr mzCalibration)
{

  if(mzCalibration == nullptr)
    {

      throw pappso::PappsoException(
        QObject::tr("ERROR in %1, %2, %3 msp_mzCalibration is null")
          .arg(__FILE__)
          .arg(__FUNCTION__)
          .arg(__LINE__));
    }
  msp_mzCalibration = mzCalibration;
}

} // namespace pappso
