#pragma once

// For inclusion in ctaylor.h only!
#include "tmath.hpp"

template <class T, int Nvar, class S>
static ctaylor<T, Nvar> operator/(const S & x, const ctaylor<T, Nvar> & t) {
  ctaylor<T, Nvar> res;
#ifdef CTAYLOR_SPARSE
  res.isscalar = t.isscalar;
  if (res.isscalar) {
    res.c[0] = x / t.c[0];
  } else {
    T tmp[Nvar + 1];
    inv_expand<T, Nvar>(tmp, t.c[0]);
    for (int i = 0; i <= Nvar; i++)
      tmp[i] *= x;
    ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  }
#else
  T tmp[Nvar + 1];
  inv_expand<T, Nvar>(tmp, t.c[0]);
  for (int i = 0; i <= Nvar; i++)
    tmp[i] *= x;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
#endif
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> operator/(const ctaylor<T, Nvar> & t1,
                                  const ctaylor<T, Nvar> & t2) {
  ctaylor<T, Nvar> res;
#ifdef CTAYLOR_SPARSE
  if (t1.isscalar)
    return t1.c[0] / t2;
  else if (t2.isscalar)
    return t1 / t2.c[0];
  res.isscalar = 0;
#endif
  T tmp[Nvar + 1];
  inv_expand<T, Nvar>(tmp, t2.c[0]);
  ctaylor_rec<T, Nvar>::compose(res.c, t2.c, tmp);
  res *= t1;
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> operator/(const ctaylor<T, Nvar> & t, const T & x) {
  ctaylor<T, Nvar> tmp = t;
  tmp *= 1 / x;
  return tmp;
}

template <class T, int Nvar, class S>
static ctaylor<T, Nvar> operator/(const ctaylor<T, Nvar> & t, const S & x) {
  ctaylor<T, Nvar> tmp = t;
  tmp *= 1 / T(x);
  return tmp;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> abs(const ctaylor<T, Nvar> & t) {
  if (t.c[0] < 0)
    return -t;
  else
    return t;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> exp(const ctaylor<T, Nvar> & t) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(exp(t.c[0]));
#endif
  T tmp[Nvar + 1];
  exp_expand<T, Nvar>(tmp, t.c[0]);
  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

// exp(x)-1, but accurate for small x
template <class T, int Nvar>
static ctaylor<T, Nvar> expm1(const ctaylor<T, Nvar> & t) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(2 * exp(t.c[0] / 2) * sinh(t.c[0] / 2));
#endif
  T tmp[Nvar + 1];
  exp_expand<T, Nvar>(tmp, t.c[0]);

  // Only constant value is affected by the cancellation
  if (fabs(t.c[0]) > 1e-3)
    tmp[0] -= 1;
  else
    tmp[0] = 2 * exp(t.c[0] / 2) * sinh(t.c[0] / 2);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> log(const ctaylor<T, Nvar> & t) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(log(t.c[0]));
#endif
  T tmp[Nvar + 1];
  log_expand<T, Nvar>(tmp, t.c[0]);
  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

// We need this version with double a argument to prevent truncation
// to int.
template <class T, int Nvar>
static ctaylor<T, Nvar> pow(const ctaylor<T, Nvar> & t, const double & a) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(pow(t.c[0], a));
#endif
  T tmp[Nvar + 1];
  pow_expand<T, Nvar>(tmp, t.c[0], a);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> sqrt(const ctaylor<T, Nvar> & t) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(sqrt(t.c[0]));
#endif
  T tmp[Nvar + 1];
  sqrt_expand<T, Nvar>(tmp, t.c[0]);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> cbrt(const ctaylor<T, Nvar> & t) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(cbrt(t.c[0]));
#endif
  T tmp[Nvar + 1];
  cbrt_expand<T, Nvar>(tmp, t.c[0]);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

// Integer exponent version is analytical at t[0] = 0
// This function gets priority over the normal pow
// when the exponent is an integer, but does not force
// conversion to integer.
template <class T, int Nvar>
static ctaylor<T, Nvar> pow(const ctaylor<T, Nvar> & t, int n) {
  if (n > 0) {
    ctaylor<T, Nvar> res = t;
    while (n-- > 1)
      res *= t;
    return res;
  } else if (n < 0) {
    return pow(t, double(n));
  } else {
    ctaylor<T, Nvar> res(1);
    return res;
  }
}

template <class T, int Nvar>
static ctaylor<T, Nvar> atan(const ctaylor<T, Nvar> & t) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(atan(t.c[0]));
#endif
  T tmp[Nvar + 1];
  atan_expand<T, Nvar>(tmp, t.c[0]);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> erf(const ctaylor<T, Nvar> & t) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(erf(t.c[0]));
#endif
  T tmp[Nvar + 1];
  erf_expand<T, Nvar>(tmp, t.c[0]);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> sin(const ctaylor<T, Nvar> & t) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(sin(t.c[0]));
#endif
  T tmp[Nvar + 1];
  sin_expand<T, Nvar>(tmp, t.c[0]);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> cos(const ctaylor<T, Nvar> & t) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(cos(t.c[0]));
#endif
  T tmp[Nvar + 1];
  cos_expand<T, Nvar>(tmp, t.c[0]);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> asin(const ctaylor<T, Nvar> & t) {
  T tmp[Nvar + 1];
  asin_expand<T, Nvar>(tmp, t.c[0]);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> acos(const ctaylor<T, Nvar> & t) {
  T tmp[Nvar + 1];
  acos_expand<T, Nvar>(tmp, t.c[0]);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> asinh(const ctaylor<T, Nvar> & t) {
#ifdef CTAYLOR_SPARSE
  if (t.isscalar)
    return ctaylor<T, Nvar>(asinh(t.c[0]));
#endif
  T tmp[Nvar + 1];
  asinh_expand<T, Nvar>(tmp, t.c[0]);

  ctaylor<T, Nvar> res;
  ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
  return res;
}

/*
  The original function is unstable for small t[0] values similarly to
  the Boys function. Use an [8,8] Pade approximation when |t[0]| is
  small. This works less well but still ok in single precision.
 */
template <class T, int Nvar>
static ctaylor<T, Nvar> sqrtx_asinh_sqrtx(const ctaylor<T, Nvar> & t) {
  assert(t.c[0] > -0.5);
#ifdef CTAYLOR_SPARSE
  if (t.isscalar) {
    T sqrtx = sqrt(t.c[0]);
    return ctaylor<T, Nvar>(sqrtx * asinh(sqrtx));
  }
#endif
// Coefficients of an [8,8] Pade approximation at x = 0
#define ASINH_TABSIZE 9
  static const T P[ASINH_TABSIZE] = {0,
                                     3.510921856028398e3,
                                     1.23624388373212e4,
                                     1.734847003883674e4,
                                     1.235072285222234e4,
                                     4.691117148130619e3,
                                     9.119186273274577e2,
                                     7.815848629220836e1,
                                     1.96088643023654e0};
  static const T Q[ASINH_TABSIZE] = {3.510921856028398e3,
                                     1.29475924799926e4,
                                     1.924308297963337e4,
                                     1.474357149568687e4,
                                     6.176496729255528e3,
                                     1.379806958043824e3,
                                     1.471833349002349e2,
                                     5.666278232986776e0,
                                     2.865104054302032e-2};
  if (fabs(t.c[0]) < 0.5) {
    // Shift polys, divide and compose
    assert(Nvar < ASINH_TABSIZE);
    T tmp[Nvar + 1], pq[9];
    for (int i = 0; i < ASINH_TABSIZE; i++)
      pq[i] = Q[i];
    tfuns<T, ASINH_TABSIZE - 1>::shift(pq, t.c[0]);
    inv_expand<T, Nvar>(tmp, pq[0]);
    tfuns<T, Nvar>::compose(tmp, pq);
    for (int i = 0; i < ASINH_TABSIZE; i++)
      pq[i] = P[i];
    tfuns<T, ASINH_TABSIZE - 1>::shift(pq, t.c[0]);
    tfuns<T, Nvar>::multo(tmp, pq);
    ctaylor<T, Nvar> res;
    ctaylor_rec<T, Nvar>::compose(res.c, t.c, tmp);
    return res;
  } else {
    // This is the unstable form
    ctaylor<T, Nvar> s = sqrt(t);
    return s * asinh(s);
  }
}

template <class T, int Nvar>
static ctaylor<T, Nvar> min(const ctaylor<T, Nvar> & a, const ctaylor<T, Nvar> & b) {
  if (a <= b)
    return a;
  else
    return b;
}

template <class T, int Nvar>
static ctaylor<T, Nvar> max(const ctaylor<T, Nvar> & a, const ctaylor<T, Nvar> & b) {
  if (a > b)
    return a;
  else
    return b;
}
