#pragma once

#ifndef TZEROFINDER_H
#define TZEROFINDER_H

#include <cmath>
#include <assert.h>

enum { FZ_OK = 0, FZ_FTOL = 1, FZ_XTOL = 2, FZ_MAX_ITER = 3, FZ_NOT_ZERO = 4 };

/*

//Daniele
//  Use the one at the end instead. This may overshoot - plus, it does not
//  bisecate the interval... (I suppose it has never been used much :) )

template< class  unaryOp        ,
          class  unaryOpDerivate>
bool  findZero_Newton ( double          x0      ,
                        double          x1      ,
                        unaryOp         f       ,
                        unaryOpDerivate fd1     ,
                        double          xtol    ,
                        double          ftol    ,
                        int             maxIter ,
                        double          &x      ,
                        int             &err    )
{
  double  f_x_n_plus_1  = 0.0   ;
  double  f_x_n         = f(x1) ;
  double  f_x_n_minus_1 = f(x0) ;

  if( f_x_n*f_x_n_minus_1 > 0 )
  {
    err = FZ_NOT_ZERO;
    return  false;
  }

  int i;
  err=FZ_OK;

  if( f_x_n         == 0  ||
      f_x_n_minus_1 == 0    )
  {
    x   = (f_x_n == 0.0) ? x1 : x0;
    err = FZ_OK;
    return true;
  }

  double  x_n_plus_1  ;
  double  x_n         ;
  double  x_n_minus_1 ;

  x_n         = x1;
  x_n_minus_1 = x0;

  for(i=0;i<maxIter;++i)
  {
    // the first time x_n = x1
    double  fd1_x_n = fd1(x_n);
    assert( fd1_x_n != 0.0 );

    x_n_plus_1 = x_n - f_x_n / fd1_x_n;
    if( fabs(x_n - x_n_plus_1) < xtol )
    {
      err=FZ_XTOL;
      break;
    }

    f_x_n_plus_1 = f(x_n_plus_1);
    if( fabs(f_x_n_plus_1) < ftol )
    {
      err=FZ_FTOL;
      break;
    }

    x_n_minus_1   = x_n;
    x_n           = x_n_plus_1;
    f_x_n         = f_x_n_plus_1;
  }

  x=x_n_plus_1;
  err = (i == maxIter ? FZ_MAX_ITER : err);
  return true;
}
*/

//-----------------------------------------------------------------------------

/*!

  Bisection algorithm.
  Get an object and a method of object class.
  x1 and x2 are extremes of test and xtol
  is the accuracy.

    Return:
    the minimum or,
    -2  if min it's unreachable.
    -1  if min does not exist
    Remark:
    Max iterations numbers is fixed to 100 in in maxIter.
    This value fix the accuracy to 2e-100.

      From Numerical Recipes.
*/

template <class unaryOp>
bool findZero_bisection(double x0, double x1, unaryOp f, double xtol,
                        int maxIter, double &x, int &err) {
  int i;
  err = FZ_OK;
  double dx, fx0, fmid, xmid, rtb;

  fx0  = f(x0);
  fmid = f(x1);

  if (fx0 * fmid > 0.0) {
    err = FZ_NOT_ZERO;
    return false;
  }

  if (fx0 == 0 || fmid == 0) {
    x   = (fmid == 0.0) ? x1 : x0;
    err = FZ_OK;
    return true;
  }

  rtb = fx0 < 0.0 ? (dx = x1 - x0, x0) : (dx = x0 - x1, x1);

  for (i = 1; i <= maxIter; i++) {
    fmid = f(xmid        = rtb + (dx *= 0.5));
    if (fmid <= 0.0) rtb = xmid;
    if (fabs(dx) < xtol || fmid == 0.0) {
      x   = rtb;
      err = FZ_XTOL;
      break;
    }
  }
  err = (i == maxIter ? FZ_MAX_ITER : err);
  return true;
}

template <class unaryOp>
bool findZero_secant(double x0, double x1, unaryOp f, double xtol, double ftol,
                     int maxIter, double &x, int &err) {
  double f_x_n_plus_1 = 0.0, f_x_n = f(x1), f_x_n_minus_1 = f(x0);

  if (f_x_n * f_x_n_minus_1 > 0) {
    err = FZ_NOT_ZERO;
    return false;
  }

  int i;
  err = FZ_OK;

  if (f_x_n == 0 || f_x_n_minus_1 == 0) {
    x = (f_x_n == 0.0) ? x1 : x0;
    return true;
  }

  double x_n_plus_1 = x1, x_n = x1, x_n_minus_1 = x0;

  for (i = 0; i < maxIter; ++i) {
    double den = f_x_n - f_x_n_minus_1;
    assert(den != 0.0);
    if (den == 0.0) {
      x_n = x_n_plus_1;
      err = FZ_FTOL;
      break;
    }

    x_n_plus_1 = x_n - (x_n - x_n_minus_1) * f_x_n / den;
    if (fabs(x_n - x_n_plus_1) < xtol) {
      err = FZ_XTOL;
      break;
    }

    f_x_n_plus_1 = f(x_n_plus_1);
    if (fabs(f_x_n_plus_1) < ftol) {
      err = FZ_FTOL;
      break;
    }

    x_n_minus_1   = x_n;
    x_n           = x_n_plus_1;
    f_x_n_minus_1 = f_x_n;
    f_x_n         = f_x_n_plus_1;
  }

  x   = x_n_plus_1;
  err = (i == maxIter ? FZ_MAX_ITER : err);
  return true;
}

template <class unaryOp1, class unaryOp2>
bool findZero_Newton(double x0, double x1, unaryOp1 f, unaryOp2 f1, double xtol,
                     double ftol, int maxIter, double &x, int &err) {
  double f_0   = f(x0);
  double f_1   = f(x1);
  double f_x_n = f_0, f_x_n_plus_1 = 0.0;

  bool s_0 = (f_0 > 0);
  bool s_1 = (f_1 > 0);
  bool s_n;

  if (s_0 && s_1) {
    err = FZ_NOT_ZERO;
    return false;
  }

  int i;
  err = FZ_OK;

  if (f_0 == 0 || f_1 == 0) {
    x = (f_1 == 0.0) ? x1 : x0;
    return true;
  }

  double x_n = x1, x_n_plus_1;

  for (i = 0; i < maxIter; ++i) {
    double den                 = f1(x_n);
    if (den > 1e-2) x_n_plus_1 = x_n - f_x_n / den;

    if (den <= 1e-2 || x0 > x_n_plus_1 || x1 < x_n_plus_1)
      x_n_plus_1 = 0.5 * (x0 + x1);

    if (fabs(x_n - x_n_plus_1) < xtol) {
      err = FZ_XTOL;
      break;
    }

    f_x_n_plus_1 = f(x_n_plus_1);
    if (fabs(f_x_n_plus_1) < ftol) {
      err = FZ_FTOL;
      break;
    }

    x_n   = x_n_plus_1;
    f_x_n = f_x_n_plus_1;
    s_n   = (f_x_n > 0);

    if (s_n == s_0)
      x0 = x_n;
    else
      x1 = x_n;
  }

  x   = x_n_plus_1;
  err = (i == maxIter ? FZ_MAX_ITER : err);
  return true;
}

#endif
