/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 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
 */


////////////////////////////// Qt includes
#include <QStringList>
#include <QFile>
#include <QRegularExpression>
#include <QDebug>
#include <QVector>


////////////////////////////// local includes
#include <minexpert/nongui/MassSpecDataFileLoaderXy.hpp>
#include <globals/globals.hpp>


namespace msXpSmineXpert
{

int LIMIT_PARSED_LINES_COUNT = 100;

MassSpecDataFileLoaderXy::MassSpecDataFileLoaderXy(const QString &fileName)
  : MassSpecDataFileLoader{fileName}
{
}


MassSpecDataFileLoaderXy::~MassSpecDataFileLoaderXy()
{
}


int
MassSpecDataFileLoaderXy::readSpectrumCount()
{
  // By definition, this kind of file contains data for a single spectrum.

  return 1;
}


QString
MassSpecDataFileLoaderXy::formatDescription() const
{
  QString description = QString(
    "The xy file format describes mass data with lines\n"
    "formatted like the following: \n"
    "<number><separator><number> \n"
    "<number> : whatever representation of a real number\n"
    "<separator> : whatever string that is not a digit nor a decimal dot\n"
    "Any line may be empty\n"
    "Any line may be a comment if started with '#'\n"
    "This file format holds data for a single mass spectrum.\n");

  return description;
}


bool
MassSpecDataFileLoaderXy::canLoadData(QString fileName)
{

  QFile file(fileName);

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

      return false;
    }

  QRegularExpressionMatch regExpMatch;

  QString line;

  int linesRead = 0;

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

      if(linesRead >= LIMIT_PARSED_LINES_COUNT)
        {
          return true;
        }

      if(line.startsWith('#'))
        {

          // qDebug() << __FILE__ << __LINE__ << "line starts with '#'";

          if(line.contains("##JCAMP-DX="))
            // This is the DX file format.
            return false;
          else
            continue;
        }

      if(line.isEmpty() || msXpS::gEndOfLineRegExp.match(line).hasMatch())
        {
          // qDebug() << __FILE__ << __LINE__
          //<< "line is empty or only end of line";

          continue;
        }

      // qDebug() << __FILE__ << __LINE__ << "Current xy format line:" << line;

      regExpMatch = msXpS::gXyFormatMassDataRegExp.match(line);

      if(regExpMatch.hasMatch())
        {
          continue;
        }
      else
        {

          // qDebug() << __FILE__ << __LINE__ << "The real data did not match.";

          return false;
        }
    }

  return true;
}


bool
MassSpecDataFileLoaderXy::canLoadDataFromString(const QString &msDataString)
{
  QStringList msDataList = msDataString.split("\n");

  if(msDataList.isEmpty())
    return false;

  QRegularExpressionMatch regExpMatch;
  int linesRead = 0;

  for(int iter = 0; iter < msDataList.size(); ++iter)
    {
      ++linesRead;

      QString line = msDataList.at(iter);

      if(linesRead >= LIMIT_PARSED_LINES_COUNT)
        {
          return true;
        }

      if(line.startsWith('#'))
        {
          // qDebug() << __FILE__ << __LINE__ << "line starts with '#'";

          if(line.contains("##JCAMP-DX="))
            // This is the DX file format.
            return false;
          else
            continue;
        }

      if(line.isEmpty() || msXpS::gEndOfLineRegExp.match(line).hasMatch())
        {
          // qDebug() << __FILE__ << __LINE__
          //<< "line is empty or only end of line";

          continue;
        }

      // qDebug() << __FILE__ << __LINE__ << "Current xy format line:" << line;

      regExpMatch = msXpS::gXyFormatMassDataRegExp.match(line);

      if(regExpMatch.hasMatch())
        {
          continue;
        }
      else
        {

          // qDebug() << __FILE__ << __LINE__ << "The real data did not match.";

          return false;
        }
    }

  return true;
}


msXpSlibmass::MassSpectrum *
MassSpecDataFileLoaderXy::loadData()
{
  if(m_fileName.isEmpty())
    {
      qDebug() << __FILE__ << __LINE__
               << "The file name is empty, cannot load data.";
      return nullptr;
    }

  QFile file(m_fileName);

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

  QRegularExpressionMatch regExpMatch;

  // We only need a new mass spectrum when the data are NOT streamed, that
  // is when the data are loaded fully.

  msXpSlibmass::MassSpectrum *newSpectrum = new msXpSlibmass::MassSpectrum();

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

      if(line.isEmpty())
        continue;

      // If the line has only a newline character, go on.
      if(msXpS::gEndOfLineRegExp.match(line).hasMatch())
        {
          // qDebug() << __FILE__ << __LINE__
          // << "Was only end of line";
          continue;
        }

      if(line.startsWith("#"))
        continue;

      // At this point, if the line does not match the real data regexp, then
      // that would be an error.

      msXpSlibmass::DataPoint *p_dataPoint = new msXpSlibmass::DataPoint(line);
      if(p_dataPoint != Q_NULLPTR && p_dataPoint->isValid())
        newSpectrum->append(p_dataPoint);
      else
        {
          delete newSpectrum;

          return nullptr;
        }
    }

  file.close();

  return newSpectrum;
}


int
MassSpecDataFileLoaderXy::loadDataFromString(const QString &msDataString,
                                             MassSpecDataSet *massSpecDataSet)
{
  if(massSpecDataSet == nullptr)
    qFatal("Fatal error at %s@%d. Null pointer. Program aborted.",
           __FILE__,
           __LINE__);

  QStringList msDataList = msDataString.split("\n");

  if(msDataList.isEmpty())
    return 0;

  msXpSlibmass::MassSpectrum *newSpectrum = new msXpSlibmass::MassSpectrum();

  QRegularExpressionMatch regExpMatch;

  for(int iter = 0; iter < msDataList.size(); ++iter)
    {
      QString line = msDataList.at(iter);

      if(line.startsWith('#') || line.isEmpty() ||
         msXpS::gEndOfLineRegExp.match(line).hasMatch())
        continue;

      msXpSlibmass::DataPoint *p_dataPoint = new msXpSlibmass::DataPoint(line);
      if(p_dataPoint != Q_NULLPTR && p_dataPoint->isValid())
        newSpectrum->append(p_dataPoint);
      else
        {
          delete newSpectrum;

          return -1;
        }
    }

  massSpecDataSet->m_massSpectra.append(newSpectrum);

  // We need to have one rtHash element, but the retention time will be 0.

  massSpecDataSet->m_rtHash.insert(0, newSpectrum);

  // qDebug() << __FILE__ << __LINE__
  //<< "size of the mass spectrum " << newSpectrum->size();

  return massSpecDataSet->m_massSpectra.size();
}


int
MassSpecDataFileLoaderXy::loadData(MassSpecDataSet *massSpecDataSet)
{
  if(massSpecDataSet == nullptr)
    qFatal("Fatal error at %s@%d. Null pointer. Program aborted.",
           __FILE__,
           __LINE__);

  msXpSlibmass::MassSpectrum *newSpectrum = loadData();

  if(newSpectrum == nullptr)
    return -1;

  massSpecDataSet->m_massSpectra.append(newSpectrum);

  // We need to have one rtHash element, but the retention time will be 0.

  massSpecDataSet->m_rtHash.insert(0, newSpectrum);

  // qDebug() << __FILE__ << __LINE__
  //<< "size of the mass spectrum " << newSpectrum->size();

  return massSpecDataSet->m_massSpectra.size();
}


int
MassSpecDataFileLoaderXy::loadData(QVector<double> *keyVector,
                                   QVector<double> *valVector)
{
  if(keyVector == nullptr || valVector == nullptr)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  msXpSlibmass::MassSpectrum *newSpectrum = loadData();

  if(newSpectrum == nullptr)
    return -1;

  newSpectrum->toVectors(keyVector, valVector);

  // Sanity check:
  if(keyVector->size() != valVector->size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  return keyVector->size();
}


int
MassSpecDataFileLoaderXy::loadData(QMap<double, double> *keyValMap)
{
  if(keyValMap == nullptr)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  msXpSlibmass::MassSpectrum *newSpectrum = loadData();

  if(newSpectrum == nullptr)
    return -1;

  newSpectrum->toMap(keyValMap);

  return keyValMap->size();
}


} // namespace msXpSmineXpert
