/***************************************************************************
**
**  This file is part of QGpCoreWave.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2004-09-17
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>
#include "RefractionDippingModel.h"
#include "Seismic1DModel.h"
#include "TiltNode.h"
#include "TiltPath.h"

namespace QGpCoreWave {

  /*!
    \class RefractionDippingModel::RefractonContext RefractionDippingModel.h
    \brief Brief description of class still missing

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  RefractionDippingModel::RefractionContext::RefractionContext()
  {
    TRACE;
    addVariable( "n", new RefractionDippingModel::VariableN(this));
    addVariable( "dL", new RefractionDippingModel::VariableDL(this));
    addVariable( "dR", new RefractionDippingModel::VariableDR(this));
    addVariable( "v", new RefractionDippingModel::VariableV(this));
  }

  QString RefractionDippingModel::RefractionContext::helpCode() const
  {
    TRACE;
    return "   n        Number of layers in model\n"
           "   dL[i]    Depth on left side for layer i [0,n-2]\n"
           "   dR[i]    Depth on right side for layer i [0,n-2]\n"
           "   v[i]     Velocity for layer i [0,n-1]\n";
  }

  QVariant RefractionDippingModel::VariableN::value(const QString&) const
  {
    TRACE;
    return _context->model()->layerCount();
  }

  QVariant RefractionDippingModel::VariableDL::value(const QString& index) const
  {
    TRACE;
    RefractionDippingModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()-1) {
      return m->depthLeft(i);
    } else {
      return QVariant();
    }
  }

  void RefractionDippingModel::VariableDL::setValue(const QString& index, const QVariant& val)
  {
    TRACE;
    RefractionDippingModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()-1) {
      m->setDepthLeft(i, val.toDouble());
    }
  }

  QVariant RefractionDippingModel::VariableDR::value(const QString& index) const
  {
    TRACE;
    RefractionDippingModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()-1) {
      return m->depthRight(i);
    } else {
      return QVariant();
    }
  }

  void RefractionDippingModel::VariableDR::setValue(const QString& index, const QVariant& val)
  {
    TRACE;
    RefractionDippingModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      m->setDepthRight(i, val.toDouble());
    }
  }

  QVariant RefractionDippingModel::VariableV::value(const QString& index) const
  {
    TRACE;
    RefractionDippingModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      return m->slowness(i);
    } else {
      return QVariant();
    }
  }

  void RefractionDippingModel::VariableV::setValue(const QString& index, const QVariant& val)
  {
    TRACE;
    RefractionDippingModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      m->setSlowness(i, val.toDouble());
    }
  }

/*!
  \class RefractionDippingModel RefractionDippingModel.h
  \brief A layered model with tilted interfaces

  Each layer has one unique slowness. The bottom interface geometry is defined by its left and right
  depths. Depths are defined at xLeft() and xRight().
*/

RefractionDippingModel::RefractionDippingModel()
{
  TRACE;
  initMembers();
}

RefractionDippingModel::RefractionDippingModel(int nLayers)
{
  TRACE;
  _layerCount=nLayers;
  initMembers();
  allocateData();
}

RefractionDippingModel::RefractionDippingModel(const RefractionDippingModel& o)
  : GeophysicalModel()
{
  TRACE;
  _layerCount=o._layerCount;
  initMembers();
  allocateData();
  for(int i=1;i<_layerCount;i++) {
    _depthL[i]=o._depthL[i];
    _depthR[i]=o._depthR[i];
    _slow[i]=o._slow[i];
    _minX[i]=o._minX[i];
    _maxX[i]=o._maxX[i];
  }
}

RefractionDippingModel::~RefractionDippingModel()
{
  TRACE;
  if(_cosangle) end();
  deleteData();
}

/*!
  Initialize internal members

  Internal use only.
*/
void RefractionDippingModel::initMembers()
{
  _depthL=0;
  _depthR=0;
  _slow=0;
  _minX=0;
  _maxX=0;
  _cosangle=0;
  _sinangle=0;
  _shift=0;
  _h=0;
  _critl=0;
  _critr=0;
}

/*!
  Delete all data vectors

  Internal use only.
*/
void RefractionDippingModel::deleteData()
{
  TRACE;
  delete [] _depthL;
  delete [] _depthR;
  delete [] _slow;
  delete [] _minX;
  delete [] _maxX;
  _depthL=0;
  _depthR=0;
  _slow=0;
  _minX=0;
  _maxX=0;
}

void RefractionDippingModel::allocateData()
{
  TRACE;
  if(_layerCount>0) {
    _depthL=new double [_layerCount];
    _depthR=new double [_layerCount];
    _depthL[0]=0.0;
    _depthR[0]=0.0;
    _slow=new double [_layerCount];
    _minX=new double [_layerCount];
    _maxX=new double [_layerCount];
  }
}

/*!
  Convert \a ref horizontal layered model to a titl layered model using \a pitch profile.
  The layer pitch is a real number between -1 and 1. \a pitch profile must contain exactly
  the same number of layers than \a ref.

    \li -1  left thickness is forced to 0
    \li 0   means no pitch.
    \li 1   right thickness is forced to 0

*/
void RefractionDippingModel::fromSeismic1DModel(const Seismic1DModel& ref, const QVector<double>& pitch,
                                                Seismic1DModel::SlownessType slowType)
{
  TRACE;
  if(_layerCount!=ref.layerCount()) {
    _layerCount=ref.layerCount();
    deleteData();
    allocateData();
  }
  ASSERT(_layerCount==pitch.count());
  int n1=_layerCount-1;
  for(int i=0; i<n1;i++) {
    switch(slowType) {
    case Seismic1DModel::P:
      _slow[i]=ref.slowP(i);
      break;
    case Seismic1DModel::S:
      _slow[i]=ref.slowS(i);
      break;
    }
    _depthL[i+1]=_depthL[i] + (1.0+pitch[i])*ref.h(i);
    _depthR[i+1]=_depthR[i] + (1.0-pitch[i])*ref.h(i);
  }
  switch(slowType) {
  case Seismic1DModel::P:
    _slow[n1]=ref.slowP(n1);
    break;
  case Seismic1DModel::S:
    _slow[n1]=ref.slowS(n1);
    break;
  }
}

/*!
  Geometrical and physical parameters are supposed to be all initialized.

  Compute relative angles between layers. For each layer, the critical angle angle is computed.
  Incidence angle are then back propagated to the surface (refraction at angle lower than critical).
*/
void RefractionDippingModel::begin()
{
  TRACE;
  _cosangle=new double [_layerCount];
  _sinangle=new double [_layerCount];
  _critl=new double *[_layerCount];
  _critr=new double *[_layerCount];
  _shift=new double [_layerCount];
  _h=new double [_layerCount];
  double * angle=new double [_layerCount];

  // Init computed parameter for first layer
  angle[0]=atan((_depthL[0]-_depthR[0])/(_xR-_xL));
  _cosangle[0]=cos(angle[0]);
  _sinangle[0]=sin(angle[0]);
  _minX[0]=0.5*(_xR+_xL);
  _maxX[0]=0.5*(_xR+_xL);
  _critl[0]=0;
  _critr[0]=0;
  for(int i=1;i<_layerCount;i++) {
    angle[i]=atan((_depthL[i]-_depthR[i])/(_xR-_xL));
    _cosangle[i]=cos(angle[i]);
    _sinangle[i]=sin(angle[i]);
    double tmp=(_depthL[i]-_depthL[i-1])/cos(angle[i]-angle[i-1]);
    _shift[i]=-tmp*_sinangle[i-1];
    _h[i]=tmp*_cosangle[i];
    _minX[i]=0.5*(_xR+_xL);
    _maxX[i]=0.5*(_xR+_xL);
    if(_slow[i]<_slow[i-1]) { // refraction at critical angle
      double inc0=asin(_slow[i]/_slow[i-1]);
      _critl[i]=new double [i];
      _critl[i][i-1]=inc0;
      int j;
      for(j=i-2;j>=0;j--) {
        double inc=_critl[i][j+1]-(angle[j+2]-angle[j+1]);
        double a=_slow[j+1]/_slow[j]*sin(inc);
        if(a<1.0) _critl[i][j]=asin(a);
        else {
          App::log(tr("Critical angle cannot be reached in layer %1 from LeftToRight\n").arg(j) );
          delete [] _critl[i]; // this is not a good candidate for critical angle
          _critl[i]=0;
        }
      }
      _critr[i]=new double [i];
      _critr[i][i-1]=-inc0;
      for(j=i-2;j>=0;j--) {
        double inc=_critr[i][j+1]-(angle[j+2]-angle[j+1]);
        double a=_slow[j+1]/_slow[j]*sin(inc);
        if(a<1.0) _critr[i][j]=asin(a);
        else {
          App::log(tr("Critical angle cannot be reached in layer %1 from RightToLeft\n").arg(j) );
          delete [] _critr[i]; // this is not a good candidate for critical angle
          _critr[i]=0;
        }
      }
    } else {
      _critl[i]=0;
      _critr[i]=0;
    }
  }
  delete [] angle;
}

void RefractionDippingModel::end()
{
  TRACE;
  delete [] _cosangle;
  delete [] _sinangle;
  delete [] _shift;
  delete [] _h;
  _cosangle=0;
  _sinangle=0;
  _shift=0;
  _h=0;
  int i;
  for(i=1;i<_layerCount;i++) delete [] _critl[i];
  delete [] _critl;
  for(i=1;i<_layerCount;i++) delete [] _critr[i];
  delete [] _critr;
  _critl=0;
  _critr=0;
}

/*!
  Return time needed to propagate across layer \a iLayer if the target layer is \a iDeepestLayer.
  \a x is set the local coordinate of the ray at the bottom interface. Local coordinates
  are counted from the reference profile.
*/
double RefractionDippingModel::propagate(Direction dir, int iLayer, int iDeepestLayer, double& x) const
{
  TRACE;
  double inc;
  SAFE_UNINITIALIZED(inc,0);
  switch(dir) {
  case LeftToRight:
    inc=_critl[iDeepestLayer][iLayer-1];
    break;
  case RightToLeft:
    inc=_critr[iDeepestLayer][iLayer-1];
    break;
  }
  double cosab, cosb, sinb, d, dt;
  d=sqrt(x*x+_h[iLayer]*_h[iLayer]);
  cosb=_h[iLayer]/d;
  sinb=x/d;
  cosab=_cosangle[iLayer]*cosb-_sinangle[iLayer]*sinb; // cos(a+b)
  double cosinc=cos(inc);
  double dcosinc=d/cosinc;
  // time to cross the layer
  dt=_slow[iLayer-1]*cosab*dcosinc;
  /* intersection of the ray with interface counted from the reference profile
     sin(a+b-i)=sin(a+b)cos(i)-cos(a+b)sin(i)
               =[sin(a)cos(b)+cos(a)sin(b)]cos(i)-cos(a+b)sin(i) */
  x=((_sinangle[iLayer]*cosb+_cosangle[iLayer]*sinb)*cosinc-cosab*sin(inc))*
    dcosinc-_shift[iLayer];
  return dt;
}

/*!
  Returns the paths for \a src and \a rec. No computation is performed here. Nodes are assumed to initialized
  with TiltNode::init() which calculate the paths.
*/
bool RefractionDippingModel::node2paths(const TiltNode& src, const TiltNode& rec,
                                      const TiltPath *& srcPaths, const TiltPath *& recPaths) const
{
  double xSrc=src.x();
  double xRec=rec.x();
  if(xSrc<xRec) {
    srcPaths=src.right();
    recPaths=rec.left();
    return true;
  } else if(xSrc>xRec) {
    srcPaths=src.left();
    recPaths=rec.right();
    return true;
  } else {
    srcPaths=0;
    recPaths=0;
    return false;
  }
}

/*!
  Return travel time between two nodes \a src and \a rec. \a minTimeLayer is set to the deepest layer.
*/
double RefractionDippingModel::travelTime(const TiltNode& src, const TiltNode& rec, int& minTimeLayer)
{
  TRACE;
  const TiltPath * srcPaths, * recPaths;
  minTimeLayer=0;
  if(!node2paths(src, rec, srcPaths, recPaths)) return 0.0;
  return travelTime(srcPaths, recPaths, minTimeLayer);
}

/*!
  Return travel time between two paths \a srcPaths and \a recPaths. \a minTimeLayer is set to the deepest layer.
*/
double RefractionDippingModel::travelTime(const TiltPath *& srcPaths, const TiltPath *& recPaths, int& minTimeLayer)
{
  TRACE;
  minTimeLayer=0;
  double t, newt, tupdown;
  t=_slow[0]*fabs(recPaths[0].abscissa(0)-srcPaths[0].abscissa(0));
  int i;
  for(i=1;i<_layerCount;i++) {
    newt=_slow[i]*fabs(recPaths[i-1].abscissa(i)-srcPaths[i-1].abscissa(i));
    tupdown=recPaths[i-1].time()+srcPaths[i-1].time();
    //printf("travelTime %lg %lg %lg %lg\n",newt,tupdown,newt+tupdown,t);
    if(newt+tupdown<t) {
      t=newt+tupdown;
      minTimeLayer=i;
    }
  }
  double x;
  int pathIndex=minTimeLayer-1;
  if(pathIndex<0) pathIndex=0;
  for(i=0;i<=minTimeLayer;i++) {
    x=srcPaths[pathIndex].abscissa(i);
    if(x<_minX[i]) _minX[i]=x;
    else if(x>_maxX[i]) _maxX[i]=x;
    x=recPaths[pathIndex].abscissa(i);
    if(x<_minX[i]) _minX[i]=x;
    else if(x>_maxX[i]) _maxX[i]=x;
  }
  return t;
}

/*!
  Return ray path between two nodes \a src and \a rec.
*/
Curve<Point2D> RefractionDippingModel::ray(const TiltNode& src, const TiltNode& rec)
{
  Curve<Point2D> rayPoints;
  const TiltPath * srcPaths, * recPaths;
  if(!node2paths(src, rec, srcPaths, recPaths)) return rayPoints;
  int endLayer;
  travelTime(srcPaths, recPaths, endLayer);
  double x;
  int i;
  int lastpray=endLayer*2+1;
  rayPoints.resize(lastpray+1);
  int pathIndex=endLayer-1;
  if(pathIndex<0) pathIndex=0;

  for(i=0;i<=endLayer;i++) {
    x=srcPaths[pathIndex].abscissa(i);
    rayPoints[i].setX(_xL+x*_cosangle[i] );
    rayPoints[i].setY(_depthL[i]-x*_sinangle[i] );
  }

  for(i=0;i<=endLayer;i++) {
    x=recPaths[pathIndex].abscissa(i);
    rayPoints[lastpray-i].setX(_xL+x*_cosangle[i] );
    rayPoints[lastpray-i].setY(_depthL[i]-x*_sinangle[i] );
  }

  return rayPoints;
}

bool RefractionDippingModel::setValue(double& var, const StringSection& field, int iLayer, const QString& varName, bool optional)
{
  TRACE;
  bool ok=true;
  if(field.isValid()) {
    var=field.toDouble(&ok);
    if(ok && var>0.0) {
      return true;
    } else {
      App::log(tr("Bad value for %1 at layer %2: %3\n").arg(varName).arg(iLayer).arg(var) );
    }
  } else if(!optional) {
    App::log(tr("Missing value for %1 at layer %2\n").arg(varName).arg(iLayer) );
  }
  return false;
}

QString RefractionDippingModel::formatHelp()
{
  TRACE;
  return "  Line 1    <number of layers including half-space for first model>\n"
         "  Line 2    <x left depth profile> <x right depth profile>\n"
         "  Line 3    <left thickness (m)> <right thickness (m)> <V (m/s)>\n"
         "  ....\n"
         "  Line n    0 0 <V (m/s)>\n"
         "  Line n+1  <number of layers including half-space for second model>\n"
         "  ....";
}

/*!
  Loads model from a text stream

    Format:
      \verbatim
      layerCount
      xLeft xRight
      thicknessLeft thicknessRight V (layer 1)
      ...
      0 0 V (layer n, half-space)
      \endverbatim

  Returns false if an error occured during reading or if the read model is not consistent.
  It returns true even if the number of layers is null or cannot be read. This is the responsability
  of the caller to test if the number of layers is greater than 1 before doing anything else.
*/
bool RefractionDippingModel::fromStream(QTextStream& s, QString * comments)
{
  TRACE;
  // read the number of layers
  StringSection field, fields;
  const QChar * ptr;
  QString buf;
  buf=File::readLineNoComments(s, comments);
  fields.set(buf);
  fields.trimmed();
  ptr=0;
  field=fields.nextField(ptr);
  if(field.isValid()) {
    bool ok=true;
    _layerCount=field.toInt(&ok);
    if(!ok && !field.isEmpty()) { // Not a blank line but failed
      App::log(tr("Tilted layered model: bad number of layers: \"%1\" interpreted as %2\n").arg(field.toString()).arg(_layerCount) );
       _layerCount=0;
       return false;
    }
  } else {
    _layerCount=0;
    return true;  // This is just a blank line, before doing anything, you must test if the number of layer is >0
  }
  if(_layerCount>0 && !s.atEnd()) {
    allocateData();
    buf=File::readLineNoComments(s, comments);
    fields.set(buf);
    fields.trimmed();
    ptr=0;
    field=fields.nextField(ptr);
    if(field.isValid()) {
      _xL=field.toDouble();
    } else return false;
    field=fields.nextField(ptr);
    if(field.isValid()) {
      _xR=field.toDouble();
    } else return false;
    // read the data
    _depthL[0]=0.0;
    _depthR[0]=0.0;
    for(int i=0;i<_layerCount;i++) {
      if(s.atEnd()) {
        App::log(tr("Tilted layered model: reaching the end of file, %1 layers found, expecting %2\n")
                        .arg(i).arg(_layerCount));
        _layerCount=0;
        return false;
      }
      buf=File::readLineNoComments(s, comments);
      fields.set(buf);
      fields.trimmed();
      ptr=0;
      field=fields.nextField(ptr);
      if(i<_layerCount-1) {
        if(!setValue(_depthL[i+1], field, i, tr("left thickness"), false) ) return false;
        _depthL[i+1] += _depthL[i];
      }
      field=fields.nextField(ptr);
      if(i<_layerCount-1) {
        if(!setValue(_depthR[i+1], field, i, tr("right thickness"), false) ) return false;
        _depthR[i+1] += _depthR[i];
     }
      field=fields.nextField(ptr);
      if(!setValue(_slow[i], field, i, tr("V"), false) ) return false;
      // Switch to slowness
      _slow[i]=1/_slow[i];
    }
    return true;
  } else {
    _layerCount=0;
    return false;
  }
}

/*!
  Returns model as a string
*/
QString RefractionDippingModel::toString() const
{
  TRACE;
  QString str;
  str=QString("%1 layers, width %2 m\n").arg(_layerCount).arg(fabs(_xR-_xL));
  str+="  Left depth(m) Right depth(m)  Velocity(m/s)\n";
  for(int i=0;i<_layerCount-1;i++) {
    str+=QString::number(_depthL[i],'f',1).leftJustified(15,' ',true);
    str+=QString::number(_depthR[i],'f',1).leftJustified(15,' ',true);
    str+=QString::number(1.0/_slow[i],'f',1).leftJustified(15,' ',true);
    str+="\n";
  }
  str+=QString("-").leftJustified(15,' ',true);
  str+=QString("-").leftJustified(15,' ',true);
  str+=QString::number(1/_slow[_layerCount-1],'f',1).leftJustified(14,' ',true);
  str+="\n";
  return str;
}

/*!
  Saves model to a text stream

    Format:
      \verbatim
      layerCount
      xLeft xRight
      thicknessLeft thicknessRight V (layer 1)
      ...
      0 0 V (layer n, half-space)
      \endverbatim
*/
void RefractionDippingModel::toStream(QTextStream& s) const
{
  TRACE;
  static const QString str3("%1 %2 %3\n");
  s << _layerCount << "\n"
    << _xL << " " << _xR << "\n";
  for(int i=0;i<_layerCount-1;i++)
    s << str3.arg(_depthL[i+1]-_depthL[i], 0, 'g', 20)
             .arg(_depthR[i+1]-_depthR[i], 0, 'g', 20)
             .arg(1/_slow[i], 0, 'g', 20);
  s << str3.arg(0.0, 0, 'g', 20)
            .arg(0.0, 0, 'g', 20)
            .arg(1/_slow[_layerCount-1], 0, 'g', 20) << flush;
}

} // namespace QGpCoreWave
