/***************************************************************************
**
**  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-10-21
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>

#include "Dispersion.h"
#include "Ellipticity.h"

namespace QGpCoreWave {

/*!
  \class Dispersion Dispersion.h
  \brief Calculate Love and Rayleigh dispersion curves
  
  The main function used to calculate the dispersion curve is calculate().
*/

Dispersion::Dispersion(int nModes, const QVector<double>* x)
  : ModalStorage(nModes, x)
{
  TRACE;
  _precision=1e-7; // Default precision without ellipticity
}

/*!
  \fn void Dispersion::setPrecision(double p)
  Set precision in slowness for dispersion computation. If you need more than 1e-16, you must use the big number version of calculate().
*/

/*!
  \fn bool Dispersion::calculate(Rayleigh& model, Ellipticity * ell=0)
  Calculate the dispersion for Rayleigh modes.
*/

/*!
  \fn bool Dispersion::calculate(RayleighBigNum& model, Ellipticity * ell=0)
  Calculate the dispersion for Rayleigh modes with high precision.
*/

/*!
  \fn bool Dispersion::calculate(Love& model)
  Calculate the dispersion for Love modes.
*/

/*!
  \fn bool Dispersion::calculate(ModelType& model, Ellipticity * ell)

  Calculates the dispersion curve for the input model modelS. The type of curve
  calculated, either Love or Rayleigh, depends upon the class type for models.
  (see LoveSlowness or RayleighSlowness).

  ell is optional, if non-null and if Rayleigh is computed, the ellipticity
  of Rayleigh modes are computed

  Internal use only.\n
*/

/*!
  Used by the ellipticity computation, it allows the refinement of the ellipticity
  peak. The method for dispersion computation is the same as calculate().
  It is just a resume to a particular frequency sample. \a lastIndex is the x index of the fixed and already computed
  curve. It avoids searching in the x list every time this function is called.
  \a lastIndex must be less than xCount()-1.
*/
bool Dispersion::refine(int modeIndex, int lastIndex, double omega, RootSolver<Rayleigh>& solver, Ellipticity& ell)
{
  TRACE;
  ASSERT(lastIndex<xCount()-1);
  ModalRefine& dispRef=refineAdd(omega);
  ModalRefine& ellRef=ell.refineAdd(omega);
  RealValue * values=mode(0)+lastIndex;

  Rayleigh * waveModel=solver.values();
  const Seismic1DModel * model=waveModel->model();
  double minSlow=model->minSlowS();
  double maxSlow, curSlow, lastSupBound=model->maxSlowR(), newCurSlow;
  SAFE_UNINITIALIZED(curSlow,0);
  bool forbitVelocityInversion=model->checkVelocityInversion()==-1;
  // Initialize equation's polarity
  waveModel->setOmega(0.05); // the sign of polarity is the same at all frequencies
  solver.setPolarity(model->maxSlowR());

  for(int iMode=0; iMode <= modeIndex; iMode++ ) {
    solver.setPrecision(_precision);
    if(iMode==0) {
      maxSlow=model->maxSlowR();
    } else {
      maxSlow=curSlow;
      solver.inversePolarity();
    }
    while(true) {
      waveModel->setOmega(omega);
      //printf("\nFrequency=%.15lg Hz\n\n",0.5 * omega/M_PI);
      bool errorDetected=false;
      curSlow=values[iMode*xCount()+1].value();
      if( ! solver.searchDown(curSlow, minSlow, maxSlow) ) {
        if(iMode==0) {
          // for fundamental mode this condition can never occur unless a mode jumping occured
          App::log(tr("** Warning ** : mode jumping for mode %1 (end), reducing "
                      "step ratio to %2\n").arg(iMode).arg(Number::toDouble(solver.searchStep()*0.1)));
          errorDetected=true;
        } else {
          dispRef.setValid(iMode, false);
          ellRef.setValid(iMode, false);
          curSlow=minSlow;
        }
      } else {
        solver.neville();
        newCurSlow=solver.lower();
        //printf("%lg %lg-->%lg %lg\n",minSlow, curSlow, newCurSlow, maxSlow);
        waveModel->y(0.5 * (solver.upper() + newCurSlow) );
        ellRef.setValue(iMode, waveModel->ellipticity());
        if(newCurSlow - curSlow > _precision) {
          if(forbitVelocityInversion) {
            errorDetected=true;
            App::log(tr("** Warning ** : retrograde dispersion not allowed (mode %1), "
                        "reducing step ratio to %2\n").arg(iMode).arg(Number::toDouble(solver.searchStep()*0.1)));
          } else {
            // Inversions are allowed, check at last point that no mode exist at a higher slowness
            waveModel->setOmega(x( lastIndex) );
            solver.setRelativeStep(solver.searchStep() * 0.1);
            if(solver.searchUp(lastSupBound, maxSlow) ) {
              // recalculating curve because of mode jumping
              App::log(tr("** Warning ** : mode jumping for mode %1 (middle), "
                          "reducing step ratio to %2\n").arg(iMode).arg(Number::toDouble(solver.searchStep()*0.1)));
              errorDetected=true;
            }
            solver.setRelativeStep(solver.searchStep() * 10.0);
          }
        }
        if(!errorDetected) {
          lastSupBound=solver.upper();
          curSlow=newCurSlow;
          dispRef.setValue(iMode, curSlow);
          // When calculating the dispersion curve, stepRatio is positive to search downwards
          // (From maxSlow towards minSlow)
          // Here we want to search carefully upwards: from curSlow to maxSlow
          // If at least one root is found, this proves that mode jumping occured
          if(curSlow > minSlow) {
            // The curSlow was calculated using _solve and _x2 is the value returned by
            // Solve (slightly greater than the true root).
            // Here we need the _x1 value, e.g. slightly less than the true root, in
            // order to be sure that the root eventually found will not be the same
            // as curSlow
            curSlow=solver.upper();
          }
          // Contrary to normal dispersion curve computation, here we know the next point on the curve
          // Hence, we simulate its computation and we must find the same number, if not mode jumping occured.
          waveModel->setOmega(x( lastIndex) );
          //printf("Check refine jumpig: frequency=%.15lg Hz\n",0.5 * x(lastIndex)/M_PI);
          double correctSlow=values[iMode*xCount()].value();
          bool ok;
          if(iMode==0) {
            ok=solver.searchDown(curSlow, minSlow, maxSlow);
          } else {
            double lastMaxSlow=values[(iMode-1)*xCount()].value();
            if(curSlow>lastMaxSlow) curSlow=lastMaxSlow;
            ok=solver.searchDown(curSlow, minSlow, lastMaxSlow);
          }
          if(ok) {
            solver.neville();
            newCurSlow=solver.lower();
          }
          //printf("%lg %lg-->%lg %lg=%lg %lg %lg\n",minSlow, curSlow, newCurSlow, lastMaxSlow, correctSlow, newCurSlow-correctSlow, _precision);
          if( !ok || fabs(newCurSlow-correctSlow)>_precision) {
            ok=true;
            App::log(tr("** Warning ** : mode jumping for mode %1 (refine), reducing "
                        "step ratio to %2\n").arg(iMode).arg(Number::toDouble(solver.searchStep()*0.1)));
            errorDetected=true;
          }
        }
      }
      if(errorDetected) {
        // recalculating curve because of mode jumping
        if(solver.searchStep() < 1e-4) {
          App::log(tr("** Warning ** : dispersion curve impossible to obtain "
                      "for mode %1\n").arg(iMode));
          return false;
        }
        solver.setPrecision(solver.precision() * 0.1);
        solver.setRelativeStep(solver.searchStep() * 0.1);
      } else break;
    }
  }
  return true;
}

/*!
  Convert phase slownesses into group slownesses
*/
void Dispersion::setGroupSlowness()
{
  TRACE;
  int nx=xCount()-1;
  RealValue * tmp=new RealValue[xCount()];
  for(int im=0; im<modeCount(); im++) {
    RealValue * point=mode(im);
    for(int i=1; i<nx; i++) {
      if(point[i-1].isValid() && point[i].isValid() && point[i+1].isValid()) {
        double val=point[i].value();
        double  derslow=0.5*((val-point[i-1].value())/(x(i)-x(i-1))+
                             (point[i+1].value()-val)/(x(i+1)-x(i)));
        derslow*=x(i);
        tmp[i]=val+derslow;
      } else {
        tmp[i].setValid(false);
      }
    }
    point[0].setValid(false);
    for(int i=1; i<nx; i++) {
      point[i]=tmp[i];
    }
    point[nx].setValid(false);
  }
  delete [] tmp;
}

/*!
  Returns a new ModalStorage with wavenumber differences between modes
*/
ModalStorage * Dispersion::deltaK() const
{
  TRACE;
  ModalStorage * s=new ModalStorage(modeCount()-1, x());
  for(int im=modeCount()-1; im>0; im--) {
    const RealValue * m1=mode(im);
    const RealValue * m2=mode(im-1);
    RealValue * dk=s->mode(im-1);
    for(int is=xCount()-1; is>=0; is--) {
      if(m1[is].isValid() && m2[is].isValid()) {
        dk[is].setValue(x(is)*(m2[is].value()-m1[is].value()));
        dk[is].setValid(true);
      } else {
        dk[is].setValid(false);
      }
    }
  }
  return s;
}


} // namespace QGpCoreWave
