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

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

namespace QGpCoreWave {

  /*!
    \class Ellipticity Ellipticity.h
    \brief Calculate Rayleigh ellipticity curves

    The main function used to calculate the ellipticity curve is calculate().

    Experience has shown that a 1e-10 precision on dc is necessary to refine the ellipticity down to 1e-3
    The precision used to compute the original curve must be the same as for the refines.
  */

  inline double Ellipticity::refineMax(int modeIndex, int iOmega, Dispersion& disp,
                                           RootSolver<Rayleigh>& solver, bool& badSampling)
  {
    TRACE;
  //#define REFINEMAX_DEBUG
    RealValue * point=mode(modeIndex);
    double x1=x(iOmega-2), ell1=fabs(point[iOmega-2].value());
    double x2=x(iOmega-1), ell2=fabs(point[iOmega-1].value());
    double x3              , ell3;
    double x4=x(iOmega)  , ell4=fabs(point[iOmega].value());
    SAFE_UNINITIALIZED(ell3,0);
    SAFE_UNINITIALIZED(x3,0);

    // Initialize step ratio, contrary to normal dispersion computation, step ratio is not reset before each mode
    Rayleigh * waveModel=solver.values();
    const Seismic1DModel * model=waveModel->model();
    solver.setRelativeStep(0.5 * (model->maxSlowR() - model->maxSlowS())/model->maxSlowR() * (2 * M_PI/x2) );

    double x20=x2;
    double minEll=ell1;
    if(ell4<minEll) minEll=ell4;
    minEll-=minEll*1e-10;
    badSampling=false;
  #ifdef REFINEMAX_DEBUG
    printf("#---- %15lg %15lg %15lg %15lg\n", 0.5/M_PI*x1, 0.5/M_PI*x2, 0.5/M_PI*x3, 0.5/M_PI*x4);
    printf("#     %15lg %15lg %15lg %15lg\n", ell1, ell2, ell3, ell4);
  #endif
    while(x4-x1>1e-3) {
      if(x2-x1>x4-x2) {
        // insert a point between x1 and x2
        x3=x2;
        ell3=ell2;
        x2=0.5*(x3+x1);
        if(!disp.refine(modeIndex, x2<=x20 ? iOmega-2 : iOmega-1, x2, solver, *this)) break;
        ell2=fabs(lastRefine().value(modeIndex).value());
        if(ell2<minEll) badSampling=true;
      } else {
        x3=0.5*(x4+x2);
        if(!disp.refine(modeIndex, x3<=x20 ? iOmega-2 : iOmega-1, x3, solver, *this)) break;
        ell3=fabs(lastRefine().value(modeIndex).value());
        if(ell3<minEll) badSampling=true;
      }
  #ifdef REFINEMAX_DEBUG
      printf("#---- %15lg %15lg %15lg %15lg\n", 0.5/M_PI*x1, 0.5/M_PI*x2, 0.5/M_PI*x3, 0.5/M_PI*x4);
      printf("#     %15lg %15lg %15lg %15lg\n", ell1, ell2, ell3, ell4);
  #endif
      if(ell3<ell2) {
        x4=x3;
  #ifdef REFINEMAX_DEBUG
        ell4=ell3;
  #endif
      }
      else {
        x1=x2;
        x2=x3;
  #ifdef REFINEMAX_DEBUG
        ell1=ell2;
  #endif
        ell2=ell3;
      }
    }
    disp.refineSort();
    refineSort();
    if(ell3>ell2) return x3; else return x2;
  }

  /*!
    Return a list of the frequency of all peaks within the sampled range
  */
  QList<double> Ellipticity::peaks(int modeIndex, Dispersion& disp, Rayleigh * waveModel)
  {
    QList<double> peaks;
    double fac=0.5/M_PI;

    RootSolver<Rayleigh> solver(waveModel);
    RealValue * point=mode(modeIndex);
    int n=xCount();

    int i,j;
    for(j=1; j<n && !point[j].isValid(); j++) {}
    if(j==n) return peaks;
    for(i=j+1; i<n && !point[i].isValid(); i++ ) {}
    if(i==n) return peaks;
    double lastSlope=point[i].value()-point[j].value();
    j=i;
    for(i++; i<n; i++ ) {
      if(point[i].isValid()) {
        double slope=fabs(point[i].value())-fabs(point[j].value());
        j=i;
        if(slope<=0 && lastSlope>0 && lastSlope-slope>1e-10) {
          // Found a maximum in the sampled curve that is at distance > 0.01 the ellipticity of the half space
          bool badSampling;
          double xr=refineMax(modeIndex, i, disp, solver, badSampling);
          peaks << fac*xr;
          if(badSampling)
            App::log(tr("** Warning ** : probably a missing peak, bad initial sampling\n") );
        }
        lastSlope=slope;
      }
    }
    return peaks;
  }

  /*!
    Find the frequency of the closest peak to \a val and return the misfit
  */
  double Ellipticity::peakMisfit(int modeIndex, const RealStatisticalValue& val, Dispersion& disp, Rayleigh * waveModel)
  {
    TRACE;
    double f0, devf0, misfit=std::numeric_limits<double>::infinity();
    f0=val.mean()*2.0*M_PI;
    if(val.stddev()>0) devf0=1.0/ (val.stddev()*2.0*M_PI); else devf0=1.0/f0;

    RootSolver<Rayleigh> solver(waveModel);
    RealValue * point=mode(modeIndex);
    int n=xCount();

    int i,j;
    for(j=1; j<n && !point[j].isValid(); j++) {}
    if(j==n) return 0.0;
    for(i=j+1; i<n && !point[i].isValid(); i++ ) {}
    if(i==n) return 0.0;
    double lastSlope=fabs(point[i].value())-fabs(point[j].value());
    j=i;
    for(i++; i<n; i++ ) {
      if(point[i].isValid()) {
        double slope=fabs(point[i].value())-fabs(point[j].value());
        j=i;
        if(slope<=0 && lastSlope>0) {
          double diff;
          // Found a maximum in the sampled curve that is at distance > 0.01 the ellipticity of the half space
          if(((diff=(f0-x(i))*devf0)>=0 && diff<misfit) ||
              ((diff=(x(i-2)-f0)*devf0)>=0 && diff<misfit) ||
              (x(i-2)<f0 && f0<x(i))) {
            // There's a chance to get a better misfit
            bool badSampling;
            double xr=refineMax(modeIndex, i, disp, solver, badSampling);
            //App::log(tr("Refined peak frequency=%1\n").arg(0.5*xr/M_PI) );
            // The peak is sufficiently refined, adjust the misfit if necessary
            diff=fabs(xr-f0)*devf0;
            if(diff<misfit) misfit=diff;
            if(badSampling)
              App::log(tr("** Warning ** : probably a missing peak, bad initial sampling\n") );
          }
        }
        lastSlope=slope;
      }
    }
    if(misfit==std::numeric_limits<double>::infinity()) {
      // monotonous curve, evaluate fundamental frequency by V0/4H
      double roughFreq=2.0 * M_PI * waveModel->model()->roughFundamentalFrequency();
      //App::log(tr("Rough fundamental frequency=%1\n").arg(0.5 * roughFreq/M_PI) );
      if(roughFreq>=x(0) && roughFreq<=x(n-1)) {
        App::log(tr("** Warning ** : estimated rough fundamental frequency inside range\n") );
        double midRange=0.5*(x(0)+x(n-1));
        if(roughFreq>midRange)
          misfit=fabs(x(n-1)-f0)*devf0;
        else
          misfit=fabs(x(0)-f0)*devf0;
      } else
        misfit=fabs(roughFreq-f0)*devf0;
    }
    refineSort();
    disp.refineSort();
    return floor(misfit*1000+0.5)*0.001; // frequency is estimated down to 1e-3, avoid classification within precision limits
  }

  /*!
    Adds a Love contribution to the ellipticity.
    To compare to measured H/V, the body wave contribution is still missing.

    \a alpha is the ratio Love energy/Rayleigh energy.

    Do not call this function more than once because internal values are directly modified.
  */
  void Ellipticity::addLove(double alpha)
  {
    int n=xCount();
    alpha=sqrt(alpha);

    for(int im=modeCount()-1; im>=0; im--) {
      RealValue * point=mode(im);
      for(int ix=0; ix<n; ix++) {
        double e=point[ix].value();
        point[ix].setValue(e+alpha*sqrt(e*e+1.0));
        //point[ix].setValue(e*(1.0+alpha));
      }
    }
  }

  /*!
    Output to stream \a s, mixing refines and original samples, removing invalid values.
    If \a modeIndex is negative, all modes are ouput.
    Insert a '#' everytime the ellipticity changes of sign with abs values > \a signThreshold.

    Comments are repeated before each mode.
  */
  void Ellipticity::toStream(QTextStream& s, int modeIndex, const QString& comments, double signThreshold) const
  {
    TRACE;
    static const QString fmt("%1 %2\n");
    double xFactor=0.5/M_PI;
    int nm=modeCount();
    for (int im=0; im<nm; im++) {
      if(modeIndex<0 || im==modeIndex) {
        s << comments;
        s << "# Mode " << im << "\n";
        double lastVal=0.0;
        for (Iterator it(this, im); !it.atEnd(); ++it) {
          const RealValue& val=it.value();
          if (val.isValid()) {
            if(fabs(val.value())>signThreshold &&
               fabs(lastVal)>signThreshold &&
               ((val.value()>0.0 && lastVal<0.0) ||
                (val.value()<0.0 && lastVal>0.0))) {
              s << comments;
              s << "#";
            }
            s << fmt.arg(it.x()*xFactor, 0, 'g', 15).arg(val.value(), 0, 'g', 15);
            lastVal=val.value();
          }
        }
      }
    }
  }
} // namespace QGpCoreWave
