/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 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 <QDebug>
#include <QFile>


/////////////////////// IsoSpec
#include <IsoSpec++/isoSpec++.h>
#include <IsoSpec++/element_tables.h>


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/globals.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataUserConfigHandler.hpp"

namespace MsXpS
{

namespace libXpertMassCore
{


/*!
\class MsXpS::libXpertMassCore::IsotopicDataUserConfigHandler
\inmodule libXpertMassCore
\ingroup PolChemDefBuildingdBlocks
\inheaderfile IsotopicDataUserConfigHandler.hpp

\brief The IsotopicDataUserConfigHandler class handles user-defined
\l{IsotopicData}.

The data can be loaded and written to file. The format of the data is based on
Isotope::toString(), that outputs each \l{Isotope} data in a separate line.

Comments are allowed and are on lines that start with the '#' character.
These lines are ignored when loading data.

\sa IsotopicDataLibraryHandler, IsotopicDataManualConfigHandler
\sa Isotope::toString
*/

/*!
\brief Constructs the \l{IsotopicDataUserConfigHandler}.

m_fileName is set to \a file_name
*/
IsotopicDataUserConfigHandler::IsotopicDataUserConfigHandler(
  const QString &file_name)
  : IsotopicDataBaseHandler(file_name)
{
}

/*!
\brief Constructs the IsotopicDataUserConfigHandler.

The instance will have its isotopic data member pointing to \a
isotopic_data_sp.

m_fileName is set to \a file_name
*/
IsotopicDataUserConfigHandler::IsotopicDataUserConfigHandler(
  IsotopicDataSPtr isotopic_data_sp, const QString &file_name)
  : IsotopicDataBaseHandler(isotopic_data_sp, file_name)
{
}

/*!
\brief Destructs the IsotopicDataUserConfigHandler.

Nothing is explicitely deleted in the destructor.
*/
IsotopicDataUserConfigHandler::~IsotopicDataUserConfigHandler()
{
  // qDebug();
}

/*!
\brief Loads isotopic data from \a file_name.

If \a file_name is empty, 0 is returned.

The format of the file consists in a single line of data per \l{Isotope} as
created using the Isotope::toString() function. Each line is used to create an
Isotope with the text-based constructor.

Returns the count of \l{Isotope}s created or 0 if the file does not exist or
is not readable.

\sa Isotope::Isotope and overloads
*/
qsizetype
IsotopicDataUserConfigHandler::loadData(const QString &file_name)
{
  if(file_name.isEmpty())
    {
      qCritical() << "File name is emtpy. Failed to open file for reading.";
      return 0;
    }

  // See the Isotope::toString() function that is used to write the isotopic
  // data to file. We thus expect exactly that format from the file.

  QFile file(file_name);

  if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
      qCritical() << "Failed to open file" << file_name << "for reading.";
      return 0;
    }

  // qDebug() << "Loading isotopic data from file:" << file_name;

  // We may have written comments to the file in the form of '# <text>' lines
  QRegularExpression commentRegexp("^\\s*#.*$");

  msp_isotopicData->clear();

  qsizetype isotope_count = 0;

  QTextStream in_stream(&file);

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

      // qDebug() << "simplified line:" << line;

      // Ignore empty or comment lines
      if(line.length() < 1 || commentRegexp.match(line).hasMatch())
        continue;

      IsotopeQSPtr isotope_qsp = QSharedPointer<Isotope>::create(line);

      // We do not want to update the mono/avg maps each time we load an
      // isotope. We'll call the relevant function later.
      msp_isotopicData->appendNewIsotope(isotope_qsp, /* update_maps*/ false);

      ++isotope_count;
    }
  // End of
  // while(!in.atEnd())

  // qDebug() << "Finished creating all the Isotope instances.";

  file.close();

  // At this point, it seems that the loading went fine.

  // Because we have touched the m_isotopes vector, we need to update the
  // mono/avg masses map.

  if(!msp_isotopicData->updateMassMaps())
    qFatal("Programming error. Failed to update the mass maps.");

  if(isotope_count != msp_isotopicData->size())
    qFatal("Programming error. Failed to load all the isotopes to file.");

  m_fileName = file_name;
  msp_isotopicData->setFilePath(m_fileName);

  return msp_isotopicData->size();
}

/*!
\brief Loads isotopic data from the file pointed to by the member file name
datum.

The format of the file consists in a single line of data per \l{Isotope} as
created using the Isotope::toString() function. Each line is used to create an
Isotope with the text-based constructor.

Returns the count of \l{Isotope}s created or 0 if the file does not exist or
is not readable.

\sa Isotope::Isotope and overloads
*/
qsizetype
IsotopicDataUserConfigHandler::loadData()
{
  return loadData(m_fileName);
}

/*!
\brief Writes isotopic data to \a file_name.

If \a file_name is empty, \l{m_fileName} is tried. If both are empty, the
function returns 0. If any one of the file names are correct (file_name takes
precedence over m_fileName), then m_fileName is set to that file name.

The format of the file consists in a single line of data per \l Isotope as
created using the Isotope::toString() function. Each isotope is output to
its own line.

Returns the count of \l{Isotope}s written to file or 0 if the file does not
exist or is not readable.
*/
qsizetype
IsotopicDataUserConfigHandler::writeData(const QString &file_name)
{
  // Although the isotopic data were loaded from the IsoSpec library tables, we
  // might be willing to store these data to a file.

  QString local_file_name = file_name;

  if(local_file_name.isEmpty())
    local_file_name = m_fileName;

  if(local_file_name.isEmpty())
    {
      qCritical("File name is emtpy. Failed to open file for writing.");
      return 0;
    }

  QFile file(local_file_name);

  if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
      qCritical("Failed to open file for writing.");
      return 0;
    }

  QTextStream out_stream(&file);

  out_stream
    << "# This file contains isotopic data in a format that can accommodate\n";
  out_stream
    << "# comments in the form of lines beginning with the '#' character.\n\n";

  qsizetype isotope_count = 0;

  for(auto item : msp_isotopicData->m_isotopes)
    {
      out_stream << item->toString();

      // We need to add it because toString() does not terminate the line with
      // a new line character.
      out_stream << "\n";

      ++isotope_count;
    }

  out_stream.flush();

  file.close();

  if(isotope_count != msp_isotopicData->size())
    qFatal("Programming error. Failed to write all the isotopes to file.");

  // Now we know that local_file_name is fine. Store into m_fileName.
  m_fileName = local_file_name;

  return isotope_count;
}

/*!
\brief Returns the size of the member IsotopicData.
*/
qsizetype
IsotopicDataUserConfigHandler::checkConsistency()
{
  return msp_isotopicData->size();
}


} // namespace libXpertMassCore

} // namespace MsXpS


#if 0

Example from IsoSpec.

const int elementNumber = 2;
const int isotopeNumbers[2] = {2,3};

const int atomCounts[2] = {2,1};


const double hydrogen_masses[2] = {1.00782503207, 2.0141017778};
const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610};

const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses};

const double hydrogen_probs[2] = {0.5, 0.5};
const double oxygen_probs[3] = {0.5, 0.3, 0.2};

const double* probs[2] = {hydrogen_probs, oxygen_probs};

IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts,
isotope_masses, probs), 0.99);

#endif
