/*******************************************************************************
 * Copyright (c) 2015 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/>.
 *
 ******************************************************************************/

/////////////////////// StdLib includes
#include <cmath>


/////////////////////// Qt includes
#include <QDebug>


/////////////////////// Local includes
#include "qualifiedmassspectrum.h"
#include "../utils.h"
#include "../pappsoexception.h"


namespace pappso
{

//! Construct an uninitialized QualifiedMassSpectrum.
QualifiedMassSpectrum::QualifiedMassSpectrum()
{
}


//! Construct a QualifiedMassSpectrum using a MassSpectrumId;
QualifiedMassSpectrum::QualifiedMassSpectrum(const MassSpectrumId &id)
  : m_massSpectrumId(id)
{
}


QualifiedMassSpectrum::QualifiedMassSpectrum(
  MassSpectrumSPtr mass_spectrum_SPtr)
  : msp_massSpectrum{mass_spectrum_SPtr}
{
}


//! Construct a QualifiedMassSpectrum as a copy of \p other.
QualifiedMassSpectrum::QualifiedMassSpectrum(const QualifiedMassSpectrum &other)
  : msp_massSpectrum(other.msp_massSpectrum),
    m_massSpectrumId(other.m_massSpectrumId),
    m_isEmptyMassSpectrum(other.m_isEmptyMassSpectrum),
    m_msLevel(other.m_msLevel),
    m_rt(other.m_rt),
    m_dt(other.m_dt),
    m_precursorSpectrumIndex(other.m_precursorSpectrumIndex),
    m_precursorNativeId(other.m_precursorNativeId),
    m_precursorIonData(other.m_precursorIonData),
    m_paramsMap(other.m_paramsMap)
{
  // qDebug();
}


//! Destruct this QualifiedMassSpectrum.
QualifiedMassSpectrum::~QualifiedMassSpectrum()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << " ()";
}


QualifiedMassSpectrum &
QualifiedMassSpectrum::operator=(const QualifiedMassSpectrum &other)
{
  msp_massSpectrum         = other.msp_massSpectrum;
  m_massSpectrumId         = other.m_massSpectrumId;
  m_isEmptyMassSpectrum    = other.m_isEmptyMassSpectrum;
  m_msLevel                = other.m_msLevel;
  m_rt                     = other.m_rt;
  m_dt                     = other.m_dt;
  m_precursorSpectrumIndex = other.m_precursorSpectrumIndex;
  m_precursorNativeId      = other.m_precursorNativeId;
  m_precursorIonData       = other.m_precursorIonData;
  m_paramsMap              = other.m_paramsMap;

  return *this;
}


const QualifiedMassSpectrum &
QualifiedMassSpectrum::cloneMassSpectrumSPtr()
{
  this->msp_massSpectrum =
    std::make_shared<MassSpectrum>(*this->msp_massSpectrum.get());
  return *this;
}


QualifiedMassSpectrumSPtr
QualifiedMassSpectrum::makeQualifiedMassSpectrumSPtr() const
{
  return std::make_shared<QualifiedMassSpectrum>(*this);
}


QualifiedMassSpectrumCstSPtr
QualifiedMassSpectrum::makeQualifiedMassSpectrumCstSPtr() const
{
  return std::make_shared<const QualifiedMassSpectrum>(*this);
}


//! Set the MassSpectrumSPtr.
void
QualifiedMassSpectrum::setMassSpectrumSPtr(MassSpectrumSPtr massSpectrum)
{
  msp_massSpectrum = massSpectrum;
}


//! Get the MassSpectrumSPtr.
MassSpectrumSPtr
QualifiedMassSpectrum::getMassSpectrumSPtr() const
{
  return msp_massSpectrum;
}


//! Get the MassSpectrumCstSPtr.
MassSpectrumCstSPtr
QualifiedMassSpectrum::getMassSpectrumCstSPtr() const
{
  return msp_massSpectrum;
}


//! Set the MassSpectrumId.
void
QualifiedMassSpectrum::setMassSpectrumId(const MassSpectrumId &iD)
{
  m_massSpectrumId = iD;
}


//! Get the MassSpectrumId.
const MassSpectrumId &
QualifiedMassSpectrum::getMassSpectrumId() const
{
  return m_massSpectrumId;
}

MassSpectrumId &
QualifiedMassSpectrum::getMassSpectrumId()
{
  return m_massSpectrumId;
}

void
QualifiedMassSpectrum::setEmptyMassSpectrum(bool is_empty_mass_spectrum)
{
  m_isEmptyMassSpectrum = is_empty_mass_spectrum;
}


bool
QualifiedMassSpectrum::isEmptyMassSpectrum() const
{
  return m_isEmptyMassSpectrum;
}


//! Set the mass spectrum level.
void
QualifiedMassSpectrum::setMsLevel(unsigned int level)
{
  m_msLevel = level;
}


//! Get the mass spectrum level.
unsigned int
QualifiedMassSpectrum::getMsLevel() const
{
  return m_msLevel;
}


//! Set the retention time in seconds.
void
QualifiedMassSpectrum::setRtInSeconds(pappso_double rt_in_seconds)
{
  m_rt = rt_in_seconds;
}


//! Get the retention time in seconds.
pappso_double
QualifiedMassSpectrum::getRtInSeconds() const
{
  return m_rt;
}


//! Get the retention time in minutes.
pappso_double
QualifiedMassSpectrum::getRtInMinutes() const
{
  return m_rt / 60;
}


//! Set the drift time in milliseconds.
void
QualifiedMassSpectrum::setDtInMilliSeconds(pappso_double dt_in_milli_seconds)
{
  if(std::isinf(dt_in_milli_seconds))
    m_dt = -1;
  else
    m_dt = dt_in_milli_seconds;
}


//! Get the drift time in milliseconds.
pappso_double
QualifiedMassSpectrum::getDtInMilliSeconds() const
{
  return m_dt;
}


//! Get the precursor m/z ratio.
pappso_double
QualifiedMassSpectrum::getPrecursorMz(bool *ok_p) const
{
  if(!m_precursorIonData.size())
    {
      if(ok_p != nullptr)
        *ok_p = false;

      return std::numeric_limits<double>::max();
    }

  if(ok_p != nullptr)
    *ok_p = true;

  // qDebug() << "Returning precursor mz value: " <<
  // m_precursorIonData.front().mz;
  return m_precursorIonData.front().mz;
}

//! Get the precursor mass ratio.
double
QualifiedMassSpectrum::getPrecursorMass(bool *ok_p) const
{
  if(ok_p != nullptr)
    *ok_p = false;
  bool ok_precmz = false;
  double mz_prec = getPrecursorMz(&ok_precmz);
  if(ok_precmz)
    {
      uint charge = getPrecursorCharge(&ok_precmz);
      if(ok_precmz)
        {
          // compute precursor mass given the charge state
          mz_prec = mz_prec * (double)charge;
          mz_prec -= (MHPLUS * (double)charge);
          if(ok_p != nullptr)
            *ok_p = true;
          return mz_prec;
        }
    }
  return std::numeric_limits<double>::max();
}

QString
QualifiedMassSpectrum::getPrecursorDataMzValuesAsString(
  const std::vector<PrecursorIonData> &precursor_ion_data_vector,
  const QString &separator) const
{
  QString text;

  // We do not want to use the separator if there is only one precursor in the
  // vector.

  if(precursor_ion_data_vector.size() == 1)
    return QString("%1").arg(precursor_ion_data_vector.front().mz, 0, 'f', 6);

  // If there are more than one precursor, then we should list them joined with
  // the separator.

  using iterator = std::vector<pappso::PrecursorIonData>::const_iterator;

  iterator begin_iterator   = precursor_ion_data_vector.begin();
  iterator pre_end_iterator = std::prev(precursor_ion_data_vector.end());

  for(; begin_iterator != precursor_ion_data_vector.end(); ++begin_iterator)
    {
      if(begin_iterator == pre_end_iterator)
        // No separator at the end of the string
        text += QString("%1").arg(begin_iterator->mz, 0, 'f', 6);
      else
        text +=
          QString("%1%2").arg(begin_iterator->mz, 0, 'f', 6).arg(separator);
    }

  return text;
}


QString
QualifiedMassSpectrum::getPrecursorDataMzValuesAsString(
  const QString &separator) const
{
  return getPrecursorDataMzValuesAsString(m_precursorIonData, separator);
}


QString
QualifiedMassSpectrum::getMzSortedPrecursorDataMzValuesAsString(
  const QString &separator) const
{
  // Sort the PrecursorIonData instances by increasing mz values.
  // Then craft the string using the mz values.

  std::vector<PrecursorIonData> sorted_vector =
    getPrecursorIonDataSortedWithMz();

  return getPrecursorDataMzValuesAsString(sorted_vector, separator);
}


//! Get the precursor charge.
unsigned int
QualifiedMassSpectrum::getPrecursorCharge(bool *ok_p) const
{
  if(!m_precursorIonData.size())
    {
      if(ok_p != nullptr)
        *ok_p = false;
      return std::numeric_limits<unsigned int>::max();
    }

  if(ok_p != nullptr)
    *ok_p = true;

  return m_precursorIonData.front().charge;
}


QString
QualifiedMassSpectrum::getPrecursorDataChargeValuesAsString(
  const std::vector<PrecursorIonData> &precursor_ion_data_vector,
  const QString &separator) const
{
  QString text;

  // We do not want to use the separator if there is only one precursor in the
  // vector.

  if(precursor_ion_data_vector.size() == 1)
    return QString("%1").arg(precursor_ion_data_vector.front().charge);

  // If there are more than one precursor, then we should list them joined with
  // the separator.
  for(auto item : precursor_ion_data_vector)
    {
      text += QString("%1%2").arg(item.charge).arg(separator);
    }

  return text;
}


QString
QualifiedMassSpectrum::getPrecursorDataChargeValuesAsString(
  const QString &separator) const
{
  return getPrecursorDataChargeValuesAsString(m_precursorIonData, separator);
}


QString
QualifiedMassSpectrum::getMzSortedPrecursorDataChargeValuesAsString(
  const QString &separator) const
{
  // Sort the PrecursorIonData instances by increasing mz values.
  // Then craft the string using the mz values.

  std::vector<PrecursorIonData> sorted_vector =
    getPrecursorIonDataSortedWithMz();

  return getPrecursorDataChargeValuesAsString(sorted_vector, separator);
}


//! Get the intensity of the precursor ion.
pappso_double
QualifiedMassSpectrum::getPrecursorIntensity(bool *ok_p) const
{
  if(!m_precursorIonData.size())
    {
      if(ok_p != nullptr)
        *ok_p = false;
      return std::numeric_limits<double>::max();
    }

  if(ok_p != nullptr)
    *ok_p = true;

  return m_precursorIonData.front().intensity;
}


//! Set the scan number of the precursor ion.
void
QualifiedMassSpectrum::setPrecursorSpectrumIndex(
  std::size_t precursor_spectrum_index)
{
  m_precursorSpectrumIndex = precursor_spectrum_index;
}


//! Get the scan number of the precursor ion.
std::size_t
QualifiedMassSpectrum::getPrecursorSpectrumIndex() const
{
  return m_precursorSpectrumIndex;
}


//! Set the scan native id of the precursor ion.
void
QualifiedMassSpectrum::setPrecursorNativeId(const QString &native_id)
{
  m_precursorNativeId = native_id;
}

const QString &
QualifiedMassSpectrum::getPrecursorNativeId() const
{
  return m_precursorNativeId;
}


void
QualifiedMassSpectrum::appendPrecursorIonData(
  const PrecursorIonData &precursor_ion_data)
{
  m_precursorIonData.push_back(precursor_ion_data);
}


const std::vector<PrecursorIonData> &
QualifiedMassSpectrum::getPrecursorIonData() const
{
  return m_precursorIonData;
}


std::vector<PrecursorIonData>
QualifiedMassSpectrum::getPrecursorIonDataSortedWithMz() const
{
  std::vector<PrecursorIonData> new_vector;
  new_vector.assign(m_precursorIonData.begin(), m_precursorIonData.end());

  std::sort(new_vector.begin(),
            new_vector.end(),
            [](const PrecursorIonData &a, const PrecursorIonData &b) -> bool {
              return a.mz < b.mz;
            });

  return new_vector;
}


void
QualifiedMassSpectrum::setParameterValue(
  QualifiedMassSpectrumParameter parameter, const QVariant &value)
{

  auto ret = m_paramsMap.insert(
    std::pair<QualifiedMassSpectrumParameter, QVariant>(parameter, value));

  if(ret.second == false)
    {
      ret.first->second = value;
    }
}


const QVariant
QualifiedMassSpectrum::getParameterValue(
  QualifiedMassSpectrumParameter parameter) const
{
  auto it = m_paramsMap.find(parameter);
  if(it == m_paramsMap.end())
    {
      return QVariant();
    }
  else
    {
      return it->second;
    }
}


std::size_t
QualifiedMassSpectrum::size() const
{
  if(msp_massSpectrum == nullptr)
    {
      throw pappso::PappsoException(QObject::tr("msp_massSpectrum == nullptr"));
    }
  return msp_massSpectrum.get()->size();
}


QString
QualifiedMassSpectrum::toString(bool with_data) const
{
  QString text;

  if(msp_massSpectrum != nullptr && msp_massSpectrum.get() != nullptr)
    {
      QString pointer_string =
        QString("msp_massSpectrum.get(): %1 ")
          .arg(Utils::pointerToString((void *)msp_massSpectrum.get()));

      text += pointer_string;
    }
  else
    text += QString("msp_massSpectrum is nullptr ");

  // qDebug() << text;

  QString precursor_mz_values_string;

  if(m_precursorIonData.size())
    precursor_mz_values_string += "Precursor mz values: ";

  precursor_mz_values_string += getMzSortedPrecursorDataMzValuesAsString();

  precursor_mz_values_string += "\n";

  text +=
    QString(
      "; m_massSpectrumId : %1 \n"
      "m_msLevel: %2 ; m_rt (min): %3 ; m_dt (ms): %4 ; prec. spec. "
      "index: %5 ; %6")
      .arg(m_massSpectrumId.toString())
      .arg(m_msLevel)
      .arg(getRtInMinutes(), 0, 'f', 2)
      .arg(m_dt, 0, 'f', 5)
      .arg(m_precursorSpectrumIndex != std::numeric_limits<std::size_t>::max()
             ? m_precursorSpectrumIndex
             : -1)
      .arg(precursor_mz_values_string);

  if(with_data)
    {
      text += msp_massSpectrum->toString();
    }

  return text;
}


} // namespace pappso
