
/*******************************************************************************
 * 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 "grpexperiment.h"
#include "grpprotein.h"
#include "grppeptide.h"

#include "grpgroup.h"
#include "grpsubgroup.h"
#include "../pappsoexception.h"

#include <QObject>

using namespace pappso;

GrpExperiment::GrpExperiment(GrpGroupingMonitorInterface *p_monitor)
{
  mp_monitor = p_monitor;
}

GrpExperiment::~GrpExperiment()
{
}
void
GrpExperiment::setRemoveNonInformativeSubgroups(bool ok)
{
  m_isRemoveNonInformativeSubgroups = ok;
}

void
GrpExperiment::addPostGroupingGrpProteinSpRemoval(GrpProteinSp sp_protein)
{
  GrpPeptideSet peptide_set(sp_protein.get());
  m_grpPostGroupingProteinListRemoval.addAll(peptide_set);
}


void
GrpExperiment::addPreGroupingGrpProteinSpRemoval(GrpProteinSp sp_protein)
{
  GrpPeptideSet peptide_set(sp_protein.get());
  m_grpPreGroupingProteinListRemoval.addAll(peptide_set);
}

std::vector<GrpGroupSpConst>
GrpExperiment::getGrpGroupSpList() const
{
  std::vector<GrpGroupSpConst> grp_list;
  for(GrpGroupSp group : m_grpGroupSpList)
    {
      grp_list.push_back(group);
    }
  return grp_list;
}

GrpProteinSp &
GrpExperiment::getGrpProteinSp(const QString &accession,
                               const QString &description)
{
  GrpProtein grpProtein(accession, description);
  auto insertedPair = m_mapProteins.insert(std::pair<QString, GrpProteinSp>(
    accession, std::make_shared<GrpProtein>(grpProtein)));
  if(insertedPair.second)
    {
      m_grpProteinList.push_back(insertedPair.first->second);
      m_remainingGrpProteinList.push_back(insertedPair.first->second.get());
    }
  return (insertedPair.first->second);
}

GrpPeptideSp &
GrpExperiment::setGrpPeptide(const GrpProteinSp &proteinSp,
                             const QString &sequence,
                             pappso_double mass)
{
  proteinSp.get()->countPlus();
  GrpPeptideSp sp_grppeptide =
    std::make_shared<GrpPeptide>(GrpPeptide(sequence, mass));

  auto insertedPair = m_mapPeptides.insert(
    std::pair<QString, std::map<unsigned long, GrpPeptideSp>>(
      sp_grppeptide.get()->m_sequence,
      std::map<unsigned long, GrpPeptideSp>()));
  auto secondInsertedPair =
    insertedPair.first->second.insert(std::pair<unsigned long, GrpPeptideSp>(
      (unsigned long)(mass * 100), sp_grppeptide));
  if(secondInsertedPair.second)
    {
      m_grpPeptideList.push_back(secondInsertedPair.first->second);
    }
  proteinSp.get()->push_back(secondInsertedPair.first->second.get());
  return (secondInsertedPair.first->second);
}

void
GrpExperiment::startGrouping()
{
  qDebug() << "GrpExperiment::startGrouping begin";
  if(mp_monitor != nullptr)
    mp_monitor->startGrouping(m_remainingGrpProteinList.size(),
                              m_grpPeptideList.size());
  m_isGroupingStarted = true;
  m_mapPeptides.clear();
  m_mapProteins.clear();
  qDebug() << "GrpExperiment::startGrouping sort protein list "
              "m_remainingGrpProteinList.size() "
           << m_remainingGrpProteinList.size();
  // m_remainingGrpProteinList.sort();
  // m_remainingGrpProteinList.unique();

  if(m_grpPreGroupingProteinListRemoval.size() > 0)
    {
      // TODO clean protein list to remove contaminant peptides before grouping
    }


  GrpMapPeptideToGroup grp_map_peptide_to_group;
  qDebug() << "GrpExperiment::startGrouping grouping begin";
  for(auto p_grpProtein : m_remainingGrpProteinList)
    {
      p_grpProtein->strip();
      if(p_grpProtein->m_count == 0)
        {
          // no peptides : do not group this protein
        }
      else
        {
          GrpSubGroupSp grpSubGroupSp =
            GrpSubGroup(p_grpProtein).makeGrpSubGroupSp();

          if(mp_monitor != nullptr)
            mp_monitor->groupingProtein();
          this->addSubGroupSp(grp_map_peptide_to_group, grpSubGroupSp);
        }
    }
  grp_map_peptide_to_group.clear(m_grpGroupSpList);
  qDebug() << "GrpExperiment::startGrouping grouping end";

  qDebug() << "GrpExperiment::startGrouping grouping  m_grpGroupSpList.size() "
           << m_grpGroupSpList.size();

  if(m_isRemoveNonInformativeSubgroups)
    {
      this->removeNonInformativeSubGroups();
    }

  // post grouping protein group removal
  // remove any group containing contaminants
  m_grpGroupSpList.remove_if([this](GrpGroupSp &groupSp) {
    return (
      groupSp.get()->containsAny(this->m_grpPostGroupingProteinListRemoval));
  });


  numbering();
  if(mp_monitor != nullptr)
    mp_monitor->stopGrouping();
  // GrpGroup(this, *m_remainingGrpProteinList.begin());
  qDebug() << "GrpExperiment::startGrouping end";
}


struct ContainsAny
{
  ContainsAny(const GrpPeptideSet &peptide_set) : _peptide_set(peptide_set)
  {
  }

  typedef bool result_type;

  bool
  operator()(const GrpGroupSp &testGroupSp)
  {
    return testGroupSp.get()->containsAny(_peptide_set);
  }

  GrpPeptideSet _peptide_set;
};


void
GrpExperiment::addSubGroupSp(GrpMapPeptideToGroup &grp_map_peptide_to_group,
                             GrpSubGroupSp &grpSubGroupSp) const
{
  qDebug() << "GrpExperiment::addSubGroupSp begin "
           << grpSubGroupSp.get()->getFirstAccession();

  std::list<GrpGroupSp> new_group_list;
  grp_map_peptide_to_group.getGroupList(grpSubGroupSp.get()->getPeptideSet(),
                                        new_group_list);

  if(new_group_list.size() == 0)
    {
      qDebug() << "GrpExperiment::addSubGroupSp create a new group";
      // create a new group
      GrpGroupSp sp_group = GrpGroup(grpSubGroupSp).makeGrpGroupSp();
      // m_grpGroupSpList.push_back(sp_group);

      grp_map_peptide_to_group.set(grpSubGroupSp.get()->getPeptideSet(),
                                   sp_group);
    }
  else
    {
      qDebug() << "GrpExperiment::addSubGroupSp fusion groupList.size() "
               << new_group_list.size();
      // fusion group and add the subgroup
      auto itGroup           = new_group_list.begin();
      GrpGroupSp p_keepGroup = *itGroup;
      qDebug() << "GrpExperiment::addSubGroupSp "
                  "p_keepGroup->addSubGroupSp(grpSubGroupSp) "
               << p_keepGroup.get();
      p_keepGroup->addSubGroupSp(grpSubGroupSp);
      grp_map_peptide_to_group.set(grpSubGroupSp.get()->getPeptideSet(),
                                   p_keepGroup);

      itGroup++;
      while(itGroup != new_group_list.end())
        {
          qDebug()
            << "GrpExperiment::addSubGroupSp p_keepGroup->addGroup(*itGroup) "
            << itGroup->get();
          p_keepGroup->addGroup(itGroup->get());
          grp_map_peptide_to_group.set((*itGroup)->getGrpPeptideSet(),
                                       p_keepGroup);

          // m_grpGroupSpList.remove_if([itGroup](GrpGroupSp & groupSp) {
          //    return (itGroup->get() == groupSp.get()) ;
          //});
          itGroup++;
        }
    }

  qDebug() << "GrpExperiment::addSubGroupSp end";
}

void
GrpExperiment::numbering()
{
  qDebug() << "GrpExperiment::numbering begin";
  if(mp_monitor != nullptr)
    mp_monitor->startNumberingAllGroups(m_grpGroupSpList.size());
  for(auto &&group_sp : m_grpGroupSpList)
    {
      group_sp.get()->numbering();
    }
  m_grpGroupSpList.sort([](GrpGroupSp &first, GrpGroupSp &second) {
    return ((*first.get()) < (*second.get()));
  });
  unsigned int i = 1;
  for(auto &&group_sp : m_grpGroupSpList)
    {
      group_sp.get()->setGroupNumber(i);
      i++;
    }

  qDebug() << "GrpExperiment::numbering end";
}

std::vector<GrpProteinSpConst>
GrpExperiment::getGrpProteinSpList() const
{
  std::vector<GrpProteinSpConst> grouped_protein_list;
  if(!m_isGroupingStarted)
    {
      throw PappsoException(
        QObject::tr("unable to get grouped protein list before grouping"));
    }
  for(auto &&protein_sp : m_grpProteinList)
    {
      if(protein_sp.get()->getGroupNumber() > 0)
        {
          grouped_protein_list.push_back(protein_sp);
        }
    }
  return grouped_protein_list;
}

void
GrpExperiment::removeNonInformativeSubGroups()
{
  qDebug() << "GrpExperiment::removeNonInformativeSubGroups begin";
  if(mp_monitor != nullptr)
    mp_monitor->startRemovingNonInformativeSubGroupsInAllGroups(
      m_grpGroupSpList.size());

  std::list<GrpGroupSp> old_grp_group_sp_list(m_grpGroupSpList);
  m_grpGroupSpList.clear();
  auto it_group = old_grp_group_sp_list.begin();
  while(it_group != old_grp_group_sp_list.end())
    {
      if(mp_monitor != nullptr)
        mp_monitor->removingNonInformativeSubGroupsInGroup();
      if(it_group->get()->removeNonInformativeSubGroups())
        {
          // need to regroup it
          GrpGroupSp old_group_sp = *it_group;
          GrpMapPeptideToGroup grp_map_peptide_to_group;

          std::list<GrpSubGroupSp> dispatch_sub_group_set =
            old_group_sp.get()->getSubGroupSpList();
          for(GrpSubGroupSp &grp_subgroup : dispatch_sub_group_set)
            {
              addSubGroupSp(grp_map_peptide_to_group, grp_subgroup);
            }
          grp_map_peptide_to_group.clear(m_grpGroupSpList);
        }
      else
        {
          qDebug() << "GrpExperiment::removeNonInformativeSubGroups  no "
                      "removeNonInformativeSubGroups";
          m_grpGroupSpList.push_back(*it_group);
        }
      it_group++;
    }
  if(mp_monitor != nullptr)
    mp_monitor->stopRemovingNonInformativeSubGroupsInAllGroups(
      m_grpGroupSpList.size());

  qDebug() << "GrpExperiment::removeNonInformativeSubGroups end";
}
