/***************************************************************************
**
**  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 <QGpCoreTools.h>
#include "KmaxSolver.h"

namespace ArrayCore {

/*!
  \class KmaxSolver KmaxSolver.h
  \brief Computes Kmax according to Wathelet et al. (2008)

  \todo Do not inherit TheoreticalLinearFK but rather use a pointer

  TheoreticalFK is used for peak search. TheoreticalLinearFK to find the intersection
  with level line at 0.5 around found peak (fixing azimuth).
*/

KmaxSolver::KmaxSolver(const QVector<Point2D>& stations, double kmin, double threshold)
    : TheoreticalLinearFK(stations)
{
  TRACE;
  _kmax=std::numeric_limits<double>::infinity();
  _kminHalf=0.5*kmin;
  _dk=0.25*kmin;
  _terminated=false;
  _valid=false;

  setThreshold(threshold);
  setCenter(0.0, 0.0);
  setGrid(2.0*kmin, std::numeric_limits<double>::infinity(), _dk);
  // Disregard any peak with amplitude lower than 0.1
  // Even if we are interested in peaks exceeding 0.5, the coarse grid search has
  // a step of kmin/4, hence first peak estimates might be less than 0.5 even if peak is
  // above 0.5.
  setNoiseLevel(0.1);
  // Doing this forces us to takeFunction() in destructor
  setFunction(this);
}

KmaxSolver::~KmaxSolver()
{
  TRACE;
  // Concentric search is the owner of this, remove this ownership
  takeFunction();
}

/*!
  Return true if running in a parallel thread.
*/
bool KmaxSolver::calculate()
{
  // Get possible limits for array response map
  double dmin, dmax;
  distanceRange(dmin, dmax);
  double sf=shapeFactor();
  if(sf<0.1 || sf>10.0) { // Linear array detected
    _kmax=2.0*M_PI/dmin;
    _valid=true;
    return false;
  } else {
    start();
    return true;
  }
}

void KmaxSolver::run()
{
  TRACE;
  double minKmax=std::numeric_limits<double>::infinity();
  // Get the theoretical limit kmax
  double val;
  double maxR=std::numeric_limits<double>::infinity();
  while(r0()<=maxR) {
    val=maximum(&_terminated);
    //printf("Peak at %lf (%lf)\n", maxRadius(), val);
    // A peak has been found and refined. We accept only those exceeding threshold.
    if(val>threshold()) {
      // Find the intersection with line at _aliasingLevel
      double r=maxRadius();
      RootSolver<KmaxSolver> kInv(this);
      setAngle(maxAngle(r));
      kInv.setPrecision(1e-7);
      kInv.setRelativeStep(0.5);
      kInv.setPolarity(0.0);
      if(kInv.searchDownSlope(r, _kminHalf, r)) {
        kInv.neville();
        double k=kInv.lower();
        if(k<minKmax && k>_kminHalf) {
          if(maxR==std::numeric_limits<double>::infinity()) {
            maxR=r0()+_dk; // Go to one additional round from first eligible peak
                           // before finishing
            setMaximumRadius(maxR);
          }
          minKmax=k;
        }
      } else {
        App::log(tr("Error no root found searching kmax\n") );
      }
    }
    if(_terminated.testAndSetOrdered(true,true)) {
      return;
    }
  }
  _kMutex.lock();
  _kmax=minKmax;
  _valid=true;
  _kMutex.unlock();
}

double KmaxSolver::kmax(bool& ok) const
{
  TRACE;
  QMutexLocker ml(&_kMutex);
  ok=_valid;
  if(ok) {
    return _kmax;
  } else {
    return r0();
  }
}

void KmaxSolver::terminate()
{
  TRACE;
  _terminated.fetchAndStoreOrdered(true);
  wait();
}

} // namespace ArrayCore
