/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  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: 2018-01-04
**  Copyright: 2018-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "AngleSearch.h"

namespace QGpCoreMath {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  AngleSearch::AngleSearch()
    : FunctionSearch()
  {
    TRACE;
    _angles=0;
    _n=0;
  }

  /*!
    Description of destructor still missing
  */
  AngleSearch::~AngleSearch()
  {
    TRACE;
    if(_angles) {
      delete [] (_angles-1);
    }
  }

  void AngleSearch::setGrid(int stepCount)
  {
    TRACE;
    ASSERT(_function);
    _n=stepCount;
    if(_angles) {
      delete [] (_angles-1);
    }
    _angles=new double[_n+2]+1;

    double da=2.0*M_PI/(double)stepCount;
    for(int i=-1; i<_n; i++) {
      _angles[i]=da*(double)i;
    }
    _angles[_n]=2.0*M_PI; // Avoid eventually precision errors

    if(_function->initGrid(_n)) {
      App::log(2, tr("Initializing angle search with %1 values\n").arg(_n));
      for(int k=0; k<_n; k++) {
        _function->initGrid(_angles[k], k);
      }
    }
  }

  inline void AngleSearch::refineMax(int k, double maxVal)
  {
    double val=refineMax(_angles[k-1], _angles[k+1], maxVal);
    FunctionSearchMaximum * max=_function->createMaximum(val, _pos);
    if(max) {
      _maxima.append(max);
    }
  }


  /*!
    Search all angles for the maximum and refine it
  */
  void AngleSearch::globalMax(double absThres)
  {
    TRACE;
    int maxk=0;
    double val, maxVal=-std::numeric_limits<double>::infinity();
    for(int k=0; k<_n; k++) {
      val=value(k);
      if(val>maxVal) {
        maxVal=val;
        maxk=k;
      }
    }
    refineMax(maxk, maxVal);
    FunctionSearch::globalMax(absThres);
  }

  /*!
    Description still missing
  */
  void AngleSearch::localMax(int nMax, double absThres, double relThres)
  {
    TRACE;
    double * map=new double[_n+2]+1;
    // Fill in the map with the standard cached grid
    for(int k=0; k<_n; k++) {
      map[k]=value(k);
    }
    map[-1]=map[_n-1];
    map[_n]=map[0];
    //for(int k=-1; k<=_n; k++) {
    //  printf("%5.i %25.20lf %25.20lf\n", k, _angles[k], map[k]);
    //}

    clearMaxima();
    for(int k=0; k<_n; k++) {
      if(map[k]>map[k-1] &&
         map[k]>map[k+1]) {
        refineMax(k, map[k]);
      }
    }
    delete [] (map-1);
    FunctionSearch::localMax(nMax, absThres, relThres);
  }

  inline void AngleSearch::checkForMax(double angle, double& maxVal)
  {
    TRACE;
    double val=_function->value(angle);
    if(val>maxVal) {
      maxVal=val;
      _pos.setX(angle);
    }
  }

  /*!
    Refines the maximum of the grid in an area defined by x and y limits.
    return value of the refined maximum
  */
  double AngleSearch::refineMax(const double& min, const double& max, double maxVal)
  {
    TRACE;
    _pos.setX((min+max)*0.5);
    double delta=(max-min)*0.5;

    double absPrecision;
    if(_relative[0]) {
      absPrecision=fabs(_pos.x()*_precision[0]);
      // Check that precision is not null along any axis
      if(absPrecision==0.0) {
        absPrecision=fabs(_precision[0]*max);
      }
    } else {
      absPrecision=_precision[0];
    }

    while(delta>absPrecision) {
      delta*=0.5;
      checkForMax(_pos.x()-delta, maxVal);
      checkForMax(_pos.x()+delta, maxVal);
    }
    return maxVal;
  }

} // namespace QGpCoreMath

