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


/////////////////////// Std includes
#include <iostream>

using std::cout;
using namespace std;

/////////////////////// Qt includes
#include <QFileInfo>
#include <QList>
#include <QStringList>
#include <QDebug>
#include <qmath.h>


/////////////////////// Local includes
#include <minexpert/nongui/CommandLineRunner.hpp>
#include <minexpert/nongui/MassSpecDataFileLoaderSqlite3.hpp>
#include <minexpert/nongui/MassSpecDataFileFormatAnalyzer.hpp>
#include <minexpert/nongui/MassSpecDataFileLoaderXy.hpp>
#include <minexpert/nongui/MassSpecDataFileLoaderPwiz.hpp>
#include <minexpert/nongui/FileFormatConverter.hpp>

#include <globals/globals.hpp>
#include <minexpert/nongui/globals.hpp>


namespace msXpSmineXpert
{


//! Construct an empy CommandLineRunner instance
CommandLineRunner::CommandLineRunner()
{
}


//! Destruct \c this CommandLineRunner instance
CommandLineRunner::~CommandLineRunner()
{
}


//! Tell which input file names match existing files on disk.
/*!

  When the parsing of the command line is performed, the input file names are
  available in a list. Iterate in the list and for each file name determine if
  it exists on disk. If a file name matches a file on disk, append its file name
  to the returned string list.

  \return A string list containing all the files names that matched a
  corresponding existing file on disk.

  \sa allInputFilesExist()

*/
QStringList
CommandLineRunner::existingInputFiles()
{
  QStringList existingFiles;

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

      if(fileInfo.exists())
        existingFiles.append(m_currentInputFileName);
      else
        msXpS::qStdOut() << __FILE__ << __LINE__ << __FUNCTION__ << "File"
                         << m_currentInputFileName << "not found";
    }

  return existingFiles;
}


//! Tell if all the files in input actually do exist
/*!

  When the parsing of the command line is performed, the input file names are
  available in a list. Iterate in the list and for each file name determine if
  it exists on disk. If a file name does not match an existing file on disk,
  increment \p *missingFiles. Conversely, if a matching file on disk is found,
  increment \p *existingFiles.

  \param existingFiles pointer to an int where to set the number of existing
  files on disk. Might be nullptr, in which case the counter is not modified.

  \param missingFiles pointer to an int where to set the number of missing
  files on disk. Might be nullptr, in which case the counter is not modified.

  \return true if all the files were found, false otherwise.

  \sa existingInputFiles()
  \sa allInputFilesExist()
  */
bool
CommandLineRunner::allInputFilesExist(int *existingFiles, int *missingFiles)
{
  // Initialize the variables to meaningful values

  int notFound = 0;
  int found    = 0;

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

      if(!fileInfo.exists())
        {
          ++notFound;
        }
      else
        {
          ++found;
        }
    }

  if(existingFiles != nullptr)
    *existingFiles = found;

  if(missingFiles != nullptr)
    *missingFiles = notFound;

  return (m_inFileNames.size() == found);
}


//! Determines which input file exist or do not
/*!

  When the parsing of the command line is performed, the input file names are
  available in a list. Iterate in the list and for each file name determine if
  it exists on disk. If a file name does not match an existing file on disk,
  append its file name to \p *missingFiles. Conversely, if a matching file on
  disk is found, append the file name to \p *existingFiles.

  \param existingFiles pointer to a string list where to store the file names
  of the found files.

  \param missingFiles pointer to a string list where to store the file names
  of the not-found files.

  \return true if all the files were found, false otherwise.

  \sa existingInputFiles()
  \sa allInputFilesExist()

*/
bool
CommandLineRunner::allInputFilesExist(QStringList *existingFiles,
                                      QStringList *missingFiles)
{
  if(existingFiles == nullptr || missingFiles == nullptr)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  bool allExist = true;

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

      if(!fileInfo.exists())
        {
          allExist = false;
          missingFiles->append(m_currentInputFileName);
        }
      else
        existingFiles->append(m_currentInputFileName);
    }

  return allExist;
}


// FIXME doc
//! Tell if the GUI should be run right away.
/*!

  When parsing the command line, establish if the argument to the program call
  are such that the program's graphical user interface must be run without
  delay. The program might indeed be run in console mode, when a file format
  conversion is run.

  A typical example that makes this function return false is when a file
  format conversion is required. Another typical example is when a list of
  file names is provided with no other argument: this function returns true
  and the GUI load all these files. Finally, if the single argument is the
  file name of a script file, then the program should run the GUI right away
  and start executing the commands in the script file.

  Note that note of the parameters might be nullptr.

  \param existingFiles pointer to a string list in which to store the file
  names of the existing files actually found on disk.

  \param errors pointer to a string in which to describe the errors encountered.

  \return true if the program can be run through its GUInterface; false
  otherwise.

*/
bool
CommandLineRunner::validateCommandLine(QStringList *existingFiles,
                                       QStringList *missingFiles,
                                       QString *errors,
                                       bool *shouldRunGui)
{

  // If there were filenames in input, check them.
  if(!m_inFileNames.isEmpty())
    {
      // First off fill in the lists of existing/missing files.
      bool doAllFilesExist = allInputFilesExist(existingFiles, missingFiles);

      if(!doAllFilesExist)
        {
          errors->append("The following input files do were not found:\n");
          errors->append(missingFiles->join(" , "));
          errors->append("\n");

          return false;
        }

      // Check if the user provided a script file name, which would make no
      // sense here, since we have input file names.

      if(!m_scriptFileName.isEmpty())
        {
          errors->append(
            "Having input file names and a script file name "
            "does not make sense.\n");

          return false;
        }

      // If either a conversion or an export is required, check parameters.
      if(m_isFileConversion || m_isExportToXyFiles)
        {

          // If there are more than one input file names, then having an output
          // file name makes no sense, it must be a directory.

          if(existingFiles->size() > 1)
            {
              QFileInfo fileInfo(m_outFileName);

              if(!fileInfo.isDir())
                {
                  errors->append(
                    "Having multiple input files and an output file does not "
                    "make sense. "
                    "Please replace the output file name with an output "
                    "directory name.\n");

                  return false;
                }
            }

          // At this point we know we have input file(s) and either file
          // conversion or file export. We can say:

          *shouldRunGui = false;
          return true;
        }

      // At this point we know we have input file(s), we do not want to run a
      // script, we do  not want to run a file conversion neither a file
      // export. We just want to open one or more files in mineXpert:

      *shouldRunGui = true;
      return true;
    }
  // End of
  // if(!m_inFileNames.isEmpty())
  else
    // There are NO file names in the m_inFileNames list.
    {

      if(!m_scriptFileName.isEmpty())
        {
          QFileInfo file(m_scriptFileName);
          if(!file.exists())
            {
              errors->append("The script file name is not a reachable file.\n");

              return false;
            }

          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
          //<< "Script file exists indeed.";

          *shouldRunGui = true;

          return true;
        }

      if(m_isFileConversion || m_isExportToXyFiles)
        {
          errors->append(
            "Asking for conversion or export without at least one "
            "input file name does not make sense.\n");

          return false;
        }

      if(!m_isFileConversion && !m_isExportToXyFiles)
        {
          // At this point it is fine to run the GUI, since no file conversion
          // nor export is asked nor as script is to be run. We just open the
          // program with nothing special to do.

          *shouldRunGui = true;
          return true;
        }
    }

  // If we are here, fatal!
  qFatal(
    "Fatal error at %s@%d -- %s. "
    "Programming error: we should never reach here."
    "Program aborted.",
    __FILE__,
    __LINE__,
    __FUNCTION__);

  return false;
}


//! Main acting function that triggers the running of the program.
/*!

  This functions analyses (using helper functions) the command line parameters
  (if any) and takes action depending on these parameters.

  For example, if a file format conversion is asked for, that conversion is
  run without even starting the Qt application execution loop.

  \param errors pointer to a string in which to store all the errors
  encountered during the analysis of the command line. May not be nullptr.

  \return

  - true if the GUI should be run right away
  - true if a script file name was provided
  - the exit status of a conversion run if a conversion run is started
  - false otherwise

*/
bool
CommandLineRunner::run(QString *errors)
{

  // Is a file conversion asked for?
  if(m_isFileConversion)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__
               << "-- Conversion asked.";

      return runConversion(errors);
    }

  // Is a spectrum export to xy-formatted files asked for?
  if(m_isExportToXyFiles)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__
               << "-- Export to xy-formatted files asked.";

      return runExportToXyFiles(errors);
    }

  return false;
}


//! Start a file format conversion
/*!

  mineXpert can run in graphical user interface mode but also without a GUI
  when a file format conversion is asked for. Typically, this is the case when
  the user passes argument to the command line to convert a mzML file into the
  private SQLite3-based file format.

  The actual file format conversion work is performed by the
  FileFormatConverter class.

  \param errors pointer to a string in which to append errors encountered
  during the run of the file format conversion.

  \return true if the conversion was successful; false otherwise.

  \sa FileFormatConverter.

*/
bool
CommandLineRunner::runConversion(QString *errors)
{
  bool noErrorAtAll = true;

  qDebug() << __FILE__ << __LINE__ << "Entering runConversion().";

  // For each input file, create a new file that reproduces the data but in
  // SQLite3 db format.

  FileFormatConverter loader;

  for(int iter = 0; iter < m_inFileNames.size(); ++iter)
    {
      loader.setInFileName(m_inFileNames.at(iter));
      loader.setOutFileName(m_outFileName);

      bool success = loader.convert(errors);

      if(success)
        {
          qDebug() << __FILE__ << __LINE__
                   << "Conversion for file:" << loader.inFileName()
                   << "success.";
        }
      else
        {
          noErrorAtAll = false;

          // The errors string will be used later by the calling function

          // cout << "\n" <<  __FILE__ << __LINE__ << __FUNCTION__
          //<< "-- Conversion for file:" << loader.inFileName().toStdString()
          //<< "failure with erros:\n\n"
          //<< errors->toStdString() << endl;

          // Do not stop the loop, certains files will work out correctly and
          // since we return false (success is false), then the caller will know
          // something went wrong and will look at errors.
        }
    }

  return noErrorAtAll;
}


//! Start an export to xy-formatted files.
/*!

  mineXpert can run in graphical user interface mode but also without a GUI
  when an export to xy-formatted files is asked for. Typically, this is the
  case when the user passes argument to the command line to export spectra
  from an mzML file to xy-formatted files.

  The export is actually handled by the MassSpecDataFileLoaderPwiz class.

  \param errors pointer to a string in which to append errors encountered
  during the run of the file format conversion.

  \return true if the export was successful; false otherwise.

  \sa MassSpecDataFileLoaderPwiz.

*/
bool
CommandLineRunner::runExportToXyFiles(QString *errors)
{
  bool noErrorAtAll = true;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__;

  // For each input file, create new files that will receive each spectrum
  // in a new file. Each spectrum will be in a new file, such that there
  // will be as many files as exported spectra. This is for a single input
  // file. The names of the new files will be crafted using the spectrum
  // index as a marker and the required output file name as the main
  // template.


  for(int iter = 0; iter < m_inFileNames.size(); ++iter)
    {
      QString curFileName = m_inFileNames.at(iter);

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
      //<< "Current file to export name:" << curFileName;

      MassSpecDataFileLoaderPwiz loader;

      loader.setFileName(curFileName);

      // The call below will let the loader find how many spectra there are in
      // the file. This is essential for the startIndex/endIndex feature to work
      // correctly.
      if(loader.readSpectrumCount() == 0)
        {
          errors->append(QString("The file %1 to export contains no data:\n")
                           .arg(curFileName));

          noErrorAtAll = false;

          continue;
        }

      int res =
        loader.loadDataToXyFiles(m_outFileName, m_startIndex, m_endIndex);

      if(res >= 0)
        {
          qDebug() << __FILE__ << __LINE__
                   << "Export to xy-formatted files for file:" << curFileName
                   << ": success.";
        }
      else if(res < 0)
        {
          noErrorAtAll = false;

          // The errors string will be used later by the calling function

          errors->append(QString("Failed exporting %1.\n").arg(curFileName));
        }
    }

  return noErrorAtAll;
}


//! Craft a string with all the command line arguments.
/*!

  \return A string containing all the command line parameters nicely formatted.

*/
QString
CommandLineRunner::asText()
{
  QString text = "input files: " + m_inFileNames.join(" , ") + "\n";
  text += "output file: " + m_outFileName + "\n";
  text +=
    "conversion: " + QString("%1\n").arg(m_isFileConversion ? "true" : "false");
  text += "script file: " + m_scriptFileName + "\n";

  return text;
}


} // namespace msXpSmineXpert
