/* 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 <massxpert/nongui/PolChemDefCatParser.hpp>
#include <massxpert/nongui/PolChemDefSpec.hpp>
#include <massxpert/nongui/ConfigSetting.hpp>
#include <massxpert/nongui/ConfigSettings.hpp>


namespace msXpSmassXpert
{

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<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;

  // 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", msXpS::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", msXpS::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<PolChemDefSpec *> *polChemDefSpecList,
                           int forSystemOrUser)
{
  qint64 lineLength;

  QString line;

  char buffer[1024];

  int equalSignIdx = 0;
  int count        = 0;

  PolChemDefSpec *polChemDefSpec = 0;

  // We are given a QList in which to store all the
  // 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 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 AtomDefSpec object.
      polChemDefSpec = new(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", msXpS::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", msXpS::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 msXpSmassXpert
