/***************************************************************************
**
**  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: 2007-11-12
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>

#include "AbstractFKFunction.h"
#include "FKGradientSearch.h"
#include "FKGridSearch.h"
#include "FKSteeringOneComponent.h"
#include "FKCrossSpectrum.h"

#include "ConvFKVertical.h"
#include "ConvFKRadial.h"
#include "ConvFKTransverse.h"
#include "ConvFKRayleigh.h"
#include "OmniFKVertical.h"
#include "OmniFKRayleigh.h"
#include "PoggiFKVertical.h"
#include "PoggiFKRadial.h"
#include "HRFKVertical.h"
#include "HRFKRadial.h"
#include "HRFKTransverse.h"
#include "HRFKRayleigh.h"
#include "HRFKRayleighRadial.h"
#include "HRFKRayleighFixedEll.h"
#include "HRFKDirectRayleigh.h"
#include "HRFKDirectRayleighFixedEll.h"
#include "HRFKDirectRayleighRadial.h"
#include "HRFKDirectRayleighRefined.h"
#include "HRFKDirectRayleighVertical.h"
#include "HRFKDirectRadial.h"
#include "HRFKDirectLove.h"
#include "ActiveConvFKVertical.h"
#include "ActiveHRFKVertical.h"
#include "ActiveHRFKRayleigh.h"
#include "ActiveHRFKDirectRayleigh.h"
#include "ActiveConvFKRayleigh.h"

namespace ArrayCore {

  /*!
    \class AbstractFKFunction AbstractFKFunction.h
    \brief AbstractFKFunction map calculator

    Compute the AbstractFKFunction map for a given array of stations.
  */

  /*!
    Init unlimited boundaries for wavenumber spaces (array, slowness and total)
    Null Gaussian weights.
  */
  AbstractFKFunction::AbstractFKFunction(FKCache * gridCache)
  {
    _steering=new FKSteeringOneComponent;
    _steering->setArray(gridCache->array());
    static_cast<FKSteeringOneComponent *>(_steering)->resize();

    _crossSpectrum=nullptr;
    _gridCache=gridCache;

    _squaredKmaxArray=0.0;
    _squaredKminArray=0.0;
    _squaredSlowMin=0.0;
    _squaredSlowMax=std::numeric_limits<double>::infinity();
    _squaredKminTotal=0.0;
    _squaredKmaxTotal=std::numeric_limits<double>::infinity();
    _minAzimuth=0.0;
    _maxAzimuth=2.0*M_PI;
  }

  AbstractFKFunction::~AbstractFKFunction()
  {
    delete _steering;
  }

  /*void AbstractFKFunction::setGrid(FKGradientSearch * g, double cacheStep, double step, double size)
  {
    g->setScales(Point(step, step, 1.0));
    g->setGrid(Point(size, size, 0.0));
  }*/

  void AbstractFKFunction::setGrid(FKGridSearch * g, double step, double size)
  {
    g->setGrid(-size, size, step, -size, size, step);
  }

  /*!
    Automatically called by search object upon initialization.
    If returned value is false, grid is assumed to be already initialized.
  */
  bool AbstractFKFunction::initGrid(int n)
  {
    if(_gridCache->isEmpty(FKCache::OneComponent)) {
      App::log(tr("Caching steering vectors for 1-component... (%1 values)\n").arg(n));
      _gridCache->resize(FKCache::OneComponent, n);
      return true;
    } else {
      return false;
    }
  }

  void AbstractFKFunction::initGrid(const Point& k, int index)
  {
    FKSteeringOneComponent::init(_gridCache, index , k);
  }

  /*!
    Angles according to math convention in degrees.
  */
  void AbstractFKFunction::setAzimuthRange(double min, double max)
  {
    _minAzimuth=Angle::degreesToRadians(min);
    _maxAzimuth=Angle::degreesToRadians(max);

  }

  void AbstractFKFunction::setTotalLimits(double squaredOmega)
  {
    _squaredKmaxTotal=squaredOmega*_squaredSlowMax;
    if(_squaredKmaxTotal>_squaredKmaxArray) {
      _squaredKmaxTotal=_squaredKmaxArray;
    }
    _squaredKminTotal=squaredOmega*_squaredSlowMin;
    if(_squaredKminTotal<_squaredKminArray) {
      _squaredKminTotal=_squaredKminArray;
    }
  }

  /*!
    Return true if \a k is close to the limits (<1% relative).
    \a frequency is used only for reporting.
  */
  bool AbstractFKFunction::isOnLimit(const Point& k, double frequency) const
  {
    double k2=k.x()*k.x()+k.y()*k.y();
    // 1% on squared k corresponds to 0.5% on slowness if the k limit is controled by a maximum slowness
    if(fabs(k2-minimumK2())<0.01*minimumK2() ||
       fabs(k2-maximumK2())<0.01*maximumK2()) {
      APP_LOG(2, tr("  %1 (rad/m)^2 (%5 m/s) at %2 Hz on limits %3 or %4\n")
                          .arg(k2).arg(frequency)
                          .arg(minimumK2()).arg(maximumK2())
                          .arg(2.0*M_PI*frequency/sqrt(k2)))
      return true;
    } else {
      return false;
    }
  }

  bool AbstractFKFunction::refine(const Point2D& k, double ell,
                                  FKPeaks * results,
                                  const WaveNumberConverter& conv)
  {
    Q_UNUSED(k)
    Q_UNUSED(ell)
    Q_UNUSED(results)
    Q_UNUSED(conv)
    return false;
  }

  /*Point AbstractFKFunction::gradient(const Point& k) const
  {
    if(isInsideLimits(k)) {
      FKPower p(_steering);
      setGradient(k, p);
      return p.gradient();
    } else {
      return 0.0;
    }
  }

  double AbstractFKFunction::concavity(const Point& k) const
  {
    if(isInsideLimits(k)) {
      FKPower p(_steering);
      p.initOneComponent();  // does not hurt for three components
      setGradient(k, p);
      setHessian(k, p);
      return p.concavity();
    } else {
      return 0.0;
    }
  }

  Point AbstractFKFunction::stepDirection(const Point& k) const
  {
    if(isInsideLimits(k)) {
      FKPower p(_steering);
      p.initOneComponent();  // does not hurt for three components
      setGradient(k, p);
      setHessian(k, p);
      if(p.concavity()<0.0) {
        return p.newtonStep();
      }
    }
    return Point();
  }*/

  AbstractFKFunction * AbstractFKFunction::create(Type t, ArrayStations::Mode mode,
                                                  const FKParameters * param,
                                                  FKCache * gridCache)
  {
    TRACE;
    AbstractFKFunction * f=nullptr;
    switch(t) {
    case Rayleigh:
      switch(mode) {
      case ArrayStations::Vertical:
        switch(param->processType()) {
        case FKParameters::Conventional:
          f=new ConvFKVertical(gridCache);
          break;
        case FKParameters::DirectSteering:
        case FKParameters::DirectSteeringRadial:
        case FKParameters::DirectSteeringRefined:
        case FKParameters::DirectSteeringVertical:
        case FKParameters::RTBF:
        case FKParameters::RTBFRadial:
        case FKParameters::PoggiVertical:
        case FKParameters::PoggiRadial:
          f=new HRFKVertical(gridCache);
          break;
        case FKParameters::Omni:
          f=new OmniFKVertical(gridCache);
          break;
        case FKParameters::ActiveRTBF:
        case FKParameters::ActiveDirectSteering:
          f=new ActiveHRFKVertical(gridCache);
          break;
        case FKParameters::ActiveConventional:
          f=new ActiveConvFKVertical(gridCache);
          break;
        }
        break;
      case ArrayStations::Horizontal:
        switch(param->processType()) {
        case FKParameters::Conventional:
        case FKParameters::ActiveConventional:
          f=new ConvFKRadial(gridCache);
          break;
        case FKParameters::DirectSteering:
        case FKParameters::ActiveDirectSteering:
        case FKParameters::DirectSteeringRadial:
        case FKParameters::DirectSteeringRefined:
        case FKParameters::DirectSteeringVertical:
          f=new HRFKDirectRadial(gridCache);
          break;
        case FKParameters::RTBF:
        case FKParameters::RTBFRadial:
        case FKParameters::ActiveRTBF:
        case FKParameters::PoggiVertical:
        case FKParameters::PoggiRadial:
        case FKParameters::Omni:
          f=new HRFKRadial(gridCache);
          break;
        }
        break;
      case ArrayStations::ThreeComponents:
        switch(param->processType()) {
        case FKParameters::Conventional:
          f=new ConvFKRayleigh(gridCache);
          break;
        case FKParameters::RTBFRadial:
          if(param->fixedEllipticityFileName().isEmpty()) {
            f=new HRFKRayleighRadial(gridCache);
          }
          break;
        case FKParameters::RTBF:
          f=new HRFKRayleigh(gridCache);
          break;
        case FKParameters::DirectSteering:
          if(param->fixedEllipticityFileName().isEmpty()) {
            f=new HRFKDirectRayleigh(gridCache);
          }
          break;
        case FKParameters::DirectSteeringRadial:
          f=new HRFKDirectRayleighRadial(gridCache);
          break;
        case FKParameters::DirectSteeringVertical:
          f=new HRFKDirectRayleighVertical(gridCache);
          break;
        case FKParameters::DirectSteeringRefined:
          f=new HRFKDirectRayleighRefined(gridCache);
          break;
        case FKParameters::Omni:
          f=new OmniFKRayleigh(gridCache);
          break;
        case FKParameters::PoggiVertical:
          f=new PoggiFKVertical(gridCache);
          break;
        case FKParameters::PoggiRadial:
          f=new PoggiFKRadial(gridCache);
          break;
        case FKParameters::ActiveRTBF:
          f=new ActiveHRFKRayleigh(gridCache);
          break;
        case FKParameters::ActiveDirectSteering:
          f=new ActiveHRFKDirectRayleigh(gridCache);
          break;
        case FKParameters::ActiveConventional:
          f=new ActiveConvFKRayleigh(gridCache);
          break;
        }
        break;
      }
      break;
    case RayleighFixedEll:
      switch(mode) {
      case ArrayStations::Vertical:
      case ArrayStations::Horizontal:
        break;
      case ArrayStations::ThreeComponents:
        switch(param->processType()) {
        case FKParameters::Conventional:
        case FKParameters::ActiveConventional:
        case FKParameters::ActiveRTBF:
        case FKParameters::DirectSteeringRadial:
        case FKParameters::DirectSteeringVertical:
        case FKParameters::DirectSteeringRefined:
        case FKParameters::Omni:
        case FKParameters::PoggiVertical:
        case FKParameters::PoggiRadial:
          break;
        case FKParameters::RTBF:
        case FKParameters::RTBFRadial:
          if(!param->fixedEllipticityFileName().isEmpty()) {
            HRFKRayleighFixedEll * f=new HRFKRayleighFixedEll(gridCache);
            if(!f->loadFixedEllipticityCurve(param->fixedEllipticityFileName())) {
              delete f;
              f=nullptr;
            }
          }
          break;
        case FKParameters::DirectSteering:
        case FKParameters::ActiveDirectSteering:
          if(!param->fixedEllipticityFileName().isEmpty()) {
            HRFKDirectRayleighFixedEll * f=new HRFKDirectRayleighFixedEll(gridCache);
            if(!f->loadFixedEllipticityCurve(param->fixedEllipticityFileName())) {
              delete f;
              f=nullptr;
            }
          }
          break;
        }
        break;
      }
      break;
    case Love:
      switch(mode) {
      case ArrayStations::Vertical:
        break;
      case ArrayStations::Horizontal:
      case ArrayStations::ThreeComponents:
        switch(param->processType()) {
        case FKParameters::Conventional:
        case FKParameters::ActiveConventional:
          f=new ConvFKTransverse(gridCache);
          break;
        case FKParameters::DirectSteering:
        case FKParameters::ActiveDirectSteering:
        case FKParameters::DirectSteeringRadial:
        case FKParameters::DirectSteeringRefined:
        case FKParameters::DirectSteeringVertical:
          f=new HRFKDirectLove(gridCache);
          break;
        case FKParameters::RTBF:
        case FKParameters::RTBFRadial:
        case FKParameters::ActiveRTBF:
        case FKParameters::PoggiVertical:
        case FKParameters::PoggiRadial:
        case FKParameters::Omni:
          f=new HRFKTransverse(gridCache);
          break;
        }
        break;
      }
      break;
    }
    if(f) {
      // Limiting search to kmin speedup computation but may pick alias peaks
      // Better to filter out after FK search
      //f->setMinimumWavenumber(param->minimumWaveNumber());
      // Reduce the gridded box to a circle of radius effective grid size
      f->setMaximumWavenumber(param->effectiveGridSize());
      f->setMaximumSlowness(param->maximumSlowness());
    }
    return f;
  }

} // namespace ArrayCore
