/**
 * \file pappsomspp/amino_acid/aastringcodec.cpp
 * \date 09/05/2023
 * \author Olivier Langella
 * \brief code and decodefrom  amino acid string to integer
 */

/*******************************************************************************
 * 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 "aastringcodec.h"
#include <QDebug>

using namespace pappso;

AaStringCodec::AaStringCodec(const AaCode &aaCode) : m_aaCode(aaCode)
{

  m_base = m_aaCode.getSize() + 1;
  m_units.resize(10);
  uint32_t unit = 1;
  for(auto &this_unit : m_units)
    {
      this_unit = unit;
      unit *= m_base;
    }
}

AaStringCodec::AaStringCodec(const AaStringCodec &other)
  : m_aaCode(other.m_aaCode)
{
  m_base  = other.m_base;
  m_units = other.m_units;
}

AaStringCodec::~AaStringCodec()
{
}


uint32_t
pappso::AaStringCodec::code(const QString &aa_str) const
{

  std::size_t pos = 0;
  uint32_t code   = 0;
  for(auto &aa_char : aa_str)
    {
      code += m_aaCode.getAaCode(aa_char.toLatin1()) * m_units[pos];
      pos++;
    }
  return code;
}

uint32_t
pappso::AaStringCodec::codeLlc(const QString &aa_str) const
{
  std::vector<uint8_t> llc_vec;

  for(auto &aa_char : aa_str)
    {
      llc_vec.push_back(m_aaCode.getAaCode(aa_char.toLatin1()));
    }
  std::sort(llc_vec.begin(), llc_vec.end(), std::greater<uint8_t>());


  std::size_t pos = 0;
  uint32_t code   = 0;
  for(auto &aa_code : llc_vec)
    {
      code += (uint32_t)aa_code * m_units[pos];
      pos++;
    }
  return code;
}

uint32_t
pappso::AaStringCodec::codeLlc(std::vector<uint8_t>::const_iterator it_begin,
                               std::size_t size) const
{
  std::vector<uint8_t> llc_vec;

  for(std::size_t i = 0; i < size; i++)
    {
      llc_vec.push_back(*it_begin);
      it_begin++;
    }
  std::sort(llc_vec.begin(), llc_vec.end(), std::greater<uint8_t>());


  std::size_t pos = 0;
  uint32_t code   = 0;
  for(auto &aa_code : llc_vec)
    {
      code += (uint32_t)aa_code * m_units[pos];
      pos++;
    }
  return code;
}


QString
pappso::AaStringCodec::decode(uint32_t code) const
{
  QString aa_suite;

  while(code > 0)
    {
      aa_suite.append(m_aaCode.getAa((uint8_t)(code % m_base)).getLetter());
      code /= m_base;
    }

  // qDebug() << aa_suite;

  return aa_suite;
}

QStringList
pappso::AaStringCodec::decode(const std::vector<uint32_t> &code_list) const
{
  QStringList aa_string_list;
  for(auto code : code_list)
    {
      aa_string_list << decode(code);
    }
  return aa_string_list;
}


double
pappso::AaStringCodec::getMass(uint32_t code) const
{
  double mass = 0;

  while(code > 0)
    {
      mass += m_aaCode.getMass((uint8_t)(code % m_base));
      code /= m_base;
    }

  return mass;
}


std::vector<CodeToMass>
pappso::AaStringCodec::generateLlcCodeListUpToMaxPeptideSize(
  std::size_t size) const
{
  std::vector<CodeToMass> llc_list;
  if(size == 0)
    return llc_list;
  std::vector<uint8_t> model;
  for(uint8_t p = 1; p <= size; p++)
    {
      model.resize(p, 0);

      for(uint8_t i = 1; i < m_base; i++)
        {
          model[0] = i;
          if(p == 1)
            {
              llc_list.push_back(generateCodeMassFromModel(model));
            }
          else
            {
              recGenerateModel(llc_list, model, 1);
            }
        }
    }
  return llc_list;
}


std::vector<CodeToMass>
pappso::AaStringCodec::generateLlcCodeListByMaxPeptideSize(
  std::size_t size) const
{
  std::vector<CodeToMass> llc_list;
  if(size == 0)
    return llc_list;
  std::vector<uint8_t> model;
  model.resize(size, 0);

  for(uint8_t i = 1; i < m_base; i++)
    {
      model[0] = i;
      recGenerateModel(llc_list, model, 1);
    }
  return llc_list;
}

void
pappso::AaStringCodec::recGenerateModel(std::vector<CodeToMass> &glist,
                                        std::vector<uint8_t> &model,
                                        std::size_t position) const
{
  if(position == model.size())
    return;

  if(position == model.size() - 1)
    {
      uint8_t max = model[position - 1];
      for(uint8_t i = 1; i <= max; i++)
        {
          model[position] = i;
          glist.push_back(generateCodeMassFromModel(model));
        }
    }
  else
    {
      uint8_t max = model[position - 1];
      for(uint8_t i = 1; i <= max; i++)
        {
          model[position] = i;
          recGenerateModel(glist, model, position + 1);
        }
    }
}

pappso::CodeToMass
pappso::AaStringCodec::generateCodeMassFromModel(
  const std::vector<uint8_t> &model) const
{
  CodeToMass code_mass;
  std::size_t pos = 0;
  for(auto aacode : model)
    {
      code_mass.mass += m_aaCode.getMass(aacode);

      code_mass.code += (uint32_t)aacode * m_units[pos];
      pos++;
    }

  // qDebug() << code_mass.code << " " << code_mass.mass;
  return code_mass;
}


std::size_t
pappso::AaStringCodec::getLimitMax(std::size_t size) const
{

  std::size_t code = 0;
  for(std::size_t pos = 0; pos < size; pos++)
    {
      code += (std::size_t)(m_base - 1) * (std::size_t)m_units[pos];
    }
  return code;
}

bool
pappso::AaStringCodec::codeOnlyContains(uint32_t code,
                                        const std::vector<uint8_t> &aa_ok) const
{

  while(code > 0)
    {
      if(std::find(aa_ok.begin(), aa_ok.end(), (uint8_t)(code % m_base)) ==
         aa_ok.end())
        return false;

      code /= m_base;
    }
  return true;
}

bool
pappso::AaStringCodec::uniqueCodeContainsAminoAcid(uint32_t code,
                                                   uint8_t aa_ok,
                                                   int times) const
{

  int number = 0;
  while(code > 0)
    {
      if(aa_ok == (uint8_t)(code % m_base))
        {
          number++;
          if(number == times)
            return true;
        }

      code /= m_base;
    }
  return false;
}


const pappso::AaCode &
pappso::AaStringCodec::getAaCode() const
{
  return m_aaCode;
}
