/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the MsXpertSuite project.
 *
 * The MsXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// Std lib includes


/////////////////////// Qt includes
#include <QDebug>
#include <QFileInfo>


/////////////////////// IsoSpec


/////////////////////// Local includes
#include "MassDataCborMassSpectrumHandler.hpp"


namespace MsXpS
{

namespace libXpertMass
{

// FIXME TODO DOCUMENTATION

MassDataCborMassSpectrumHandler::MassDataCborMassSpectrumHandler(
  QObject *parent_p)
  : MassDataCborBaseHandler(parent_p)
{
}


// When providing a Trace, it is implicit that this is to write that to file.
MassDataCborMassSpectrumHandler::MassDataCborMassSpectrumHandler(
  QObject *parent_p, const pappso::Trace &trace)
  : MassDataCborBaseHandler(parent_p), m_trace(trace)
{
}


MassDataCborMassSpectrumHandler::~MassDataCborMassSpectrumHandler()
{
}


bool
MassDataCborMassSpectrumHandler::writeFile(const QString &output_file_name)
{
  // The format for this kind of CBOR pappso::Trace data is the following:

  // First the quint64 that represents MassDataType. In our specific case, that
  // must be MassDataType::MASS_SPECTRUM.

  // Then there is the title of data, which is according to the specification:
  //
  // Major type 3:  a text string, specifically a string of Unicode characters
  // that is encoded as UTF-8 (that is, not a QByteArray, but a QString).

  // Then there is a MAP with the following key/value pairs:

  // start_map

  // "X_LABEL" / value (text string)
  // "Y_LABEL" / value (text string)
  //
  // "X_DATA" / value (base64 ByteArray)
  // "Y_DATA" / value (base64 ByteArray)

  // end_map

  QString local_file_name = output_file_name;

  if(local_file_name.isEmpty())
    local_file_name = m_outputFileName;

  QFile file(local_file_name);

  bool res = file.open(QIODevice::WriteOnly);

  if(!res)
    {
      qDebug() << "Failed to open the file for write.";
      return false;
    }

  msp_writer = std::make_shared<QCborStreamWriter>(&file);

  // qDebug() << "Now writing data to file:" << local_file_name;

  // Start of array containing all mapped items
  msp_writer->startMap(7);

  // First the MassDataType: this is the very *first* data bit in the data
  // stream.
  msp_writer->append("DATA_TYPE");
  msp_writer->append(static_cast<quint64>(MassDataType::MASS_SPECTRUM));

  msp_writer->append("TITLE");
  msp_writer->append(m_title);

  msp_writer->append("TRACE_COLOR");
  msp_writer->append(m_colorByteArray);

  msp_writer->append("X_LABEL");
  msp_writer->append("m/z");

  msp_writer->append("X_DATA");
  msp_writer->append(m_trace.xAsBase64Encoded());

  msp_writer->append("Y_LABEL");
  msp_writer->append("intensity");

  msp_writer->append("Y_DATA");
  msp_writer->append(m_trace.yAsBase64Encoded());

  msp_writer->endMap();
  // Close the map now.

  file.close();

  return res;
}


void
MassDataCborMassSpectrumHandler::writeByteArray(QByteArray &byte_array)
{
  msp_writer = std::make_shared<QCborStreamWriter>(&byte_array);

  // qDebug() << "Now writing data byte array.";

  // Start of array containing all mapped items
  msp_writer->startMap(7);

  // First the MassDataType: this is the very *first* data bit in the data
  // stream.
  msp_writer->append("DATA_TYPE");
  msp_writer->append(static_cast<quint64>(MassDataType::MASS_SPECTRUM));

  msp_writer->append("TITLE");
  msp_writer->append(m_title);

  msp_writer->append("TRACE_COLOR");
  msp_writer->append(m_colorByteArray);

  msp_writer->append("X_LABEL");
  msp_writer->append("m/z");

  msp_writer->append("X_DATA");
  msp_writer->append(m_trace.xAsBase64Encoded());

  msp_writer->append("Y_LABEL");
  msp_writer->append("intensity");

  msp_writer->append("Y_DATA");
  msp_writer->append(m_trace.yAsBase64Encoded());

  msp_writer->endMap();
  // Close the map now.
}


bool
MassDataCborMassSpectrumHandler::readContext(QCborStreamReaderSPtr &reader_sp)
{
  // The format for this kind of CBOR pappso::Trace data is the following:

  // The whole file is actually a map that contains the key/value
  // pairs below.

  // The text string values below are described in this way in the specif:
  // Major type 3:  a text string, specifically a string of Unicode characters
  // that is encoded as UTF-8 (that is, not a QByteArray, but a QString).

  // The containers, map, strings and byte arrays dot not need the reader to
  // explicitely advance to next().

  // The simple value, like integers, do need that reader's next() call.

  // So now, the MAP with the following key/value pairs:

  // start_map

  // "DATA_TYPE" / value (quint64)
  // "TITLE" / value (text string)
  // "X_LABEL" / value (text string)
  // "Y_LABEL" / value (text string)
  //
  // "X_DATA" / value (base64 ByteArray)
  // "Y_DATA" / value (base64 ByteArray)

  // end_map

  // These are needed to store the x and y data as two different byte arrays.

  while(reader_sp->lastError() == QCborError::NoError && reader_sp->hasNext())
    {
      QCborStreamReader::Type type = reader_sp->type();
      // qDebug() << "Type is:" << type;

      if(type == QCborStreamReader::UnsignedInteger)
        {
          // In this format, the QCborStreamReader::UnsignedInteger datum is
          // only got as the very first data bit in the file. This bit of
          // information is a quint64 representing MassDataType::MASS_SPECTRUM.

          // Now check that the read value actually corresponds to the expected
          // mass data type for which this object is working!

          if(m_currentKey == "DATA_TYPE")
            {
              // quint64 data_type = 0;
              // data_type = reader_sp->toUnsignedInteger();
              // qDebug() << "The mass data type:" << data_type;

              if(static_cast<MassDataType>(reader_sp->toUnsignedInteger()) !=
                 MassDataType::MASS_SPECTRUM)
                {
                  qDebug()
                    << "The expected DATA_TYPE::MASS_SPECTRUM was not found.";
                  return false;
                }

              m_massDataType = MassDataType::MASS_SPECTRUM;

              m_currentKey = QString();
            }

          // qDebug() << "At this point, check if it has next:"
          //<< reader_sp->hasNext();

          // We had what we wanted, go to next.
          reader_sp->next();
        }
      else if(type == QCborStreamReader::String)
        {
          // Read a CBOR string, concatenating all
          // the chunks into a single string.
          QString str;
          auto chunk = reader_sp->readString();
          while(chunk.status == QCborStreamReader::Ok)
            {
              str += chunk.data;
              chunk = reader_sp->readString();
            }

          if(chunk.status == QCborStreamReader::Error)
            {
              // handle error condition
              qDebug() << "There was an error reading string chunk.";
              str.clear();
            }

          // qDebug() << "The string that was read:" << str;

          // If the current key is empty, this string value is a new key or tag.
          // Otherwise, it's a value.

          if(m_currentKey.isEmpty())
            {
              // qDebug() << "Setting m_currentKey:" << str;
              m_currentKey = str;
            }
          else
            {
              // Depending on what we had already, we will set the proper member
              // datum.
              if(m_currentKey == "TITLE")
                {
                  // qDebug() << "Setting m_title:" << str;
                  m_title = str;
                }

              if(m_currentKey == "X_LABEL")
                {
                  // qDebug() << "Setting m_xLabel:" << str;

                  m_xLabel = str;
                }

              if(m_currentKey == "Y_LABEL")
                {
                  // qDebug() << "Setting m_yLabel:" << str;
                  m_yLabel = str;
                }

              // At this point we can reset m_currentKey.
              m_currentKey = QString();
            }
        }

      // The ByteArray data for X_DATA and Y_DATA.
      // They are preceded by the keys "X_DATA" and "Y_DATA" respectively.

      else if(type == QCborStreamReader::ByteArray)
        {
          // Read a byte array. That could be either the X_DATA or the Y_DATA.

          QByteArray array;

          auto chunk = reader_sp->readByteArray();

          while(chunk.status == QCborStreamReader::Ok)
            {
              array.append(chunk.data);
              chunk = reader_sp->readByteArray();
            }

          if(m_currentKey == "X_DATA")
            {
              m_xBase64Data = array;
            }
          else if(m_currentKey == "Y_DATA")
            {
              m_yBase64Data = array;
            }
          else if(m_currentKey == "TRACE_COLOR")
            {
              m_colorByteArray = array;
            }
          else
            {
              qDebug() << "Error in the data read from file.";
              return false;
            }

          m_currentKey = QString();
        }

      // The MAP has the following key/value pairs:

      // start_map

      // "TITLE" / value (text string)
      // "X_LABEL" / value (text string)
      // "Y_LABEL" / value (text string)
      //
      // "X_DATA" / value (base64 ByteArray)
      // "Y_DATA" / value (base64 ByteArray)

      // end_map

      else if(type == QCborStreamReader::Map)
        {

          reader_sp->enterContainer();

          // Read elements until end of map is reached
          bool res = readContext(reader_sp);
          if(res)
            reader_sp->leaveContainer();
          else
            return false;
        }
      else
        {
          // Ignore all other types, go to the next element
          reader_sp->next();
        }
    }

  if(reader_sp->lastError() != QCborError::NoError)
    qDebug() << "There was an error: " << reader_sp->lastError()
             << "Returning false.";

  // Return true if there were no errors
  // qDebug() << "Returning: " << !reader_sp->lastError();

  return !reader_sp->lastError();
}


bool
MassDataCborMassSpectrumHandler::readByteArray(const QByteArray &byte_array)
{
  msp_reader = std::make_shared<QCborStreamReader>(byte_array);

  bool res = readContext(msp_reader);

  // qDebug() << "After finishing the read, m_title is:" << m_title
  //<< "and mass data type is:" << static_cast<quint64>(m_massDataType);

  // At this point we need to decode the arrays and with the data initialize the
  // pappso::Trace.

  QByteArray x_array;
  QByteArray y_array;

  QByteArray::FromBase64Result decoding_result = QByteArray::fromBase64Encoding(
    m_xBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);

  if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok)
    x_array = decoding_result.decoded;
  else
    {
      qDebug() << "Failed to decode the " << m_xLabel << "data";
      return false;
    }

  decoding_result = QByteArray::fromBase64Encoding(
    m_yBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);

  if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok)
    y_array = decoding_result.decoded;
  else
    {
      qDebug() << "Failed to decode the " << m_yLabel << "data";
      return false;
    }

  m_trace.clear();

  m_trace.initialize(QString(x_array), QString(y_array));

  return res;
}


bool
MassDataCborMassSpectrumHandler::readFile(const QString &input_file_name)
{
  // The format for this kind of CBOR pappso::Trace data is the following:

  // First the quint64 that represents MassDataType. In our specific case, that
  // must be MassDataType::MASS_SPECTRUM.

  // Then there is the title of data, which is according to the specification:
  //
  // Major type 3:  a text string, specifically a string of Unicode characters
  // that is encoded as UTF-8 (that is, not a QByteArray, but a QString).

  // Then there is a MAP with the following key/value pairs:

  // start_map

  // "X_LABEL" / value (text string)
  // "Y_LABEL" / value (text string)
  //
  // "X_DATA" / value (base64 ByteArray)
  // "Y_DATA" / value (base64 ByteArray)

  // end_map


  QString local_file_name = input_file_name;

  if(local_file_name.isEmpty())
    local_file_name = m_inputFileName;

  QFileInfo file_info(local_file_name);

  if(!file_info.exists())
    {
      qDebug() << "File not found.";
      return false;
    }

  QFile file(local_file_name);

  bool res = file.open(QIODevice::ReadOnly);

  if(!res)
    {
      qDebug() << "Failed to open the file for read.";
      return false;
    }

  // qDebug() << "Now starting the CBOR data read.";

  msp_reader = std::make_shared<QCborStreamReader>(&file);

  res = readContext(msp_reader);

  file.close();

  // qDebug() << "After finishing the read, m_title is:" << m_title
  //<< "and mass data type is:" << static_cast<quint64>(m_massDataType);

  // At this point we need to decode the arrays and with the data initialize the
  // pappso::Trace.

  QByteArray x_array;
  QByteArray y_array;

  QByteArray::FromBase64Result decoding_result = QByteArray::fromBase64Encoding(
    m_xBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);

  if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok)
    x_array = decoding_result.decoded;
  else
    {
      qDebug() << "Failed to decode the " << m_xLabel << "data";
      return false;
    }

  decoding_result = QByteArray::fromBase64Encoding(
    m_yBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);

  if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok)
    y_array = decoding_result.decoded;
  else
    {
      qDebug() << "Failed to decode the " << m_yLabel << "data";
      return false;
    }

  m_trace.clear();

  m_trace.initialize(QString(x_array), QString(y_array));

  return res;
}


void
MassDataCborMassSpectrumHandler::setXLabel(const QString &label)
{
  m_xLabel = label;
}


QString
MassDataCborMassSpectrumHandler::getXLabel() const
{
  return m_xLabel;
}


void
MassDataCborMassSpectrumHandler::setYLabel(const QString &label)
{
  m_yLabel = label;
}


QString
MassDataCborMassSpectrumHandler::getYLabel() const
{
  return m_yLabel;
}

void
MassDataCborMassSpectrumHandler::setTrace(const pappso::Trace &trace)
{
  m_trace = trace;
}


pappso::Trace
MassDataCborMassSpectrumHandler::getTrace() const
{
  return m_trace;
};


void
MassDataCborMassSpectrumHandler::clearTrace()
{
  m_trace.clear();
}


void
MassDataCborMassSpectrumHandler::setTraceColor(
  const QByteArray &color_byte_array)
{
  m_colorByteArray = color_byte_array;
}


QByteArray
MassDataCborMassSpectrumHandler::getTraceColor() const
{
  return m_colorByteArray;
}


} // namespace libXpertMass


} // namespace MsXpS

