/* 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>


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


/////////////////////// Local includes
#include "globals.hpp"
#include "PeakCentroid.hpp"
#include "IsotopicDataLibraryHandler.hpp"


namespace MsXpS
{

namespace libXpertMass
{


/*!
\class MsXpS::libXpertMass::IsotopicDataLibraryHandler
\inmodule libXpertMass
\ingroup PolChemDefBuildingdBlocks
\inheaderfile IsotopicDataLibraryHandler.hpp

\brief The IsotopicDataLibraryHandler class handles \l{IsotopicData} from the
IsoSpec element data tables directly from the library's data. These are the
reference, pristine, \e{unmodified}, isotopic data.

The IsoSpec element data tables are the following:

\list
\li elem_table_ID
\li elem_table_atomicNo
\li elem_table_mass
\li elem_table_massNo
\li elem_table_extraNeutrons
\li elem_table_element
\li elem_table_symbol
\li elem_table_Radioactive
\li elem_table_probability
\li elem_table_log_probability
\endlist

The data tables are all of the same length and the data in each row of a
given table matches the contents of that same row in all the other tables. For
example, the first two rows of table elem_table_ID are:

1

1

These two rows match the same rows in elem_table_mass:

1.00782503227

2.01410177819

and the the same rows in elem_table_element:

"hydrogen"

"hydrogen"

  By reading, row-by-row, the data from the same row number in each one of the
tables, one constructs a fully qualified \l{Isotope}.

  \sa IsotopicDataUserConfigHandler, IsotopicDataManualConfigHandler
*/

/*!
    \typedef IsotopicDataLibraryHandlerSPtr
    \relates IsotopicDataLibraryHandler

    Synonym for std::shared_ptr<IsotopicDataLibraryHandler>.
*/

/*!
    \typedef IsotopicDataLibraryHandlerCstSPtr
    \relates IsotopicDataLibraryHandler

    Synonym for std::shared_ptr<const IsotopicDataLibraryHandler>.
*/


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

  The instance will have empty member data.
*/
IsotopicDataLibraryHandler::IsotopicDataLibraryHandler()
{
}

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

  The instance will have its isotopic data member pointing to \a
isotopic_data_sp.
*/
IsotopicDataLibraryHandler::IsotopicDataLibraryHandler(
  IsotopicDataSPtr isotopic_data_sp)
  : IsotopicDataBaseHandler(isotopic_data_sp)
{
}


// IsotopicDataLibraryHandler::IsotopicDataLibraryHandler(const QString
// &file_name) : IsotopicDataBaseHandler(file_name)
//{
//}


// IsotopicDataLibraryHandler::IsotopicDataLibraryHandler(
// IsotopicDataSPtr isotopic_data_sp, const QString &file_name)
//: IsotopicDataBaseHandler(isotopic_data_sp, file_name)
//{
//}

/*!
  \brief Destructs the \l{IsotopicDataLibraryHandler}.

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


std::size_t
IsotopicDataLibraryHandler::loadData([[maybe_unused]] const QString &filename)
{
  return loadData();
}

/*!
  \brief Loads isotopic data directly from IsoSpec library' element data tables.

  The member isotopic data are cleared before setting new data read from the
library's element data tables.

  The code iterates, row-by-row, in the all the tables and extracts
the data to fill in the Isotope data:

  \code
  IsotopeSPtr isotope_sp =
        std::make_shared<Isotope>(IsoSpec::elem_table_ID[iter],
                                  QString(IsoSpec::elem_table_element[iter]),
                                  QString(IsoSpec::elem_table_symbol[iter]),
                                  IsoSpec::elem_table_atomicNo[iter],
                                  IsoSpec::elem_table_mass[iter],
                                  IsoSpec::elem_table_massNo[iter],
                                  IsoSpec::elem_table_extraNeutrons[iter],
                                  IsoSpec::elem_table_probability[iter],
                                  IsoSpec::elem_table_log_probability[iter],
                                  IsoSpec::elem_table_Radioactive[iter]);
  \endcode

  Returns the count of \l{Isotope}s that were allocated and stored in the
msp_isotopicData member.
  */
std::size_t
IsotopicDataLibraryHandler::loadData()
{

  // We need to allocate one Isotope instance for each element
  // in the various arrays in the IsoSpec++ source code header file.

  // extern const int elem_table_ID[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
  // extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
  // extern const double
  // elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
  // extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
  // extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
  // extern const int
  // elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
  // extern const char*
  // elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const
  // char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const
  // bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern
  // const double
  // elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];

  // Big sanity check, all the arrays must be the same length!
  std::size_t array_length = checkConsistency();
  if(array_length < 1)
    return false;

  // Clear all the data, since this function might be called multiple times.
  msp_isotopicData->clear();

  for(std::size_t iter = 0; iter < array_length; ++iter)
    {

      QString elem_element = QString(IsoSpec::elem_table_element[iter]);

      // These are the last items in the various tables. We do not handle them
      // at the moment.
      if(elem_element == "electron" || elem_element == "missing electron" ||
         elem_element == "protonation")
        continue;

      IsotopeSPtr isotope_sp =
        std::make_shared<Isotope>(IsoSpec::elem_table_ID[iter],
                                  QString(IsoSpec::elem_table_element[iter]),
                                  QString(IsoSpec::elem_table_symbol[iter]),
                                  IsoSpec::elem_table_atomicNo[iter],
                                  IsoSpec::elem_table_mass[iter],
                                  IsoSpec::elem_table_massNo[iter],
                                  IsoSpec::elem_table_extraNeutrons[iter],
                                  IsoSpec::elem_table_probability[iter],
                                  IsoSpec::elem_table_log_probability[iter],
                                  IsoSpec::elem_table_Radioactive[iter]);

      // 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_sp, false);
    }

  // Now ask that the mono/avg mass maps be updated.
  if(!msp_isotopicData->updateMassMaps())
    qFatal("Programming error. Failed to update the mass maps.");

  // qDebug() << "Done loading data with :" << msp_isotopicData->size()
  //<< "isotopes in the isotopic data.";

  return msp_isotopicData->size();
}

/*!
  \brief Write all the IsotopicData to \a file_name.

  If \a file_name is empty, 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.

  \sa Isotope::Isotope(const QString &text)
*/
std::size_t
IsotopicDataLibraryHandler::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.

  if(file_name.isEmpty() && m_fileName.isEmpty())
    return 0;

  QString temp_file_name;

  // The passed filename takes precedence over the member datum. So copy
  // that file name to the member datum.

  if(!file_name.isEmpty())
    temp_file_name = file_name;
  else
    temp_file_name = m_fileName;

  QFile file(temp_file_name);

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

  QTextStream out(&file);

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

  std::size_t isotope_count = 0;

  for(auto item : msp_isotopicData->m_isotopes)
    {
      out << item->toString();
      // We need to add it because toString() does not terminate the line with
      // a new line character.
      out << "\n";

      ++isotope_count;
    }

  out.flush();

  file.close();

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

  return isotope_count;
}

/*!
  \brief Checks the consistency in all the IsoSpec library's different isotopic
data tables.

  This function essentially verifies that each table has the same row count as
all the other ones.

  Returns the count of isotopes in the isotopic data.
*/
std::size_t
IsotopicDataLibraryHandler::checkConsistency()
{
  std::size_t array_length = sizeof(IsoSpec::elem_table_atomicNo) /
                             sizeof(IsoSpec::elem_table_atomicNo[0]);

  // qDebug() << "The array length is:" << array_length;

  // All the tables in the header file of the IsoSpec library must
  // have exactly the same size.

  if(IsoSpec::isospec_number_of_isotopic_entries != array_length)
    {
      qFatal("Found corruption: the size of arrays is not like expected.");
    }

  // Now test each table one by one.
  std::size_t tested_length = sizeof(IsoSpec::elem_table_probability) /
                              sizeof(IsoSpec::elem_table_probability[0]);
  if(tested_length != array_length)
    {
      qDebug()
        << "Found corruption: at least two arrays are not of the same length."
        << "tested_length:" << tested_length;

      return 0;
    }

  tested_length =
    sizeof(IsoSpec::elem_table_mass) / sizeof(IsoSpec::elem_table_mass[0]);
  if(tested_length != array_length)
    {
      qDebug()
        << "Found corruption: at least two arrays are not of the same length."
        << "tested_length:" << tested_length;

      return 0;
    }

  tested_length =
    sizeof(IsoSpec::elem_table_massNo) / sizeof(IsoSpec::elem_table_massNo[0]);
  if(tested_length != array_length)
    {
      qDebug()
        << "Found corruption: at least two arrays are not of the same length."
        << "tested_length:" << tested_length;

      return 0;
    }

  tested_length = sizeof(IsoSpec::elem_table_extraNeutrons) /
                  sizeof(IsoSpec::elem_table_extraNeutrons[0]);
  if(tested_length != array_length)
    {
      qDebug()
        << "Found corruption: at least two arrays are not of the same length."
        << "tested_length:" << tested_length;

      return 0;
    }

  tested_length = sizeof(IsoSpec::elem_table_element) /
                  sizeof(IsoSpec::elem_table_element[0]);
  if(tested_length != array_length)
    {
      qDebug()
        << "Found corruption: at least two arrays are not of the same length."
        << "tested_length:" << tested_length;

      return 0;
    }

  tested_length =
    sizeof(IsoSpec::elem_table_symbol) / sizeof(IsoSpec::elem_table_symbol[0]);
  if(tested_length != array_length)
    {
      qDebug()
        << "Found corruption: at least two arrays are not of the same length."
        << "tested_length:" << tested_length;

      return 0;
    }

  tested_length = sizeof(IsoSpec::elem_table_Radioactive) /
                  sizeof(IsoSpec::elem_table_Radioactive[0]);
  if(tested_length != array_length)
    {
      qDebug()
        << "Found corruption: at least two arrays are not of the same length."
        << "tested_length:" << tested_length;

      return 0;
    }

  tested_length = sizeof(IsoSpec::elem_table_log_probability) /
                  sizeof(IsoSpec::elem_table_log_probability[0]);
  if(tested_length != array_length)
    {
      qDebug()
        << "Found corruption: at least two arrays are not of the same length."
        << "tested_length:" << tested_length;

      return 0;
    }

  return tested_length;
}


} // namespace libXpertMass

} // 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
