/**
 * \file pappsomspp/mass_range.cpp
 * \date 4/3/2015
 * \author Olivier Langella
 * \brief object to handle a mass range (an mz value + or - some delta)
 */

/*******************************************************************************
 * Copyright (c) 2015 Olivier Langella <Olivier.Langella@moulon.inra.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@moulon.inra.fr> - initial API and
 *implementation
 ******************************************************************************/

#include "precision.h"
#include "mzrange.h"
#include "exception/exceptionnotpossible.h"
#include <QStringList>
#include <cmath>
#include <QDebug>
#include <QRegularExpression>

namespace pappso
{


PrecisionFactory::MapDaltonPrecision PrecisionFactory::m_mapDalton = [] {
  MapDaltonPrecision ret;

  return ret;
}();


PrecisionFactory::MapPpmPrecision PrecisionFactory::m_mapPpm = [] {
  MapPpmPrecision ret;

  return ret;
}();


PrecisionFactory::MapResPrecision PrecisionFactory::m_mapRes = [] {
  MapResPrecision ret;

  return ret;
}();


pappso_double
PrecisionBase::getNominal() const
{
  return m_nominal;
}


PrecisionPtr
PrecisionFactory::fromString(const QString &str)
{

  // The format of the string is <number><space *><string> with string either
  // "ppm" or "dalton" or "res".
  //
  // If there only once component, that is, <string> is omitted and charge is
  // not provided, then "dalton" is considered.

  QStringList list = str.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);

  if(list.size() > 0)
    {
      bool ok;
      pappso_double value = list[0].toDouble(&ok);
      if(!ok)
        {
          throw ExceptionNotPossible(
            QObject::tr("ERROR getting precision from string :\nunable to "
                        "convert %1 to number in %2")
              .arg(value)
              .arg(str));
        }
      if(list.size() == 1)
        {
          return PrecisionFactory::getDaltonInstance(value);
        }
      else if(list.size() == 2)
        {
          if(list[1].toLower() == "dalton")
            {
              return PrecisionFactory::getDaltonInstance(value);
            }

          if(list[1].toLower() == "ppm")
            {
              return PrecisionFactory::getPpmInstance(value);
            }

          if(list[1].toLower() == "res")
            {
              return PrecisionFactory::getResInstance(value);
            }

          throw ExceptionNotPossible(
            QObject::tr("ERROR getting precision from string :\nprecision "
                        "unit %1 to not known in  %2")
              .arg(list[1])
              .arg(str));
        }
    }

  throw ExceptionNotPossible(QObject::tr("ERROR getting precision from string "
                                         ":\nunable to convert %1 to precision")
                               .arg(str));
}

PrecisionPtr
PrecisionFactory::getDaltonInstance(pappso_double value)
{
  MapDaltonPrecision::iterator it = m_mapDalton.find(value);
  if(it == m_mapDalton.end())
    {
      // not found
      std::pair<MapDaltonPrecision::iterator, bool> insert_res =
        m_mapDalton.insert(std::pair<pappso_double, DaltonPrecision *>(
          value, new DaltonPrecision(value)));
      it = insert_res.first;
    }
  else
    {
      // found
    }
  return it->second;
}


PrecisionPtr
PrecisionFactory::getPpmInstance(pappso_double value)
{
  if(!value)
    throw ExceptionNotPossible(
      QObject::tr("Fatal error at precision.cpp "
                  "-- ERROR trying to set a Resolution precision value of 0. "
                  "Program aborted."));

  MapPpmPrecision::iterator it = m_mapPpm.find(value);

  if(it == m_mapPpm.end())
    {
      // Not found.
      std::pair<MapPpmPrecision::iterator, bool> insert_res =
        m_mapPpm.insert(std::pair<pappso_double, PpmPrecision *>(
          value, new PpmPrecision(value)));
      it = insert_res.first;
    }
  else
    {
      // found
    }
  return it->second;
}


PrecisionPtr
PrecisionFactory::getResInstance(pappso_double value)
{
  // qDebug() << "Requested a Resolution precision instance for value:" <<
  // value;

  if(!value)
    throw ExceptionNotPossible(
      QObject::tr("Fatal error at precision.cpp "
                  "-- ERROR trying to set a Resolution precision value of 0. "
                  "Program aborted."));

  MapResPrecision::iterator it = m_mapRes.find(value);

  if(it == m_mapRes.end())
    {
      // not found
      std::pair<MapResPrecision::iterator, bool> insert_res =
        m_mapRes.insert(std::pair<pappso_double, ResPrecision *>(
          value, new ResPrecision(value)));
      it = insert_res.first;
    }
  else
    {
      // found
    }
  return it->second;
}

PrecisionPtr
PrecisionFactory::getPrecisionPtrFractionInstance(PrecisionPtr origin,
                                                  double fraction)
{
  PrecisionUnit unit = origin->unit();
  double value       = origin->getNominal() * fraction;


  return getPrecisionPtrInstance(unit, value);
}

PrecisionPtr
PrecisionFactory::getPrecisionPtrInstance(PrecisionUnit unit, double value)
{

  switch(unit)
    {
      case PrecisionUnit::dalton:
        return getDaltonInstance(value);
      case PrecisionUnit::ppm:
        return getPpmInstance(value);
      case PrecisionUnit::res:
        return getResInstance(value);
      case PrecisionUnit::mz:
        return getDaltonInstance(value);
      case PrecisionUnit::none:
        throw ExceptionNotPossible(QObject::tr("Unknown precision unit"));

      default:
        throw ExceptionNotPossible(QObject::tr("Unknown precision unit"));
        break;
    }

  return nullptr;
}

DaltonPrecision::DaltonPrecision(pappso_double x) : PrecisionBase(x)
{
}

DaltonPrecision::~DaltonPrecision()
{
}

PrecisionUnit
DaltonPrecision::unit() const
{
  return PrecisionUnit::dalton;
}

pappso_double
DaltonPrecision::delta([[maybe_unused]] pappso_double value) const
{
  return m_nominal;
}

QString
DaltonPrecision::toString() const
{
  return (QString("%1 dalton").arg(m_nominal));
}


PpmPrecision::PpmPrecision(pappso_double x) : PrecisionBase(x)
{
}


PpmPrecision::~PpmPrecision()
{
}

PrecisionUnit
PpmPrecision::unit() const
{
  return PrecisionUnit::ppm;
}


pappso_double
PpmPrecision::delta(pappso_double value) const
{
  return ((value / ONEMILLION) * m_nominal);
}


QString
PpmPrecision::toString() const
{
  return (QString("%1 ppm").arg(m_nominal));
}


ResPrecision::ResPrecision(pappso_double x) : PrecisionBase(x)
{
}


ResPrecision::~ResPrecision()
{
}

PrecisionUnit
ResPrecision::unit() const
{
  return PrecisionUnit::res;
}


pappso_double
ResPrecision::delta(pappso_double value) const
{
  return (value / m_nominal);
}


QString
ResPrecision::toString() const
{
  return (QString("%1 res").arg(m_nominal));
}

} // namespace pappso
