
/////////////////////// StdLib includes


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


/////////////////////// libpwiz includes
#include <pwiz/data/msdata/DefaultReaderList.hpp>


/////////////////////// Local includes
#include "bafasciimsrunreader.h"
#include "pappsomspp/core/utils.h"
#include "pappsomspp/core/pappsoexception.h"
#include "pappsomspp/core/exception/exceptionnotfound.h"
#include "pappsomspp/core/exception/exceptionnotpossible.h"
#include "pappsomspp/core/exception/exceptionnotimplemented.h"
#include "pappsomspp/core/exception/exceptionnotrecognized.h"


namespace pappso
{

BafAsciiMsRunReader::BafAsciiMsRunReader(MsRunIdCstSPtr &msrun_id_csp)
  : pappso::MsRunReader(msrun_id_csp)
{
  // Run the initialization function that checks that the file exists!

  initialize();
}


void
BafAsciiMsRunReader::initialize()
{
  // Use the accept function to parse the whole file, check its
  // contents validity and also count the number of spectra (that is,
  // of lines).
  if(!accept(mcsp_msRunId->getFileName()))
    {
      throw(pappso::ExceptionNotRecognized(QString("Failed to initialize reading of file %1 "
                                                   "in BafAsciiMsRunReader, for MS run %2:\n")
                                             .arg(mcsp_msRunId->getFileName())
                                             .arg(mcsp_msRunId.get()->toString())));
    }
}


BafAsciiMsRunReader::~BafAsciiMsRunReader()
{
}

QString
BafAsciiMsRunReader::craftLineParserRegExpPattern() const
{

  // Construct the regular expression pattern, piecemeal...

  // The retention time as the very first value in the line.
  QString regexp_pattern =
    QString("^(%1)").arg(Utils::unsignedDoubleNumberNoExponentialRegExp.pattern());

  // The ionization mode (positive or negative)
  regexp_pattern += QString(",([+-])");

  // The ion source type.
  regexp_pattern += QString(",(ESI|MALDI)");

  // The MS level (ms1 for full scan mass spectrum)
  regexp_pattern += QString(",ms(\\d)");

  // Do no know what this is for.
  regexp_pattern += QString(",(-)");

  // The type of peak (profile or centroid).
  regexp_pattern += QString(",(profile|line)");

  // The m/z range of the mass spectrum.
  regexp_pattern += QString(",(%1-%2)")
                      .arg(Utils::unsignedDoubleNumberNoExponentialRegExp.pattern())
                      .arg(Utils::unsignedDoubleNumberNoExponentialRegExp.pattern());

  // The count of peaks following this element in the remaining of the line.
  regexp_pattern += QString(",(\\d+)");

  // Finally the whole set of ,<mz><space><intensity> pairs to
  // then end of the line.
  regexp_pattern += QString("(.*$)");

  // qDebug() << "The full regexp_pattern:" << regexp_pattern;

  return regexp_pattern;
}

QRegularExpression
BafAsciiMsRunReader::craftLineParserRegExp(QString &pattern) const
{
  QRegularExpression regexp = QRegularExpression(pattern);

  if(!regexp.isValid())
    {
      qDebug() << "The regular expression is not valid:" << regexp.errorString();
      return QRegularExpression();
    }

  return QRegularExpression(pattern);
}

bool
BafAsciiMsRunReader::parseMassSpectrumLine(QString &line,
                                           MassSpectrumLineData &ms_line_data,
                                           QRegularExpression &line_regexp) const
{
  // We get a line from the file, exactly as is and we have to
  // parse it using regexp to extract all the data out of it,
  // which are then set to ms_line_data.

  line = line.trimmed();

  // qDebug() << "Current brukerBafAscii format line " << line_count << ": "
  //          << line.left(30) << " ... " << line.right(30);

  QRegularExpressionMatch regexp_match = line_regexp.match(line);
  bool ok                              = false;

  if(regexp_match.hasMatch())
    {
      // qDebug() << "The match succeeded.";

      double retention_time = regexp_match.captured(1).toDouble(&ok);
      if(!ok)
        {
          qDebug() << "Failed to extract the retention time of the mass spectrum.";
          return false;
        }
      ms_line_data.retentionTime = retention_time;


      ms_line_data.ionizationMode = regexp_match.captured(2);
      ms_line_data.ionSourceType  = regexp_match.captured(3);

      int ms_level = regexp_match.captured(4).toInt(&ok);
      if(!ok)
        {
          qDebug() << "Failed to extract the MS level of the mass spectrum.";
          return false;
        }
      ms_line_data.msLevel = ms_level;

      QString dash      = regexp_match.captured(5);
      ms_line_data.dash = dash;

      ms_line_data.peakShapeType = regexp_match.captured(6);

      QString mz_range = regexp_match.captured(7);

      double mz_range_start = mz_range.left(mz_range.indexOf("-")).toDouble(&ok);
      if(!ok)
        {
          qDebug() << "Failed to extract the start of the m/z range.";
          return false;
        }
      double mz_range_end = mz_range.right(mz_range.indexOf("-") + 1).toDouble(&ok);
      if(!ok)
        {
          qDebug() << "Failed to extract the end of the m/z range.";
          return false;
        }
      ms_line_data.mz_range = std::pair<double, double>(mz_range_start, mz_range_end);

      // qDebug() << qSetRealNumberPrecision(10)
      //          << "mz_range_start: " << mz_range_start
      //          << "mz_range_end: " << mz_range_end;

      std::size_t peak_count = regexp_match.captured(8).toULongLong(&ok);
      if(!ok)
        {
          qDebug() << "Failed to extract the number of peaks in the mass "
                      "spectrum.";
          return false;
        }
      ms_line_data.peakCount = peak_count;

      QString peaks         = regexp_match.captured(9);
      ms_line_data.peakList = peaks.split(",", Qt::SkipEmptyParts);

      // qDebug() << "The number of peaks:" << ms_line_data.peakList.size();

      // Sanity check:
      if(static_cast<std::size_t>(ms_line_data.peakList.size()) != ms_line_data.peakCount)
        {
          qDebug() << "The number of peaks in the mass spectrum does not "
                      "match the advertised one.";
          return false;
        }

      // qDebug() << "The retention time:" << retention_time
      //          << "the ionization mode: " << ionization_mode
      //          << "the source type: " << source_type
      //          << "MS level is:" << ms_level
      //          << "peak shape type: " << peak_shape_type
      //          << "m/z range: " << mz_range << "peak count: " <<
      //          peak_count
      //          << "and peaks: " << peaks.left(100) << " ... "
      //          << peaks.right(100) << "";
    }
  else
    {
      qDebug() << "The match failed.";
      return false;
    }

  return true;
}

bool
BafAsciiMsRunReader::accept(const QString &file_name) const
{
  qDebug();

  // Here we just test all the lines of the file to check that they comply with
  // the BafAscii format (ascii export from Baf file using the Bruker
  // DataAnalysis/Compass software)..

  // The format of the file, for MS1 data only is the following:
  // <rt time in min>,+,ESI,ms1,-,profile,<m/z range start>-<m/z range
  // end>,512704,<m/z> <int>,<m/z> <int>,.. One such line per retention time
  // value.

  QString line_regexp_pattern    = craftLineParserRegExpPattern();
  QRegularExpression line_regexp = craftLineParserRegExp(line_regexp_pattern);

  if(!line_regexp.isValid())
    {
      qDebug() << "Failed to craft the regular expresssion";

      return false;
    }

  QFile file(file_name);

  if(!file.open(QFile::ReadOnly | QFile::Text))
    {
      qDebug() << "Failed to open file" << file_name;

      return false;
    }

  MassSpectrumLineData ms_line_data;

  while(!file.atEnd())
    {
      QString line = file.readLine().trimmed();

      if(!parseMassSpectrumLine(line, ms_line_data, line_regexp))
        {
          qDebug() << "Failed to parse a line.";

          file.close();
          return false;
        }

      ++m_spectrumCount;
    }

  file.close();

  if(m_spectrumCount >= 1)
    {
      qDebug() << "The file seems indeed to be BafAscii.";
      return true;
    }

  qDebug() << "The file does not seem to be BafAscii.";
  return false;
}


pappso::MassSpectrumSPtr
BafAsciiMsRunReader::massSpectrumSPtr(std::size_t spectrum_index)
{
  return qualifiedMassSpectrum(spectrum_index).getMassSpectrumSPtr();
}


pappso::MassSpectrumCstSPtr
BafAsciiMsRunReader::massSpectrumCstSPtr(std::size_t spectrum_index)
{
  return qualifiedMassSpectrum(spectrum_index).getMassSpectrumCstSPtr();
}


QualifiedMassSpectrum
BafAsciiMsRunReader::qualifiedMassSpectrumFromBafAsciiMSDataFile(std::size_t spectrum_index,
                                                                 bool want_binary_data) const
{
  // qDebug();

  // Each line holds the data of one mass spectrum. There are other
  // metadata at the beginning of the line.

  QString file_name = mcsp_msRunId->getFileName();

  QFile file(file_name);

  if(!file.open(QFile::ReadOnly | QFile::Text))
    {
      throw(PappsoException(QString("%1-%2 Error reading file %3 in "
                                    "BafAsciiMsRunReader, for MS run %4:\n")
                              .arg(__FILE__)
                              .arg(__LINE__)
                              .arg(mcsp_msRunId->getFileName())
                              .arg(mcsp_msRunId.get()->toString())));
    }

  // The format of the file, for MS1 data only is the following:
  // <rt time in min>,+,ESI,ms1,-,profile,<m/z range start>-<m/z range
  // end>,512704,<m/z> <int>,<m/z> <int>,.. One such line per retention time
  // value.

  QString line_regexp_pattern    = craftLineParserRegExpPattern();
  QRegularExpression line_regexp = craftLineParserRegExp(line_regexp_pattern);

  if(!line_regexp.isValid())
    {
      throw(PappsoException(QString("%1-%2 Failed to craft the regular expresssion while reading "
                                    "file %3 in BafAsciiMsRunReader, for MS run %4:")
                              .arg(__FILE__)
                              .arg(__LINE__)
                              .arg(mcsp_msRunId->getFileName())
                              .arg(mcsp_msRunId.get()->toString())));
    }

  QualifiedMassSpectrum qualified_mass_spectrum;
  MassSpectrumLineData ms_line_data;
  std::size_t mass_spectrum_index = 0;

  while(!file.atEnd())
    {
      if(mass_spectrum_index < spectrum_index)
        continue;


      QString line = file.readLine().trimmed();

      if(!parseMassSpectrumLine(line, ms_line_data, line_regexp))
        {
          file.close();

          throw(PappsoException(QString("%1-%2 Failed to parse line while reading file %3 "
                                        "in BafAsciiMsRunReader, for MS run %4:\n")
                                  .arg(__FILE__)
                                  .arg(__LINE__)
                                  .arg(mcsp_msRunId->getFileName())
                                  .arg(mcsp_msRunId.get()->toString())));
        }

      // At this point we have all the data inside the ms_line_data structure.
      // If the user wants the binary data, that is, the mass spectrometric
      // data, we need to prepare these.

      MassSpectrumId mass_spectrum_id(mcsp_msRunId, mass_spectrum_index);

      qualified_mass_spectrum.setMassSpectrumId(mass_spectrum_id);
      qualified_mass_spectrum.setMsLevel(ms_line_data.msLevel);
      qualified_mass_spectrum.setRtInSeconds(ms_line_data.retentionTime * 60);

      // Should we create the peak list?

      if(want_binary_data)
        {
          // qDebug() << "The binary data are wanted.";

          MassSpectrum mass_spectrum;
          bool ok = false;

          for(int iter = 0; iter < ms_line_data.peakList.size(); ++iter)
            {
              QString pair_string = ms_line_data.peakList.at(iter);

              // qDebug() << "The pair string:" << pair_string;

              QStringList mz_and_intensity_list = pair_string.split(" ", Qt::SkipEmptyParts);

              double mz = mz_and_intensity_list.first().toDouble(&ok);
              if(!ok)
                {
                  file.close();

                  throw(PappsoException(QString("%1-%2 Failed to parse line while reading file %3 "
                                                "in BafAsciiMsRunReader, for MS run %4:\n")
                                          .arg(__FILE__)
                                          .arg(__LINE__)
                                          .arg(mcsp_msRunId->getFileName())
                                          .arg(mcsp_msRunId.get()->toString())));
                }

              // qDebug() << qSetRealNumberPrecision(10) << "m/z:" << mz;

              double intensity = mz_and_intensity_list.last().toDouble(&ok);
              if(!ok)
                {
                  file.close();

                  throw(PappsoException(QString("%1-%2 Failed to parse line while reading file %3 "
                                                "in BafAsciiMsRunReader, for MS run %4:\n")
                                          .arg(__FILE__)
                                          .arg(__LINE__)
                                          .arg(mcsp_msRunId->getFileName())
                                          .arg(mcsp_msRunId.get()->toString())));
                }

              // qDebug() << qSetRealNumberPrecision(10)
              //          << "intensity:" << intensity;

              // qDebug() << "Emplacing back with:" << mz << "-" << intensity;

              mass_spectrum.emplace_back(mz, intensity);
            }

          MassSpectrumSPtr spectrum_sp = mass_spectrum.makeMassSpectrumSPtr();
          qualified_mass_spectrum.setMassSpectrumSPtr(spectrum_sp);
        }
      else
        qualified_mass_spectrum.setMassSpectrumSPtr(nullptr);

      // qDebug() << "qualified mass spectrum has size:"
      //          << qualified_mass_spectrum.getMassSpectrumSPtr()->size();

      break;
    }
  // End of
  // while(!file.atEnd())

  file.close();

  return qualified_mass_spectrum;
}


QualifiedMassSpectrum
BafAsciiMsRunReader::qualifiedMassSpectrum(std::size_t spectrum_index, bool want_binary_data) const
{
  // qDebug();

  QualifiedMassSpectrum qualified_mass_spectrum =
    qualifiedMassSpectrumFromBafAsciiMSDataFile(spectrum_index, want_binary_data);

  // qDebug() << "qualified mass spectrum has size:"
  //          << qualified_mass_spectrum.getMassSpectrumSPtr()->size();

  return qualified_mass_spectrum;
}


void
BafAsciiMsRunReader::readSpectrumCollection(SpectrumCollectionHandlerInterface &handler)
{
  readSpectrumCollectionByMsLevel(handler, 0);
}


void
BafAsciiMsRunReader::readSpectrumCollectionByMsLevel(SpectrumCollectionHandlerInterface &handler,
                                                     unsigned int ms_level)
{
  qDebug();

  // Each line holds the data of one mass spectrum. There are other
  // metadata at the beginning of the line.

  // Does the handler consuming the mass spectra read from file want these
  // mass spectra to hold the binary data arrays (mz/i vectors)?

  const bool want_binary_data = handler.needPeakList();

  QString file_name = mcsp_msRunId->getFileName();

  QFile file(file_name);

  if(!file.open(QFile::ReadOnly | QFile::Text))
    {
      throw(PappsoException(QString("%1-%2 Error reading file %3 in "
                                    "BafAsciiMsRunReader, for MS run %4:\n")
                              .arg(__FILE__)
                              .arg(__LINE__)
                              .arg(mcsp_msRunId->getFileName())
                              .arg(mcsp_msRunId.get()->toString())));
    }

  // The format of the file, for MS1 data only is the following:
  // <rt time in min>,+,ESI,ms1,-,profile,<m/z range start>-<m/z range
  // end>,512704,<m/z> <int>,<m/z> <int>,.. One such line per retention time
  // value.

  QString line_regexp_pattern    = craftLineParserRegExpPattern();
  QRegularExpression line_regexp = craftLineParserRegExp(line_regexp_pattern);

  if(!line_regexp.isValid())
    {
      throw(PappsoException(
        QString("%1-%2 Failed to craft the regular expresssion while reading file %3 "
                "in BafAsciiMsRunReader, for MS run %4:\n")
          .arg(__FILE__)
          .arg(__LINE__)
          .arg(mcsp_msRunId->getFileName())
          .arg(mcsp_msRunId.get()->toString())));
    }

  MassSpectrumLineData ms_line_data;
  std::size_t mass_spectrum_index = 0;

  while(!file.atEnd())
    {
      // qDebug() << "Iterating at line index:" << mass_spectrum_index;

      // If the user of this reader instance wants to stop reading the
      // spectra, then break this loop.
      if(handler.shouldStop())
        {
          qDebug() << "The operation was cancelled. Breaking the loop.";
          break;
        }

      QString line = file.readLine().trimmed();

      if(!parseMassSpectrumLine(line, ms_line_data, line_regexp))
        {
          file.close();

          throw(PappsoException(QString("%1-%2 Failed to parse line whilereading file %3 "
                                        "in BafAsciiMsRunReader, for MS run %4:\n")
                                  .arg(__FILE__)
                                  .arg(__LINE__)
                                  .arg(mcsp_msRunId->getFileName())
                                  .arg(mcsp_msRunId.get()->toString())));
        }

      // qDebug() << "The size of the peaks string list:"
      //          << ms_line_data.peakList.size();

      // At this point we have all the data inside the ms_line_data structure.
      // If the user wants the binary data, that is, the mass spectrometric
      // data, we need to prepare these.

      MassSpectrumId mass_spectrum_id(mcsp_msRunId, mass_spectrum_index);

      QualifiedMassSpectrum qualified_mass_spectrum(mass_spectrum_id);

      qualified_mass_spectrum.setMsLevel(ms_line_data.msLevel);
      qualified_mass_spectrum.setRtInSeconds(ms_line_data.retentionTime * 60);

      // Should we create the peak list?

      if(want_binary_data)
        {
          // qDebug() << "The binary data are wanted.";

          MassSpectrum mass_spectrum;
          bool ok = false;

          for(int iter = 0; iter < ms_line_data.peakList.size(); ++iter)
            {
              QString pair_string = ms_line_data.peakList.at(iter);

              // qDebug() << "The pair string:" << pair_string;

              QStringList mz_and_intensity_list = pair_string.split(" ", Qt::SkipEmptyParts);

              double mz = mz_and_intensity_list.first().toDouble(&ok);
              if(!ok)
                {
                  file.close();

                  throw(PappsoException(QString("%1-%2 Failed to parse line while reading file %3 "
                                                "in BafAsciiMsRunReader, for MS run %4:\n")
                                          .arg(__FILE__)
                                          .arg(__LINE__)
                                          .arg(mcsp_msRunId->getFileName())
                                          .arg(mcsp_msRunId.get()->toString())));
                }

              // qDebug() << qSetRealNumberPrecision(10) << "m/z:" << mz;

              double intensity = mz_and_intensity_list.last().toDouble(&ok);
              if(!ok)
                {
                  file.close();

                  throw(PappsoException(QString("%1-%2 Failed to parse line while reading file %3 "
                                                "in BafAsciiMsRunReader, for MS run %4:\n")
                                          .arg(__FILE__)
                                          .arg(__LINE__)
                                          .arg(mcsp_msRunId->getFileName())
                                          .arg(mcsp_msRunId.get()->toString())));
                }

              // qDebug() << qSetRealNumberPrecision(10)
              //          << "intensity:" << intensity;

              // qDebug() << "Emplacing back with:" << mz << "-" << intensity;

              mass_spectrum.emplace_back(mz, intensity);
            }

          MassSpectrumSPtr spectrum_sp = mass_spectrum.makeMassSpectrumSPtr();
          qualified_mass_spectrum.setMassSpectrumSPtr(spectrum_sp);
        }
      else
        qualified_mass_spectrum.setMassSpectrumSPtr(nullptr);

      // qDebug() << "qualified mass spectrum has size:"
      //          << qualified_mass_spectrum.getMassSpectrumSPtr()->size();

      // The handler will receive the index of the mass spectrum in the
      // current run via the mass spectrum id member datum.
      // Only hand over the mass spectrum to the handler if the
      // requested ms_level matches: if ms_level is 0, then any
      // mass spectrum of any msLevel will be ok. If ms_level is not 0,
      // then we only hand over the mass spectrum if its msLevel is
      // exactly equal to ms_level.
      if(ms_level == 0 || qualified_mass_spectrum.getMsLevel() == ms_level)
        {
          handler.setQualifiedMassSpectrum(qualified_mass_spectrum);
        }

      // This is and index of the spectrum in the file, not a count
      // of the mass spectra actually handed over to the handler.
      ++mass_spectrum_index;
    }
  // End of
  // while(!file.atEnd())

  file.close();

  qDebug() << "Loading ended";
  handler.loadingEnded();
}

void
BafAsciiMsRunReader::readSpectrumCollection2([[maybe_unused]] const MsRunReadConfig &config,
                                             SpectrumCollectionHandlerInterface &handler)
{
  return readSpectrumCollection(handler);
}

std::size_t
BafAsciiMsRunReader::spectrumListSize() const
{
  return m_spectrumCount;
}

bool
BafAsciiMsRunReader::releaseDevice()
{
  return true;
}

bool
BafAsciiMsRunReader::acquireDevice()
{
  return true;
}

XicCoordSPtr
BafAsciiMsRunReader::newXicCoordSPtrFromSpectrumIndex(std::size_t spectrum_index [[maybe_unused]],
                                                      pappso::PrecisionPtr precision
                                                      [[maybe_unused]]) const
{
  throw ExceptionNotImplemented(
    QObject::tr("Not implemented %1 %2 %3").arg(__FILE__).arg(__FUNCTION__).arg(__LINE__));
}

XicCoordSPtr
BafAsciiMsRunReader::newXicCoordSPtrFromQualifiedMassSpectrum(
  const pappso::QualifiedMassSpectrum &mass_spectrum [[maybe_unused]],
  pappso::PrecisionPtr precision [[maybe_unused]]) const
{
  throw ExceptionNotImplemented(
    QObject::tr("Not implemented %1 %2 %3").arg(__FILE__).arg(__FUNCTION__).arg(__LINE__));
}

std::size_t
BafAsciiMsRunReader::spectrumStringIdentifier2SpectrumIndex(const QString &spectrum_identifier
                                                            [[maybe_unused]])
{
  throw pappso::ExceptionNotImplemented(
    QObject::tr("%1 %2 %3 not implemented").arg(__FILE__).arg(__FUNCTION__).arg(__LINE__));
}

} // namespace pappso
