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


/////////////////////// Local includes
#include "PolChemDefCatParser.hpp"
#include <MsXpS/libXpertMassCore/PolChemDefSpec.hpp>
#include "ConfigSetting.hpp"
#include "ConfigSettings.hpp"


namespace MsXpS
{

namespace MassXpert
{


PolChemDefCatParser::PolChemDefCatParser()
{
}


PolChemDefCatParser::~PolChemDefCatParser()
{
}

void
PolChemDefCatParser::setUserType(UserType user_type)
{
  m_userType = user_type;
}


UserType
PolChemDefCatParser::getUserType() const
{
  return m_userType;
}

void
PolChemDefCatParser::setConfigAddMode(ContainerItemAddMode push_mode)
{
  m_configAddMode = push_mode;
}

ContainerItemAddMode
PolChemDefCatParser::getConfigAddMode() const
{
  return m_configAddMode;
}

int
PolChemDefCatParser::parseFile(
  ConfigSettingsSPtr config_settings_sp,
  const QString &file_path,
  std::vector<libXpertMassCore::PolChemDefSpecSPtr> &pol_chem_def_specs,
  UserType user_type)
{
  if(config_settings_sp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  qint64 lineLength;

  QString line;

  char buffer[1024];

  int equalSignIdx = 0;
  int count        = 0;

  // We are given a container in which to store all the
  // libXpertMassCore::PolChemDefSpecSPtr instances that we create by parsing the
  // catalogue files.
  //
  // Each line in the catalogue that we may have to parse gives the
  // name of a polymer chemistry definition, like "protein", and the
  // full pathname of the file in which that definition is stored and
  // the full pathname to the directory where all its corresponding
  // stuff is stored.  Example:
  // protein=/some/dir/protein/protein.xml
  //
  // All we have to do is parse each line and for each valid one
  // create a libXpertMassCore::PolChemDefSpecSPtr item that we add to the
  // container passed as parameter.

  QFile file(file_path);

  if(!file.open(QFile::ReadOnly))
    return -1;

  // qDebug() << "Begin file:" << file_path.toAscii();


  // Get the first line of the file. Next we enter in to a
  // while loop.

  lineLength = file.readLine(buffer, sizeof(buffer));

  while(lineLength != -1)
    {
      // The line is now in buffer, and we want to convert
      // it to Unicode by setting it in a QString.
      line = buffer;

      // Remove all the spaces from the borders: Whitespace means any
      // character for which QChar::isSpace() returns true. This
      // includes the ASCII characters '\t', '\n', '\v', '\f', '\r',
      // and ' '.

      line = line.trimmed();

      // The line that is in line should contain something like:

      // protein=protein/protein.xml(relative path)

      // or:

      // protein=/some/dir/protein/protein.xml(absolute path)
      //
      // Note, however that lines beginning with either ' '(space) or
      // '\n'(newline) or '#' are comments.

      if(line.isEmpty() || line.startsWith('#', Qt::CaseInsensitive))
        {
          lineLength = file.readLine(buffer, sizeof(buffer));
          continue;
        }

      // Now some other checks.
      equalSignIdx = line.indexOf('=', 0, Qt::CaseInsensitive);

      if(equalSignIdx == -1 || line.count('=', Qt::CaseInsensitive) > 1)
        return -1;

      // Ok at this point, we might have a nicely parseable line, it
      // makes sens to allocate a new libXpertMassCore::AtomDefSpec object
      // using the name string
      libXpertMassCore::PolChemDefSpecSPtr pol_chem_def_spec_sp =
        std::make_shared<libXpertMassCore::PolChemDefSpec>(line.left(equalSignIdx));

      // Of course we have to make sure that we are getting
      // a file path corresponding to something we actually
      // can use.

      QString polChemDefFile = line.right(line.length() - equalSignIdx - 1);

      QFileInfo fileInfo(polChemDefFile);

      if(fileInfo.isRelative())
        {
          // The polymer chemistry definition file is not an
          // absolute filePath. We have to change it into an
          // absolute filePath by prepending the directory path
          // where it is located.

          // Get the config setting instance pointer for the proper key:
          const ConfigSettingSPtr config_setting_sp =
            config_settings_sp->getConfigSetting("polChemDefsDir", user_type);

          if(config_setting_sp == nullptr)
            {
              pol_chem_def_spec_sp.reset();
              return -1;
            }

          QString file_name = QString("%1/%2")
                                .arg(config_setting_sp->m_value.toString())
                                .arg(polChemDefFile);

          QFile file(file_name);

          if(!file.exists())
            {
              pol_chem_def_spec_sp.reset();
              return -1;
            }

          pol_chem_def_spec_sp->setFilePath(file.fileName());
        }
      else
        {
          if(!fileInfo.exists())
            {
              pol_chem_def_spec_sp.reset();
              return -1;
            }

          pol_chem_def_spec_sp->setFilePath(polChemDefFile);
        }

      // OK, the file exists, and thus we can set the newly allocated
      // PolChemDefSpec object to the container passed as parameter.

      if(m_configAddMode == ContainerItemAddMode::APPEND)
        pol_chem_def_specs.push_back(pol_chem_def_spec_sp);
      else
        pol_chem_def_specs.insert(pol_chem_def_specs.begin(),
                                  pol_chem_def_spec_sp);

      ++count;

      lineLength = file.readLine(buffer, sizeof(buffer));
    }
  // while(lineLength != -1)

  file.close();

  return count;
}

int
PolChemDefCatParser::parseFiles(
  ConfigSettingsSPtr config_settings_sp,
  std::vector<libXpertMassCore::PolChemDefSpecSPtr> &pol_chem_def_specs,
  UserType user_type)
{
  if(config_settings_sp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  QDir dir;

  int count    = 0;
  int totCount = 0;

  // Depending on m_parseActions, we are going to get all the files
  // that are polymer chemistry def catalogues in the different
  // locations and parse these files one after the other.

  if(user_type == UserType::BOTH)
    {
      count =
        parseFiles(config_settings_sp, pol_chem_def_specs, UserType::SYSTEM);
      if(count == -1)
        return -1;
      totCount += count;

      count =
        parseFiles(config_settings_sp, pol_chem_def_specs, UserType::USER);
      if(count == -1)
        return -1;
      totCount += count;

      return totCount;
    }

  // We have to parse all the catalogue files in the system
  // configuration directory.

  // Get the config setting instance pointer for the proper key:
  const ConfigSettingSPtr config_setting_sp =
    config_settings_sp->getConfigSetting("polChemDefsDir", user_type);

  if(config_setting_sp == nullptr)
    qFatal() << "Programming error. Failed to get configuration setting "
                      "for key 'polChemDefsDir'.";

  dir.setPath(config_setting_sp->m_value.toString());

  // qDebug() << "System polChemDefsDir:"
  // << dir.path();

  if(dir.exists())
    {
      // All the polchem definition catalogues must have the
      // "polChemDefsCat" filename suffix. So we filter the list of
      // files in the directory according to that wildcard expression.

      QStringList filters;
      filters << "*polChemDefsCat";
      dir.setNameFilters(filters);

      dir.setFilter(QDir::Files | QDir::NoSymLinks);

      QFileInfoList file_list = dir.entryInfoList();

      for(int iter = 0; iter < file_list.size(); ++iter)
        {
          count = parseFile(config_settings_sp,
                            file_list.at(iter).absoluteFilePath(),
                            pol_chem_def_specs,
                            user_type);

          if(count == -1)
            return -1;
          else
            totCount += count;
        }
    }
  else
    {
      qDebug() << "The polymer chemistry definitions directory does not exist.";
    }

  return totCount;
}


} // namespace MassXpert
} // namespace MsXpS
