/**
 * \file pappsomspp/core/processing/cbor/mzcbor/spectrum.h
 * \date 24/11/2025
 * \author Olivier Langella
 * \brief PSI spectrum object for mzML/mzCBOR
 */

/*******************************************************************************
 * Copyright (c) 2025 Olivier Langella <Olivier.Langella@universite-paris-saclay.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 "spectrum.h"
#include "pappsomspp/core/exception/exceptionnotfound.h"
#include "pappsomspp/core/processing/cbor/mzcbor/cvparammap.h"
#include "pappsomspp/core/processing/cbor/mzcbor/scan.h"
#include "pappsomspp/core/trace/datapoint.h"
#include <qdebug.h>
#include <qjsonarray.h>
#include <qjsonobject.h>
#include <qlogging.h>
#include <qtypes.h>
#include <unistd.h>


void
pappso::cbor::mzcbor::Spectrum::fromCbor(CborStreamReader &reader, bool want_binary_data)
{
  QString txt_value;
  // cborBinaryDataArray.clear();
  precursorList.clear();
  defaultArrayLength = 0;
  index              = 0;
  cvParamMap.clear();
  scanListCvParamMap.clear();
  scanList.clear();
  id.clear();
  qDebug();

  if(reader.isInvalid())
    {
      throw pappso::PappsoException(
        QObject::tr("ERROR in Spectrum::fromCbor :\ncbor is not valid"));
    }
  if(reader.enterContainer())
    {
      while(reader.hasNext() && (!reader.isInvalid()))
        {
          if(reader.isString())
            {
              reader.decodeString(txt_value);
              qDebug() << txt_value;
              if(txt_value == "id")
                {
                  reader.decodeString(txt_value);
                  qDebug() << txt_value;
                  id = txt_value;
                }
              else if(txt_value == "index")
                {
                  index = reader.toUnsignedInteger();
                  reader.next();
                }
              else if(txt_value == "defaultArrayLength")
                {
                  defaultArrayLength = reader.toUnsignedInteger();
                  reader.next();
                }
              else if(txt_value == "cvParam")
                {
                  cvParamMap.fromCbor(reader);
                }
              else if(txt_value == "scanList")
                {
                  // mpa_cborReader->next();
                  reader.enterContainer();

                  while(reader.hasNext() && (!reader.isInvalid()))
                    {
                      if(reader.isString())
                        {
                          if(reader.decodeString(txt_value))
                            {
                              qDebug() << txt_value;
                              if(txt_value == "cvParam")
                                {
                                  scanListCvParamMap.fromCbor(reader);
                                }
                              else if(txt_value == "scan")
                                {
                                  // scan array
                                  reader.enterContainer();

                                  while(reader.hasNext() && (!reader.isInvalid()))
                                    {
                                      // scan array
                                      Scan scan;
                                      scan.fromCbor(reader);
                                      scanList.emplace_back(scan);
                                    }
                                  reader.leaveContainer();
                                }
                            }
                          else
                            {
                              reader.next();
                            }
                        }
                      else
                        {
                          reader.next();
                        }
                    }
                  reader.leaveContainer();
                }
              else if(txt_value == "precursorList")
                {

                  reader.enterContainer();

                  while(reader.hasNext() && (!reader.isInvalid()))
                    {
                      if(reader.isString())
                        {
                          if(reader.decodeString(txt_value))
                            {
                              qDebug() << txt_value;

                              if(txt_value == "cvParam")
                                {
                                  precursorListCvParamMap.fromCbor(reader);
                                }
                              else if(txt_value == "precursor")
                                {

                                  reader
                                    .enterContainer(); //                                     array

                                  while(reader.hasNext() && (!reader.isInvalid()))
                                    {
                                      // scan array
                                      Precursor precursor;
                                      precursor.fromCbor(reader);
                                      precursorList.emplace_back(precursor);
                                    }
                                  reader
                                    .leaveContainer(); //                                     array

                                  // reader.next();
                                }
                              else
                                {
                                  reader.next();
                                }
                            }
                          else
                            {
                              reader.next();
                            }
                        }
                      else
                        {
                          reader.next();
                        }
                    }
                  reader.leaveContainer();
                }
              else if(txt_value == "binaryDataArray")
                {
                  if(want_binary_data)
                    {

                      reader.enterContainer(); // start array

                      while(reader.hasNext())
                        {

                          BinaryDataArray data_array;
                          data_array.fromCbor(reader);
                          binaryDataArrayList.emplace_back(data_array);
                        }

                      reader.leaveContainer(); // end array
                    }
                  else
                    {
                      reader.next();
                    }
                }
              else
                {
                  reader.next();
                }
            }
          // reader.next();
        }

      reader.leaveContainer();
    }
  else
    {
      throw pappso::PappsoException(QObject::tr("ERROR in Spectrum::fromCbor : no container"));
    }


  if(reader.lastError() != QCborError::NoError)
    {
      throw pappso::PappsoException(
        QObject::tr("ERROR in Spectrum::fromCbor :\n%1").arg(reader.lastError().toString()));
    }
}


QJsonObject
pappso::cbor::mzcbor::Spectrum::toJsonObject() const
{
  QJsonObject spectrum_json;
  spectrum_json.insert("id", id);
  spectrum_json.insert("index", (qint64)index);
  spectrum_json.insert("defaultArrayLength", (qint64)defaultArrayLength);
  spectrum_json.insert("cvParam", cvParamMap.toJsonArray());

  QJsonObject scan_list;
  // QJsonObject scan_list_cvparam;
  scan_list.insert("cvParam", scanListCvParamMap.toJsonArray());
  QJsonArray scan_array;
  for(auto &scan : scanList)
    {
      scan_array.append(scan.toJsonObject());
    }
  scan_list.insert("scan", scan_array);
  spectrum_json.insert("scanList", scan_list);

  if(!precursorList.empty())
    {
      QJsonObject precursor_list;
      // QJsonObject scan_list_cvparam;
      precursor_list.insert("cvParam", precursorListCvParamMap.toJsonArray());
      QJsonArray precursor_array;
      for(auto &precursor : precursorList)
        {
          precursor_array.append(precursor.toJsonObject());
        }
      precursor_list.insert("precursor", precursor_array);
      spectrum_json.insert("precursorList", precursor_list);
    }

  if(!binaryDataArrayList.empty())
    {
      Trace trace;

      decodeTrace(trace);


      spectrum_json.insert("spectrum", trace.toJsonObject());
    }

  return spectrum_json;
}

std::map<QString, pappso::cbor::mzcbor::CvParam>
pappso::cbor::mzcbor::Spectrum::readScanCvParams(CborStreamReader &reader)
{
  CvParamMap cv_params;
  reader.enterContainer();
  QString txt_value;

  while(reader.hasNext() && (!reader.isInvalid()))
    {
      if(reader.isString())
        {
          if(reader.decodeString(txt_value))
            {
              qDebug() << txt_value;
              if(txt_value == "cvParam")
                {
                  cv_params.fromCbor(reader);
                }
              else
                {
                  reader.next();
                }
            }
        }
      else
        {
          reader.next();
        }
    }

  reader.leaveContainer();
  return cv_params;
}


qint64
pappso::cbor::mzcbor::Spectrum::getTotalIonCount() const
{
  //   <cvParam cvRef="MS" accession="MS:1000285" value="17377980" name="total ion current" />

  auto it = cvParamMap.find("MS:1000285");
  if(it == cvParamMap.end())
    {
      throw pappso::ExceptionNotFound(QObject::tr("total ion count not found in cvParam map"));
    }
  qDebug() << it->first << " " << it->second.valueInt;
  return it->second.valueInt;
}


uint
pappso::cbor::mzcbor::Spectrum::getMsLevel() const
{
  //        <cvParam cvRef="MS" accession="MS:1000511" value="1" name="ms level" />

  auto it = cvParamMap.find("MS:1000511");
  if(it == cvParamMap.end())
    {
      throw pappso::ExceptionNotFound(QObject::tr("MS level not found in cvParam map"));
    }
  qDebug() << it->first << " " << it->second.valueInt;
  return it->second.valueInt;
}

double
pappso::cbor::mzcbor::Spectrum::getRtInSeconds() const
{
  if(scanList.size() == 0)
    {
      throw pappso::ExceptionNotFound(QObject::tr("scan list is empty in spectrum"));
    }
  if(scanList.size() > 1)
    {
      throw pappso::ExceptionNotFound(QObject::tr("too many scans in spectrum"));
    }
  return scanList.front().getRtInSeconds();
}

void
pappso::cbor::mzcbor::Spectrum::decodeTrace(pappso::Trace &trace) const
{
  qDebug();
  std::vector<pappso_double> xVector;
  std::vector<pappso_double> yVector;
  for(auto &binary_array : binaryDataArrayList)
    {
      qDebug();
      if(binary_array.isIntensity())
        {
          qDebug();
          binary_array.decodeVector(defaultArrayLength, yVector);
        }
      else if(binary_array.isMz())
        {
          qDebug();
          binary_array.decodeVector(defaultArrayLength, xVector);
        }
      else
        {

          throw pappso::ExceptionNotFound(QObject::tr(
            "error in pappso::cbor::mzcbor::Spectrum::decodeTrace: binary vector type not found"));
        }
      qDebug();
    }

  qDebug();
  trace.initialize(xVector, yVector);
  qDebug();
}

void
pappso::cbor::mzcbor::Spectrum::toMgf(QTextStream &output, MsRunIdCstSPtr msrun_id_csptr) const
{
  // BEGIN IONS
  // TITLE=20120906_balliau_extract_1_A01_urnb-1.2016.2016.3
  // RTINSECONDS=679.467
  // PEPMASS=417.210251631473 92010.6328125
  // CHARGE=3+
  // 88.03964233 1132.5438232422
  // 91.05783844 1945.9479980469
  // 93.03697968 6423.5581054688
  // 93.8877182 949.33984375
  output << "BEGIN IONS" << Qt::endl;
  QString title;
  if(msrun_id_csptr.get() != nullptr)
    {
      title = msrun_id_csptr.get()->getSampleName();
    }

  output << "TITLE=" << title << "." << index << Qt::endl;
  output << "RTINSECONDS=" << QString::number(scanList.front().getRtInSeconds(), 'f', 4)
         << Qt::endl;
  bool is_ok;
  double intensity = precursorList.front().selectedIonList.front().getIntensity(&is_ok);
  if(is_ok)
    {
      output << "PEPMASS="
             << QString::number(precursorList.front().selectedIonList.front().getMz(), 'f', 8)
             << " " << QString::number(intensity, 'g', 10) << Qt::endl;
    }
  else
    {
      output << "PEPMASS="
             << QString::number(precursorList.front().selectedIonList.front().getMz(), 'f', 8)
             << Qt::endl;
    }
  output << "CHARGE=" << precursorList.front().selectedIonList.front().getChargeState() << "+"
         << Qt::endl;

  Trace trace;
  decodeTrace(trace);

  for(const pappso::DataPoint &one_point : trace)
    {
      output << QString::number(one_point.x, 'f', 8) << " " << QString::number(one_point.y, 'f', 8)
             << Qt::endl;
    }
  output << "END IONS" << Qt::endl;
}
