/////////////////////// StdLib includes
#include <iostream>
#include <iomanip>


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


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


/////////////////////// Local includes
#include "bafasciifilereader.h"
#include "pappsomspp/core/exception/exceptionnotfound.h"
#include "pappsomspp/core/utils.h"
#include "pappsomspp/core/types.h"
#include "pappsomspp/core/msrun/msrunid.h"


namespace pappso
{

static const std::size_t CHECKED_LINES_COUNT = 10;

BafAsciiFileReader::BafAsciiFileReader(const QString &file_name) : MsFileReader{file_name}
{
  // To avoid initializing multiple times (costly process), we
  // only initialize when needed, that is, upon getMsRunIds().
  // initialize();
}


BafAsciiFileReader::~BafAsciiFileReader()
{
}

bool
BafAsciiFileReader::initialize(std::size_t &line_count)
{
  // Here we just test some the lines of the file to check that they comply with
  // the brukerBafAscii format.

  line_count = 0;

  QFile file(m_fileName);

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

      return false;
    }

  // 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(",([+-])");

  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+)");

  regexp_pattern += QString("(.*$)");

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

  QRegularExpression line_regexp(regexp_pattern);

  QRegularExpressionMatch regexp_match;

  QString line;
  bool file_reading_failed = false;
  bool ok                  = false;

  // Reading, parsing and checking lines is extremely time consuming.
  // What we want here is reduce the time all the file's lines are
  // read. We could say that we want to parse and check the first
  // CHECKED_LINES_COUNT lines and then avoid parsing and checking, just go
  // through the lines. At the end of the file, the number of lines that have
  // been read is stored in the out parameter line_count.
  std::size_t iter = 0;

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

      ++iter;
      // qDebug() << "Read one line more: (not yet checked)" << iter;
      if(iter > CHECKED_LINES_COUNT)
        continue;

      if(line.startsWith('#') || line.isEmpty() || Utils::endOfLineRegExp.match(line).hasMatch())
        continue;

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

      regexp_match = line_regexp.match(line);

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

          regexp_match.captured(1).toDouble(&ok);
          if(!ok)
            {
              qDebug() << "Failed to extract the retention time of the mass spectrum.";

              file_reading_failed = true;

              break;
            }

          QString ionization_mode = regexp_match.captured(2);
          QString source_type     = regexp_match.captured(3);

          regexp_match.captured(4).toInt(&ok);
          if(!ok)
            {
              qDebug() << "Failed to extract the MS level of the mass spectrum.";

              file_reading_failed = true;

              break;
            }

          QString peak_shape_type = regexp_match.captured(6);

          QString mz_range = regexp_match.captured(7);

          mz_range.left(mz_range.indexOf("-")).toDouble(&ok);
          if(!ok)
            {
              qDebug() << "Failed to extract the start of the m/z range.";

              file_reading_failed = true;

              break;
            }

          mz_range.right(mz_range.indexOf("-") + 1).toDouble(&ok);
          if(!ok)
            {
              qDebug() << "Failed to extract the end of the m/z range.";

              file_reading_failed = true;

              break;
            }

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

          int peak_count = regexp_match.captured(8).toInt(&ok);
          if(!ok)
            {
              qDebug() << "Failed to extract the number of peaks in the mass "
                          "spectrum.";

              file_reading_failed = true;

              break;
            }

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

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

          // Sanity check:
          if(peaks_stringlist.size() != peak_count)
            {
              // qDebug() << "The number of peaks in the mass spectrum does not
              // "
              //             "match the advertised one.";

              file_reading_failed = true;

              break;
            }

          // 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) << "";

          // If we are here, that means that the read line has conformed
          // to the format expected.
          ++line_count;
          // qDebug() << "Checked one line more:" << line_count;
        }
      // End end of
      // if(regexp_match.hasMatch())
      else
        {
          qDebug() << "The match failed.";
          file_reading_failed = true;

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

  file.close();

  if(!file_reading_failed && line_count >= 1)
    {
      m_fileFormat = Enums::MsDataFormat::brukerBafAscii;
      return true;
    }

  m_fileFormat = Enums::MsDataFormat::unknown;

  // qDebug() << "The number of parsed mass spectra: " << line_count;

  // qDebug() << "Detected file format:"
  //          << Utils::msDataFormatAsString(m_fileFormat)
  //          << "with number of spectra: " << line_count;

  return false;
}


Enums::MsDataFormat
BafAsciiFileReader::getFileFormat()
{
  return m_fileFormat;
}


std::vector<MsRunIdCstSPtr>
BafAsciiFileReader::getMsRunIds(const QString &run_prefix)
{
  std::vector<MsRunIdCstSPtr> ms_run_ids;

  std::size_t ms_data_line_count = 0;

  if(!initialize(ms_data_line_count))
    return ms_run_ids;

  // Finally create the MsRunId with the file name.
  MsRunId ms_run_id(m_fileName);
  ms_run_id.setMsDataFormat(m_fileFormat);

  // We need to set the unambiguous xmlId string.
  ms_run_id.setXmlId(QString("%1%2").arg(run_prefix).arg(Utils::getLexicalOrderedString(0)));

  // Craft a meaningful sample name because otherwise all the files loaded from
  // text files will have the same sample name and it will be difficult to
  // differentiate them.
  // Orig version:
  // ms_run_id.setRunId("Single spectrum");
  // Now the sample name is nothing but the file name without the path.

  QFileInfo file_info(m_fileName);

  // qDebug() << "file name:" << m_fileName;

  QString sample_name = file_info.fileName();

  // qDebug() << "sample name:" << sample_name;

  ms_run_id.setRunId(sample_name);

  // Now set the sample name to the run id:

  ms_run_id.setSampleName(ms_run_id.getRunId());

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "Current ms_run_id:" << ms_run_id.toString();

  // Finally make a shared pointer out of it and append it to the vector.
  ms_run_ids.push_back(std::make_shared<MsRunId>(ms_run_id));

  return ms_run_ids;
}


} // namespace pappso
