/**
 * \file pappsomspp/processing/cbor/mzcbor/mzmlconvert.cpp
 * \date 19/11/2025
 * \author Olivier Langella
 * \brief convert mzML to 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 "mzmlconvert.h"
#include <QDebug>
#include <qlogging.h>
#include <QObject>
#include <zlib.h>
#include "pappsomspp/core/pappsoexception.h"
#include "pappsomspp/config.h"
#include "binarydataarray.h"
#include "cvparam.h"


pappso::cbor::mzcbor::MzmlConvert::MzmlConvert(pappso::UiMonitorInterface *p_monitor,
                                               pappso::cbor::CborStreamWriter *p_output)
  : mp_monitor(p_monitor), mp_cborWriter(p_output)
{
  m_elementToStoreInArray.clear();
  m_elementToStoreInArray << "cv" << "userParam" << "cvParam" << "binaryDataArray" << "spectrum"
                          << "sourceFile"
                          << "referenceableParamGroup" << "software" << "instrumentConfiguration"
                          << "processingMethod" << "dataProcessing" << "scan" << "scanWindow"
                          << "precursor" << "selectedIon";


  m_elementStash.clear();
  m_currentSpectrumSize = 0;
  m_doubleArray.clear();
  m_runIdList.clear();
  m_runAndSpectrumOffsetList.clear();
  m_runAndSpectrumIdList.clear();
}

pappso::cbor::mzcbor::MzmlConvert::~MzmlConvert()
{
}


void
pappso::cbor::mzcbor::MzmlConvert::writeMzcborIndex(pappso::cbor::CborStreamWriter *writer) const
{
  writer->startMap();

  writer->writeInformations(PAPPSOMSPP_NAME, PAPPSOMSPP_VERSION, "mzCBORindex", "mzMLconvert");

  writer->append("from");
  writer->append(m_uuid);

  writer->append("runIdList");
  writer->writeArray(m_runIdList);


  std::vector<std::size_t> msrun_size;


  writer->append("runSpectrumIndexList");
  writer->startArray(m_runAndSpectrumOffsetList.size());
  for(auto &spectrum_offset_list : m_runAndSpectrumOffsetList)
    {
      msrun_size.push_back(spectrum_offset_list.size());
      writer->writeArray(spectrum_offset_list);
    }
  writer->endArray();


  writer->append("runSpectrumNativeIdList");
  writer->startArray(m_runAndSpectrumIdList.size());
  for(auto &spectrum_id_list : m_runAndSpectrumIdList)
    {
      writer->writeArray(spectrum_id_list);
    }
  writer->endArray();

  // TIC
  bool size_ok = true;
  for(std::size_t i = 0; i < msrun_size.size(); i++)
    {
      if(m_runAndSpectrumTotalIonCountList.at(i).size() != msrun_size.at(i))
        {
          size_ok = false;
        }
    }
  if(size_ok)
    {
      writer->append("runSpectrumTotalIonCountList");
      writer->startArray(m_runAndSpectrumIdList.size());
      for(auto &spectrum_tic_list : m_runAndSpectrumTotalIonCountList)
        {
          writer->writeArray(spectrum_tic_list);
        }
      writer->endArray();
    }

  // retention time
  size_ok = true;
  for(std::size_t i = 0; i < msrun_size.size(); i++)
    {
      if(m_runAndSpectrumRtList.at(i).size() != msrun_size.at(i))
        {
          size_ok = false;
        }
    }
  if(size_ok)
    {

      writer->append("runSpectrumRtList");
      writer->startArray(m_runAndSpectrumRtList.size());
      for(auto &spectrum_rt_list : m_runAndSpectrumRtList)
        {
          writer->writeArray(spectrum_rt_list);
        }
      writer->endArray();
    }
  else
    {
      // qFatal() << "wrong size";
    }

  // MS Level
  size_ok = true;
  for(std::size_t i = 0; i < msrun_size.size(); i++)
    {
      if(m_runAndSpectrumMsLevelList.at(i).size() != msrun_size.at(i))
        {
          size_ok = false;
        }
    }
  if(size_ok)
    {
      writer->append("runSpectrumMsLevelList");
      writer->startArray(m_runAndSpectrumMsLevelList.size());
      for(auto &spectrum_mslevel_list : m_runAndSpectrumMsLevelList)
        {
          writer->writeArray(spectrum_mslevel_list);
        }
      writer->endArray();
    }

  writer->endMap();
}


void
pappso::cbor::mzcbor::MzmlConvert::readStream()
{
  mp_cborWriter->startMap();
  if(m_qxmlStreamReader.readNextStartElement())
    {
      qDebug() << m_qxmlStreamReader.name().toString();
      if(m_qxmlStreamReader.name().toString() == "indexedmzML")
        {
          m_qxmlStreamReader.readNextStartElement();
        }
      if(m_qxmlStreamReader.name().toString() == "mzML")
        {
          // write mzCBOR header
          mp_cborWriter->append("mzCBOR");
          mp_cborWriter->startMap();

          mp_cborWriter->append("mode");
          mp_cborWriter->append(0);

          mp_cborWriter->writeInformations(
            PAPPSOMSPP_NAME, PAPPSOMSPP_VERSION, "mzCBOR", "mzMLconvert");

          m_uuid = mp_cborWriter->getUuid();
          mp_cborWriter->endMap();

          mp_cborWriter->append(m_qxmlStreamReader.name().toString());

          mp_cborWriter->startMap();
          mp_cborWriter->append("xmlns");
          mp_cborWriter->append(m_qxmlStreamReader.namespaceUri());
          attributeListToCbor(m_qxmlStreamReader.attributes());
          mp_cborWriter->endMap();


          bool array_started = false;
          QString last_element;
          while(m_qxmlStreamReader.readNextStartElement())
            {
              qDebug();
              insideElement(last_element, array_started);
              last_element = m_qxmlStreamReader.name().toString();
              qDebug();
            }
        }
      else
        {
          m_qxmlStreamReader.raiseError(QObject::tr("Not an mzML input file"));
          m_qxmlStreamReader.skipCurrentElement();
        }
    }
  mp_cborWriter->endMap();
}


void
pappso::cbor::mzcbor::MzmlConvert::getCurrentSpectrumSize()
{
  // defaultArrayLength	1552

  m_currentSpectrumSize = m_qxmlStreamReader.attributes().value("defaultArrayLength").toULongLong();
}


void
pappso::cbor::mzcbor::MzmlConvert::readBinaryDataArrayList()
{

  qDebug() << m_qxmlStreamReader.name();
  // m_qxmlStreamReader.skipCurrentElement();
  /*
   *
          <binaryDataArray encodedLength="9092">
            <cvParam cvRef="MS" accession="MS:1000515" value="" name="intensity array"
   unitAccession="MS:1000131" unitName="number of counts" unitCvRef="MS" /> <cvParam cvRef="MS"
   accession="MS:1000523" value="" name="64-bit float" /> <cvParam cvRef="MS" accession="MS:1000574"
   value="" name="zlib compression" /> <binary>*/
  std::size_t count = m_qxmlStreamReader.attributes().value("count").toULongLong();

  mp_cborWriter->append("binaryDataArray");
  mp_cborWriter->startArray(count);
  while(m_qxmlStreamReader.readNext() && !m_qxmlStreamReader.isEndElement())
    {
      if(m_qxmlStreamReader.isStartElement())
        {
          BinaryDataArray binary_data_array;
          binary_data_array.fromMzml(m_qxmlStreamReader);
          binary_data_array.toCbor(*mp_cborWriter);
          // writeZlibDataArray();
        }
    }
  mp_cborWriter->endArray();
  qDebug() << m_qxmlStreamReader.name();
}


void
pappso::cbor::mzcbor::MzmlConvert::insideElement(QString &last_element_in, bool &array_started_in)
{
  m_elementStash.push_back(m_qxmlStreamReader.name().toString());


  qDebug() << m_qxmlStreamReader.name();

  if(m_elementStash.back() == "spectrum")
    {
      // qDebug() << m_qxmlStreamReader.attributes().value("id").toString();
      m_runAndSpectrumIdList.back().push_back(
        m_qxmlStreamReader.attributes().value("id").toString());
      getCurrentSpectrumSize();
      // qFatal();
    }


  // stop an array ?
  qDebug() << "current element=" << m_elementStash.back();
  qDebug() << "last_element=" << last_element_in;
  if(array_started_in && (last_element_in != m_qxmlStreamReader.name().toString()))
    {
      mp_cborWriter->endArray();
      qDebug() << "mp_cborWriter->endArray()";
      array_started_in = false;
    }
  if(m_elementStash.back() == "binaryDataArrayList")
    {
      qDebug() << "readBinaryDataArrayList()";
      readBinaryDataArrayList();
    }

  else
    {


      // start an array ?
      if(m_elementToStoreInArray.contains(m_elementStash.back()))
        {
          // start an array ?
          if((!array_started_in) && (last_element_in != m_elementStash.back()))
            {
              mp_cborWriter->append(m_elementStash.back());
              mp_cborWriter->startArray();

              qDebug() << "mp_cborWriter->startArray()";
              array_started_in = true;
            }
        }

      if(m_elementStash.back() == "spectrum")
        {
          if(mp_cborWriter->device() != nullptr)
            m_runAndSpectrumOffsetList.back().push_back(mp_cborWriter->device()->pos());
        }


      if(!array_started_in)
        mp_cborWriter->append(m_elementStash.back());

      bool array_started = false;


      if(m_elementStash.back() == "cvParam")
        {

          // *********** special treatment for cvParam **********
          if(!array_started_in)
            {
              throw pappso::PappsoException(
                QObject::tr("unable to write cvParam outside CBOR array"));
            }
          // array_started = true;
          qDebug() << m_qxmlStreamReader.name() << " "
                   << m_elementStash.at(m_elementStash.size() - 2);
          CvParam cv_param;
          cv_param.fromMzml(m_qxmlStreamReader);
          cv_param.toCbor(*mp_cborWriter);
          qDebug() << cv_param.name;

          if(m_elementStash.at(m_elementStash.size() - 2) == "spectrum")
            {
              qDebug() << "cvparam in spectrum";
              if(cv_param.accession == "MS:1000511")
                {
                  m_runAndSpectrumMsLevelList.back().push_back(cv_param.getExpectedUint8());
                  qDebug() << m_runAndSpectrumMsLevelList.back().back();
                }
              else if(cv_param.accession == "MS:1000285")
                { // TIC
                  m_runAndSpectrumTotalIonCountList.back().push_back(cv_param.getExpectedDouble());
                }
            }
          else if(m_elementStash.at(m_elementStash.size() - 2) == "scan")
            {
              if(cv_param.accession == "MS:1000016")
                { // rt
                  double rt = cv_param.getExpectedDouble();

                  if(cv_param.unitAccession == "UO:0000031")
                    {
                      // // minutes
                      rt = rt * 60;
                    }
                  m_runAndSpectrumRtList.back().push_back(rt);
                }
            }
          qDebug();
          // *********** special treatment for cvParam **********
          //
          qDebug() << "finish cvParam " << cv_param.accession;
        }
      else
        {

          mp_cborWriter->startMap();
          attributeListToCbor(m_qxmlStreamReader.attributes());

          QString last_element;

          while(m_qxmlStreamReader.readNext() && !m_qxmlStreamReader.isEndElement())
            {
              qDebug() << m_qxmlStreamReader.name();
              if(m_qxmlStreamReader.isCharacters())
                {
                  // clean content:
                  QStringView content = m_qxmlStreamReader.text().trimmed();
                  if((m_qxmlStreamReader.text().toString() == "\n") ||
                     (m_qxmlStreamReader.text().toString() == "\n\t"))
                    {
                    }
                  else
                    {
                      // text node
                      if(!content.isEmpty())
                        {
                          qDebug() << "text isCharacters" << content.mid(0, 10);
                          mp_cborWriter->append("@text@");
                          mp_cborWriter->append(content);
                        }
                    }
                }
              else if(m_qxmlStreamReader.isStartElement())
                {
                  QString tmp_element = m_qxmlStreamReader.name().toString();
                  qDebug() << tmp_element;
                  insideElement(last_element, array_started);
                  last_element = tmp_element;
                }
            }

          if(array_started)
            {
              mp_cborWriter->endArray();
            }

          mp_cborWriter->endMap();

          qDebug() << m_qxmlStreamReader.name();
        }
    }

  qDebug() << m_elementStash.back();
  m_elementStash.pop_back();
}


void
pappso::cbor::mzcbor::MzmlConvert::attributeValueToCbor(const QStringView &value_str)
{
  bool ok(false);
  double d = value_str.toDouble(&ok);
  if(ok)
    {
      if(value_str.contains('.'))
        {
          mp_cborWriter->append(d);
        }
      else
        {
          qint64 bigint = value_str.toLongLong(&ok);
          if(ok)
            {
              mp_cborWriter->append(bigint);
            }
        }
    }
  else
    {
      mp_cborWriter->append(value_str);
    }
}


void
pappso::cbor::mzcbor::MzmlConvert::attributeListToCbor(const QXmlStreamAttributes &xml_attributes)
{
  for(auto &xml_attribute : xml_attributes)
    {
      qDebug() << xml_attribute.name() << " " << xml_attribute.value();
      mp_cborWriter->append(xml_attribute.name());
      attributeValueToCbor(xml_attribute.value());

      if((m_elementStash.size() > 0) && (m_elementStash.back() == "run") &&
         (xml_attribute.name() == "id"))
        {
          m_runAndSpectrumOffsetList.push_back(std::vector<qint64>());
          m_runAndSpectrumIdList.push_back(std::vector<QString>());
          m_runAndSpectrumMsLevelList.push_back(std::vector<std::uint8_t>());
          m_runAndSpectrumRtList.push_back(std::vector<double>());
          m_runAndSpectrumTotalIonCountList.push_back(std::vector<qint64>());
          m_runIdList.push_back(xml_attribute.value().toString());
        }
    }
}

const std::vector<QString> &
pappso::cbor::mzcbor::MzmlConvert::getRunIdList() const
{

  return m_runIdList;
}

const std::vector<std::vector<qint64>> &
pappso::cbor::mzcbor::MzmlConvert::getRunAndSpectrumOffsetList() const
{
  return m_runAndSpectrumOffsetList;
}

const std::vector<std::vector<QString>> &
pappso::cbor::mzcbor::MzmlConvert::getRunAndSpectrumIdList() const
{
  return m_runAndSpectrumIdList;
}
