/**
 * \file pappsomspp/vendors/tims/timsbindec.h
 * \date 23/08/2019
 * \author Olivier Langella
 * \brief binary file handler of Bruker's TimsTof raw data
 */

/*******************************************************************************
 * Copyright (c) 2019 Olivier Langella <Olivier.Langella@u-psud.fr>.
 *
 * This file is part of the PAPPSOms++ library.
 *
 *     PAPPSOms++ 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++ 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++.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

#include "timsbindec.h"
#include "../../../pappsomspp/pappsoexception.h"
#include "../../../pappsomspp/exception/exceptionnotimplemented.h"
#include "timsframetype1.h"
#include <QDataStream>
#include <zstd.h>

using namespace pappso;

TimsBinDec::TimsBinDec(const QFileInfo &timsBinFile, int timsCompressionType)
  : m_timsBinFile(timsBinFile.absoluteFilePath())
{
  m_timsCompressionType = timsCompressionType;
  if((timsCompressionType != 1) && (timsCompressionType != 2))
    {
      throw pappso::ExceptionNotImplemented(
        QObject::tr("compression type %1 not handled by this library")
          .arg(timsCompressionType));
    }
  if(m_timsBinFile.isEmpty())
    {
      throw pappso::PappsoException(
        QObject::tr("No TIMS binary file name specified"));
    }
  QFile file(m_timsBinFile);
  if(!file.open(QIODevice::ReadOnly))
    {
      throw PappsoException(
        QObject::tr("ERROR opening TIMS binary file %1 for read")
          .arg(timsBinFile.absoluteFilePath()));
    }
}


TimsBinDec::TimsBinDec(const TimsBinDec &other)
  : m_timsBinFile(other.m_timsBinFile)
{
}

TimsBinDec::~TimsBinDec()
{
  if(mpa_memoryBuffer != nullptr)
    {
      delete[] mpa_memoryBuffer;
      mpa_memoryBuffer = nullptr;
    }


  if(mpa_decompressMemoryBuffer != nullptr)
    {
      delete[] mpa_decompressMemoryBuffer;
    }
}


void
TimsBinDec::indexingFile()
{

  qDebug();
  QFile file(m_timsBinFile);
  QDataStream bin_in(&file);
  bin_in.setByteOrder(QDataStream::ByteOrder::LittleEndian);
  // m_indexArray.push_back(0);

  // QChar first_char;
  // txt_in >> first_char;
  quint32 number_in;
  qint64 position(0);
  qint8 trash;
  while(!bin_in.atEnd())
    {
      while((!bin_in.atEnd()) && (position % 4 != 0))
        {
          bin_in >> trash;
          position += 1;
          if(trash != 0)
            { /*
               throw PappsoException(
                 QObject::tr("ERROR reading TIMS frame %1 TIMS binary file %2: "
                             "trash%3 != 0")
                   .arg(m_indexArray.size())
                   .arg(m_timsBinFile.fileName())
                   .arg(trash));*/
            }
        }
      quint32 frame_len(0);
      while((!bin_in.atEnd()) && (frame_len == 0))
        {
          bin_in >> frame_len;
          // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__
          //      << " i=" << i;
          position += 4;
        }

      // m_indexArray.push_back(position - 4);
      /*
            qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__
                     << " frame_len=" << frame_len
                     << " frameid=" << m_indexArray.size() - 1
                     << " back=" << m_indexArray.back() << " position=" <<
         position;


            quint64 next_frame = m_indexArray.back() + frame_len;
      */

      bin_in >> number_in;
      position += 4;
      qDebug() << " number_in(nb scans)=" << number_in
               << " position=" << position;
      ;
      /*

            while((!bin_in.atEnd()) && (position < next_frame))
              {
                bin_in >> trash;
                // qDebug() << __FILE__ << " " << __FUNCTION__ << " " <<
         __LINE__
                //      << " i=" << i;
                position += 1;
              }*/
    }
  qDebug();
}

TimsFrameSPtr
TimsBinDec::getTimsFrameSPtrByOffset(std::size_t timsId, std::size_t timsOffset)
{
  qDebug() << "timsId:" << timsId << "timsOffset:" << timsOffset;

  // QMutexLocker locker(&m_mutex);
  QFile file(m_timsBinFile);
  if(!file.open(QIODevice::ReadOnly))
    {
      throw PappsoException(
        QObject::tr("ERROR opening TIMS binary file %1 for read")
          .arg(m_timsBinFile));
    }
  qDebug();
  bool seekpos_ok = file.seek(timsOffset);
  if(!seekpos_ok)
    {
      throw PappsoException(
        QObject::tr("ERROR reading TIMS frame %1 TIMS binary file %2: "
                    "m_timsBinFile.seek(%3) failed")
          .arg(timsId)
          .arg(m_timsBinFile)
          .arg(timsOffset));
    }

  qDebug();
  quint32 frame_length;
  file.read((char *)&frame_length, 4);
  // frame_length = qToBigEndian(frame_length);

  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__
  //       << " frame_length=" << frame_length;

  qDebug();
  quint32 scan_number;
  file.read((char *)&scan_number, 4);
  // scan_number = qToBigEndian(scan_number);
  frame_length -= 8;

  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__
  //        << " pos=" << m_timsBinFile.pos();

  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__
  //       << " scan_number=" << scan_number;
  // m_timsBinFile.seek(m_indexArray.at(timsId) + 8);


  qDebug();

  // if (m_memoryBufferSize
  if(mpa_memoryBuffer == nullptr)
    {
      qDebug() << "mpa_memoryBuffer == nullptr";
      m_memoryBufferSize = (qint64)frame_length + 10;
      mpa_memoryBuffer   = new char[m_memoryBufferSize];
    }
  if((m_memoryBufferSize - 10) < frame_length)
    {
      if(mpa_memoryBuffer != nullptr)
        {
          delete[] mpa_memoryBuffer;
        }
      m_memoryBufferSize = (qint64)frame_length + 10;
      mpa_memoryBuffer   = new char[m_memoryBufferSize];
    }


  // QByteArray frame_byte_array(mpa_memoryBuffer, m_memoryBufferSize);

  qDebug();
  qint64 read_length = file.read(mpa_memoryBuffer, (qint64)frame_length + 2);
  qDebug();
  file.close();

  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__
  //        << " +frame_length-1="
  //      << (quint8) * (frame_byte_array.constData() + frame_length - 1);
  // qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__
  //       << " +frame_length="
  //     << (quint8) * (frame_byte_array.constData() + frame_length);
  // m_timsBinFile.seek(m_indexArray.at(timsId) + 8);

  if(read_length - 2 != (qint64)frame_length)
    {
      throw PappsoException(
        QObject::tr("ERROR reading TIMS frame %1 TIMS binary file %2: "
                    "read_length=%3 != %4frame_length")
          .arg(timsId)
          .arg(m_timsBinFile)
          .arg(read_length)
          .arg(frame_length));
    }
  qDebug();
  TimsFrameSPtr frame_sptr;
  if(frame_length > 0)
    {
      qDebug();
      if(m_timsCompressionType == 2)
        {
          auto decompressed_size2 =
            ZSTD_getFrameContentSize(mpa_memoryBuffer, frame_length);
          qDebug();
          if(decompressed_size2 == ZSTD_CONTENTSIZE_UNKNOWN)
            {
              throw PappsoException(
                QObject::tr("ERROR reading TIMS frame %1 TIMS binary file %2: "
                            " decompressed_size2 == ZSTD_CONTENTSIZE_UNKNOWN, "
                            "frame_length=%3")
                  .arg(timsId)
                  .arg(m_timsBinFile)
                  .arg(frame_length));
            }
          qDebug();
          if(decompressed_size2 == ZSTD_CONTENTSIZE_ERROR)
            {
              qDebug();
              throw PappsoException(
                QObject::tr("ERROR reading TIMS frame %1 TIMS binary file %2: "
                            " decompressed_size2 == ZSTD_CONTENTSIZE_ERROR, "
                            "frame_length=%3")
                  .arg(timsId)
                  .arg(m_timsBinFile)
                  .arg(frame_length));
            }
          qDebug() << " decompressed_size2=" << decompressed_size2;

          if(m_decompressMemoryBufferSize < (decompressed_size2 + 10))
            {
              if(mpa_decompressMemoryBuffer != nullptr)
                {
                  delete[] mpa_decompressMemoryBuffer;
                }
              m_decompressMemoryBufferSize = decompressed_size2 + 10;
              mpa_decompressMemoryBuffer =
                new char[m_decompressMemoryBufferSize];
            }
          std::size_t decompressed_size =
            ZSTD_decompress(mpa_decompressMemoryBuffer,
                            m_decompressMemoryBufferSize,
                            mpa_memoryBuffer,
                            frame_length);
          qDebug();

          if(decompressed_size != decompressed_size2)
            {
              throw PappsoException(
                QObject::tr("ERROR reading TIMS frame %1 TIMS binary file %2: "
                            "decompressed_size != decompressed_size2")
                  .arg(timsId)
                  .arg(m_timsBinFile)
                  .arg(decompressed_size)
                  .arg(decompressed_size2));
            }

          qDebug();

          frame_sptr = std::make_shared<TimsFrame>(
            timsId, scan_number, mpa_decompressMemoryBuffer, decompressed_size);
        }
      else
        {

          if(m_timsCompressionType == 1)
            {
              frame_sptr = std::make_shared<TimsFrameType1>(
                timsId, scan_number, mpa_memoryBuffer, frame_length);
            }
        }
      // delete[] mpa_decompressMemoryBuffer;
    }
  else
    {
      frame_sptr = std::make_shared<TimsFrame>(timsId, scan_number, nullptr, 0);
    }
  return frame_sptr;
}
/*
TimsFrameCstSPtr
TimsBinDec::getTimsFrameCstSPtr(std::size_t timsId)
{
  return getTimsFrameCstSPtrByOffset(timsId, m_indexArray[timsId]);
}
*/
