/***************************************************************************
**
**  This file is part of gpprofile.
**
**  gpprofile 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.
**
**  gpprofile 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: 2008-12-15
**  Copyright: 2008-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>
#include <QGpCoreWave.h>
#include "ProfileReader.h"

/*!
  \class ProfileReader ProfileReader.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
ProfileReader::ProfileReader()
    : ArgumentStdinReader()
{
  TRACE;
  _maxDepth=100.0;
  _nDepths=100;
  _nProfiles=0;
  _model=SurfaceWaveModels;
  _profile=Vs;
  _output=Original;
  _pseudo3DModels=nullptr;
  _pseudo3DPositions=nullptr;
}

ProfileReader::~ProfileReader()
{
  TRACE;
  switch(_output) {
  case Original:
  case Resample:
  case AverageDepths:
  case AverageAt:
  case AverageProfiles:
  case StddevProfiles:
  case MinMax:
    break;
  case Pseudo3D:
    delete [] _pseudo3DModels;
    delete [] _pseudo3DPositions;
    break;
  }
}

bool ProfileReader::setOptions(int& argc, char ** argv)
{
  TRACE;
  // Check arguments
  int i, j=1;
  for(i=1; i<argc; i++) {
    QByteArray arg=argv[i];
    if(arg[0]=='-') {
      if(arg=="-original") {
        _output=Original;
      } else if(arg=="-surfacewave-models") {
        _model=SurfaceWaveModels;
      } else if(arg=="-refraction-models") {
        _model=RefractionModels;
        App::log(tr("gpprofile: refraction models are not yet supported\n") );
        return false;
      } else if(arg=="-resistivity-models") {
        _model=ResistivityModels;
        if(_profile==Vs) { // Change default value if still set to default
          _profile=Resistivity;
        }
      } else if(arg=="-resample") {
        _output=Resample;
      } else if(arg=="-average-profiles") {
        _output=AverageProfiles;
      } else if(arg=="-stddev-profiles") {
        _output=StddevProfiles;
      } else if(arg=="-average-depths") {
        _output=AverageDepths;
      } else if(arg=="-minmax") {
        _output=MinMax;
      } else if(arg=="-pseudo3d") {
        CoreApplication::checkOptionArg(i, argc, argv);
        QString s(argv[i]);
        if(!_pseudo3DAt.fromString(s)) {
          App::log(tr("gpprofile: bad position for option '-pseudo3d'\n") );
          return false;
        }
        _output=Pseudo3D;
      } else if(arg=="-average-at") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _output=AverageAt;
        _maxDepth=atof(argv[i] );
        _nDepths=1;
      } else if(arg=="-d" || arg=="-max-depth") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _maxDepth=atof(argv[i]);
      } else if(arg=="-n") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _nDepths=atoi(argv[i]);
        if(_nDepths<=0) {
          App::log(tr("gpprofile: negative or null number of depth samples (option -n)\n") );
          return false;
        }
      } else if(arg=="-vp") {
        _profile=Vp;
      } else if(arg=="-vs") {
        _profile=Vs;
      } else if(arg=="-rho") {
        _profile=Rho;
      } else if(arg=="-nu") {
        _profile=Poisson;
      } else if(arg=="-imp") {
        _profile=Impedance;
      } else if(arg=="-res") {
        _profile=Resistivity;
      } else if(arg=="-model") {
        _profile=Model;
      } else {
        App::log(tr("gpprofile: bad option %1, see -help\n").arg(argv[i]) );
        return false;
      }
    } else {
      argv[j++]=argv[i];
    }
  }
  if(j < argc) {
    argv[j]=nullptr;
    argc=j;
  }
  // Sampling for resampled profiles
  QTextStream sOut(stdout);
  switch(_output) {
  case Original:
    break;
  case Resample:
  case AverageDepths: {
      if(_profile==Model) {
        App::log(tr("gpprofile: '-model' option not allowed in this output mode\n") );
        return false;
      }
      _sampling.resize(_nDepths+1);
      double dDepth=_maxDepth/static_cast<double>(_nDepths);
      for(int i=0; i<=_nDepths; i++) {
        _sampling[i]=static_cast<double>(i)*dDepth;
      }
    }
    break;
  case AverageAt:
    if(_profile==Model) {
      App::log(tr("gpprofile: '-model' option not allowed in this output mode\n") );
      return false;
    }
    _sampling.resize(1);
    _sampling[0]=_maxDepth;
    break;
  case AverageProfiles: {
      if(_profile==Model) {
        App::log(tr("gpprofile: '-model' option not allowed in this output mode\n") );
        return false;
      }
      _vMin.resize(_nDepths);
      double dDepth=_maxDepth/static_cast<double>(_nDepths);
      for(int i=0;i<_nDepths;i++) {
        double d=static_cast<double>(i)*dDepth;
        _vMin[i].setX(0.0);
        _vMin[i].setY(d);
      }
      break;
    }
  case StddevProfiles: {
      if(_profile==Model) {
        App::log(tr("gpprofile: '-model' option not allowed in this output mode\n") );
        return false;
      }
      _vMin.resize(_nDepths);
      _vMax.resize(_nDepths);
      double dDepth=_maxDepth/static_cast<double>(_nDepths);
      for(int i=0;i<_nDepths;i++) {
        double d=static_cast<double>(i)*dDepth;
        _vMin[i].setX(0.0);
        _vMin[i].setY(d);
        _vMax[i].setX(0.0);
        _vMax[i].setY(d);
      }
      break;
    }
  case MinMax: {
      if(_profile==Model) {
        App::log(tr("gpprofile: '-model' option not allowed in this output mode\n") );
        return false;
      }
      _vMin.resize(_nDepths);
      _vMax.resize(_nDepths);
      double dDepth=_maxDepth/static_cast<double>(_nDepths);
      for(int i=0;i<_nDepths;i++) {
        double d=static_cast<double>(i)*dDepth;
        _vMin[i].setX(std::numeric_limits<double>::infinity());
        _vMax[i].setX(-std::numeric_limits<double>::infinity());
        _vMin[i].setY(d);
        _vMax[i].setY(d);
      }
      break;
    }
  case Pseudo3D:
    if(_model!=SurfaceWaveModels) {
      App::log(tr("gpprofile: '-pseudo3D' option not yet supported for other model type than 'Surface Wave'\n") );
      return false;
    }
    _pseudo3DModels=new Seismic1DModel[3];
    _pseudo3DPositions=new Point[3];
    sOut << tr("# Reference models at") << endl;
    break;
  }
  return true;
}

bool ProfileReader::parse(QTextStream& s)
{
  TRACE;
  switch(_model) {
  case SurfaceWaveModels:
    return parseSurfaceWaveModels(s);
  case RefractionModels:
    break;
  case ResistivityModels:
    return parseResistivityModels(s);
  }
  return false;
}

bool ProfileReader::parseSurfaceWaveModels(QTextStream& s)
{
  TRACE;
  QTextStream sOut(stdout);
  Seismic1DModel m;
  QString comments;
  if(!m.fromStream(s, &comments)) {
    return false;
  }
  if(m.layerCount()>0) {
    switch (_output) {
    case Original: {
        profileComment(sOut, comments);
        if(_profile==Model) {
          m.toStream(sOut);
        } else {
          Profile p=profile(m);
          p.toStream(sOut, true);
        }
      }
      break;
    case Resample: {
        profileComment(sOut, comments);
        Profile p=profile(m);
        p.resample(_sampling);
        p.setSamples(_sampling);
        p.toStream(sOut, false);
      }
      break;
    case AverageDepths: {
        profileComment(sOut, comments);
        Profile p=profile(m);
        p.inverse();
        p.resample(_sampling);
        p.average();
        p.setSamples(_sampling);
        p.inverse();
        p.toStream(sOut, false);
      }
      break;
    case AverageAt: {
        profileComment(sOut, comments);
        Profile p=profile(m);
        p.inverse();
        p.resample(_sampling);
        p.average();
        p.setSamples(_sampling);
        p.inverse();
        sOut << p.values()[0] << endl;
      }
      break;
    case AverageProfiles: {
        Profile p=profile(m);
        for(int i=0;i<_nDepths;i++) {
          double v=log10(p.uniformAt(_vMin[i].y()));
          _vMin[i].setX(_vMin[i].x() + v);
        }
      }
      break;
    case StddevProfiles: {
        Profile p=profile(m);
        for(int i=0;i<_nDepths;i++) {
          double v=log10(p.uniformAt(_vMin[i].y()));
          _vMin[i].setX(_vMin[i].x()+v);
          _vMax[i].setX(_vMax[i].x()+v*v);
        }
      }
      break;
    case MinMax: {
        Profile p=profile(m);
        for(int i=0;i<_nDepths;i++) {
          double v=p.uniformAt(_vMin[i].y());
          if(v<_vMin[i].x()) _vMin[i].setX(v);
          if(v>_vMax[i].x()) _vMax[i].setX(v);
        }
      }
      break;
    case Pseudo3D: {
        if(_nProfiles>2) {
          App::log(tr("gpprofile: only 3 models are expected for pseudo 3D mode.\n") );
          return false;
        }
        sOut << comments;
        QRegExp r("\n?# position ([0-9\\.,\\- ]{3,})[\n$]");
        if(r.lastIndexIn(comments)<0) {
          App::log(tr("gpprofile: model index %1 has no position information in its comments, see '-help'\n").arg(_nProfiles) );
          return false;
        }
        _pseudo3DModels[_nProfiles]=m;
        if(!_pseudo3DPositions[_nProfiles].fromString(r.cap(1))) {
          App::log(tr("gpprofile: model index %1 has not a readable position in its comments: %2\n").arg(_nProfiles).arg(r.cap(1)) );
          return false;
        }
      }
      break;
    }
    _nProfiles++;
  }
  return true;
}

bool ProfileReader::parseResistivityModels(QTextStream& s)
{
  TRACE;
  QTextStream sOut(stdout);
  Resistivity1DModel m;
  QString comments;
  if(!m.fromStream(s, &comments)) {
    return false;
  }
  if(m.layerCount()>0) {
    switch (_output) {
    case Original: {
        profileComment(sOut, comments);
        if(_profile==Model) {
          m.toStream(sOut);
        } else {
          Profile p=profile(m);
          p.toStream(sOut, true);
        }
      }
      break;
    case Resample: {
        profileComment(sOut, comments);
        Profile p=profile(m);
        p.resample(_sampling);
        p.setSamples(_sampling);
        p.toStream(sOut, false);
      }
      break;
    case AverageDepths: {
        profileComment(sOut, comments);
        Profile p=profile(m);
        p.inverse();
        p.resample(_sampling);
        p.average();
        p.setSamples(_sampling);
        p.inverse();
        p.toStream(sOut, false);
      }
      break;
    case AverageAt: {
        profileComment(sOut, comments);
        Profile p=profile(m);
        p.inverse();
        p.resample(_sampling);
        p.average();
        p.setSamples(_sampling);
        p.inverse();
        sOut << p.values()[0] << endl;
      }
      break;
    case AverageProfiles: {
        Profile p=profile(m);
        for(int i=0;i<_nDepths;i++) {
          double v=log10(p.uniformAt(_vMin[i].y()));
          _vMin[i].setX(_vMin[i].x()+v);
        }
      }
      break;
    case StddevProfiles: {
        Profile p=profile(m);
        for(int i=0;i<_nDepths;i++) {
          double v=log10(p.uniformAt(_vMin[i].y()));
          _vMin[i].setX(_vMin[i].x()+v);
          _vMax[i].setX(_vMax[i].x()+v*v);
        }
      }
      break;
    case MinMax: {
        Profile p=profile(m);
        for(int i=0;i<_nDepths;i++) {
          double v=p.uniformAt(_vMin[i].y());
          if(v<_vMin[i].x()) _vMin[i].setX(v);
          if(v>_vMax[i].x()) _vMax[i].setX(v);
        }
      }
      break;
    case Pseudo3D: {
        ASSERT(false);
      }
      break;
    }
    _nProfiles++;
  }
  return true;
}

Profile ProfileReader::profile(const Seismic1DModel& m) const
{
  TRACE;
  switch (_profile) {
  case Vp:
    return m.vpProfile();
  case Vs:
    break;
  case Rho:
    return m.rhoProfile();
  case Poisson:
    return m.poissonProfile();
  case Impedance:
    return m.impedanceProfile();
  case Resistivity:
  case Model: // Should never happen
    ASSERT(false);
    break;
  }
  return m.vsProfile();
}

Profile ProfileReader::profile(const Resistivity1DModel& m) const
{
  TRACE;
  switch (_profile) {
  case Resistivity:
    break;
  case Vp:
  case Vs:
  case Rho:
  case Poisson:
  case Impedance:
  case Model: // Should never happen
    ASSERT(false);
    break;
  }
  return m.profile();
}

void ProfileReader::profileComment(QTextStream& sOut, const QString& comments) const
{
  TRACE;
  sOut << comments;
  switch (_profile) {
  case Vp:
    sOut << "# Vp" << endl;
    break;
  case Vs:
    sOut << "# Vs" << endl;
    break;
  case Rho:
    sOut << "# Density" << endl;
    break;
  case Poisson:
    sOut << "# Poisson's ratio" << endl;
    break;
  case Impedance:
    sOut << "# Impedance" << endl;
    break;
  case Resistivity:
    sOut << "# Resistivity" << endl;
    break;
  case Model:
    break;
  }
}


bool ProfileReader::terminate()
{
  TRACE;
  QTextStream sOut(stdout);
  switch(_output) {
  case Original:
  case Resample:
  case AverageDepths:
  case AverageAt:
    break;
  case AverageProfiles: {
      profileComment(sOut, QString());
      double factor=1.0/static_cast<double>(_nProfiles);
      for(int i=0;i<_nDepths;i++) {
        _vMin[i].setX(pow(10.0, _vMin[i].x()*factor));
      }
      sOut << "# Average\n"
           << _vMin.toString();
    }
    break;
  case StddevProfiles: {
      profileComment(sOut, QString());
      double factor=1.0/static_cast<double>(_nProfiles);
      double factor1=1.0/static_cast<double>(_nProfiles-1);
      for(int i=0;i<_nDepths;i++) {
        double s=_vMin[i].x();
        double s2=_vMax[i].x();
        s*=factor;
        s2-=s*s*_nProfiles;
        s2*=factor1;
        if(s2<0.0) {
          s2=0.0;
        }
        s=pow(10.0, s);
        s2=pow(10.0, sqrt(s2));
        _vMin[i].setX(s/s2);
        _vMax[i].setX(s*s2);
      }
      sOut << "# Average/Stddev\n"
           << _vMin.toString()
           << "# Average*Stddev\n"
           << _vMax.toString();
    }
    break;
  case MinMax:
    profileComment(sOut, QString());
    sOut << "# Min\n"
         << _vMin.toString()
         << "# Max\n"
         << _vMax.toString();
    break;
  case Pseudo3D: {
      Seismic1DModel m;
      if(_nProfiles!=3) {
        App::log(tr("gpprofile: 3 models are expected for pseudo 3D mode.\n") );
        return false;
      }
      if(!m.interpole(_pseudo3DAt, _pseudo3DPositions, _pseudo3DModels)) {
        return false;
      }
      profileComment(sOut, QString());
      sOut << tr("# Interpolated at %1").arg(_pseudo3DAt.toString(20)) << endl;
      if(_profile==Model) {
        m.toStream(sOut);
      } else {
        Profile p=profile(m);
        p.toStream(sOut, true);
      }
    }
    break;
  }
  return true;
}
