/* 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 <QFile>
#include <QDir>
#include <QDebug>


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


namespace MsXpS
{

namespace massxpert
{


  PolChemDefCatParser::PolChemDefCatParser()
  {
  }


  PolChemDefCatParser::~PolChemDefCatParser()
  {
  }


  void
  PolChemDefCatParser::setFilePath(const QString &filePath)
  {
    m_filePath = filePath;
  }


  const QString &
  PolChemDefCatParser::getFilePath()
  {
    return m_filePath;
  }


  void
  PolChemDefCatParser::setPendingMode(int pendingMode)
  {
    m_pendingMode = pendingMode;
  }


  int
  PolChemDefCatParser::getPendingMode()
  {
    return m_pendingMode;
  }


  void
  PolChemDefCatParser::setConfigSysUser(int configSysUser)
  {
    m_configSysUser = configSysUser;
  }


  int
  PolChemDefCatParser::getConfigSysUser()
  {
    return m_configSysUser;
  }


  int
  PolChemDefCatParser::parseFiles(
    const ConfigSettings *configSettings,
    QList<libXpertMass::PolChemDefSpec *> *polChemDefSpecList)
  {
    mp_configSettings = configSettings;
    if(mp_configSettings == nullptr)
      qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

    QDir dir;

    int count    = 0;
    int totCount = 0;

    // libXpertMass::Polymer chemistry definition catalogue files are located in
    // polChemDefs directories that are located in turn in either
    // system-wide configuration places, like
    // /etc/massxpert/polChemDefs, for example on UNIX-like systems,
    // and/or in private user locations, like
    // $HOME/.massxpert/polChemDefs.

    Q_ASSERT(polChemDefSpecList);

    // Depending on m_configSysUser, 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(m_configSysUser & POLCHEMDEF_CAT_PARSE_SYSTEM_CONFIG)
      {
        // We have to parse all the catalogue files in the system
        // configuration directory.

        // Get the config setting instance pointer for the proper key:
        const ConfigSetting *configSetting = mp_configSettings->value(
          "polChemDefsDir", UserType::USER_TYPE_SYSTEM);
        if(configSetting == nullptr)
          qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

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

        // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
        //<< "System polChemDefsDir:" << configSetting->m_value.toString();

        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 list = dir.entryInfoList();

            for(int iter = 0; iter < list.size(); ++iter)
              {
                QFileInfo fileInfo = list.at(iter);

                m_filePath = fileInfo.absoluteFilePath();
                count =
                  parse(polChemDefSpecList, POLCHEMDEF_CAT_PARSE_SYSTEM_CONFIG);

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

    if(m_configSysUser & POLCHEMDEF_CAT_PARSE_USER_CONFIG)
      {
        // We have to parse all the catalogue files in the user
        // configuration directory.

        const ConfigSetting *configSetting =
          mp_configSettings->value("polChemDefsDir", UserType::USER_TYPE_USER);
        dir.setPath(configSetting->m_value.toString());

        // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
        //<< "User polChemDefsDir:" << configSetting->m_value.toString();

        if(dir.exists())
          {
            // All the atom 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 list = dir.entryInfoList();

            for(int iter = 0; iter < list.size(); ++iter)
              {
                QFileInfo fileInfo = list.at(iter);

                m_filePath = fileInfo.absoluteFilePath();
                count =
                  parse(polChemDefSpecList, POLCHEMDEF_CAT_PARSE_USER_CONFIG);

                if(count == -1)
                  return -1;
                else
                  totCount += count;
              }
          }
      }

    return totCount;
  }


  int
  PolChemDefCatParser::parse(
    QList<libXpertMass::PolChemDefSpec *> *polChemDefSpecList, int forSystemOrUser)
  {
    qint64 lineLength;

    QString line;

    char buffer[1024];

    int equalSignIdx = 0;
    int count        = 0;

    libXpertMass::PolChemDefSpec *polChemDefSpec = 0;

    // We are given a QList in which to store all the
    // libXpertMass::PolChemDefSpec* instances that we create by parsing the
    // catalogue files.
    //
    // Each line in the catalogue(s) 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 libXpertMass::PolChemDefSpec item that we append/prepend to the
    // QList passed as parameter.

    Q_ASSERT(polChemDefSpecList);

    QFile file(m_filePath);

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

    // qDebug() << "Begin file:" << m_filePath.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 libXpertMass::AtomDefSpec object.
        polChemDefSpec = new(libXpertMass::PolChemDefSpec);

        polChemDefSpec->setName(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.

            if(forSystemOrUser == POLCHEMDEF_CAT_PARSE_SYSTEM_CONFIG)
              {
                const ConfigSetting *configSetting = mp_configSettings->value(
                  "polChemDefsDir", UserType::USER_TYPE_SYSTEM);
                QFile file(configSetting->m_value.toString() + "/" +
                           polChemDefFile);

                if(!file.exists())
                  {
                    delete polChemDefSpec;
                    return -1;
                  }

                polChemDefSpec->setFilePath(file.fileName());
              }
            else if(forSystemOrUser == POLCHEMDEF_CAT_PARSE_USER_CONFIG)
              {
                const ConfigSetting *configSetting = mp_configSettings->value(
                  "polChemDefsDir", UserType::USER_TYPE_USER);
                QFile file(configSetting->m_value.toString() + "/" +
                           polChemDefFile);

                if(!file.exists())
                  {
                    delete polChemDefSpec;
                    return -1;
                  }

                polChemDefSpec->setFilePath(file.fileName());
              }
            else
              qFatal(
                "%s@%d: Failed to parse polymer chemistry definition "
                "catalogues.",
                __FILE__,
                __LINE__);
          }
        else
          {
            if(!fileInfo.exists())
              {
                delete polChemDefSpec;
                return -1;
              }

            polChemDefSpec->setFilePath(polChemDefFile);
          }


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

        if(m_pendingMode == POLCHEMDEF_CAT_PARSE_APPEND_CONFIG)
          polChemDefSpecList->append(polChemDefSpec);
        else
          polChemDefSpecList->prepend(polChemDefSpec);

        // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
        //<< "Added (pre/app/ended) polymer chemistry definition:" <<
        // polChemDefSpec->filePath();

        ++count;

        // qDebug() << "Parsed line: " << line.toAscii();

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

    file.close();

    // qDebug() << "Endfile:" << m_filePath.toAscii();

    return count;
  }

} // namespace massxpert

} // namespace MsXpS
