/***************************************************************************
**
**  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)
**
***************************************************************************/

#ifndef DISPERSION_H
#define DISPERSION_H

#include <math.h>

#include <QGpCoreTools.h>

#include "Ellipticity.h"
#include "ModalStorage.h"
#include "Love.h"
#include "Rayleigh.h"
#include "QGpCoreWaveDLLExport.h"

namespace QGpCoreWave {

class Dispersion : public ModalStorage
{
  TRANSLATIONS("Dispersion")
public:
  QGPCOREWAVE_EXPORT Dispersion(int nModes, const QVector<double>* x);

  QGPCOREWAVE_EXPORT void setGroupSlowness();

  bool calculate(Rayleigh * waveModel, Ellipticity * ell=nullptr, QAtomicInt * terminated=nullptr)
  {
    return calculate<double, Rayleigh>(waveModel, ell, terminated);
  }
#ifdef BIGNUM
  bool calculate(RayleighBigNum * waveModel, Ellipticity * ell=0, QAtomicInt * terminated=0)
  {
    return calculate<mp_real, RayleighBigNum>(waveModel, ell, terminated);
  }
#endif
  bool calculate(Love * waveModel, QAtomicInt * terminated=nullptr)
  {
    return calculate<double, Love>(waveModel, nullptr, terminated);
  }
  void setPrecision(double p) {_precision=p;}
  QGPCOREWAVE_EXPORT bool refine(int modeIndex, int lastIndex, double omega, RootSolver<Rayleigh>& modelS, Ellipticity& ell);
  QGPCOREWAVE_EXPORT ModalStorage * deltaK() const;
private:
  template <class RealType, class WaveModel>
  bool calculate(WaveModel * waveModel, Ellipticity * ell, QAtomicInt * terminate);
  double _precision;
};

template <class RealType, class WaveModel>
bool Dispersion::calculate(WaveModel * waveModel, Ellipticity * ell, QAtomicInt * terminated)
{
  TRACE;
  RealType * curMode=new RealType [xCount()];
  RealType * lastMode=new RealType [xCount()];
  if(ell) ell->refineClear();
  refineClear();
//#define calculate_DEBUG

  const Seismic1DModel * model=waveModel->model();
  RootSolverTemplate<RealType, WaveModel> solver(waveModel);
  RealType minSlow=model->minSlowS();
  RealType maxSlow, curSlow, lastSupBound=model->maxSlowR(), newCurSlow;
  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());
  int lastOmega=xCount() - 1;

  for(int iMode=0; iMode < modeCount(); iMode++ ) {
#ifdef calculate_DEBUG
    printf( "***************** Mode %i ******************\n", iMode);
#endif
    if(ell) {
      solver.setPrecision(_precision*1e-3);  // Down to 1e-10 in default case (see constructor)
    } else {
      solver.setPrecision(_precision);       // Down to 1e-7 in default case  (see constructor)
    }
    if(iMode==0) {
      maxSlow=model->maxSlowR();
      // 2009-02-15 Reduce factor from 0.5 to 0.25 to avoid too many tries
      //            With 0.5 it appears that mode jumping occurs quite a lot
      solver.setRelativeStep(0.25 * (model->maxSlowR() - model->maxSlowS())/maxSlow);
    } else {
      RealType * tmpMode=lastMode;
      lastMode=curMode;
      curMode=tmpMode;
      maxSlow=lastMode[ lastOmega ];
      solver.setRelativeStep(0.5 * (model->maxSlowR() - model->maxSlowS())/maxSlow * (2 * M_PI/x(lastOmega) ));
      solver.inversePolarity();
    }
    RealValue * ellValues;
    if(ell && iMode<ell->modeCount()) {
      ellValues=ell->mode(iMode);
    } else {
      ellValues=nullptr;
    }
    RealValue * slownessValues=mode(iMode);

    while(true) {
      bool errorYetDetected=false;
      curSlow=maxSlow;
      for(int i=lastOmega; i>=0; i--) {
#ifdef calculate_DEBUG
        printf( "***************** Frequency %i omega %lf f %lf\n", i, x(i), x(i)/(2*M_PI));
#endif
        if(terminated && terminated->testAndSetOrdered(true,true)) {
          // Catch user interrupt
          delete [] curMode;
          delete [] lastMode;
          return false;
        }
        waveModel->setOmega(x(i));
        if(iMode>0) {
          maxSlow=lastMode[i];
          if(curSlow > maxSlow) curSlow=maxSlow;
        }
        //printf("%20.10lf %20.10lf %20.10lf\n",dble(curSlow),dble(_minSlow),dble(maxSlow));
        if(solver.searchDown(curSlow, minSlow, maxSlow)) {
          if(!solver.neville()) {
            delete [] curMode;
            delete [] lastMode;
            return false;
          }
          newCurSlow=solver.lower();
        } else {
          if(iMode==0) {
            // for fundamental mode this condition can never occur unless a mode jumping occured
            errorYetDetected=true;
            App::log(2, tr("** Warning ** : mode jumping for mode %1 (end), reducing "
                           "step ratio to %2\n").arg(iMode).arg(Number::toDouble(solver.searchStep() * 0.1)));
            break;
          } else {
            // The end of the curve, the mode does not exist at this period (no root)
            for(int j=i; j>=0; j--) {
              slownessValues[j].setValid(false);
              curMode[j]=minSlow;
            }
            if(ellValues) {
              for(int j=i; j>=0; j--) ellValues[j].setValid(false);
            }
            curSlow=minSlow;
            break;
          }
        }
        if(ellValues) {
          /* Take a mid slowness between the refined bounds and recompute the
           propagator matrices to get the corresponding sub-determinents necessary
           for ellipticity computation. Keep ellipticity with its sign. */
          waveModel->y(0.5*(solver.upper()+newCurSlow));
          ellValues[ i ].setValue(waveModel->ellipticity());
        }
        double newLastSupBound=solver.upper(); // searchUp may alter it
        //printf("%20.10lf %20.10lf %20.10lf\n",x(i)/(2*M_PI), modelS.infBoundRoot(), modelS.supBoundRoot());
        if(i < lastOmega && newCurSlow - curSlow > _precision) {
          if(forbitVelocityInversion) {
            errorYetDetected=true;
            App::log(2, tr("** Warning ** : retrograde dispersion not allowed (mode %1), "
                           "reducing step ratio to %2\n").arg(iMode).arg(Number::toDouble(solver.searchStep() * 0.1)));
            break;
          } else {
            // Inversions are allowed, check at last point that no mode exist at a higher slowness
            waveModel->setOmega(x( i + 1) );
            solver.setRelativeStep(solver.searchStep() * 0.1);
            if(solver.searchUp(lastSupBound, iMode>0 ? lastMode[ i + 1 ] : maxSlow) ) {
              // recalculating curve because of mode jumping
              App::log(2, tr("** Warning ** : mode jumping for mode %1 (middle at %3 Hz), "
                             "reducing step ratio to %2\n")
                        .arg(iMode)
                        .arg(Number::toDouble(solver.searchStep()))
                        .arg(x(i)/(2.0*M_PI)));
              errorYetDetected=true;
              solver.setRelativeStep(solver.searchStep() * 10.0);
              break;
            }
            solver.setRelativeStep(solver.searchStep() * 10.0);
          }
        }
        lastSupBound=newLastSupBound;
        curSlow=newCurSlow;
        curMode[ i ]=curSlow;
        slownessValues[ i ].setValue(Number::toDouble(curSlow) );
        slownessValues[ i ].setValid(true);
#ifdef calculate_DEBUG
        printf( "%30.20lf %30.20lf\n", 0.5 * x(i)/M_PI, slownessValues[ i ].value());
#endif
      }
      // 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();
      }
      if(errorYetDetected || solver.searchUp(curSlow, maxSlow) ) {
        // recalculating curve because of mode jumping
        if(solver.searchStep() < 1e-4) {
          App::log(2, tr("** Warning ** : dispersion curve impossible to obtain "
                         "for mode %1" ).arg(iMode));
          for( ; iMode < modeCount(); iMode++ ) {
            RealValue * slownessValues=mode(iMode);
            for(int j=lastOmega;j >= 0;j-- ) {
              slownessValues[ j ].setValid(false);
            }
          }
          delete [] curMode;
          delete [] lastMode;
          return false;
        }
        solver.setPrecision(solver.precision() * 0.1);
        solver.setRelativeStep(solver.searchStep() * 0.1);
        if( !errorYetDetected)
          App::log(2, tr("** Warning ** : mode jumping for mode %1 (end), reducing "
                         "step ratio to %2\n").arg(iMode).arg(Number::toDouble(solver.searchStep())));
        if(iMode > 0) maxSlow=lastMode[ lastOmega ];
      } else break;
    }
  }
  delete [] curMode;
  delete [] lastMode;
  return true;
}

} // namespace QGpCoreWave

#endif // DISPERSION_H
