/***************************************************************************
**
**  This file is part of geopsy.
**
**  geopsy 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.
**
**  geopsy 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 Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2003-12-01
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsyCore.h>
#include <GeopsyGui.h>
#include <QGpGuiTools.h>

#include "Engine.h"
#include "WindowEnvironment.h"
#include "MainWindow.h"
#include "ToolFactory.h"
#include "GeopsyAbout.h"
#include "geopsyVersion.h"
#include "geopsyInstallPath.h"

PACKAGE_INFO("geopsy", GEOPSY)

ApplicationHelp * help();

// Signal file options
static QString database, groupName;
static QStringList signalFiles;
static QString format;
static QByteArray serverAddress="localhost";
static QString streamSelection;
static qint16 serverPort=18000;

int modeGroupList(int argc, char ** argv);
int modeGui(int argc, char ** argv, bool seedlinkView);
int modeExport(int argc, char ** argv, QString exportFile);
int modeWaveform(int argc, char ** argv, QString exportFile);

int showGroups();
SignalFileFormat getExportFormat(QString fStr);
SignalFileFormat getImportFormat(QString fStr);
bool listExportFormats();
bool listImportFormats();

void loadSignals(int& argc, char ** argv);
SubSignalPool loadSignals(SignalDatabase * db, bool viewFiles);
bool checkRemainingArguments(int argc, char ** argv);

static bool debugStream=false;

int main(int argc, char ** argv)
{
  skipOpenBlasMultiThreading();
  // Basic options: selection of the main modes
  enum Mode {Gui, Export, Waveform, GroupList, SeedlinkView};
  Mode mode=Gui;
  QString toolName, exportFile, waveformFile;

  // Usually inside Application but here it is not yet created
  CoreApplication::setArgumentList(argc, argv);
  // Check arguments
  int j=1;
  for(int i=1; i<argc; i++) {
    QByteArray arg=argv[i];
    if(arg[0]=='-') {
      if(arg=="-db") {
        CoreApplication::checkOptionArg(i, argc, argv);
        database=argv[i];
      } else if(arg=="-file") {
        App::log(tr("geopsy: option '-file' is obsolete, files to import are specified without option, see -help.\n") );
        CoreApplication::checkOptionArg(i, argc, argv);
        signalFiles.append(argv[i] );
      } else if(arg=="-import-format") {
        CoreApplication::checkOptionArg(i, argc, argv);
        format=argv[i];
      } else if(arg=="-group") {
        CoreApplication::checkOptionArg(i, argc, argv);
        groupName=argv[i];
      } else if(arg=="-export") {
        CoreApplication::checkOptionArg(i, argc, argv);
        exportFile=argv[i];
        mode=Export;
      } else if(arg=="-waveform") {
        CoreApplication::checkOptionArg(i, argc, argv);
        waveformFile=argv[i];
        mode=Waveform;
      } else if(arg=="-groups") {
        if(CoreApplication::checkOptionArg(i, argc, argv, false)) {
          groupName=argv[i];
        }
        mode=GroupList;
      } else if(arg=="-server") {
        CoreApplication::checkOptionArg(i, argc, argv);
        mode=SeedlinkView;
        serverAddress=argv[i];
      } else if(arg=="-port") {
        CoreApplication::checkOptionArg(i, argc, argv);
        mode=SeedlinkView;
        serverPort=atoi(argv[i]);
      } else if(arg=="-streams") {
        CoreApplication::checkOptionArg(i, argc, argv);
        mode=SeedlinkView;
        streamSelection=argv[i];
      } else if(arg=="-debug-stream") {
        debugStream=true;
      } else if(arg=="-export-formats") {
        CoreApplication a(argc, argv, help);
        return listExportFormats();
      } else if(arg=="-import-formats") {
        CoreApplication a(argc, argv, help);
        return listImportFormats();
      } else if(arg=="-debug-tools") {
        printf("-- Testing available tools --\n");
        CoreApplication a(argc, argv, help);
        GeopsyCoreEngine gp(true, true);
        return 0;
      } else if(arg=="-clear-tools") {
        CoreApplication a(argc, argv, help);
        GeopsyPluginSettings reg;
        reg.clear();
        App::log(tr("Tools cleared\n"));
        return 0;
      } else if(arg=="-add-tool-path") {
        CoreApplication a(argc, argv, help);
        CoreApplication::checkOptionArg(i, argc, argv);
        GeopsyPluginSettings reg;
        QStringList paths=reg.paths();
        paths.append(argv[i]);
        reg.setPaths(paths);
        return 0;
      } else {
        argv[j++]=argv[i]; 
      }
    } else {
      argv[j++]=argv[i];
    }
  }
  if(j < argc) {
    argv[j]=nullptr;
    argc=j;
  }

  switch(mode) {
  case GroupList:
    return modeGroupList(argc, argv);
  case SeedlinkView:
  case Gui:
    break;
  case Export:
    return modeExport(argc, argv, exportFile);
  case Waveform:
    return modeWaveform(argc, argv, waveformFile);
  };
  return modeGui(argc, argv, mode==SeedlinkView);
}

int modeGui(int argc, char ** argv, bool seedlinkView)
{
  Application a(argc, argv, help);

  loadSignals(argc, argv);
  if(!checkRemainingArguments(argc,argv)) {
    return 2;
  }

  SciFigsGlobal s;
  // Splash screen with licence
  GeopsyAbout * splash=new GeopsyAbout(nullptr, Qt::SplashScreen | Qt::WindowStaysOnTopHint);
  splash->setAttribute(Qt::WA_DeleteOnClose);
  QRect rootRect=QApplication::desktop()->geometry();
  QPoint p=rootRect.center();
  splash->move(p.x()-splash->width()/2, p.y()-splash->height()/2);
  splash->show();
  while(!splash->painted()) {
    qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
  }
  qApp->connect(qApp, SIGNAL(aboutToQuit()), splash, SLOT(close()));

  // Create main widget
  Engine gp;
  WindowEnvironment we;
  MainWindow * w=static_cast<MainWindow *>(we.addWindow());
  if(debugStream) {
    Application::instance()->setStream(new StandardStream(stdout));
  }

  w->startupPreferences();
  if(seedlinkView) {
    w->seedlink(serverAddress, serverPort, streamSelection);
  } else {
    loadSignals(w->database(), true);
  }

 int appReturn=qApp->exec();
  return appReturn;
}

int modeExport(int argc, char ** argv, QString exportFile)
{
  CoreApplication a(argc, argv, help);

  // Other options
  bool showProgress=false;
  bool useOriginalBaseName=false;
  int maximumSignalsPerFile=0;
  SignalFileFormat exportFormat;
  // Check geopsy main arguments
  int j=1;
  for(int i=1; i<argc; i++) {
    QByteArray arg=argv[i];
    if(arg[0]=='-') {
      if(arg=="-export-format") {
        CoreApplication::checkOptionArg(i, argc, argv);
        exportFormat=getExportFormat(argv[i] );
      } else if(arg=="-export-base-name") {
        useOriginalBaseName=true;
      } else if(arg=="-export-max-signal") {
        CoreApplication::checkOptionArg(i, argc, argv);
        maximumSignalsPerFile=atoi(argv[i]);
        if(maximumSignalsPerFile<1) {
          App::log(tr("geopsy: maximum number of signals per file must be greater than 0\n") );
          return 2;
        }
      } else if(arg=="-progress") {
        showProgress=true;
      } else if(arg=="-format") {
        App::log(tr("geopsy: option '-format' is obsolete, replace it by '-export-format'\n") );
        CoreApplication::checkOptionArg(i, argc, argv);
        exportFormat=getExportFormat(argv[i] );
      } else {
        argv[j++]=argv[i]; 
      }
    } else {
      argv[j++]=argv[i];
    }
  }
  if(j < argc) {
    argv[j]=nullptr;
    argc=j;
  }

  if(exportFile.isEmpty()) {
    App::log(tr("geopsy: export file name is empty, see -h\n") );
    return 2;
  }
  Engine gp;
  gp.setConsoleProgress(showProgress);
  SignalDatabase db;
  loadSignals(argc, argv);
  SubSignalPool subPool=loadSignals(&db, false);
  if(!checkRemainingArguments(argc,argv)) {
    return 2;
  }
  // Check if there are signals
  if(subPool.isEmpty()) {
    App::log(tr("geopsy: no signal available, check options -file, -db or -group, see -h for help\n") );
    return 2;
  }
  return subPool.save(exportFile, useOriginalBaseName, exportFormat, maximumSignalsPerFile);
}

int modeWaveform(int argc, char ** argv, QString waveformFile)
{
  CoreApplication a(argc, argv, help);

  // Other options
  bool showProgress=false;
  // Check geopsy main arguments
  int j=1;
  for(int i=1; i<argc; i++) {
    QByteArray arg=argv[i];
    if(arg[0]=='-') {
      if(arg=="-progress") {
        showProgress=true;
      } else {
        argv[j++]=argv[i];
      }
    } else {
      argv[j++]=argv[i];
    }
  }
  if(j < argc) {
    argv[j]=nullptr;
    argc=j;
  }

  if(waveformFile.isEmpty()) {
    App::log(tr("geopsy: waveform script file name is empty, see -h\n") );
    return 2;
  }
  GeopsyCoreEngine gp;
  gp.setConsoleProgress(showProgress);
  SignalDatabase db;
  loadSignals(argc, argv);
  SubSignalPool subPool=loadSignals(&db, false);
  if(!checkRemainingArguments(argc,argv)) {
    return 2;
  }
  // Check if there are signals
  if(subPool.isEmpty()) {
    App::log(tr("geopsy: no signal available, check options -file, -db or -group, see -h for help\n") );
    return 2;
  }
  QFile f(waveformFile);
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("geopsy: cannot open file %1\n").arg(waveformFile) );
    return 2;
  }
  if(subPool.isEmpty()) {
    App::log(tr("geopsy: no signals left, aborting.\n") );
    return 2;
  }
  SignalProcess process;
  process.setCurrentSubPool(&subPool);
  SignalProcessScript script(&process);
  return script.run(f.readAll()) ? 0 : 2;
}

int modeGroupList(int argc, char ** argv)
{
  CoreApplication a(argc, argv, help);

  if(!checkRemainingArguments(argc,argv)) {
    return 2;
  }
  GeopsyCoreEngine gp;
  if(!database.isEmpty()) {
    gp.defaultDatabase()->open(database);
    if(groupName.isEmpty()) {
      gp.defaultDatabase()->masterGroup()->printList(QString());
    } else {
      AbstractSignalGroup * g=gp.defaultDatabase()->findGroup(groupName);
      if(!g) {
        App::log(tr("geopsy: unknown group '%1'\n").arg(groupName) );
        return 2;
      }
      g->printList(QString());
    }
    return 0;
  } else {
    App::log(tr("geopsy: no database selected, see -h\n") );
    return 2;
  }
}

/*!
  Must be called after all other option parsing. Only non-option arguments are extracted.
*/
void loadSignals(int& argc, char ** argv)
{
  // Get file list from non-option arguments
  int j=1;
  for(int i=1;i<argc; i++) {
    QByteArray arg=argv[i];
    if(arg[0]!='-') {
      signalFiles.append(argv[i] );
    } else if(arg=="--") {
      for(i++;i<argc; i++) {
        argv[j++]=argv[i];
      }
      break;
    } else {
      argv[j++]=argv[i];
    }
  }
  if(j < argc) {
    argv[j]=nullptr;
    argc=j;
  }
}

SubSignalPool loadSignals(SignalDatabase * db, bool viewFiles)
{
  SubSignalPool subPool;
  if(!database.isEmpty() && db->open(database)) {
    if(!groupName.isEmpty()) {
      AbstractSignalGroup * group=db->findGroup(groupName);
      if(group) {
        subPool=group->subPool();
        if(viewFiles && !subPool.isEmpty()) {
          GeopsyCoreEngine::instance()->newSignalViewer(subPool);
        }
      } else {
        App::log(tr("geopsy: unknown group of signals (%1), see -groups\n").arg(groupName) );
      }
    }
  }
  if(!signalFiles.isEmpty()) {
    if(subPool.isEmpty()) {
      subPool=db->load(signalFiles, getImportFormat(format), viewFiles);
    } else {
      db->load(signalFiles, getImportFormat(format), viewFiles);
    }
  }
  return subPool;
}

bool checkRemainingArguments(int argc, char ** argv)
{
  if(argc>1) {
    for(int i=1; i<argc; i++) {
      App::log(tr("geopsy: bad option '%1', see -h\n").arg(argv[i]) );
    }
    return false;
  } else {
    return true;
  }
}

SignalFileFormat getExportFormat(QString fStr)
{
  SignalFileFormat format=SignalFileFormat::fromName(fStr);
  if(format.id()==SignalFileFormat::Unknown) {
    App::log(tr("geopsy: unknown file format %1\n").arg(fStr) );
    exit(2);
  } else if(format.isReadOnly()) {
    App::log(tr("geopsy: file format %1 is not supported for export.\n").arg(fStr) );
    exit(2);
  }
  return format;
}

SignalFileFormat getImportFormat(QString fStr)
{
  if(fStr.isEmpty() || fStr.toLower()=="automatic") return SignalFileFormat::Unknown;
  SignalFileFormat format=SignalFileFormat::fromName(fStr);
  if(format.id()==SignalFileFormat::Unknown) {
    App::log(tr("geopsy: unknown file format %1\n").arg(fStr) );
    exit(2);
  }
  return format;
}

bool listExportFormats()
{
  TRACE;
  GeopsyCoreEngine gp; // Get access to formats defined by plugins
  QStringList formats;
  for(int i=1; i<SignalFileFormat::FormatCount; i++) {
    SignalFileFormat format(static_cast<SignalFileFormat::Format>(i));
    if(!format.isReadOnly()) {
      formats << format.name();
    }
  }
  QList<SignalFileFormat> customList= GeopsyCoreEngine::instance()->customFileFormats()->list();
  for(QList<SignalFileFormat>::iterator it=customList.begin(); it!=customList.end(); it++) {
    SignalFileFormat& format=*it;
    if(!format.isReadOnly()) {
      formats.append(format.name());
    }
  }
  std::sort(formats.begin(), formats.end());
  printf( "# File formats Geopsy can export:\n");
  int n=formats.count();
  for(int i=0;i<n;i++) {
    printf( "  %s\n",formats.at(i).toLatin1().data());
  }
  return 0;
}

bool listImportFormats()
{
  TRACE;
  GeopsyCoreEngine gp; // Get access to formats defined by plugins
  QStringList formats;
  for(int i=1; i<SignalFileFormat::FormatCount; i++) {
    formats << SignalFileFormat(static_cast<SignalFileFormat::Format>(i)).name();
  }
  QList<SignalFileFormat> customList= GeopsyCoreEngine::instance()->customFileFormats()->list();
  for(QList<SignalFileFormat>::iterator it=customList.begin(); it!=customList.end(); it++) {
    SignalFileFormat& format=*it;
    formats.append(format.name());
  }
  std::sort(formats.begin(), formats.end());
  formats.prepend("Automatic");
  printf( "# File formats Geopsy can import:\n");
  int n=formats.count();
  for(int i=0;i<n;i++) {
    printf( "  %s\n",formats.at(i).toLatin1().data());
  }
  return 0;
}

ApplicationHelp * help()
{
  ApplicationHelp * h=new ApplicationHelp;
  h->setOptionSummary("[OPTIONS] [FILE1 [FILE2 ...]] [-- [TOOL OPTIONS]]");
  h->setComments( "Graphical user interface for a geopsy database. To use tools from command line interface, you must specify "
                  "either signal files (FILE1, FILE2,...) or a group inside a database (options -db and -group). To import several "
                  "files with wildcards, put the file list inside quotes to prevent shell expansion which is likely to produce "
                  "errors ('Argument list too long') if there are many files. You can add new signals to an existing database "
                  "by providing a list of file patterns and a database with option '-db'.\n"
                  "All tool options with arguments must be placed after '--'.");
  h->addGroup("Geopsy","geopsy");
  h->addOption( "-import-format <FORMAT>", "Set file format for files to import (FILE1, FILE2,..., default=Automatic)." );
  h->addOption( "-db <FILE.gpy>", "Open database contained in FILE.gpy" );
  h->addOption( "-group <GROUP>", "Use group of signals named GROUP" );
  h->addOption( "-groups [<PARENT>]", "List the groups available in current database. If PARENT is specified, the groups contained by PARENT are listed." );
  h->addOption( "-export-formats", "List the signal file formats Geopsy can export" );
  h->addOption( "-import-formats", "List the signal file formats Geopsy can import" );
  h->addOption( "-export <FILE>", "Export file specified by option -file to FILE with format -format" );
  h->addOption( "-export-format <FORMAT>", "Specify file format for option -export. The default format is guessed from the extension of "
                                           "the exported file. See option -export-formats for a list of available formats. This option cannot "
                                           "be used without option '-export'.");
  h->addOption( "-export-base-name", "Tries to keep original base names when exporting files (option '-export'). In this case, FILE must be a directory." );
  h->addOption( "-export-max-signals <N>", "Limits the number of signals per file to N." );
  h->addOption( "-waveform <FILE.qs>", "Executes waveform script in FILE.qs to process selected waveforms (group or files)." );
  h->addOption( "-progress","Show progress bar in terminal (only if no active gui)" );
  h->addGroup( "Seed link","seedlink" );
  h->addOption("-server <SERVER>","On start-up connect to SERVER on port specified by option '-port' (default=localhost).");
  h->addOption("-port <PORT>","Set the port number used by option '-server' (default=18000).");
  h->addOption("-streams <STREAMS>","Defines the streams to start. Specification of streams is either the same "
               "as for slinktool(SL) or regular expressions(RX):\n"
               "  RX:stream1[:selectors1],stream2[:selectors2],...\n"
               "  SL:stream1[:selectors1],stream2[:selectors2],...\n"
               "  'stream' is in NET_STA format, followed by a selection of streams. If no selector is given, "
               "all streams are started.\n"
               "  Examples:\n"
               "    \"RX:WA_WAU[0-9]{2}:HH.\"\n"
               "    \"RX:WA_WAU[0-9]{2}:01HH.\"\n"
               "    \"SL:WA_WAU01:HHZ HHE HHN, WA_WAU02:00HH?.D, WA_WAU03,...\"");
  h->addGroup( "Tool debug","toolDebug" );
  h->addOption("-debug-stream","Debug mode (redirect all logs to stdout)");
  h->addOption("-clear-tools","Reset to default tool list." );
  h->addOption("-debug-tools","Test available tools and exit." );
  h->addOption("-add-tool-path <PATH>","Add PATH to the list of paths to search for tools." );
  GeopsyCoreEngine gp;
  ToolFactory tools;
  tools.setHelp(h);
  return h;
}
