/**
 * \file pappsomspp/amino_acid/aacode.cpp
 * \date 03/05/2023
 * \author Olivier Langella
 * \brief give an integer code to each amino acid
 */

/*******************************************************************************
 * Copyright (c) 2023 Olivier Langella <Olivier.Langella@u-psud.fr>.
 *
 * This file is part of PAPPSOms-tools.
 *
 *     PAPPSOms-tools 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-tools 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-tools.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

#include "pappsomspp/core/amino_acid/aacode.h"
#include "pappsomspp/core/exception/exceptionnotfound.h"
#include "pappsomspp/core/exception/exceptionoutofrange.h"

using namespace pappso;

AaCode::AaCode()
{
  m_asciiTable.resize(90, 0);

  m_aaCollection.push_back(Aa('A'));
  m_aaCollection.push_back(Aa('C'));
  m_aaCollection.push_back(Aa('D'));
  m_aaCollection.push_back(Aa('E'));
  m_aaCollection.push_back(Aa('F'));
  m_aaCollection.push_back(Aa('G'));
  m_aaCollection.push_back(Aa('H'));
  m_aaCollection.push_back(Aa('I'));
  m_aaCollection.push_back(Aa('K'));
  m_aaCollection.push_back(Aa('M'));
  m_aaCollection.push_back(Aa('N'));
  m_aaCollection.push_back(Aa('P'));
  m_aaCollection.push_back(Aa('Q'));
  m_aaCollection.push_back(Aa('R'));
  m_aaCollection.push_back(Aa('S'));
  m_aaCollection.push_back(Aa('T'));
  m_aaCollection.push_back(Aa('V'));
  m_aaCollection.push_back(Aa('W'));
  m_aaCollection.push_back(Aa('Y'));

  updateNumbers();
}

pappso::AaCode::AaCode(const pappso::AaCode &other)
{

  m_asciiTable = other.m_asciiTable;

  m_aaCollection = other.m_aaCollection;
}

AaCode::~AaCode()
{
}

std::size_t
pappso::AaCode::getSize() const
{
  return 19;
}


uint8_t
pappso::AaCode::getAaCode(char aa_letter) const
{
  // qDebug() << aa_letter << " " << (uint8_t)aa_letter;
  // qDebug() << m_asciiTable[77];
  uint8_t aa_code = m_asciiTable[aa_letter];

  if(aa_code == 0)
    {
      throw pappso::ExceptionOutOfRange(
        QObject::tr("getAaCode(char aa_letter) error, %1 is null : no amino acid for letter \"%2\"")
          .arg(aa_code)
          .arg(aa_letter));
    }
  else if(aa_code >= m_massCollection.size())
    {
      throw pappso::ExceptionOutOfRange(
        QObject::tr("getAaCode(char aa_letter) error, %1 amino acid code not found in "
                    "m_aaCollection for letter \"%2\"")
          .arg(aa_code)
          .arg(aa_letter));
    }
  return aa_code;
}

uint8_t
pappso::AaCode::getAaCode(pappso::Enums::AminoAcidChar aa) const
{

  uint8_t aa_code = m_asciiTable[(char)aa];

  if(aa_code == 0)
    {
      throw pappso::ExceptionOutOfRange(
        QObject::tr("getAaCode(pappso::Enums::AminoAcidChar aa) error, %1 is null : no amino acid "
                    "for letter \"%2\"")
          .arg(aa_code)
          .arg(char(aa)));
    }
  else if(aa_code >= m_massCollection.size())
    {
      throw pappso::ExceptionOutOfRange(
        QObject::tr("getAaCode(pappso::Enums::AminoAcidChar aa) error, %1 amino acid code not "
                    "found in m_aaCollection for letter \"%2\"")
          .arg(aa_code)
          .arg(char(aa)));
    }
  return aa_code;
}


const pappso::Aa &
pappso::AaCode::getAa(char aa_letter) const
{

  if(aa_letter == 'L')
    return m_leucine;
  auto it = std::find_if(m_aaCollection.begin(), m_aaCollection.end(), [aa_letter](const Aa &aa) {
    if(aa.getLetter() == aa_letter)
      return true;
    return false;
  });
  if(it != m_aaCollection.end())
    {
      return *it;
    }
  throw pappso::ExceptionNotFound(
    QObject::tr("error, %1 amino acid not found in m_aaCollection").arg(aa_letter));
}


const pappso::Aa &
pappso::AaCode::getAa(uint8_t aa_code) const
{
  if(aa_code == 0)
    {
      throw pappso::ExceptionOutOfRange(
        QObject::tr("error, 0 is null : no amino acid").arg(aa_code));
    }
  else if(aa_code > 19)
    {
      throw pappso::ExceptionOutOfRange(
        QObject::tr("error, %1 amino acid code not found in m_aaCollection").arg(aa_code));
    }
  return m_aaCollection[aa_code - 1];
}


void
pappso::AaCode::addAaModification(char aa_letter, pappso::AaModificationP aaModification)
{

  auto it = std::find_if(m_aaCollection.begin(), m_aaCollection.end(), [aa_letter](const Aa &aa) {
    if(aa.getLetter() == aa_letter)
      return true;
    return false;
  });
  if(it != m_aaCollection.end())
    {
      it->addAaModification(aaModification);
    }
  else
    {
      throw pappso::ExceptionNotFound(
        QObject::tr("error, %1 amino acid not found in m_aaCollection").arg(aa_letter));
    }

  updateNumbers();
}


void
pappso::AaCode::updateNumbers()
{

  std::sort(m_aaCollection.begin(), m_aaCollection.end(), [](const Aa &aa1, const Aa &aa2) {
    return aa1.getMass() < aa2.getMass();
  });

  std::size_t n = 1;
  for(const Aa &aa : m_aaCollection)
    {
      // qDebug() << aa.getLetter() << " " << n;
      m_asciiTable[aa.getLetter()] = n;
      n++;
    }
  m_asciiTable['L'] = m_asciiTable['I'];

  updateMass();
}

void
pappso::AaCode::updateMass()
{
  m_massCollection.resize(1);

  for(const Aa &aa : m_aaCollection)
    {
      m_massCollection.push_back(aa.getMass());
    }
}


double
pappso::AaCode::getMass(uint8_t aa_code) const
{
  return m_massCollection[aa_code];
}

double
pappso::AaCode::getMass(char aa_letter) const
{
  try
    {
      return m_massCollection[this->getAaCode(aa_letter)];
    }

  catch(const pappso::PappsoException &err)
    {
      throw pappso::PappsoException(
        QObject::tr("getMass(char aa_letter) failed :\n%1").arg(err.qwhat()));
    }
}

uint8_t
pappso::AaCode::getAaCodeByMass(double mass, pappso::PrecisionPtr precision) const
{
  double delta    = precision->delta(mass);
  double mass_min = mass - delta;
  double mass_max = mass + delta;
  uint8_t aa_code = 0;
  for(uint8_t i = 1; i < m_massCollection.size(); i++)
    {
      if(m_massCollection.at(i) >= mass_min)
        {
          if(m_massCollection.at(i) <= mass_max)
            {
              aa_code = i;
            }
          break;
        }
    }
  return aa_code;
}

const std::vector<Aa> &
pappso::AaCode::getAaCollection() const
{
  return m_aaCollection;
}
