/***************************************************************************
**
**  This file is part of ArrayCore.
**
**  ArrayCore 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.
**
**  ArrayCore 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: 2020-01-29
**  Copyright: 2020
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "HRFKRayleighRadial.h"
#include "FKGridSearch.h"
#include "FKCrossSpectrum.h"
#include "FKPower.h"
#include "FKSteeringTwoComponentRayleighRadial.h"
#include "WaveNumberConverter.h"
#include "FKPeaks.h"
#include "HRFKRayleighEllipticity.h"

namespace ArrayCore {

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

    Full description of class still missing
  */

  double HRFKRayleighRadial::value(const Point& kell, int index) const
  {
    if(isInsideLimits(kell)) {
      FKPower p(&FKSteeringTwoComponentRayleighRadial::cache(_gridCache, index));
      p.setCaponValue(_crossSpectrum->rotatedRayleigh(kell));
      return p.value();
    } else {
      return -1.0;
    }
  }

  double HRFKRayleighRadial::value(const Point& kell) const
  {
    if(isInsideLimits(kell)) {
      FKSteeringTwoComponentRayleighRadial * s=static_cast<FKSteeringTwoComponentRayleighRadial *>(_steering);
      double ell=::tan(kell.z());
      s->initValue(kell, ell);
      FKPower p(s);
      p.setCaponValue(_crossSpectrum->rotatedRayleigh(kell));
      return p.value();
    } else {
      return -1.0;
    }
  }

  double HRFKRayleighRadial::ellipticity(const Point& kell)
  {
    // The interest of this medthod is to catch the ellipticity around 0
    // Noise estimation is always very high for low ellipticity absolute value.
    // Several tests showed that nothing can be gained from complex computations
    // Keep it simple... use HRFKRayleigh for a better estimation of higher ellipticities
    return ::tan(kell.z());
    //A test with tiny angles proportionnal to ellipticity
    /*const ComplexMatrix& covmat=_crossSpectrum->rotatedRayleigh(kell);
    FKGridSearch grid;
    grid.setEllipticityPrecision(0);
    double ellh=0.0, ellz=0.0;
    double delta=fabs(kell.z())*0.5;

    HRFKRayleighRadialEllipticity * fh;
    fh=new HRFKRayleighRadialEllipticity(_gridCache);
    grid.setFunction(fh);
    fh->setK(kell);
    fh->setCrossSpectrum(&covmat);
    grid.setGrid(kell.z()-delta, kell.z()+delta, delta*0.1);
    grid.globalMax(0.0);
    if(grid.maximaCount()>0) {
      ellh=::tan(grid.pos().x());
    }

    HRFKRayleighVerticalEllipticity * fz;
    fz=new HRFKRayleighVerticalEllipticity(_gridCache);
    grid.setFunction(fz);
    fz->setK(kell);
    fz->setCrossSpectrum(&covmat);
    grid.setGrid(kell.z()-delta, kell.z()+delta, delta*0.1);
    grid.globalMax(0.0);
    if(grid.maximaCount()>0) {
      ellz=::tan(grid.pos().x());
    }

    return adjustEllipticity(ellh, ellz);*/
  }

  /*!
    Adjust k with a better estimation of ellipticity that the raw maximization of power
  */
  bool HRFKRayleighRadial::refine(const Point2D & k, double ell0,
                                  FKPeaks * results,
                                  const WaveNumberConverter& conv)
  {
    return false;
    /*FKGridSearch grid;
    HRFKRayleigh * f=new HRFKRayleigh(_gridCache);
    f->setCrossSpectrum(_crossSpectrum);
    grid.setFunction(f);
    double gridStep=_crossSpectrum->parameters()->gridStep();
    grid.setGrid(k.x()-gridStep, k.x()+gridStep, 0.5*gridStep,
                 k.y()-gridStep, k.y()+gridStep, 0.5*gridStep,
                 ell0-M_PI/20, ell0+M_PI/20, M_PI/40);
    grid.localMaxBest(INT_MAX);
    double velocity=1.0/conv.slowness(k);
    double ellDeg=Angle::radiansToDegrees(ell0);
    APP_LOG(5, tr("%1 Hz: refining at %2 m/s with ellipticity %3 deg, found %4 maxima\n")
            .arg(conv.frequency())
            .arg(velocity)
            .arg(ellDeg)
            .arg(grid.maximaCount()))
    FunctionMaximaIterator it;
    for(it=grid.begin(); it!=grid.end(); ++it) {
      const FunctionSearchMaximum& m=**it;
      Point newkell=m.pos();
      APP_LOG(6, tr("%1 Hz: refined %2 m/s with ellipticity %3 deg to %4 m/s and %5 deg\n")
              .arg(conv.frequency())
              .arg(velocity)
              .arg(ellDeg)
              .arg(1.0/conv.slowness(newkell))
              .arg(Angle::radiansToDegrees(newkell.z())))
      results->add(newkell, newkell.z(), 0, m.value(), conv, minimumSlowness());
    }*/
    FKGridSearch grid;
    HRFKRayleigh * f=new HRFKRayleigh(_gridCache);
    f->setCrossSpectrum(_crossSpectrum);
    grid.setFunction(f);
    double gridStep=_crossSpectrum->parameters()->gridStep();
    double ellMax=ell0+M_PI/20;
    for(double ell=ell0-M_PI/20; ell<=ellMax; ell+=M_PI/180) {
      grid.setGrid(k.x()-gridStep, k.x()+gridStep, 0.5*gridStep,
                   k.y()-gridStep, k.y()+gridStep, 0.5*gridStep,
                   ell, ell, 0.0);
      grid.globalMax();
      const FunctionSearchMaximum& m=**grid.begin();
      Point newkell=m.pos();
      results->add(newkell, newkell.z(), 0.0, m.value(), conv, minimumSlowness());
    }
    return true;
  }

} // namespace ArrayCore

