/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.minexpert.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 <QApplication>
#include <QtGlobal>
#include <QStringList>
#include <QVector>
#include <QDebug>


/////////////////////// Std includes
#include <iostream>
#include <string>
#include <algorithm>
#include <limits>

////////////////////// Command line stuff include
#include <tclap/CmdLine.h>
#include <tclap/DocBookOutput.h>


/////////////////////// Local includes
#include "../config.h"
#include "gui/Application.hpp"
#include "nongui/CommandLineRunner.hpp"
#include "gui/MainWindow.hpp"


using std::cout;
using namespace std;
using namespace TCLAP;

double notANumber = qSNaN();

namespace msXpSmineXpert
{

void printGreetings();
void printVersion();
void printConfig(const QString & = QString());

// We need to redefine the way the version printing is performed because we want
// to specify things that TCLAP cannot know (version of Qt being used, for
// example).
class TCLAPOutput : public TCLAP::StdOutput
{
  public:
  virtual void
  version(CmdLineInterface &c)
  {
    printVersion();
  }
};

void
printGreetings()
{
  QString greetings = QObject::tr("mineXpert, version %1\n\n").arg(VERSION);

  greetings += "Type 'minexpert --help' for help\n\n";

  greetings +=
    "mineXpert is Copyright 2016-2017 \n"
    "by Filippo Rusconi.\n\n"
    "mineXpert comes with ABSOLUTELY NO WARRANTY.\n"
    "mineXpert is free software, "
    "covered by the GNU General\n"
    "Public License Version 3 or later, "
    "and you are welcome to change it\n"
    "and/or distribute copies of it under "
    "certain conditions.\n"
    "Check the file COPYING in the distribution "
    "and/or the\n"
    "'Help/About(Ctrl+H)' menu item of the program.\n"
    "\nHappy mineXpert'ing!\n\n";

  cout << greetings.toStdString();
}


void
printVersion()
{
  QString version = QObject::tr(
                      "mineXpert, version %1 -- "
                      "Compiled against Qt, version %2\n")
                      .arg(VERSION)
                      .arg(QT_VERSION_STR);

  cout << version.toStdString();
}


void
printConfig(const QString &execName)
{
  QString config = QObject::tr(
                     "mineXpert: "
                     "Compiled with the following configuration:\n"
                     "EXECUTABLE BINARY FILE: = %1\n"
                     "MINEXPERT_BIN_DIR = %2\n"
                     "MINEXPERT_JAVASCRIPT_DIR = %2\n"
                     "MINEXPERT_DATA_DIR = %3\n"
                     "MINEXPERT_DOC_DIR = %5\n")
                     .arg(execName)
                     .arg(MINEXPERT_BIN_DIR)
                     .arg(MINEXPERT_JAVASCRIPT_DIR)
                     .arg(MINEXPERT_DATA_DIR)
                     .arg(MINEXPERT_DOC_DIR);

  cout << config.toStdString();
}

} // namespace msXpSmineXpert


int
main(int argc, char **argv)
{
  // Command line parsing using tclap. Everything should be enclosed in a try:

  // All the input files that are verified to be existing will have their name
  // stored here. The same for missing input files.
  QStringList existingInputFiles;
  QStringList missingInputFiles;

  QString *scriptContents = Q_NULLPTR;

  msXpSmineXpert::CommandLineRunner cmdLineRunner;

  try
    {
      QString cmdLineProgUsageMsg =
        "mineXpert is a program that can be used:\n";
      cmdLineProgUsageMsg +=
        "\t to perform analyses on any mass spectrum that has a\n";
      cmdLineProgUsageMsg +=
        "\t proper file format. The program operates integrally\n";
      cmdLineProgUsageMsg +=
        "\t in a graphical user interface. When mining the data\n";
      cmdLineProgUsageMsg +=
        "\t it is possible to send a number of metadata of a given mass peak\n";
      cmdLineProgUsageMsg += "\t to a file specified in the options.\n";
      cmdLineProgUsageMsg +=
        "\t This program has a special inclination to the analysis of\n";
      cmdLineProgUsageMsg += "\t drift time mass spectrometry data.\n";

      CmdLine cmdLine(cmdLineProgUsageMsg.toStdString(), ' ', VERSION);

      msXpSmineXpert::TCLAPOutput myTCLAPOutput;

      cmdLine.setOutput(&myTCLAPOutput);

      // license switch
      SwitchArg printLicenseArg(
        "l", "license", "Print the license of this software.", cmdLine, false);

      // version switch
      SwitchArg printVersionArg(
        "V",
        "Version",
        "Print the contextual version of this software.",
        cmdLine,
        false);

      // config switch
      SwitchArg printConfigArg("c",
                               "config",
                               "Print the configuration of this software.",
                               cmdLine,
                               false);

      // Now all the specific command line options.

      // When asking for mineXpert-specific actions, a number of
      // command line arguments might be given, more than once,
      // from a defined set of values.

      // convert file format switch
      SwitchArg convertArg("x",
                           "convert",
                           "Convert from mzML to SQLite3 db format.",
                           cmdLine,
                           false);

      // export to xy-formatted files switch
      SwitchArg exportArg(
        "",
        "exportXY",
        "Export each spectrum to a different xy-formatted file.",
        cmdLine,
        false);

      // start index of the spectrum range to export to xy-formatted files.
      ValueArg<int> startIndexArg(
        "",
        "startIndex",
        "Index of the first spectrum to export to xy-formatted file.",
        false,
        -1,
        "int",
        cmdLine);

      // end index of the spectrum range to export to xy-formatted files.
      ValueArg<int> endIndexArg(
        "",
        "endIndex",
        "Index of the last spectrum to export to xy-formatted file.",
        false,
        -1,
        "int",
        cmdLine);

      // streamed switch
      SwitchArg streamedArg("s",
                            "streamed",
                            "When opening one or more files "
                            "in the gui, do that in streamed "
                            "mode, otherwise in full.",
                            cmdLine,
                            false);

      // This is only to generate the docbook refentry code for the man page.
      // TCLAP::DocBookOutput docbookOutput;
      // docbookOutput.usage(cmdLine);


      // The files
      UnlabeledMultiArg<string> inFilesArg(
        "inputFiles",
        "None, one or more input files, listed as the last "
        "arguments on the command line (separated by space).",
        false,
        "input file names",
        cmdLine,
        false);

      ValueArg<string> outFileArg("o",
                                  "outFile",
                                  "File to store the task results to "
                                  "(stdout to output to the console).",
                                  false,
                                  "",
                                  "",
                                  cmdLine);

      ValueArg<string> scriptFileArg("j",
                                     "scriptFile",
                                     "Script file to load and execute.",
                                     false,
                                     "",
                                     "",
                                     cmdLine);

      cmdLine.parse(argc, argv);

      // qDebug() << __FILE__ << __LINE__
      //<< "argc" << argc << "argv:" << argv[0];

      // Start working on the various parameters.

      bool isPrintLicense = printLicenseArg.getValue();
      bool isPrintVersion = printVersionArg.getValue();
      bool isPrintConfig  = printConfigArg.getValue();

      // The user might ask for the license to be shown:
      if(isPrintLicense)
        {
          msXpSmineXpert::printGreetings();
        }

      // The user might ask for the version to be shown:
      if(isPrintVersion)
        {
          msXpSmineXpert::printVersion();
          return 0;
        }

      // The user might ask for the config to be shown:
      if(isPrintConfig)
        {
          msXpSmineXpert::printConfig(
            QString("%1 (run as %2)").arg(MINEXPERT_TARGET_NAME).arg(argv[0]));
          return 0;
        }

      // If any of the following occurred, then just exit without
      // actually running the program.
      if(isPrintLicense || isPrintVersion || isPrintConfig)
        {
          exit(0);
        }
      // At this point start doing the work on the real stuff.

      // Are we willing to convert file formats?
      cmdLineRunner.m_isFileConversion = convertArg.getValue();

      // Is the file reading streamed ?
      cmdLineRunner.m_isStreamed = streamedArg.getValue();

      // Are the spectra to be exported to xy-formatted files?
      cmdLineRunner.m_isExportToXyFiles = exportArg.getValue();

      cmdLineRunner.m_startIndex = startIndexArg.getValue();
      cmdLineRunner.m_endIndex   = endIndexArg.getValue();

      // The data files in input
      vector<string> inFiles = inFilesArg.getValue();

      for(auto iter : inFiles)
        {
          cmdLineRunner.m_inFileNames.append(QString::fromStdString(iter));
        }

      // The data file in output
      cmdLineRunner.m_outFileName =
        QString::fromStdString(outFileArg.getValue());

      // qDebug() << __FILE__ << __LINE__
      //<< "outFileArg:" << cmdLineRunner.m_outFileName;

      cmdLineRunner.m_scriptFileName =
        QString::fromStdString(scriptFileArg.getValue());

      // qDebug() << __FILE__ << __LINE__
      //<< "scriptFileArg:" << cmdLineRunner.m_scriptFileName;

      // QString cmdLineAsText = cmdLineRunner.asText();
      // qDebug() << __FILE__ << __LINE__ << "Command line:" << cmdLineAsText;

      QString errors;
      bool shouldRunGui = false;

      // Check if, with the current command line arguments, the program can run
      // the tasks asked and then simply return or if the arguments are not
      // enough to carry over a set of tasks and thus we should run the GUI.

      bool success = cmdLineRunner.validateCommandLine(
        &existingInputFiles, &missingInputFiles, &errors, &shouldRunGui);

      if(!success)
        {
          cerr << __FILE__ << __LINE__
               << "The command line validation failed with these errors:\n\n"
               << errors.toStdString() << endl;

          exit(1);
        }

      // If we should not run the GUI right away, then run!

      errors.clear();

      if(!shouldRunGui)
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
          //<< "Should not run GUI.";

          QCoreApplication app(argc, argv);

          success = cmdLineRunner.run(&errors);

          if(!success)
            {
              cerr << __FILE__ << __LINE__
                   << "The following errors were encountered:\n\n"
                   << errors.toStdString() << endl;

              exit(1);
            }
          else
            {
              cerr << __FILE__ << __LINE__ << "Process terminated: success.\n";

              exit(0);
            }
        }

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
      //<< "Should run GUI.";
    }
  catch(TCLAP::ArgException &e)
    {
      cerr << "error: " << e.error() << " for arg " << e.argId() << endl;
      exit(1);
    }

  // qDebug() << __FILE__ << __LINE__
  //<< "Will now run the program. Number of input files:"
  //<< existingInputFiles.size();

  Q_INIT_RESOURCE(application);

  // Qt stuff starts here.
  msXpSmineXpert::Application application(argc, argv, "minexpert");

  application.processEvents();

  msXpSmineXpert::MainWindow *mainWindow =
    new msXpSmineXpert::MainWindow("mineXpert");
  mainWindow->show();

  // Test if we have a script file to run.
  if(!cmdLineRunner.m_scriptFileName.isEmpty())
    {

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
      //<< "The script file name is not empty.";

      QFile file(cmdLineRunner.m_scriptFileName);

      if(!file.exists())
        {
          qDebug() << __FILE__ << __LINE__ << "The script file"
                   << cmdLineRunner.m_scriptFileName
                   << "was not found. Exiting.";

          exit(1);
        }

      if(!file.open(QFile::ReadOnly))
        {
          qDebug() << __FILE__ << __LINE__ << "Failed to open the script file"
                   << cmdLineRunner.m_scriptFileName << ". Exiting.";

          exit(1);
        }

      QTextStream stream(&file);
      scriptContents = new QString(stream.readAll());

      if(scriptContents != Q_NULLPTR && !scriptContents->isEmpty())
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
          //<< "The script file contents: " << *scriptContents;

          file.close();

          QString explanation = QString("Running script from file %1\n")
                                  .arg(cmdLineRunner.m_scriptFileName);

          mainWindow->showScriptingWnd();
          mainWindow->mp_scriptingWnd->runScript(*scriptContents, explanation);

          delete scriptContents;
        }
    }
  else
    {

      // At this point load all the existing input files that might be in the
      // command line.

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "Should open mass
      // spectrometry files:" << existingInputFiles;

      mainWindow->openMassSpectrometryFiles(existingInputFiles,
                                            cmdLineRunner.m_isStreamed);
    }

  return application.exec();
}
// End of
// main(int argc, char **argv)
