
/**
 * \file pappsomspp/masschroq/input/jsoninput.cpp
 * \date 02/01/2025
 * \author Olivier Langella
 * \brief process json document input
 */

/*******************************************************************************
 * Copyright (c) 2025 Olivier Langella
 *<Olivier.Langella@universite-paris-saclay.fr>.
 *
 * This file is part of MassChroQ.
 *
 *     MassChroQ 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.
 *
 *     MassChroQ 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 MassChroQ.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/


#include "pappsomspp/core/masschroq/input/jsoninput.h"
#include "pappsomspp/core/masschroq/utils.h"
#include "pappsomspp/core/exception/exceptionnotfound.h"
#include "pappsomspp/core/exception/exceptionnotrecognized.h"
#include "pappsomspp/core/peptide/peptideproformaparser.h"
#include "pappsomspp/core/msfile/msfileaccessor.h"
#include <QtConcurrent>
#include <QStringList>

pappso::masschroq::JsonInput::JsonInput(pappso::UiMonitorInterface &ui_monitor,
                                        const QString &tmp_dir_name,
                                        const QJsonDocument &json_doc)
  : m_jsonDocument(json_doc), m_tmpDirName(tmp_dir_name), m_uiMonitor(ui_monitor)
{
}

pappso::masschroq::JsonInput::~JsonInput()
{
}

void
pappso::masschroq::JsonInput::action(CborOutputStream &cbor_output)
{
  readAlignmentMethodSp();
  readQuantificationMethodSp();
  read_protein_list();
  read_peptide_list();
  cbor_output.writeActionBegin("quantification");


  readProjectParameters();

  // QJsonDocument doc;
  // doc.setObject(params);
  // qDebug() << doc.toJson();
  m_isMatchBetweenRun = msp_quantificationMethod.get()->getMatchBetweenRun();

  pappso::ProjectParam param({pappso::ProjectParamCategory::quantification, "", QVariant()});


  param.name  = "mcq_version";
  param.value = pappso::masschroq::Utils::getVersion();
  m_projectParameters.setProjectParam(param);

  m_projectParameters.merge(msp_alignmentMethod.get()->getProjectParameters());
  m_projectParameters.merge(msp_quantificationMethod.get()->getProjectParameters());

  cbor_output.writeProjectParameters(m_projectParameters);

  qDebug();

  QJsonObject methods = m_jsonDocument.object().value("masschroq_methods").toObject();
  cbor_output.writeJsonObject("masschroq_methods", methods);

  cbor_output.getCborStreamWriter().append("identification_data");
  cbor_output.getCborStreamWriter().startMap();
  QJsonObject msrun_list = documentFind("identification_data", "msrun_list").toObject();
  cbor_output.writeJsonObject("msrun_list", msrun_list);
  QJsonObject protein_list = documentFind("identification_data", "protein_list").toObject();
  cbor_output.writeJsonObject("protein_list", protein_list);
  QJsonObject peptide_list = documentFind("identification_data", "peptide_list").toObject();
  cbor_output.writeJsonObject("peptide_list", peptide_list);
  QJsonObject msrunpeptide_list =
    documentFind("identification_data", "msrunpeptide_list").toObject();
  cbor_output.writeJsonObject("msrunpeptide_list", msrunpeptide_list);
  cbor_output.getCborStreamWriter().endMap();

  qDebug();
  readMzDataFiles();
  read_msrun_peptide_observations();

  QJsonObject actions = m_jsonDocument.object().value("actions").toObject();
  cbor_output.writeJsonObject("actions", actions);

  readAction(cbor_output);
}

const QJsonValue
pappso::masschroq::JsonInput::documentFind(const QString &key1, const QString &key2) const
{
  QJsonObject obj1 = m_jsonDocument.object();
  auto it1         = obj1.find(key1);
  if(it1 != obj1.end())
    {
      QJsonObject obj2 = it1.value().toObject();
      auto it2         = obj2.find(key2);
      if(it2 != obj2.end())
        {
          return it2.value();
        }
      else
        {
          throw pappso::ExceptionNotFound(
            QObject::tr("key2 %1 not found in json document element %2").arg(key2).arg(key1));
        }
    }
  else
    {
      throw pappso::ExceptionNotFound(QObject::tr("key1 %1 not found in json document").arg(key1));
    }
}


void
pappso::masschroq::JsonInput::readMzDataFiles()
{
  qDebug();
  std::map<QString, QString> msfilepathlist;
  const QJsonObject msrun_list(documentFind("identification_data", "msrun_list").toObject());
  auto it = msrun_list.begin();
  while(it != msrun_list.end())
    {
      qDebug() << it.key();
      qDebug() << it.value().toObject().value("file");
      //<data_file id="samp0" format="mzxml" path="bsa1.mzXML"
      // type="profile" />
      msfilepathlist.insert({it.key(), it.value().toObject().value("file").toString()});
      it++;
    }


  qDebug();
  std::function<pappso::masschroq::MsRunPeptideListSp(const std::pair<QString, QString> &)>
    mapFilenameList = [](const std::pair<QString, QString> &mapit) {
      pappso::MsFileAccessor file_access(mapit.second, mapit.first);
      file_access.setPreferredFileReaderType(Enums::MsDataFormat::brukerTims,
                                             Enums::FileReaderType::tims_ms2);

      std::vector<pappso::MsRunIdCstSPtr> msrunid_list = file_access.getMsRunIds();


      if(file_access.getFileFormat() == Enums::MsDataFormat::unknown)
        {
          throw pappso::ExceptionNotRecognized("MS data file format not recognized");
        }
      if(msrunid_list.size() == 0)
        {
          throw pappso::PappsoException("msrunid_list.size() == 0");
        }


      pappso::MsRunReaderSPtr run_reader = file_access.getMsRunReaderSPtrByRunId("", mapit.first);
      run_reader.get()->setMonoThread(true);
      pappso::masschroq::MsRunSp msrun = std::make_shared<pappso::masschroq::MsRun>(run_reader);
      run_reader.get()->releaseDevice();
      run_reader.get()->setMonoThread(false);
      return std::make_shared<pappso::masschroq::MsRunPeptideList>(msrun);
    };

  qDebug();

  m_uiMonitor.appendText(QObject::tr("reading %1 msruns").arg(msfilepathlist.size()));


  pappso::UiMonitorInterface *local_monitor = &m_uiMonitor;
  std::map<QString, pappso::masschroq::MsRunPeptideListSp> *p_localPtrOnmsrunpeptidelist =
    &m_msfileList;

  std::function<void(std::size_t, const pappso::masschroq::MsRunPeptideListSp)> reduce_function =
    [local_monitor, p_localPtrOnmsrunpeptidelist](
      std::size_t result, const pappso::masschroq::MsRunPeptideListSp msrun) {
      local_monitor->setStatus(QObject::tr("MS run '%1' from file %2: added ")
                                 .arg(msrun.get()
                                        ->getMsRunSp()
                                        .get()
                                        ->getMsRunReaderSPtr()
                                        .get()
                                        ->getMsRunId()
                                        .get()
                                        ->getXmlId())
                                 .arg(msrun.get()
                                        ->getMsRunSp()
                                        .get()
                                        ->getMsRunReaderSPtr()
                                        .get()
                                        ->getMsRunId()
                                        .get()
                                        ->getFileName()));

      p_localPtrOnmsrunpeptidelist->insert({msrun.get()
                                              ->getMsRunSp()
                                              .get()
                                              ->getMsRunReaderSPtr()
                                              .get()
                                              ->getMsRunId()
                                              .get()
                                              ->getXmlId(),
                                            msrun});
      result++;
    };

  QFuture<std::size_t> res = QtConcurrent::mappedReduced<std::size_t>(msfilepathlist.begin(),
                                                                      msfilepathlist.end(),
                                                                      mapFilenameList,
                                                                      reduce_function,
                                                                      QtConcurrent::OrderedReduce);
  res.waitForFinished();

  m_uiMonitor.appendText("reading msruns finished");
  if(m_msfileList.begin()->second.get()->getMsRunSp().get()->hasTimsTofMobilityIndex())
    {
      m_uiMonitor.appendText("ion mobility grid enabled");
      msp_ionMobilityGrid = std::make_shared<pappso::IonMobilityGrid>();
    }
}

void
pappso::masschroq::JsonInput::readAlignmentMethodSp()
{
  msp_alignmentMethod = std::make_shared<pappso::masschroq::AlignmentMethod>("align1");

  msp_alignmentMethod.get()->setJsonObject(
    documentFind("masschroq_methods", "alignment_method").toObject());
}

void
pappso::masschroq::JsonInput::readQuantificationMethodSp()
{
  msp_quantificationMethod = std::make_shared<pappso::masschroq::QuantificationMethod>("qm1");


  msp_quantificationMethod.get()->setJsonObject(
    documentFind("masschroq_methods", "quantification_method").toObject());

  /*
    "quantification_method": {
        "detection": {
            "type": "zivy",
            "meanfilter": 1,
            "minmax": 3,
            "maxmin": 2,
            "threshold_on_max": 5000,
            "threshold_on_min": 3000,
        }
    }
    */
}


void
pappso::masschroq::JsonInput::read_protein_list()
{


  const QJsonObject protein_list(documentFind("identification_data", "protein_list").toObject());
  //<protein desc="conta|P02769|ALBU_BOVIN SERUM ALBUMIN PRECURSOR."
  // id="P1.1" />

  auto it = protein_list.begin();
  while(it != protein_list.end())
    {
      qDebug() << it.key();
      qDebug() << it.value().toObject().value("description");

      /// create a new Protein object and set its description
      pappso::masschroq::ProteinSp p_protein;
      try
        {
          p_protein = std::make_shared<pappso::masschroq::Protein>(
            it.key(), it.value().toObject().value("description").toString());
        }
      catch(pappso::PappsoException &error)
        {
          throw pappso::ExceptionNotFound(
            QObject::tr("problem creating protein :\n%1").arg(it.key()));
        }
      /// add this protein to _p_proteins (: map<id, Protein *>)
      m_proteinMap.insert({p_protein.get()->getId(), p_protein});
      it++;
    }
}

void
pappso::masschroq::JsonInput::read_peptide_list()
{


  const QJsonObject peptide_list(documentFind("identification_data", "peptide_list").toObject());
  //<protein desc="conta|P02769|ALBU_BOVIN SERUM ALBUMIN PRECURSOR."
  // id="P1.1" />

  auto it = peptide_list.begin();
  while(it != peptide_list.end())
    {
      qDebug() << it.key();
      qDebug() << it.value().toObject().value("proforma");

      /// create a new peptide object and set its description
      pappso::masschroq::PeptideSp peptide_sp;
      try
        {

          std::vector<pappso::masschroq::ProteinSp> protein_list;
          for(auto prot_id : it.value().toObject().value("proteins").toArray())
            {
              auto it = m_proteinMap.find(prot_id.toString());
              if(it != m_proteinMap.end())
                {
                  protein_list.push_back(it->second);
                }
              else
                {
                  throw pappso::ExceptionNotFound(
                    QObject::tr("protein id %1 not found").arg(prot_id.toString()));
                }
            }


          peptide_sp = std::make_shared<pappso::masschroq::Peptide>(
            it.key(),
            pappso::PeptideProFormaParser::parseString(
              it.value().toObject().value("proforma").toString()),
            protein_list);
          m_peptideStore.push_back(peptide_sp);
          peptide_sp.get()->setMods(it.value().toObject().value("mods").toString());
          m_peptideMap.insert({peptide_sp.get()->getId(), peptide_sp});

          qDebug();
          QJsonObject json_label_list = it.value().toObject().value("label_list").toObject();

          qDebug();
          if(!json_label_list.isEmpty())
            {
              qDebug();
              peptide_sp.get()->setJsonLabelList(json_label_list);
              qDebug();
            }
        }
      catch(pappso::PappsoException &error)
        {
          throw pappso::ExceptionNotFound(
            QObject::tr("problem creating protein :\n%1").arg(it.key()));
        }
      it++;
    }

  qDebug();
}

void
pappso::masschroq::JsonInput::read_msrun_peptide_observations()
{
  qDebug();
  const QJsonObject msrunpeptide_list(
    documentFind("identification_data", "msrunpeptide_list").toObject());
  auto it = msrunpeptide_list.begin();
  while(it != msrunpeptide_list.end())
    {
      qDebug() << it.key();
      QJsonObject jmsrun_peptidelist_object = it.value().toObject();
      auto it_peptide_obs                   = jmsrun_peptidelist_object.find("peptide_obs");
      if(it_peptide_obs == jmsrun_peptidelist_object.end())
        {
          throw pappso::ExceptionNotFound(
            QObject::tr("peptide_obs not found in msrunpeptide_list %1").arg(it.key()));
        }
      QJsonObject json_msrunobs = it_peptide_obs->toObject();
      auto it_msrun             = m_msfileList.find(it.key());
      if(it_msrun == m_msfileList.end())
        {
          throw pappso::ExceptionNotFound(QObject::tr("msrun %1 not found").arg(it.key()));
        }
      pappso::masschroq::MsRunPeptideListSp msrunpep_sp = it_msrun->second;

      auto itpep = json_msrunobs.begin();
      while(itpep != json_msrunobs.end())
        {
          qDebug() << itpep.key();
          QJsonArray json_obs_list = itpep.value().toArray();
          auto it_pepmap           = m_peptideMap.find(itpep.key());
          if(it_pepmap == m_peptideMap.end())
            {
              throw pappso::ExceptionNotFound(QObject::tr("peptide %1 not found").arg(itpep.key()));
            }
          pappso::masschroq::PeptideSp peptide_sp = it_pepmap->second;
          qDebug() << "json_obs_list.size()=" << json_obs_list.size();
          for(auto json_obs_value : json_obs_list)
            {
              QJsonObject observation = json_obs_value.toObject();
              qint64 scan             = observation.value("scan").toInteger();
              qint64 index            = observation.value("index").toInteger();
              QString label           = observation.value("label").toString();
              std::uint8_t charge =
                observation.value("precursor").toObject().value("charge").toInt();
              try
                {
                  pappso::masschroq::PeptideLabel *p_label = nullptr;
                  if(!label.isEmpty())
                    {
                      p_label = peptide_sp.get()->getPeptideLabelPtr(label);
                    }
                  if(scan == 0)
                    {
                      msrunpep_sp.get()->addPeptideSpectrumIndexObservation(
                        peptide_sp, p_label, index, charge);
                    }
                  else
                    {

                      msrunpep_sp.get()->addPeptideScanNumberObservation(
                        peptide_sp, p_label, scan, charge);
                    }

                  peptide_sp.get()->addObservedChargeState(charge);
                  peptide_sp.get()->addObservedInMsRunSp(msrunpep_sp.get()->getMsRunSp());
                }
              catch(pappso::PappsoException &err)
                {
                  throw pappso::PappsoException(
                    QObject::tr("error reading peptide %1 observation scan %2 "
                                "index %3 : %4")
                      .arg(itpep.key())
                      .arg(scan)
                      .arg(index)
                      .arg(err.qwhat()));
                }
            }


          if(msp_ionMobilityGrid.get() != nullptr)
            {
              if(peptide_sp.get() != nullptr)
                {
                  peptide_sp.get()->populateIonMobilityGrid(msp_ionMobilityGrid.get());
                }
            }


          itpep++;
        }

      it++;
    }
  qDebug();
}

void
pappso::masschroq::JsonInput::computeIsotopologues(double ni_min_abundance)
{
  if(ni_min_abundance > 0)
    { /*
       for(auto &peptide_sp : m_peptideStore)
         {
           peptide_sp.get()->computeIsotopologues(ni_min_abundance);
         }*/
      std::function<void(const pappso::masschroq::PeptideSp &)> mapComputeIsotopologues =
        [ni_min_abundance](const pappso::masschroq::PeptideSp &peptide_sp) {
          peptide_sp.get()->computeIsotopologues(ni_min_abundance);
        };


      QFuture<void> res = QtConcurrent::map<std::vector<pappso::masschroq::PeptideSp>::iterator>(
        m_peptideStore.begin(), m_peptideStore.end(), mapComputeIsotopologues);
      m_uiMonitor.appendText(QObject::tr("Computing isotopologues %1").arg(ni_min_abundance));
      res.waitForFinished();

      m_uiMonitor.appendText("Computing isotopologues OK");
    }
}


void
pappso::masschroq::JsonInput::readAction(CborOutputStream &cbor_output)
{
  const QJsonObject group_list(documentFind("actions", "group_list").toObject());

  auto it_group = group_list.begin();
  while(it_group != group_list.end())
    {
      QString id                 = it_group.key();
      QJsonArray json_msrun_list = it_group.value().toArray();
      std::vector<pappso::masschroq::MsRunPeptideListSp> msrun_list;
      for(auto json_msrun : json_msrun_list)
        {

          auto it_msrun = m_msfileList.find(json_msrun.toString());
          if(it_msrun == m_msfileList.end())
            {
              throw pappso::ExceptionNotFound(
                QObject::tr("msrun %1 not found").arg(json_msrun.toString()));
            }
          msrun_list.push_back(it_msrun->second);
        }

      m_msRunGroupSpList.insert({id, std::make_shared<MsRunGroup>(id, msrun_list)});
      it_group++;
    }


  QJsonObject align_list;
  try
    {
      align_list = documentFind("actions", "align_group").toObject();
    }
  catch(const pappso::ExceptionNotFound &notfound)
    {
    }
  if(!align_list.isEmpty())
    {
      cbor_output.getCborStreamWriter().append("alignment_data");
      cbor_output.getCborStreamWriter().startArray();

      std::size_t count_q    = 1;
      QStringList group_keys = align_list.keys();
      for(auto group_id : group_keys)
        {
          auto itgroup = m_msRunGroupSpList.find(group_id);
          if(itgroup == m_msRunGroupSpList.end())
            {
              throw pappso::ExceptionNotFound(QObject::tr("group id %1 not found").arg(group_id));
            }
          else
            {
              QString ref_id =
                align_list.value(group_id).toObject().value("alignment_reference").toString();
              if(!ref_id.isEmpty())
                {
                  itgroup->second.get()->setAlignmentMethodSp(msp_alignmentMethod, ref_id);
                }
              itgroup->second.get()->setIonMobilityGridSp(msp_ionMobilityGrid);
              itgroup->second.get()->align(cbor_output, QString("a%1").arg(count_q), m_uiMonitor);
            }
          count_q++;
        }
      cbor_output.getCborStreamWriter().endArray();
    }

  QVariant quantify_all;
  try
    {
      quantify_all = documentFind("actions", "quantify_all").toVariant();
    }
  catch(const pappso::ExceptionNotFound &notfound)
    {
    }
  if(!quantify_all.isNull())
    {
      if(quantify_all.toBool())
        {
          double ni_ratio = msp_quantificationMethod.get()->getIsotopeMinimumRatio();
          computeIsotopologues(ni_ratio);
          std::size_t count_q = 1;
          cbor_output.getCborStreamWriter().append("quantification_data");
          cbor_output.getCborStreamWriter().startArray();
          for(auto &pair_group : m_msRunGroupSpList)
            {
              pair_group.second.get()->setIonMobilityGridSp(msp_ionMobilityGrid);
              pair_group.second.get()->quantify(cbor_output,
                                                QString("q%1").arg(count_q),
                                                m_tmpDirName,
                                                m_uiMonitor,
                                                msp_quantificationMethod,
                                                m_peptideStore);
            }

          cbor_output.getCborStreamWriter().endArray();
          count_q++;
        }
    }
}

void
pappso::masschroq::JsonInput::readProjectParameters()
{

  QJsonObject params = m_jsonDocument.object().value("project_parameters").toObject();

  pappso::ProjectParameters parameters(params);
  m_projectParameters.merge(parameters);
}
