/**
 * \file pappsomspp/core/processing/cbor/mzcbor/binarydataarray.cpp
 * \date 25/11/2025
 * \author Olivier Langella
 * \brief PSI BinaryDataArray 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 "binarydataarray.h"
#include "cvparam.h"
#include "pappsomspp/core/pappsoexception.h"
#include <qjsonarray.h>
#include <qlogging.h>
#include <zlib.h>

void
pappso::cbor::mzcbor::BinaryDataArray::fromCbor(CborStreamReader &reader)
{
  QString txt_value;
  reader.enterContainer();
  // qDebug() << txt_value;
  while(reader.hasNext() && (!reader.isInvalid()))
    {
      if(reader.isString())
        {
          if(reader.decodeString(txt_value))
            {
              // qDebug() << txt_value;
              if(txt_value == "bits")
                {
                  // cvParamMap = CvParam::getCvParamsMapFromCbor(reader);
                  bits = reader.toUnsignedInteger();
                  reader.next();
                }
              else if(txt_value == "isInt")
                {
                  // cvParamMap = CvParam::getCvParamsMapFromCbor(reader);
                  isInt = reader.toBool();
                  reader.next();
                }
              else if(txt_value == "unit")
                {
                  // cvParamMap = CvParam::getCvParamsMapFromCbor(reader);
                  reader.decodeString(txt_value);
                  unit = txt_value;
                }
              else if(txt_value == "compress")
                {
                  // cvParamMap = CvParam::getCvParamsMapFromCbor(reader);
                  reader.decodeString(txt_value);
                  compress = txt_value;
                }
              else if(txt_value == "data")
                {
                  // cvParamMap = CvParam::getCvParamsMapFromCbor(reader);
                  // reader.next();
                  // qDebug() << reader.type();
                  auto r = reader.readByteArray();
                  while(r.status == QCborStreamReader::Ok)
                    {
                      byteArray += r.data;
                      r = reader.readByteArray();
                    }

                  if(r.status == QCborStreamReader::Error)
                    {
                      // handle error condition
                      // qDebug() << "error";
                      byteArray.clear();
                    }
                }
              else
                {
                  reader.next();
                }
            }
          else
            {
              reader.next();
            }
        }
      else
        {
          reader.next();
        }
    }
  reader.leaveContainer();
}

void
pappso::cbor::mzcbor::BinaryDataArray::toCbor(CborStreamWriter &writer)
{

  writer.startMap();
  writer.append("unit");
  writer.append(unit);

  writer.append("bits");
  writer.append(bits);
  writer.append("isInt");
  writer.append(isInt);

  writer.append("compress");
  writer.append(compress);

  writer.append("data");
  writer.append(byteArray);
  writer.endMap();
}

void
pappso::cbor::mzcbor::BinaryDataArray::fromMzml(QXmlStreamReader &reader)
{

  qDebug();
  //<binaryDataArray encodedLength="6380">
  std::size_t encodedLength = reader.attributes().value("encodedLength").toULongLong();
  qDebug() << "encodedLength=" << encodedLength;
  while(reader.readNext() && !reader.isEndElement())
    {
      if(reader.isStartElement())
        {
          if(reader.name().toString() == "cvParam")
            {
              QString accession = reader.attributes().value("accession").toString();

              qDebug() << "accession=" << accession;
              //<cvParam cvRef="MS" accession="MS:1000523" value="" name="64-bit float" />
              if(accession == "MS:1000523")
                {
                  bits  = 64;
                  isInt = false;
                }
              else if(accession == "MS:1000519")
                {
                  /*
                   *
[Term]
id: MS:1000519
name: 32-bit integer
def: "Signed 32-bit little-endian integer." [PSI:MS]
is_a: MS:1000518 ! binary data type
*/
                  bits  = 32;
                  isInt = true;
                }
              else if(accession == "MS:1000521")
                {
                  /*
  [Term]
  id: MS:1000521
  name: 32-bit float
  def: "32-bit precision little-endian floating point conforming to IEEE-754." [PSI:MS]
  is_a: MS:1000518 ! binary data type
  */
                  bits  = 32;
                  isInt = false;
                }
              else if(accession == "MS:1000522")
                {

                  /*
    [Term]
    id: MS:1000522
    name: 64-bit integer
    def: "Signed 64-bit little-endian integer." [PSI:MS]
    is_a: MS:1000518 ! binary data type*/
                  bits  = 64;
                  isInt = true;
                }

              //<cvParam cvRef="MS" accession="MS:1000574" value="" name="zlib compression" />
              else if(accession == "MS:1000574")
                {
                  compress = "zlib";
                }
              else if(accession == "MS:1000576")
                {
                  /*
  [Term]
  id: MS:1000576
  name: no compression
  def: "No Compression." [PSI:MS]
  is_a: MS:1000572 ! binary data compression type
  */
                  compress = "none";
                }

              else if(accession == "MS:1000515")
                {
                  unit = accession;
                }
              else if(accession == "MS:1000514")
                {
                  //            <cvParam cvRef="MS" accession="MS:1000514" value="" name="m/z array"
                  //            unitAccession="MS:1000040" unitName="m/z" unitCvRef="MS" />

                  unit = accession;
                }
              else if(accession == "MS:1000595")
                {
                  //   <cvParam cvRef="MS" accession="MS:1000595" name="time array" value=""
                  //   unitCvRef="UO" unitAccession="UO:0000031" unitName="minute"/>

                  unit = accession;
                }
              else if(accession == "MS:1000786")
                {
                  //           <cvParam cvRef="MS" accession="MS:1000786" name="non-standard data
                  //           array" value="ms level" unitCvRef="UO" unitAccession="UO:0000186"
                  //           unitName="dimensionless unit"/>
                  //
                  unit = accession;
                }
              else
                {
                  reader.raiseError(
                    QObject::tr("cvParam accession %1 is not known in binaryDataArray")
                      .arg(accession));
                  throw pappso::PappsoException(
                    QObject::tr("cvParam accession %1 is not known in binaryDataArray")
                      .arg(accession));
                }
              reader.skipCurrentElement();
            }
          else if(reader.name().toString() == "binary")
            {

              while(reader.readNext() && !reader.isEndElement())
                {
                  if(reader.isCharacters())
                    {
                      // clean content:
                      QStringView content = reader.text().trimmed();
                      if((reader.text().toString() == "\n") || (reader.text().toString() == "\n\t"))
                        {
                        }
                      else
                        {
                          // text node
                          if(!content.isEmpty())
                            {
                              // qDebug() << "text isCharacters" << content.mid(0, 10);

                              if((std::size_t)reader.text().size() != encodedLength)
                                {
                                  qWarning() << "reader.text().size() != encodedLength"
                                             << reader.text().size() << " " << encodedLength;
                                }

                              // mp_cborWriter->append("@text@");
                              // mp_cborWriter->append(content);
                              byteArray = byteArray.fromBase64(reader.text().trimmed().toLatin1());
                            }
                        }
                    }
                }
            }
          else
            {
              reader.skipCurrentElement();
            }
        }
    }
  qDebug();
}


void
pappso::cbor::mzcbor::BinaryDataArray::toMzml(QXmlStreamWriter &writer)
{
  //<binaryDataArray encodedLength="1152">
  writer.writeStartElement("binaryDataArray");
  auto base64 = byteArray.toBase64();
  writer.writeAttribute("encodedLength", QString("%1").arg(base64.size()));
  //          <cvParam cvRef="MS" accession="MS:1000514" value="" name="m/z array"
  //          unitAccession="MS:1000040" unitName="m/z" unitCvRef="MS" />
  CvParam cv_param;
  cv_param.cvRef = "MS";

  if(unit == "MS:1000514")
    {
      cv_param.accession     = unit;
      cv_param.name          = "m/z array";
      cv_param.unitCvRef     = "MS";
      cv_param.unitAccession = "MS:1000040";
      cv_param.unitName      = "m/z";
      cv_param.setValue("");
      cv_param.toMzml(writer);
    }
  else if(unit == "MS:1000515")
    {
      //<cvParam cvRef="MS" accession="MS:1000515" value="" name="intensity array"
      // unitAccession="MS:1000131" unitName="number of counts" unitCvRef="MS" />

      cv_param.accession     = unit;
      cv_param.name          = "intensity array";
      cv_param.unitCvRef     = "MS";
      cv_param.unitAccession = "MS:1000131";
      cv_param.unitName      = "number of counts";
      cv_param.setValue("");
      cv_param.toMzml(writer);
    }
  else if(unit == "MS:1000595")
    {
      //   <cvParam cvRef="MS" accession="MS:1000595" name="time array" value=""
      //   unitCvRef="UO" unitAccession="UO:0000031" unitName="minute"/>

      cv_param.accession     = unit;
      cv_param.name          = "time array";
      cv_param.unitCvRef     = "UO";
      cv_param.unitAccession = "UO:0000031";
      cv_param.unitName      = "minute";
      cv_param.setValue("");
      cv_param.toMzml(writer);
    }
  else if(unit == "MS:1000786")
    {
      //           <cvParam cvRef="MS" accession="MS:1000786" name="non-standard data
      //           array" value="ms level" unitCvRef="UO" unitAccession="UO:0000186"
      //           unitName="dimensionless unit"/>
      //
      cv_param.accession     = unit;
      cv_param.name          = "non-standard data array";
      cv_param.unitCvRef     = "UO";
      cv_param.unitAccession = "UO:0000186";
      cv_param.unitName      = "dimensionless unit";
      cv_param.setValue("ms level");
      cv_param.toMzml(writer);
    }
  else
    {
      throw pappso::PappsoException(
        QObject::tr("unit accession %1 is not known in binaryDataArray").arg(unit));
    }

  //          <cvParam cvRef="MS" accession="MS:1000523" value="" name="64-bit float" />

  cv_param.unitCvRef.clear();
  cv_param.unitAccession.clear();
  cv_param.unitName.clear();
  cv_param.setValue("");
  if(isInt)
    {
      /*
  id: MS:1000519
  name: 32-bit integer
  def: "Signed 32-bit little-endian integer." [PSI:MS]
  is_a: MS:1000518 ! binary data type*/
      if(bits == 32)
        {
          cv_param.accession = "MS:1000519";
          cv_param.name      = "32-bit integer";
          cv_param.toMzml(writer);
        }
      else if(bits == 64)
        {
          /*
  [Term]
  id: MS:1000522
  name: 64-bit integer
  def: "Signed 64-bit little-endian integer." [PSI:MS]
  is_a: MS:1000518 ! binary data type*/
          cv_param.accession = "MS:1000522";
          cv_param.name      = "64-bit integer";
          cv_param.toMzml(writer);
        }
    }
  else
    {
      if(bits == 64)
        {
          cv_param.accession = "MS:1000523";
          cv_param.name      = "64-bit float";
          cv_param.toMzml(writer);
        }
      else if(bits == 32)
        {
          /*
 [Term]
 id: MS:1000521
 name: 32-bit float
 def: "32-bit precision little-endian floating point conforming to IEEE-754." [PSI:MS]
 is_a: MS:1000518 ! binary data type
 */
          cv_param.accession = "MS:1000521";
          cv_param.name      = "32-bit float";
          cv_param.toMzml(writer);
        }
    }
  //          <cvParam cvRef="MS" accession="MS:1000574" value="" name="zlib compression" />
  /*

[Term]
id: MS:1000520
name: 16-bit float
def: "OBSOLETE Signed 16-bit float." [PSI:MS]
is_a: MS:1000518 ! binary data type
is_obsolete: true


[Term]
id: MS:1000523
name: 64-bit float
def: "64-bit precision little-endian floating point conforming to IEEE-754." [PSI:MS]
is_a: MS:1000518 ! binary data type
*/

  if(compress == "zlib")
    {
      cv_param.accession = "MS:1000574";
      cv_param.name      = "zlib compression";
      cv_param.toMzml(writer);
    }
  else if(compress == "none")
    { /*
[Term]
id: MS:1000576
name: no compression
def: "No Compression." [PSI:MS]
is_a: MS:1000572 ! binary data compression type
*/
      cv_param.accession = "MS:1000576";
      cv_param.name      = "no compression";
      cv_param.toMzml(writer);
    }

  //         <binary>eJwl0W9oW1U
  // writer.writeStartElement("binary");
  writer.writeTextElement("binary", base64);
  //        </binary>
  // writer.writeEndElement();
  //      </binaryDataArray>
  writer.writeEndElement();
}


void
pappso::cbor::mzcbor::BinaryDataArray::decodeVector(std::size_t estimated_length,
                                                    std::vector<double> &double_list) const
{
  qDebug();
  int size_in_byte = 8;
  if(bits == 32)
    {
      size_in_byte = 4;
    }

  qDebug();
  //  if(result.decodingStatus == QByteArray::Base64DecodingStatus::Ok)
  //    { // Allocate buffer for decompressed data
  if(compress == "zlib")
    {
      qDebug();
      std::vector<unsigned char> data_heap;
      uLongf decompressedSize = estimated_length * size_in_byte; // Estimate size
      data_heap.resize(decompressedSize);

      // Decompress the data
      int result_zlib = uncompress(
        data_heap.data(), &decompressedSize, (Bytef *)byteArray.constData(), byteArray.size());

      if(result_zlib != Z_OK)
        {
          throw pappso::PappsoException(QObject::tr("Decompression failed: %1").arg(result_zlib));
        }

      // Resize the vector to the actual decompressed size
      data_heap.resize(decompressedSize);
      double_list.resize(decompressedSize / size_in_byte);


      // double *double_ptr = (double *)&decompressedData[0];
      std::size_t j = 0;
      for(std::size_t i = 0; i < data_heap.size(); i += size_in_byte)
        {
          if(bits == 32)
            {
              if(isInt)
                {
                  double_list[j] = *(std::int32_t *)&data_heap[i];
                }
              else
                {
                  double_list[j] = *(std::float_t *)&data_heap[i];
                }
            }
          else
            {
              if(isInt)
                {
                  double_list[j] = *(std::int64_t *)&data_heap[i];
                }
              else
                {
                  double_list[j] = *(double *)&data_heap[i];
                }
            }
          // double_ptr++;
          j++;
        }
    }
  else if(compress == "none")
    {

      qDebug() << size_in_byte << " " << byteArray.size();

      double_list.resize(byteArray.size() / size_in_byte);
      // double *double_ptr = (double *)&decompressedData[0];
      std::size_t j = 0;
      for(std::size_t i = 0; i < (std::size_t)byteArray.size(); i += size_in_byte)
        {
          qDebug() << " i=" << i;
          if(bits == 32)
            {
              if(isInt)
                {
                  double_list[j] = *(std::int32_t *)&byteArray.constData()[i];
                }
              else
                {
                  double_list[j] = *(std::float_t *)&byteArray.constData()[i];
                }
            }
          else
            {
              if(isInt)
                {
                  double_list[j] = *(std::int64_t *)&byteArray.constData()[i];
                }
              else
                {
                  qDebug();
                  double_list[j] = *(double *)&byteArray.constData()[i];
                }
            }
          // double_ptr++;
          j++;
        }
    }


  qDebug();
  // std::vector<double> v(decompressedData.cbegin(), decompressedData.cend());
  // qDebug() << j << " " << double_list.size();
}

bool
pappso::cbor::mzcbor::BinaryDataArray::isIntensity() const
{
  // <cvParam cvRef="MS" accession="MS:1000515" value="" name="intensity array"
  // unitAccession="MS:1000131" unitName="number of counts" unitCvRef="MS" />

  return unit == "MS:1000515";
}

bool
pappso::cbor::mzcbor::BinaryDataArray::isMz() const
{
  //           <cvParam cvRef="MS" accession="MS:1000514" value="" name="m/z array"
  //           unitAccession="MS:1000040" unitName="m/z" unitCvRef="MS" />

  return unit == "MS:1000514";
}

QJsonObject
pappso::cbor::mzcbor::BinaryDataArray::toJsonObject(std::size_t estimated_length) const
{
  QJsonObject binary_data;
  binary_data.insert("unit", unit);
  std::vector<double> double_list;
  decodeVector(estimated_length, double_list);

  QJsonArray double_array;

  for(auto &value : double_list)
    {
      double_array.append(value);
    }

  binary_data.insert("array", double_array);
  return binary_data;
}
