/*******************************************************************************
 * 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 "enzyme.h"
#include <QStringList>
#include <QDebug>
#include "pappsomspp/core/exception/exceptionnotpossible.h"
// #include <iostream>

namespace pappso
{
Enzyme::Enzyme()
{
  m_recognitionSite.setPattern("([KR])([^P])");
  m_miscleavage = 0;


  char vv1[] = {'A', 'R', 'N', 'D', 'C', 'Q', 'E', 'G', 'H', 'I',
                'L', 'K', 'M', 'F', 'P', 'S', 'T', 'W', 'Y', 'V'};
  m_wildCardX.assign(std::begin(vv1), std::end(vv1));

  char vv2[] = {'N', 'D'};
  m_wildCardB.assign(std::begin(vv2), std::end(vv2));

  char vv3[] = {'Q', 'E'};
  m_wildCardZ.assign(std::begin(vv3), std::end(vv3));
}

Enzyme::Enzyme(const QString &recognition_site)
{
  m_recognitionSite.setPattern(recognition_site);
  m_miscleavage = 0;


  char vv1[] = {'A', 'R', 'N', 'D', 'C', 'Q', 'E', 'G', 'H', 'I',
                'L', 'K', 'M', 'F', 'P', 'S', 'T', 'W', 'Y', 'V'};
  m_wildCardX.assign(std::begin(vv1), std::end(vv1));

  char vv2[] = {'N', 'D'};
  m_wildCardB.assign(std::begin(vv2), std::end(vv2));

  char vv3[] = {'Q', 'E'};
  m_wildCardZ.assign(std::begin(vv3), std::end(vv3));
}

Enzyme::~Enzyme()
{
}

void
Enzyme::setMiscleavage(unsigned int miscleavage)
{
  m_miscleavage = miscleavage;
}
unsigned int
Enzyme::getMiscleavage() const
{
  return m_miscleavage;
}
void
Enzyme::setMaxPeptideVariantListSize(std::size_t max_peptide_variant_list_size)
{
  m_maxPeptideVariantListSize = max_peptide_variant_list_size;
}

void
Enzyme::eat(std::int8_t sequence_database_id,
            const ProteinSp &protein_sp,
            bool is_decoy,
            EnzymeProductInterface &enzyme_product) const
{
  /*
   *        for aa in self.aa_to_cut:
            seq = seq.replace(aa, aa + ' ')
        seq_stack = []
        for s in seq.strip().split(' '):
            seq_stack.append(s)
            if len(seq_stack) > self.misscleavage + 1:
                seq_stack.pop(0)
            s2 = ""
            for s_miss in seq_stack[::-1]:
                s2 = s_miss + s2
                yield s2
  */
  qDebug() << "Enzyme::eat begin ";
  const QString sequence = protein_sp.get()->getSequence();
  qDebug() << sequence;
  QStringList peptide_list;
  int pos                                        = 0;
  int peptide_start                              = 0;
  int peptide_size                               = sequence.size();
  QRegularExpressionMatch match_recognition_site = m_recognitionSite.match(sequence, pos);
  while(match_recognition_site.hasMatch())
    {
      pos          = match_recognition_site.capturedStart(0);
      peptide_size = pos + match_recognition_site.captured(1).length() - peptide_start;
      // qDebug() << "pos=" << pos << " peptide_start=" << peptide_start << "
      // peptide_size=" << peptide_size << " " <<
      // sequence.mid(peptide_start,peptide_size);
      if(peptide_size > 0)
        {
          peptide_list.append(sequence.mid(peptide_start, peptide_size));
        }
      peptide_start += peptide_size;
      pos                    = peptide_start; // all peptides MUST be consecutive
      match_recognition_site = m_recognitionSite.match(sequence, pos);
    }
  peptide_size = sequence.size() - peptide_start;
  if(peptide_size > 0)
    {
      peptide_list.append(sequence.mid(peptide_start, peptide_size));
    }

  unsigned int start = 1;
  bool is_nter       = true;
  foreach(const QString &peptide, peptide_list)
    {
      // enzyme_product.setPeptide(sequence_database_id, protein_sp,is_decoy,
      // peptide, start,is_nter,0, false);
      sanityCheck(enzyme_product,
                  sequence_database_id,
                  protein_sp,
                  is_decoy,
                  peptide,
                  start,
                  is_nter,
                  0,
                  false);
      is_nter = false;
      start += peptide.size();
    }

  unsigned int miscleavage_i = 0;
  while(miscleavage_i < m_miscleavage)
    {
      miscleavage_i++;
      qDebug() << "miscleavage_i=" << miscleavage_i;
      int chunk_number   = miscleavage_i + 1;
      unsigned int start = 1;
      bool is_nter       = true;

      for(auto i = 0; i < peptide_list.size(); ++i)
        {
          qDebug() << "start=" << start;
          QStringList peptide_mis_list;
          for(auto j = 0; (j < chunk_number) && ((i + j) < peptide_list.size()); j++)
            {
              peptide_mis_list << peptide_list.at(i + j);
            }
          if(peptide_mis_list.size() == chunk_number)
            {
              // enzyme_product.setPeptide(sequence_database_id,
              // protein_sp,is_decoy, peptide_mis_list.join(""), start,is_nter,
              // miscleavage_i, false);
              sanityCheck(enzyme_product,
                          sequence_database_id,
                          protein_sp,
                          is_decoy,
                          peptide_mis_list.join(""),
                          start,
                          is_nter,
                          miscleavage_i,
                          false);
            }
          is_nter = false;
          start += peptide_list.at(i).size();
        }
    }
}

void
Enzyme::replaceWildcards(std::vector<std::string> *p_peptide_variant_list) const
{
  std::string new_peptide = p_peptide_variant_list->at(0);
  qDebug() << "Enzyme::replaceWildcards begin " << new_peptide.c_str();
  std::vector<std::string> old_peptide_variant_list;
  old_peptide_variant_list.assign(p_peptide_variant_list->begin(), p_peptide_variant_list->end());


  for(char wildcard : {'X', 'B', 'Z'})
    {

      std::size_t position = new_peptide.find(wildcard);
      if(position == std::string::npos)
        {
          continue;
        }
      else
        {
          p_peptide_variant_list->clear();
          /*
          new_peptide[position] = 'A';
          p_peptide_variant_list->push_back(new_peptide);
          break;
          */

          const std::vector<char> *p_x_replace_wildcard = nullptr;
          if(wildcard == 'X')
            {
              p_x_replace_wildcard = &m_wildCardX;
            }
          else if(wildcard == 'B')
            {
              p_x_replace_wildcard = &m_wildCardB;
            }
          else if(wildcard == 'Z')
            {
              p_x_replace_wildcard = &m_wildCardZ;
            }

          if(p_x_replace_wildcard != nullptr)
            {
              for(std::string orig_peptide : old_peptide_variant_list)
                {
                  for(char replace : *p_x_replace_wildcard)
                    {
                      orig_peptide[position] = replace;
                      p_peptide_variant_list->push_back(orig_peptide);
                    }
                }
            }
          else
            {
              throw ExceptionNotPossible(QObject::tr("x_replace_wildcard is empty"));
            }
          // new_peptide[position] = 'A';
          // p_peptide_variant_list->push_back(new_peptide);
          // p_peptide_variant_list->resize(1);
          // std::cerr << "Enzyme::replaceWildcards begin
          // p_peptide_variant_list.size()=" << p_peptide_variant_list->size()
          // <<
          // endl;
          break;
        }
    }
  std::vector<std::string>().swap(
    old_peptide_variant_list); // clear old_peptide_variant_list reallocating


  qDebug() << "Enzyme::replaceWildcards end " << new_peptide.c_str();
}

void
Enzyme::setTakeOnlyFirstWildcard(bool take_only_first_wildcard)
{
  m_takeOnlyFirstWildcard = take_only_first_wildcard;
}


void
Enzyme::sanityCheck(EnzymeProductInterface &enzyme_product,
                    std::int8_t sequence_database_id,
                    const ProteinSp &protein_sp,
                    bool is_decoy,
                    const PeptideStr &peptide,
                    unsigned int start,
                    bool is_nter,
                    unsigned int missed_cleavage_number,
                    bool semi_enzyme) const
{
  if(peptide.contains('X') || peptide.contains('B') || peptide.contains('Z'))
    {

      std::vector<std::string> peptide_variant_list;
      peptide_variant_list.push_back(peptide.toStdString());

      while((peptide_variant_list.at(0).find('X') != std::string::npos) ||
            (peptide_variant_list.at(0).find('B') != std::string::npos) ||
            (peptide_variant_list.at(0).find('Z') != std::string::npos))
        {
          replaceWildcards(&peptide_variant_list);
          if(peptide_variant_list.size() > m_maxPeptideVariantListSize)
            {
              peptide_variant_list.resize(m_maxPeptideVariantListSize);
              peptide_variant_list.shrink_to_fit();
            }
        }

      // peptide_variant_list.resize(2);
      if(m_takeOnlyFirstWildcard)
        {
          enzyme_product.setPeptide(sequence_database_id,
                                    protein_sp,
                                    is_decoy,
                                    QString(peptide_variant_list.at(0).c_str()),
                                    start,
                                    is_nter,
                                    missed_cleavage_number,
                                    semi_enzyme);
        }
      else
        {
          std::string peptide_variant = peptide_variant_list.back();
          while(peptide_variant_list.size() > 0)
            {
              enzyme_product.setPeptide(sequence_database_id,
                                        protein_sp,
                                        is_decoy,
                                        QString(peptide_variant.c_str()),
                                        start,
                                        is_nter,
                                        missed_cleavage_number,
                                        semi_enzyme);
              peptide_variant_list.pop_back();
              if(peptide_variant_list.size() > 0)
                {
                  peptide_variant = peptide_variant_list.back();
                }
            }
        }
      std::vector<std::string>().swap(
        peptide_variant_list); // clear peptide_variant_list reallocating
    }
  else
    {
      enzyme_product.setPeptide(sequence_database_id,
                                protein_sp,
                                is_decoy,
                                peptide,
                                start,
                                is_nter,
                                missed_cleavage_number,
                                semi_enzyme);
    }
}

const QRegularExpression &
Enzyme::getQRegExpRecognitionSite() const
{
  return m_recognitionSite;
}
} // namespace pappso
