/*--------------------------------------------------------------------------*\
 |                                                                          |
 |  Copyright (C) 2017                                                      |
 |                                                                          |
 |         , __                 , __                                        |
 |        /|/  \               /|/  \                                       |
 |         | __/ _   ,_         | __/ _   ,_                                |
 |         |   \|/  /  |  |   | |   \|/  /  |  |   |                        |
 |         |(__/|__/   |_/ \_/|/|(__/|__/   |_/ \_/|/                       |
 |                           /|                   /|                        |
 |                           \|                   \|                        |
 |                                                                          |
 |      Enrico Bertolazzi                                                   |
 |      Dipartimento di Ingegneria Industriale                              |
 |      Universita` degli Studi di Trento                                   |
 |      email: enrico.bertolazzi@unitn.it                                   |
 |                                                                          |
\*--------------------------------------------------------------------------*/

#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
#ifdef __clang__
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wvla-extension"
#pragma clang diagnostic ignored "-Wvla"
#pragma clang diagnostic ignored "-Wunused-function"
#pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
#endif

#include "PolynomialRoots.hh"

#include <cmath>
#include <iostream>
#include <algorithm>
#include <limits>

#ifndef DOXYGEN_SHOULD_SKIP_THIS

namespace PolynomialRoots {

  using  std::abs;
  static valueType const machepsi = std::numeric_limits<valueType>::epsilon();

  indexType
  Quadratic::getRealRoots( valueType r[] ) const {
    indexType nr = 0;
    if ( !cplx ) {
      r[nr++] = r0;
      if ( nrts > 1 ) r[nr++] = r1;
    }
    return nr;
  }

  indexType
  Quadratic::getPositiveRoots( valueType r[] ) const {
    indexType nr = 0;
    if ( !cplx ) {
      if ( nrts > 0 && r0 > 0 ) r[nr++] = r0;
      if ( nrts > 1 && r1 > 0 ) r[nr++] = r1;
    }
    return nr;
  }

  indexType
  Quadratic::getNegativeRoots( valueType r[] ) const {
    indexType nr = 0;
    if ( !cplx ) {
      if ( nrts > 0 && r0 < 0 ) r[nr++] = r0;
      if ( nrts > 1 && r1 < 0 ) r[nr++] = r1;
    }
    return nr;
  }

  indexType
  Quadratic::getRootsInRange( valueType a, valueType b, valueType r[] ) const {
    indexType nr = 0;
    if ( !cplx ) {
      if ( nrts > 0 && r0 >= a && r0 <= b ) r[nr++] = r0;
      if ( nrts > 1 && r1 >= a && r1 <= b ) r[nr++] = r1;
    }
    return nr;
  }

  indexType
  Quadratic::getRootsInOpenRange( valueType a, valueType b, valueType r[] ) const {
    indexType nr = 0;
    if ( !cplx ) {
      if ( nrts > 0 && r0 > a && r0 < b ) r[nr++] = r0;
      if ( nrts > 1 && r1 > a && r1 < b ) r[nr++] = r1;
    }
    return nr;
  }

  void
  Quadratic::eval( valueType x, valueType & p, valueType & dp ) const {
    valueType const & A = ABC[0];
    valueType const & B = ABC[1];
    valueType const & C = ABC[2];
    if ( std::abs(x) > 1 ) {
      valueType z  = 1/x;
      valueType x2 = x*x;
      p = ((C*z+B)*z+A)*x2;
    } else {
      p = (A*x+B)*x+C;
    }
    dp = 2*A*x+B;
  }

  /*\
   *  Calculate the zeros of the quadratic a*z^2 + b*z + c.
   *  The quadratic formula, modified to avoid overflow, is used
   *  to find the larger zero if the zeros are real and both
   *  are complex. The smaller real zero is found directly from
   *  the product of the zeros c/a.
  \*/

  void
  Quadratic::findRoots() {
    valueType const & A = ABC[0];
    valueType const & B = ABC[1];
    valueType const & C = ABC[2];

    r0 = r1 = 0;
    nrts = 0;
    cplx = dblx = false;

    if ( isZero(A) ) { // less than two roots b*z + c = 0
      if ( !isZero(B) ) { nrts = 1; r0 = -C/B; }
    } else if ( isZero(C) ) { // a*z^2 + b*z  = 0
      nrts = 2;
      dblx = isZero(B);
      if ( !dblx ) {
        r0 = -B/A;
        if ( r0 < 0 ) std::swap(r0,r1);
      }
    } else { // Compute discriminant avoiding overflow.
      valueType hb    = B/2; // b now b/2
      valueType abs_b = std::abs(hb);
      valueType abs_c = std::abs(C);
      valueType e, d;
      if ( abs_b < abs_c ) {
        e = C < 0 ? -A : A;
        e = (hb*hb)-e*abs_c;
        d = std::sqrt(std::abs(e));
      } else {
        e = 1 - (A/hb)*(C/hb);
        d = std::sqrt(std::abs(e))*abs_b;
      }
      nrts = 2;
      cplx = e < 0;
      if ( cplx ) {
        // complex conjugate zeros
        r0 = -hb/A;         // real part
        r1 = std::abs(d/A); // immaginary part
      } else {
        // real zeros
        dblx = isZero(d);
        if ( dblx ) {
          r0 = r1 = -hb/A;
        } else {
          if ( hb >= 0 ) d = -d;
          r0 = (d-hb)/A;
          //r1 = (-d-hb)/a;
          if ( !isZero(r0) ) r1 = (C/r0)/A;
          if ( r0 > r1 ) std::swap(r0,r1); // order roots
        }
      }
    }
  }

  void
  Quadratic::info( std::ostream & s ) const {
    valueType const & A = ABC[0];
    valueType const & B = ABC[1];
    valueType const & C = ABC[2];
    s << "\npoly A=" << A << " B=" << B << " C=" << C
      << "\nn. roots = " << nrts
      << "\ncomplex  = " << (cplx?"YES":"NO")
      << "\ndouble   = " << (dblx?"YES":"NO");
    if ( cplx ) {
      s << "\nx0 = (" << r0 << "," <<  r1 << ')'
        << "\nx1 = (" << r0 << "," << -r1 << ')';
    } else if ( dblx ) {
      s << "\nx0 = x1 = " << r0;
    } else if ( nrts == 1 ) {
      s << "\nx0 = " << r0;
    } else if ( nrts == 2 ) {
      s << "\nx0 = " << r0
        << "\nx1 = " << r1;
    }
    s << '\n';
  }

  bool
  Quadratic::check( std::ostream & s ) const {
    valueType const & A = ABC[0];
    valueType const & B = ABC[1];
    valueType const & C = ABC[2];
    bool ok = true;
    valueType epsi = 10 * ( std::abs(A) +
                            std::abs(B) +
                            std::abs(C) ) * machepsi;
    if ( cplx ) {
      valueType z0 = std::abs(eval( root0() ));
      valueType z1 = std::abs(eval( root1() ));
      s << "|p(r0)| = " << z0
        << "\n|p(r1)| = " << z1
        << '\n';
      ok = z0 < epsi && z1 < epsi;
    } else if ( nrts == 1 ) {
      valueType z0 = eval( real_root0() );
      s << "p(r0) = " << z0  << '\n';
      ok = std::abs(z0) < epsi;
    } else if ( nrts == 2 ) {
      valueType z0 = eval( real_root0() );
      valueType z1 = eval( real_root1() );
      s << "p(r0) = " << z0
        << "\np(r1) = " << z1
        << '\n';
      ok = std::abs(z0) < epsi && std::abs(z1) < epsi;
    }
    return ok;
  }

}

#endif

// EOF: PolynomialRoots-1-Quadratic.cc
