/***************************************************************************
**
**  This file is part of spac2disp.
**
**  spac2disp 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.
**
**  spac2disp 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: 2004-06-03
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <DinverCore.h>
#include <DinverDCCore.h>
#include <QGpCoreWave.h>
#include <QGpGuiTools.h>

#include "spac2dispVersion.h"
#include "spac2dispInstallPath.h"
#include "SpacSelector.h"
#include "Spac3CForward.h"

PACKAGE_INFO("spac2disp", SPAC2DISP)

ApplicationHelp * help();

// Options
static QString pickCurve, outputTarget;
static QString curveParser;
static QStringList loadFileNames;
static double kmin=-1, kmax=-1, vmin=-1, vmax=-1;
static int maxSolutionCount=5, cellCount=-1;
static ExportOptions exportOptions;
static AutocorrCurves autocorr;

bool loadAutocorr(int argc, char ** argv, QString& title)
{
  int i=1;
  while(i<argc) {
    QFileInfo fi(argv[i]);
    title+=fi.fileName()+" ";
    TargetList tl;
    XMLVirtualPlugin plugin(&tl, "DispersionCurve");
    XMLDinverHeader hdr(&plugin);
    if(hdr.xml_restoreFile(argv[i] )!=XMLClass::NoError) {
      Message::warning(MSG_ID, tr("Loading Dinver target/environment ..."),
                               tr("Error while reading file %1").arg(argv[i]), Message::cancel());
      return false;
    }
    autocorr.add(tl.autocorrTarget().curves());
    i++;
  }

  if(autocorr.isEmpty()) {
    QStringList files=Message::getOpenFileNames(tr("Loading SPAC files"),
                                                tr("Dinver targets (*.target);;Dinver environment (*.dinver)"));
    if(files.isEmpty()) {
      Message::critical(MSG_ID, tr("Loading SPAC files"),
                                tr("No SPAC files provided"));
      return false;
    }
    for(QStringList::iterator it=files.begin(); it!=files.end(); it++) {
      QFileInfo fi(*it);
      title+=fi.fileName()+" ";
      TargetList tl;
      XMLVirtualPlugin plugin(&tl, "DispersionCurve");
      XMLDinverHeader hdr(&plugin);
      if(hdr.xml_restoreFile( *it)!=XMLClass::NoError) {
        Message::warning(MSG_ID, tr("Loading Dinver target/project ..."),
                                 tr("Error while reading file %1").arg(*it), Message::cancel());
        return false;
      }
      autocorr.add(tl.autocorrTarget().curves());
    }
  }
  return true;
}

int verticalDispersion()
{
  QTextStream sOut(stdout);
  int n=autocorr.curves().count();
  for(int i=0; i<n; i++) {
    ModalCurve c=autocorr.dispersionCurve(i, kmin, kmax, maxSolutionCount);
    const ModalCurve& curve=autocorr.curves().at(i);
    int iRing=curve.modes().first().ringIndex();
    const AutocorrRing& ring=autocorr.ring(iRing);
    sOut << QString("# Curve %1, ring %2 (from %3 to %4)\n")
            .arg(i).arg(iRing).arg(ring.minRadius()).arg(ring.maxRadius())
         << c.toString();
  }
  return 0;
}

int verticalGui(const QString& title)
{
  SciFigsGlobal s;
  SpacSelector * w=new SpacSelector;
  w->setMaximumSolutionCount(maxSolutionCount);
  w->setWavenumberRange(kmin, kmax);
  w->setVelocityRange(vmin, vmax);
  w->setCellCount(cellCount);
  w->setWindowTitle(title);
  if(!w->createObjects(&autocorr)) {
    delete w;
    return 2;
  }
  if(!loadFileNames.isEmpty()) {
    for(QStringList::iterator it=loadFileNames.begin(); it!=loadFileNames.end(); it++) {
      w->loadCurves(*it, curveParser);
    }
  }
  w->exportPlot(exportOptions);
  int appReturn;
  if(exportOptions.exportFile().isEmpty()) {
    w->show();
    appReturn=Application::instance()->exec();
  } else {
    appReturn=0;
  }
  delete w;
  return appReturn;
}

int verticalPickCurve()
{
  SciFigsGlobal s;
  SpacSelector * w=new SpacSelector;
  w->setMaximumSolutionCount(maxSolutionCount);
  w->setWavenumberRange(kmin, kmax);
  w->setVelocityRange(vmin, vmax);
  w->setCellCount(cellCount);
  if(!w->createObjects(&autocorr)) {
    delete w;
    return 2;
  }
  if(!pickCurve.isEmpty()) {
    w->loadCurves(pickCurve, curveParser);
    w->adjustCurve(w->curves->curves().last());
  } else {
    w->autoPick();
  }
  QTextStream sOut(stdout);
  w->curves->curves().last()->proxy()->save(sOut);
  delete w;
  return 0;
}

int allDispersion()
{
  QTextStream sOut(stdout);
  Spac3CForward forward;
  forward.setCurves(autocorr);
  forward.parameterSpace().setVariableParameters();

  int omegaCount=forward.omegasCount();
  int curveCount=autocorr.curves().count();
  Curve<Point2D> rayleigh, love, alpha;
  Curve<Point2D> * targets=new Curve<Point2D>[curveCount];
  for(int i=0; i<omegaCount; i++) {
    forward.setFrequency(i);
    static const QString baseName="frequency_%1";
    double f=forward.omega(i)*0.5/M_PI;
    ModelRepository na;
    na.setForward(&forward);
    na.setStorage();
    na.setMaximumModelCount(50);
    na.openReport(baseName.arg(f)+".report");
    na.start(1, Generator::MonteCarlo);
    na.wait();
    na.setMaximumModelCount(1000);
    na.start(1, Generator::Neighborhood);
    na.wait();
    if(na.validModelCount()>0) {
      int iBest=na.bestModelIndex();
      double model[3];
      model[0]=na.variableParameterValue(iBest, 0);
      model[1]=na.variableParameterValue(iBest, 1);
      model[2]=na.variableParameterValue(iBest, 2);
      rayleigh.append(Point2D(f,model[0]));
      love.append(Point2D(f,model[1]));
      alpha.append(Point2D(f,model[2]));
      printf("%i-%lf Hz: %lf\n", i,f, na.misfit(iBest));
      forward.setModel(model);
      for(int ci=0; ci<curveCount;ci++) {
        Mode m=autocorr.curves().at(ci).modes().first();
        switch(m.polarization()) {
        case Mode::Vertical:
          targets[ci].append(Point2D(f,forward.vertical(m.ringIndex())));
          break;
        case Mode::Radial:
          targets[ci].append(Point2D(f,forward.radial(m.ringIndex())));
          break;
        case Mode::Transverse:
          targets[ci].append(Point2D(f,forward.transverse(m.ringIndex())));
          break;
        default:
          break;
        }
      }
    }
  }
  sOut << "# Rayleigh\n"
       << rayleigh.toString()
       << "# Love\n"
       << love.toString()
       << "# Alpha\n"
       <<  alpha.toString();
  for(int ci=0; ci<curveCount;ci++) {
    sOut << QString("# %1\n").arg(autocorr.curves().at(ci).modes().first().toString(Mode::Autocorr))
         << targets[ci].toString();
  }
  delete [] targets;
  return 0;
}

int main(int argc, char ** argv)
{
  Application a(argc, argv, help);

  // Options
  enum Spac2dispMode {Gui, Dispersion, PickCurve, SelectTarget};
  enum Component {Vertical, AllComponent};
  Component component=Vertical;
  Spac2dispMode spac2dispMode=Gui;

  // Check arguments
  int i, j=1;
  for(i=1; i<argc; i++) {
    QByteArray arg=argv[i];
    if(arg[0]=='-') {
      if(arg=="-V") {
        component=Vertical;
      } else if(arg=="-VRT") {
        component=AllComponent;
        spac2dispMode=Dispersion;
      } else if(arg=="-dispersion") {
        spac2dispMode=Dispersion;
      } else if(arg=="-pick") {
        if(CoreApplication::checkOptionArg(i, argc, argv, false)) {
          pickCurve=argv[i];
        }
        spac2dispMode=PickCurve;
      } else if(arg=="-select") {
        CoreApplication::checkOptionArg(i, argc, argv);
        outputTarget=argv[i];
        spac2dispMode=SelectTarget;
      } else if(arg=="-curve-parser") {
        CoreApplication::checkOptionArg(i, argc, argv);
        curveParser=argv[i];
      } else if(arg=="-load-curve") {
        CoreApplication::checkOptionArg(i, argc, argv);
        loadFileNames.append(argv[i]);
      } else if(arg=="-kmin") {
        CoreApplication::checkOptionArg(i, argc, argv);
        kmin=atof(argv[i] );
      } else if(arg=="-kmax") {
        CoreApplication::checkOptionArg(i, argc, argv);
        kmax=atof(argv[i] );
      } else if(arg=="-vmin") {
        CoreApplication::checkOptionArg(i, argc, argv);
        vmin=atof(argv[i] );
      } else if(arg=="-vmax") {
        CoreApplication::checkOptionArg(i, argc, argv);
        vmax=atof(argv[i] );
      } else if(arg=="-cell-count") {
        CoreApplication::checkOptionArg(i, argc, argv);
        cellCount=atoi(argv[i] );
      } else if(arg=="-max-solutions") {
        CoreApplication::checkOptionArg(i, argc, argv);
        maxSolutionCount=atoi(argv[i] );
      } else {
        argv[j++]=argv[i];
      }
    } else {
      argv[j++]=argv[i];
    }
  }
  if(!exportOptions.read(argc, argv)) {
    return 2;
  }
  if(j<argc) {
    argv[j]=nullptr;
    argc=j;
  }
  if(!CoreApplication::checkRemainingArgs(argc, argv)) {
    return 2;
  }

  // get autocorr curves
  if(spac2dispMode!=Gui) {
    a.setConsoleMessage();
  }
  QString title("spac2disp - ");
  if(!loadAutocorr(argc, argv, title)) {
    return 2;
  }

  int appReturn=0;
  switch(component) {
  case Vertical:
    switch(spac2dispMode) {
    case Gui:
      appReturn=verticalGui(title);
      break;
    case Dispersion:
      appReturn=verticalDispersion();
      break;
    case PickCurve:
      appReturn=verticalPickCurve();
      break;
    case SelectTarget:
      App::log(tr("Not yet implemented\n") );
      break;
    }
    break;
  case AllComponent:
    switch(spac2dispMode) {
    case Gui:
      App::log(tr("Not yet implemented\n") );
      break;
    case Dispersion:
      allDispersion();
      break;
    case PickCurve:
      App::log(tr("Not yet implemented\n") );
      break;
    case SelectTarget:
      App::log(tr("Not yet implemented\n") );
      break;
    }
    break;
  }

  return appReturn;
    /*
    struct Model {
      double alphaR, slowR, slowL, misfit;
    };
    Model solution[omegaCount];
    for(int i =0;i<omegaCount; i++) {
      solution[i].misfit=std::numeric_limits<double>::infinity();
    }

    Value * dispR=dispFactory.phaseRayleigh()->mode(0);
    Value * dispL=dispFactory.phaseLove()->mode(0);
    double inv2pi=0.5/M_PI;

    for(double alphaR=0.0; alphaR < 1.0; alphaR +=0.01) {
      fprintf(stderr,"alphaR=%lg\n", alphaR);
      for(double slowR=0.00025; slowR<0.01; slowR +=1e-4) {
        fprintf(stderr,"slowR=%lg\n", slowR);
        for(int i=0; i<omegaCount; i++) {
          dispR[i].setValue(slowR);
        }
        for(double slowL=0.00025; slowL<0.01; slowL +=1e-4) {
          for(int i=0; i<omegaCount; i++) {
            dispL[i].setValue(slowL);
          }
          autocorrFactory.calculate(alphaR, &dispFactory);
          for(int i=0; i<omegaCount; i++) {
            double misfit=0.0;
            int nValues=0;
            int nData=0;
            for(QList<ModalCurve>::ConstIterator it=curveList.begin(); it!=curveList.end(); ++it) {
              const Value * values=autocorrFactory.mode(it->modes().first());
              const FactoryPoint& p=it->at(i);
              misfit+=p.misfit2(nValues, nData, values[ p.index() ] );
            }
            //printf( "%lg %lg %lg %lg %lg\n", inv2pi*autocorrFactory.x()->at(i), alphaR, slowR, slowL, misfit);
            if(misfit<solution[i].misfit) {
              solution[i].alphaR=alphaR;
              solution[i].slowR=slowR;
              solution[i].slowL=slowL;
              solution[i].misfit=misfit;
              fprintf(stderr, "improving... %lg %lg %lg %lg %lg\n", inv2pi*autocorrFactory.x()->at(i),
                      solution[i].alphaR, solution[i].slowR, solution[i].slowL, solution[i].misfit);
            }
          }
        }
      }
    }
    for(int i=0; i<omegaCount; i++) {
      printf( "%lg %lg %lg %lg %lg\n", inv2pi*autocorrFactory.x()->at(i),
              solution[i].alphaR, solution[i].slowR, solution[i].slowL, solution[i].misfit);
    }*/
}

ApplicationHelp * help()
{
  TRACE;
  ApplicationHelp * h=new ApplicationHelp;
  h->setOptionSummary( "[OPTIONS] <SPAC FILE 1> <SPAC FILE 2> ..." );
  h->setComments( "Validate autocorrelation curves by trying to delineate a common dispersion "
                  "curve (vertical and horizontal components). SPAC FILE can be a .target or "
                  "a .dinver file." );
  h->addGroup("Spac2disp","spac2disp");
  h->addOption("-V","Vertical autocorrelation validation (default)");
  h->addOption("-VRT","Vertical, Radial and Tranverse component inversion");
  h->addOption("-dispersion","Output the dispersion curve for each input autocorrelation curve. "
                             "Only for vertical component.");
  h->addOption("-pick [<FILE>]","Automatically pick or adjust curve loaded from FILE. Curve is sent to stdout."
                                "Only for vertical component.");
  h->addOption("-select <TARGET>","Selects samples and save the result in file TARGET. Exactly two limiting curves must be "
                                  "specified with option '-load-curve'.");
  h->addOption("-load-curve <FILE>", "Loads curves from FILE. The curve are imported with the parser specified with option '-curve-parser'.");
  h->addOption("-curve-parser <FILE>", "Defines the parser for importing curves");
  h->addOption("-kmin <MIN>","Search for solutions above wave number MIN.");
  h->addOption("-kmax <MAX>","Search for solutions below wave number MAX.");
  h->addOption("-vmin <MIN>","Minimum velocity of dispersion grid.");
  h->addOption("-vmax <MAX>","Maximum velocity of dispersion grid.");
  h->addOption("-cell-count <COUNT>","Number of in dispersion grid.");
  h->addOption("-max-solutions <MAX>","Maximum number of accepted solutions, limits the number of "
                                      "archs in the Bessel function.");
  return h;
}
