/**
 * \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 "pappsomspp/core/vendors/tims/timsbindec.h"
#include "pappsomspp/core/pappsoexception.h"
#include "pappsomspp/core/exception/exceptionnotimplemented.h"
#include "pappsomspp/core/vendors/tims/timsframetype1.h"
#include <QDataStream>
#include <QThread>
#include <zstd.h>
#include "pappsomspp/core/vendors/tims/timsframerawdatachunck.h"
#include "pappsomspp/core/vendors/tims/timsdata.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_decompressMemoryBuffer != nullptr)
    {
      delete[] mpa_decompressMemoryBuffer;
    }

  if(mp_fileLinear != nullptr)
    {
      mp_fileLinear->close();
      delete mp_fileLinear;
    }
  if(mp_fileRandom != nullptr)
    {
      mp_fileRandom->close();
      delete mp_fileRandom;
    }
}

void
pappso::TimsBinDec::closeLinearRead()
{
  // qDebug();
  if(mp_fileLinear != nullptr)
    {
      mp_fileLinear->close();
      delete mp_fileLinear;
    }
  mp_fileLinear  = nullptr;
  m_firstFrameId = 0;
  m_lastFrameId  = 0;
  m_linearAccessRawDataChunckList.resize(0);
  // qDebug();
}


QFile *
TimsBinDec::getQfileLinear(std::size_t frameId,
                           const std::vector<pappso::TimsFrameRecord> &frame_record_list)
{
  if(mp_fileLinear == nullptr)
    {
      mp_fileLinear = new QFile(m_timsBinFile);
      if(!mp_fileLinear->open(QIODevice::ReadOnly))
        {
          throw PappsoException(
            QObject::tr("ERROR opening TIMS binary file %1 for read").arg(m_timsBinFile));
        }

      startLinearRead(frameId, m_linearAccessRawDataChunckDequeSize, frame_record_list);
    }

  return mp_fileLinear;
}

QFile *
pappso::TimsBinDec::getQfileRandom()
{
  if(mp_fileRandom == nullptr)
    {
      mp_fileRandom = new QFile(m_timsBinFile);
      if(!mp_fileRandom->open(QIODevice::ReadOnly))
        {
          throw PappsoException(
            QObject::tr("ERROR opening TIMS binary file %1 for read").arg(m_timsBinFile));
        }
    }
  return mp_fileRandom;
}

TimsFrameSPtr
TimsBinDec::getTimsFrameSPtrByOffset(std::size_t frameId,
                                     const std::vector<pappso::TimsFrameRecord> &frame_record_list)
{

  qDebug() << "frameId:" << frameId;

  // QMutexLocker locker(&m_mutex);
  QFile *p_file = getQfileLinear(frameId, frame_record_list);

  if(mp_fileLinear->pos() < (qint64)frame_record_list[m_firstFrameId].tims_offset)
    {
    }
  else
    {
      if(frameId > m_lastFrameId)
        {
          if(frameId - m_lastFrameId < m_linearForwardThreshold)
            {
              // move forward
              moveLinearReadForward(frame_record_list);
            }
        }
    }

  auto it = std::find_if(m_linearAccessRawDataChunckList.begin(),
                         m_linearAccessRawDataChunckList.end(),
                         [frameId](const TimsFrameRawDataChunck &chunck) {
                           if(chunck.getFrameId() == frameId)
                             return true;
                           return false;
                         });
  if(it != m_linearAccessRawDataChunckList.end())
    {
      try
        {
          return getTimsFrameFromRawDataChunck(*it);
        }
      catch(PappsoException &error)
        {

          throw PappsoException(QObject::tr("ERROR reading TIMS binary file %1 with linear QFile: "
                                            "%2")
                                  .arg(m_timsBinFile)
                                  .arg(error.qwhat()));
        }
    }

  // random access file
  // qDebug();
  p_file          = getQfileRandom();
  bool seekpos_ok = p_file->seek(frame_record_list[frameId].tims_offset);
  if(!seekpos_ok)
    {
      throw PappsoException(QObject::tr("ERROR reading TIMS frame %1 TIMS binary file %2: "
                                        "m_timsBinFile.seek(%3) failed")
                              .arg(frameId)
                              .arg(m_timsBinFile)
                              .arg(frame_record_list[frameId].tims_offset));
    }


  try
    {
      m_randemAccessFrameRawDataChunck.readTimsFrame(p_file, frameId, frame_record_list);
    }
  catch(PappsoException &error)
    {

      throw PappsoException(QObject::tr("ERROR reading TIMS binary file %1 with random QFile: "
                                        "%2")
                              .arg(m_timsBinFile)
                              .arg(error.qwhat()));
    }

  return getTimsFrameFromRawDataChunck(m_randemAccessFrameRawDataChunck);
}

void
pappso::TimsBinDec::moveLinearReadForward(
  const std::vector<pappso::TimsFrameRecord> &frame_record_list)
{
  // qDebug();
  for(std::size_t i = 0; i < m_linearForwardThreshold; i++)
    {
      auto it =
        std::min_element(m_linearAccessRawDataChunckList.begin(),
                         m_linearAccessRawDataChunckList.end(),
                         [](const TimsFrameRawDataChunck &a, const TimsFrameRawDataChunck &b) {
                           return a.getFrameId() < b.getFrameId();
                         });
      m_lastFrameId++;
      m_firstFrameId++;
      if(m_lastFrameId >= frame_record_list.size())
        break;
      it->readTimsFrame(mp_fileLinear, m_lastFrameId, frame_record_list);
    }
  // qDebug();
}


TimsFrameSPtr
TimsBinDec::getTimsFrameFromRawDataChunck(const TimsFrameRawDataChunck &raw_data_chunck)
{
  // qDebug();
  TimsFrameSPtr frame_sptr;
  if(raw_data_chunck.getCompressedSize() > 0)
    {
      // qDebug();
      if(m_timsCompressionType == 2)
        {
          auto decompressed_size2 = ZSTD_getFrameContentSize(raw_data_chunck.getMemoryBuffer(),
                                                             raw_data_chunck.getCompressedSize());
          // qDebug();
          if(decompressed_size2 == ZSTD_CONTENTSIZE_UNKNOWN)
            {
              throw PappsoException(QObject::tr("ERROR TimsBinDec::getTimsFrameFromRawDataChunck "
                                                "reading TIMS frame %1 TIMS binary file %2: "
                                                " decompressed_size2 == ZSTD_CONTENTSIZE_UNKNOWN, "
                                                "frame_length=%3")
                                      .arg(raw_data_chunck.getFrameId())
                                      .arg(m_timsBinFile)
                                      .arg(raw_data_chunck.getFrameLength()));
            }
          // qDebug();
          if(decompressed_size2 == ZSTD_CONTENTSIZE_ERROR)
            {
              // qDebug();
              throw PappsoException(QObject::tr("ERROR TimsBinDec::getTimsFrameFromRawDataChunck "
                                                "reading TIMS frame %1 TIMS binary file %2: "
                                                " decompressed_size2 == ZSTD_CONTENTSIZE_ERROR, "
                                                "frame_length=%3")
                                      .arg(raw_data_chunck.getFrameId())
                                      .arg(m_timsBinFile)
                                      .arg(raw_data_chunck.getFrameLength()));
            }
          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,
                                                          raw_data_chunck.getMemoryBuffer(),
                                                          raw_data_chunck.getCompressedSize());
          // qDebug();

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

          // qDebug();

          frame_sptr = std::make_shared<TimsFrame>(raw_data_chunck.getFrameId(),
                                                   raw_data_chunck.getFrameNumberOfScans(),
                                                   mpa_decompressMemoryBuffer,
                                                   decompressed_size);
        }
      else
        {

          if(m_timsCompressionType == 1)
            {
              frame_sptr = std::make_shared<TimsFrameType1>(raw_data_chunck.getFrameId(),
                                                            raw_data_chunck.getFrameNumberOfScans(),
                                                            raw_data_chunck.getMemoryBuffer(),
                                                            raw_data_chunck.getCompressedSize());
            }
        }
      // delete[] mpa_decompressMemoryBuffer;
    }
  else
    {
      frame_sptr = std::make_shared<TimsFrame>(
        raw_data_chunck.getFrameId(), raw_data_chunck.getFrameNumberOfScans(), nullptr, 0);
    }
  return frame_sptr;
}
/*
TimsFrameCstSPtr
TimsBinDec::getTimsFrameCstSPtr(std::size_t timsId)
{
  return getTimsFrameCstSPtrByOffset(timsId, m_indexArray[timsId]);
}
*/

void
pappso::TimsBinDec::startLinearRead(std::size_t start_frame_id,
                                    std::size_t chunk_deque_size,
                                    const std::vector<pappso::TimsFrameRecord> &frame_record_list)
{
  // qDebug();
  m_linearAccessRawDataChunckList.resize(chunk_deque_size);
  m_firstFrameId = start_frame_id;
  m_lastFrameId  = start_frame_id;

  QFile *p_file = mp_fileLinear;
  if(p_file == nullptr)
    {
      throw PappsoException(QObject::tr("ERROR mp_fileLinear == nullptr"));
    }

  bool seekpos_ok = p_file->seek(frame_record_list[start_frame_id].tims_offset);
  if(!seekpos_ok)
    {
      throw PappsoException(QObject::tr("ERROR reading TIMS frame %1 TIMS binary file %2: "
                                        "m_timsBinFile.seek(%3) failed")
                              .arg(start_frame_id)
                              .arg(m_timsBinFile)
                              .arg(frame_record_list[start_frame_id].tims_offset));
    }

  try
    {
      for(TimsFrameRawDataChunck &chunck : m_linearAccessRawDataChunckList)
        {

          chunck.readTimsFrame(p_file, start_frame_id, frame_record_list);
          m_lastFrameId = start_frame_id;
          start_frame_id++;
        }
    }
  catch(PappsoException &error)
    {

      throw PappsoException(
        QObject::tr("ERROR in TimsBinDec::startLinearRead reading TIMS binary file %1:\n "
                    " start_frame_id=%2 m_firstFrameId=%3 m_lastFrameId=%4 "
                    "%5")
          .arg(m_timsBinFile)
          .arg(start_frame_id)
          .arg(m_firstFrameId)
          .arg(m_lastFrameId)
          .arg(error.qwhat()));
    }

  // qDebug();
}
