/***************************************************************************
**
**  This file is part of gpdepths.
**
**  gpdepths 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.
**
**  gpdepths 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: 2009-02-02
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <DinverCore.h>
#include <DinverDCCore.h>
#include <QGpCoreTools.h>
#include "gpdepthsVersion.h"
#include "gpdepthsInstallPath.h"
#include "DepthDistribution.h"

#define DIFF_SIGN(A,B) ((A<0 && B>0) || (A>0 && B<0))

PACKAGE_INFO("gpdepths", GPDEPTHS)

ApplicationHelp * help();

int main(int argc, char ** argv)
{
  CoreApplication app(argc, argv, help);

  // Options
  DepthDistribution dd;
  QString outputParam;
  double vpMin=200.0;
  double vpMax=5000.0;
  double vsMin=150.0;
  double vsMax=3500.0;
  double nuMin=0.2;
  double nuMax=0.5;
  double rhoMin=2000.0;
  double rhoMax=2000.0;
  bool fixDepths=false;
  double vsLink=-1;
  QVector<Point> positions;
  // Check arguments
  int i, j=1;
  for(i=1; i<argc; i++) {
    QByteArray arg=argv[i];
    if(arg[0]=='-') {
      if(arg=="-min") {
        CoreApplication::checkOptionArg(i, argc, argv);
        dd.setMinimumWavelength(atof( argv[i] ));
      } else if(arg=="-max") {
        CoreApplication::checkOptionArg(i, argc, argv);
        dd.setMaximumWavelength(atof( argv[i] ));
      } else if(arg=="-n") {
        CoreApplication::checkOptionArg(i, argc, argv);
        if( ! dd.setLayerCount(atoi( argv[i] )) ) {
          App::log(tr("gpdepths: null or negative number of layers (%1).\n").arg(argv[i]) );
          return 2;
        }
      } else if(arg=="-o") {
        CoreApplication::checkOptionArg(i, argc, argv);
        outputParam=argv[i];
      } else if(arg=="-vp-min") {
        CoreApplication::checkOptionArg(i, argc, argv);
        vpMin=atof(argv[i]);
      } else if(arg=="-vp-max") {
        CoreApplication::checkOptionArg(i, argc, argv);
        vpMax=atof(argv[i]);
      } else if(arg=="-vs-min") {
        CoreApplication::checkOptionArg(i, argc, argv);
        vsMin=atof(argv[i]);
      } else if(arg=="-vs-max") {
        CoreApplication::checkOptionArg(i, argc, argv);
        vsMax=atof(argv[i]);
      } else if(arg=="-nu-min") {
        CoreApplication::checkOptionArg(i, argc, argv);
        nuMin=atof(argv[i]);
      } else if(arg=="-nu-max") {
        CoreApplication::checkOptionArg(i, argc, argv);
        nuMax=atof(argv[i]);
      } else if(arg=="-rho-min") {
        CoreApplication::checkOptionArg(i, argc, argv);
        rhoMin=atof(argv[i]);
      } else if(arg=="-rho-max") {
        CoreApplication::checkOptionArg(i, argc, argv);
        rhoMax=atof(argv[i]);
      } else if(arg=="-fix-depths") {
        fixDepths=true;
      } else if(arg=="-p") {
        CoreApplication::checkOptionArg(i, argc, argv);
        Point position;
        if(!position.fromString(QString(argv[i]))) {
          App::log(tr("gpdepths: error setting position from \"%1\"\n").arg(argv[i]) );
          return 2;
        }
        positions.append(position);
      } else if(arg=="-link-vs") {
        CoreApplication::checkOptionArg(i, argc, argv);
        vsLink=atof(argv[i]);
      } else {
        App::log(tr("gpdepths: bad option %1, see -help\n").arg(argv[i]) );
        return 2;
      }
    } else {
      argv[j++]=argv[i];
    }
  }
  if(j < argc) {
    argv[j]=nullptr;
    argc=j;
  }
  QTextStream sOut(stdout);
  if(!dd.calculate()) {
    return 2;
  }
  dd.report(sOut);
  if( ! outputParam.isEmpty()) {
    ParamGroundModel gm;
    ParamProfile * vp=new ParamProfile( "Vp", tr("Compression-wave velocity"), tr("m/s"), vpMin, vpMax,
                                                ParamProfile::Param, SimpleCondition::LessThan);
    gm.addProfile(vp);
    ParamProfile * nu=new ParamProfile( "Nu", tr("Poisson's Ratio"), "", nuMin, nuMax,
                                                ParamProfile::Condition, SimpleCondition::GreaterThan);
    gm.addProfile(nu);
    ParamProfile * vs=new ParamProfile( "Vs", tr("Shear-wave velocity"), tr("m/s"), vsMin, vsMax,
                                                ParamProfile::Param, SimpleCondition::LessThan);
    gm.addProfile(vs);
    ParamProfile * rho=new ParamProfile( "Rho", tr("Density"), tr("kg/m3"), rhoMin, rhoMax,
                                                ParamProfile::Param, SimpleCondition::LessThan);
    gm.addProfile(rho);
    ParamLayer * l;
    l=new ParamLayer(vp, 0);
    if(nuMin<nuMax) {
      l->setShape(ParamLayer::LinearIncrease);
      l->setMinimumDepth(dd.minimumDepth(0));
      l->setMaximumDepth(dd.maximumDepth(dd.layerCount()-1));
      vp->addLayer(l);
      vp->addLayer(new ParamLayer(vp, 1));
    } else {
      l->setTopMinimumValue(200.0);
      l->setTopMaximumValue(200.0);
      l->setBottomMinimumValue(200.0);
      l->setBottomMaximumValue(200.0);
      vp->addLayer(l);
    }
    nu->addLayer(new ParamLayer(nu, 0) );
    QString layerThicknessConditions;
    for(int i=0; i<dd.layerCount(); i++ ) {
      l=new ParamLayer(vs, i);
      if(fixDepths) {
        l->setDepth(false);
        l->setMinimumDepth(dd.thickness(i));
        l->setMaximumDepth(dd.thickness(i));
      } else {
        l->setMinimumDepth(dd.minimumDepth(i));
        l->setMaximumDepth(dd.maximumDepth(i));
        if(i>0) {
          layerThicknessConditions+=QString("linear(\"%4DVs%1\",\">\",1.0,\"%4DVs%2\",%3);\n")
                                       .arg(i).arg(i-1).arg(dd.thickness(i)*0.5);
        }
      }
      vs->addLayer(l);
    }
    vs->addLayer(new ParamLayer(vs, dd.layerCount()) );
    rho->addLayer(new ParamLayer(rho, 0) );

    if(positions.count()<=1) {
      if(positions.count()==1) {
        gm.setPosition(positions.first());
      }
      if(!fixDepths) {
        gm.setCustomConditions(layerThicknessConditions.arg(""));
      }
      XMLVirtualPlugin plugin(&gm, "DispersionCurve");
      XMLDinverHeader hdr(&plugin);
      hdr.xml_saveFile(outputParam);
    } else {
      Param3DGroundModel gm3d;
      int n=positions.count();
      QString globalCustomConditions;
      for(int i=0; i<n; i++) {
        gm.setPosition(positions.at(i));
        QString profilePrefix=QString(65+i)+"_";
        if(!fixDepths) {
          gm.setCustomConditions(layerThicknessConditions.arg(profilePrefix));
        }
        if(vsLink>0.0 && i>0) {
          for(int iLayer=0; iLayer<dd.layerCount(); iLayer++) {
            globalCustomConditions+=QString("linear(\"%2TopVs%1\", \">\", %3, \"A_TopVs%1\",0);\n")
                                    .arg(iLayer).arg(profilePrefix).arg(1.0-vsLink);
            globalCustomConditions+=QString("linear(\"%2TopVs%1\", \"<\", %3, \"A_TopVs%1\",0);\n")
                                    .arg(iLayer).arg(profilePrefix).arg(1.0+vsLink);
          }
        }
        gm3d.addModel(gm);
      }
      gm3d.setCustomConditions(globalCustomConditions);
      XMLVirtualPlugin plugin(&gm3d, "DispersionCurve2D");
      XMLDinverHeader hdr(&plugin);
      hdr.xml_saveFile(outputParam);
    }
  }
  return 0;
}

ApplicationHelp * help()
{
  TRACE;
  ApplicationHelp * h=new ApplicationHelp;
  h->setOptionSummary( "[OPTIONS]" );
  h->setComments( "Compute depth ranges for a geometrical progression of the thickness over a fixed depth range in a 1D structure. h=a*q^i" );
  h->addGroup("gpdepths", "gpdepths");
  h->addOption("-min <MIN>","Minimum wavelength, the thickness of the first layer will be set to a quarter of MIN (default=1 m).");
  h->addOption("-max <MAX>","Maximum wavelength, the depth of the last layer will be set to half of MAX (default=100 m).");
  h->addOption("-n <N>","Number of layers, excluding the half-space (default=1).");
  h->addOption("-o <FILE>","Export parameterization to FILE.");
  h->addOption("-vp-min <V>","Set minimum value for Vp (default=200 m/s).");
  h->addOption("-vp-max <V>","Set maximum value for Vp (default=5000 m/s).");
  h->addOption("-vs-min <V>","Set minimum value for Vs (default=150 m/s).");
  h->addOption("-vs-max <V>","Set maximum value for Vs (default=3500 m/s).");
  h->addOption("-nu-min <V>","Set minimum value for Poisson's ratio (default=0.2).");
  h->addOption("-nu-max <V>","Set maximum value for Poisson's ratio (default=0.5).");
  h->addOption("-rho-min <V>","Set minimum value for density (default=2000 kg/m3).");
  h->addOption("-rho-max <V>","Set maximum value for density (default=2000 kg/m3).");
  h->addOption("-fix-depths", "Constant thicknesses, do not invert for depths");
  h->addOption("-p \"x y z\"","Set position of parameterized profile. Several instance will generate "
                              "a 3D parameterized ground model (usually 3).");
  h->addOption("-link-vs <REL>","Link Vs from reference profiles with a relative deviation of REL "
                                "(only for 3D parameterized ground model, 0.05 for +/- 5%).");
  return h;
}
