/* -*- c -*- */

/*
 * This file is for the definitions of the non-c99 functions used in ufuncs.
 * All the complex ufuncs are defined here along with a smattering of real and
 * object functions.
 */

#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#include "npy_pycompat.h"


/*
 *****************************************************************************
 **                        PYTHON OBJECT FUNCTIONS                          **
 *****************************************************************************
 */

static PyObject *
Py_square(PyObject *o)
{
    return PyNumber_Multiply(o, o);
}

static PyObject *
Py_get_one(PyObject *NPY_UNUSED(o))
{
    return PyInt_FromLong(1);
}

static PyObject *
Py_reciprocal(PyObject *o)
{
    PyObject *one = PyInt_FromLong(1);
    PyObject *result;

    if (!one) {
        return NULL;
    }
#if defined(NPY_PY3K)
    result = PyNumber_TrueDivide(one, o);
#else
    result = PyNumber_Divide(one, o);
#endif
    Py_DECREF(one);
    return result;
}

/*
 * Define numpy version of PyNumber_Power as binary function.
 */
static PyObject *
npy_ObjectPower(PyObject *x, PyObject *y)
{
    return PyNumber_Power(x, y, Py_None);
}


#if defined(NPY_PY3K)
/**begin repeat
 * #Kind = Max, Min#
 * #OP = Py_GE, Py_LE#
 */
static PyObject *
npy_Object@Kind@(PyObject *i1, PyObject *i2)
{
    PyObject *result;
    int cmp;

    cmp = PyObject_RichCompareBool(i1, i2, @OP@);
    if (cmp < 0) {
        return NULL;
    }
    if (cmp == 1) {
        result = i1;
    }
    else {
        result = i2;
    }
    Py_INCREF(result);
    return result;
}
/**end repeat**/

#else
/**begin repeat
 * #Kind = Max, Min#
 * #OP = >=, <=#
 */
static PyObject *
npy_Object@Kind@(PyObject *i1, PyObject *i2)
{
    PyObject *result;
    int cmp;

    if (PyObject_Cmp(i1, i2, &cmp) < 0) {
        return NULL;
    }
    if (cmp @OP@ 0) {
        result = i1;
    }
    else {
        result = i2;
    }
    Py_INCREF(result);
    return result;
}
/**end repeat**/
#endif


/* Emulates Python's 'a or b' behavior */
static PyObject *
npy_ObjectLogicalOr(PyObject *i1, PyObject *i2)
{
    if (i1 == NULL) {
        Py_XINCREF(i2);
        return i2;
    }
    else if (i2 == NULL) {
        Py_INCREF(i2);
        return i1;
    }
    else {
        int retcode = PyObject_IsTrue(i1);
        if (retcode == -1) {
            return NULL;
        }
        else if (retcode) {
            Py_INCREF(i1);
            return i1;
        }
        else {
            Py_INCREF(i2);
            return i2;
        }
    }
}

/* Emulates Python's 'a and b' behavior */
static PyObject *
npy_ObjectLogicalAnd(PyObject *i1, PyObject *i2)
{
    if (i1 == NULL) {
        return NULL;
    }
    else if (i2 == NULL) {
        return NULL;
    }
    else {
        int retcode = PyObject_IsTrue(i1);
        if (retcode == -1) {
            return NULL;
        }
        else if (!retcode) {
            Py_INCREF(i1);
            return i1;
        }
        else {
            Py_INCREF(i2);
            return i2;
        }
    }
}


/* Emulates Python's 'not b' behavior */
static PyObject *
npy_ObjectLogicalNot(PyObject *i1)
{
    if (i1 == NULL) {
        return NULL;
    }
    else {
        int retcode = PyObject_Not(i1);
        if (retcode == -1) {
            return NULL;
        }
        else if (retcode) {
            Py_INCREF(Py_True);
            return Py_True;
        }
        else {
            Py_INCREF(Py_False);
            return Py_False;
        }
    }
}

/*
 *****************************************************************************
 **                           COMPLEX FUNCTIONS                             **
 *****************************************************************************
 */


/*
 * Don't pass structures between functions (only pointers) because how
 * structures are passed is compiler dependent and could cause segfaults if
 * umath_ufunc_object.inc is compiled with a different compiler than an
 * extension that makes use of the UFUNC API
 */

/**begin repeat
 *
 * #ctype = npy_cfloat, npy_cdouble, npy_clongdouble#
 * #ftype = npy_float, npy_double, npy_longdouble#
 * #c = f, ,l#
 * #C = F, ,L#
 * #precision = 1,2,4#
 */

/*
 * Perform the operation  result := 1 + coef * x * result,
 * with real coefficient `coef`.
 */
#define SERIES_HORNER_TERM@c@(result, x, coef)                  \
    do {                                                        \
        nc_prod@c@((result), (x), (result));                    \
        (result)->real *= (coef);                               \
        (result)->imag *= (coef);                               \
        nc_sum@c@((result), &nc_1@c@, (result));                \
    } while(0)

/* constants */
static @ctype@ nc_1@c@ = {1., 0.};
static @ctype@ nc_half@c@ = {0.5, 0.};
static @ctype@ nc_i@c@ = {0., 1.};
static @ctype@ nc_i2@c@ = {0., 0.5};
/*
 *   static @ctype@ nc_mi@c@ = {0.0@c@, -1.0@c@};
 *   static @ctype@ nc_pi2@c@ = {NPY_PI_2@c@., 0.0@c@};
 */


static void
nc_sum@c@(@ctype@ *a, @ctype@ *b, @ctype@ *r)
{
    r->real = a->real + b->real;
    r->imag = a->imag + b->imag;
    return;
}

static void
nc_diff@c@(@ctype@ *a, @ctype@ *b, @ctype@ *r)
{
    r->real = a->real - b->real;
    r->imag = a->imag - b->imag;
    return;
}

static void
nc_neg@c@(@ctype@ *a, @ctype@ *r)
{
    r->real = -a->real;
    r->imag = -a->imag;
    return;
}

static void
nc_prod@c@(@ctype@ *a, @ctype@ *b, @ctype@ *r)
{
    @ftype@ ar=a->real, br=b->real, ai=a->imag, bi=b->imag;
    r->real = ar*br - ai*bi;
    r->imag = ar*bi + ai*br;
    return;
}

static void
nc_quot@c@(@ctype@ *a, @ctype@ *b, @ctype@ *r)
{

    @ftype@ ar=a->real, br=b->real, ai=a->imag, bi=b->imag;
    @ftype@ d = br*br + bi*bi;
    r->real = (ar*br + ai*bi)/d;
    r->imag = (ai*br - ar*bi)/d;
    return;
}

static void
nc_sqrt@c@(@ctype@ *x, @ctype@ *r)
{
    *r = npy_csqrt@c@(*x);
    return;
}

static void
nc_rint@c@(@ctype@ *x, @ctype@ *r)
{
    r->real = npy_rint@c@(x->real);
    r->imag = npy_rint@c@(x->imag);
}

static void
nc_log@c@(@ctype@ *x, @ctype@ *r)
{
    *r = npy_clog@c@(*x);
    return;
}

static void
nc_log1p@c@(@ctype@ *x, @ctype@ *r)
{
    @ftype@ l = npy_hypot@c@(x->real + 1,x->imag);
    r->imag = npy_atan2@c@(x->imag, x->real + 1);
    r->real = npy_log@c@(l);
    return;
}

static void
nc_exp@c@(@ctype@ *x, @ctype@ *r)
{
    *r = npy_cexp@c@(*x);
    return;
}

static void
nc_exp2@c@(@ctype@ *x, @ctype@ *r)
{
    @ctype@ a;
    a.real = x->real*NPY_LOGE2@c@;
    a.imag = x->imag*NPY_LOGE2@c@;
    nc_exp@c@(&a, r);
    return;
}

static void
nc_expm1@c@(@ctype@ *x, @ctype@ *r)
{
    @ftype@ a = npy_exp@c@(x->real);
    r->real = a*npy_cos@c@(x->imag) - 1.0@c@;
    r->imag = a*npy_sin@c@(x->imag);
    return;
}

static void
nc_pow@c@(@ctype@ *a, @ctype@ *b, @ctype@ *r)
{
    npy_intp n;
    @ftype@ ar = npy_creal@c@(*a);
    @ftype@ br = npy_creal@c@(*b);
    @ftype@ ai = npy_cimag@c@(*a);
    @ftype@ bi = npy_cimag@c@(*b);

    if (br == 0. && bi == 0.) {
        *r = npy_cpack@c@(1., 0.);
        return;
    }
    if (ar == 0. && ai == 0.) {
        if (br > 0 && bi == 0) {
            *r = npy_cpack@c@(0., 0.);
        }
        else {
            volatile @ftype@ tmp = NPY_INFINITY;
            /* NB: there are four complex zeros; c0 = (+-0, +-0), so that unlike
             *     for reals, c0**p, with `p` negative is in general
             *     ill-defined.
             *
             *     c0**z with z complex is also ill-defined.
             */
            *r = npy_cpack@c@(NPY_NAN, NPY_NAN);

            /* Raise invalid */
            tmp -= NPY_INFINITY;
            ar = tmp;
        }
        return;
    }
    if (bi == 0 && (n=(npy_intp)br) == br) {
        if (n == 1) {
            /* unroll: handle inf better */
            *r = npy_cpack@c@(ar, ai);
            return;
        }
        else if (n == 2) {
            /* unroll: handle inf better */
            nc_prod@c@(a, a, r);
            return;
        }
        else if (n == 3) {
            /* unroll: handle inf better */
            nc_prod@c@(a, a, r);
            nc_prod@c@(a, r, r);
            return;
        }
        else if (n > -100 && n < 100) {
            @ctype@ p, aa;
            npy_intp mask = 1;
            if (n < 0) n = -n;
            aa = nc_1@c@;
            p = npy_cpack@c@(ar, ai);
            while (1) {
                if (n & mask)
                    nc_prod@c@(&aa,&p,&aa);
                mask <<= 1;
                if (n < mask || mask <= 0) break;
                nc_prod@c@(&p,&p,&p);
            }
            *r = npy_cpack@c@(npy_creal@c@(aa), npy_cimag@c@(aa));
            if (br < 0) nc_quot@c@(&nc_1@c@, r, r);
            return;
        }
    }

    *r = npy_cpow@c@(*a, *b);
    return;
}


static void
nc_prodi@c@(@ctype@ *x, @ctype@ *r)
{
    @ftype@ xr = x->real;
    r->real = -x->imag;
    r->imag = xr;
    return;
}


static void
nc_acos@c@(@ctype@ *x, @ctype@ *r)
{
    /*
     * return nc_neg(nc_prodi(nc_log(nc_sum(x,nc_prod(nc_i,
     * nc_sqrt(nc_diff(nc_1,nc_prod(x,x))))))));
     */
    nc_prod@c@(x,x,r);
    nc_diff@c@(&nc_1@c@, r, r);
    nc_sqrt@c@(r, r);
    nc_prodi@c@(r, r);
    nc_sum@c@(x, r, r);
    nc_log@c@(r, r);
    nc_prodi@c@(r, r);
    nc_neg@c@(r, r);
    return;
}

static void
nc_acosh@c@(@ctype@ *x, @ctype@ *r)
{
    /*
     * return nc_log(nc_sum(x,
     * nc_prod(nc_sqrt(nc_sum(x,nc_1)), nc_sqrt(nc_diff(x,nc_1)))));
     */
    @ctype@ t;

    nc_sum@c@(x, &nc_1@c@, &t);
    nc_sqrt@c@(&t, &t);
    nc_diff@c@(x, &nc_1@c@, r);
    nc_sqrt@c@(r, r);
    nc_prod@c@(&t, r, r);
    nc_sum@c@(x, r, r);
    nc_log@c@(r, r);
    return;
}

static void
nc_asin@c@(@ctype@ *x, @ctype@ *r)
{
    /*
     * return nc_neg(nc_prodi(nc_log(nc_sum(nc_prod(nc_i,x),
     * nc_sqrt(nc_diff(nc_1,nc_prod(x,x)))))));
     */
    if (fabs(x->real) > 1e-3 || fabs(x->imag) > 1e-3) {
        @ctype@ a, *pa=&a;
        nc_prod@c@(x, x, r);
        nc_diff@c@(&nc_1@c@, r, r);
        nc_sqrt@c@(r, r);
        nc_prodi@c@(x, pa);
        nc_sum@c@(pa, r, r);
        nc_log@c@(r, r);
        nc_prodi@c@(r, r);
        nc_neg@c@(r, r);
    }
    else {
        /*
         * Small arguments: series expansion, to avoid loss of precision
         * asin(x) = x [1 + (1/6) x^2 [1 + (9/20) x^2 [1 + ...]]]
         *
         * |x| < 1e-3 => |rel. error| < 1e-18 (f), 1e-24, 1e-36 (l)
         */
        @ctype@ x2;
        nc_prod@c@(x, x, &x2);

        *r = nc_1@c@;
#if @precision@ >= 3
        SERIES_HORNER_TERM@c@(r, &x2, 81.0@C@/110);
        SERIES_HORNER_TERM@c@(r, &x2, 49.0@C@/72);
#endif
#if @precision@ >= 2
        SERIES_HORNER_TERM@c@(r, &x2, 25.0@C@/42);
#endif
        SERIES_HORNER_TERM@c@(r, &x2, 9.0@C@/20);
        SERIES_HORNER_TERM@c@(r, &x2, 1.0@C@/6);
        nc_prod@c@(r, x, r);
    }
    return;
}


static void
nc_asinh@c@(@ctype@ *x, @ctype@ *r)
{
    /*
     * return nc_log(nc_sum(nc_sqrt(nc_sum(nc_1,nc_prod(x,x))),x));
     */
    if (fabs(x->real) > 1e-3 || fabs(x->imag) > 1e-3) {
        nc_prod@c@(x, x, r);
        nc_sum@c@(&nc_1@c@, r, r);
        nc_sqrt@c@(r, r);
        nc_sum@c@(r, x, r);
        nc_log@c@(r, r);
    }
    else {
        /*
         * Small arguments: series expansion, to avoid loss of precision
         * asinh(x) = x [1 - (1/6) x^2 [1 - (9/20) x^2 [1 - ...]]]
         *
         * |x| < 1e-3 => |rel. error| < 1e-18 (f), 1e-24, 1e-36 (l)
         */
        @ctype@ x2;
        nc_prod@c@(x, x, &x2);

        *r = nc_1@c@;
#if @precision@ >= 3
        SERIES_HORNER_TERM@c@(r, &x2, -81.0@C@/110);
        SERIES_HORNER_TERM@c@(r, &x2, -49.0@C@/72);
#endif
#if @precision@ >= 2
        SERIES_HORNER_TERM@c@(r, &x2, -25.0@C@/42);
#endif
        SERIES_HORNER_TERM@c@(r, &x2, -9.0@C@/20);
        SERIES_HORNER_TERM@c@(r, &x2, -1.0@C@/6);
        nc_prod@c@(r, x, r);
    }
    return;
}

static void
nc_atan@c@(@ctype@ *x, @ctype@ *r)
{
    /*
     * return nc_prod(nc_i2,nc_log(nc_quot(nc_sum(nc_i,x),nc_diff(nc_i,x))));
     */
    if (fabs(x->real) > 1e-3 || fabs(x->imag) > 1e-3) {
        @ctype@ a, *pa=&a;
        nc_diff@c@(&nc_i@c@, x, pa);
        nc_sum@c@(&nc_i@c@, x, r);
        nc_quot@c@(r, pa, r);
        nc_log@c@(r,r);
        nc_prod@c@(&nc_i2@c@, r, r);
    }
    else {
        /*
         * Small arguments: series expansion, to avoid loss of precision
         * atan(x) = x [1 - (1/3) x^2 [1 - (3/5) x^2 [1 - ...]]]
         *
         * |x| < 1e-3 => |rel. error| < 1e-18 (f), 1e-24, 1e-36 (l)
         */
        @ctype@ x2;
        nc_prod@c@(x, x, &x2);

        *r = nc_1@c@;
#if @precision@ >= 3
        SERIES_HORNER_TERM@c@(r, &x2, -9.0@C@/11);
        SERIES_HORNER_TERM@c@(r, &x2, -7.0@C@/9);
#endif
#if @precision@ >= 2
        SERIES_HORNER_TERM@c@(r, &x2, -5.0@C@/7);
#endif
        SERIES_HORNER_TERM@c@(r, &x2, -3.0@C@/5);
        SERIES_HORNER_TERM@c@(r, &x2, -1.0@C@/3);
        nc_prod@c@(r, x, r);
    }
    return;
}

static void
nc_atanh@c@(@ctype@ *x, @ctype@ *r)
{
    /*
     * return nc_prod(nc_half,nc_log(nc_quot(nc_sum(nc_1,x),nc_diff(nc_1,x))));
     */
    if (fabs(x->real) > 1e-3 || fabs(x->imag) > 1e-3) {
        @ctype@ a, *pa=&a;
        nc_diff@c@(&nc_1@c@, x, r);
        nc_sum@c@(&nc_1@c@, x, pa);
        nc_quot@c@(pa, r, r);
        nc_log@c@(r, r);
        nc_prod@c@(&nc_half@c@, r, r);
    }
    else {
        /*
         * Small arguments: series expansion, to avoid loss of precision
         * atan(x) = x [1 + (1/3) x^2 [1 + (3/5) x^2 [1 + ...]]]
         *
         * |x| < 1e-3 => |rel. error| < 1e-18 (f), 1e-24, 1e-36 (l)
         */
        @ctype@ x2;
        nc_prod@c@(x, x, &x2);

        *r = nc_1@c@;
#if @precision@ >= 3
        SERIES_HORNER_TERM@c@(r, &x2, 9.0@C@/11);
        SERIES_HORNER_TERM@c@(r, &x2, 7.0@C@/9);
#endif
#if @precision@ >= 2
        SERIES_HORNER_TERM@c@(r, &x2, 5.0@C@/7);
#endif
        SERIES_HORNER_TERM@c@(r, &x2, 3.0@C@/5);
        SERIES_HORNER_TERM@c@(r, &x2, 1.0@C@/3);
        nc_prod@c@(r, x, r);
    }
    return;
}

static void
nc_cos@c@(@ctype@ *x, @ctype@ *r)
{
    @ftype@ xr=x->real, xi=x->imag;
    r->real = npy_cos@c@(xr)*npy_cosh@c@(xi);
    r->imag = -npy_sin@c@(xr)*npy_sinh@c@(xi);
    return;
}

static void
nc_cosh@c@(@ctype@ *x, @ctype@ *r)
{
    @ftype@ xr=x->real, xi=x->imag;
    r->real = npy_cos@c@(xi)*npy_cosh@c@(xr);
    r->imag = npy_sin@c@(xi)*npy_sinh@c@(xr);
    return;
}

static void
nc_log10@c@(@ctype@ *x, @ctype@ *r)
{
    nc_log@c@(x, r);
    r->real *= NPY_LOG10E@c@;
    r->imag *= NPY_LOG10E@c@;
    return;
}

static void
nc_log2@c@(@ctype@ *x, @ctype@ *r)
{
    nc_log@c@(x, r);
    r->real *= NPY_LOG2E@c@;
    r->imag *= NPY_LOG2E@c@;
    return;
}

static void
nc_sin@c@(@ctype@ *x, @ctype@ *r)
{
    @ftype@ xr=x->real, xi=x->imag;
    r->real = npy_sin@c@(xr)*npy_cosh@c@(xi);
    r->imag = npy_cos@c@(xr)*npy_sinh@c@(xi);
    return;
}

static void
nc_sinh@c@(@ctype@ *x, @ctype@ *r)
{
    @ftype@ xr=x->real, xi=x->imag;
    r->real = npy_cos@c@(xi)*npy_sinh@c@(xr);
    r->imag = npy_sin@c@(xi)*npy_cosh@c@(xr);
    return;
}

static void
nc_tan@c@(@ctype@ *x, @ctype@ *r)
{
    @ftype@ sr,cr,shi,chi;
    @ftype@ rs,is,rc,ic;
    @ftype@ d;
    @ftype@ xr=x->real, xi=x->imag;
    sr = npy_sin@c@(xr);
    cr = npy_cos@c@(xr);
    shi = npy_sinh@c@(xi);
    chi = npy_cosh@c@(xi);
    rs = sr*chi;
    is = cr*shi;
    rc = cr*chi;
    ic = -sr*shi;
    d = rc*rc + ic*ic;
    r->real = (rs*rc+is*ic)/d;
    r->imag = (is*rc-rs*ic)/d;
    return;
}

static void
nc_tanh@c@(@ctype@ *x, @ctype@ *r)
{
    @ftype@ si,ci,shr,chr;
    @ftype@ rs,is,rc,ic;
    @ftype@ d;
    @ftype@ xr=x->real, xi=x->imag;
    si = npy_sin@c@(xi);
    ci = npy_cos@c@(xi);
    shr = npy_sinh@c@(xr);
    chr = npy_cosh@c@(xr);
    rs = ci*shr;
    is = si*chr;
    rc = ci*chr;
    ic = si*shr;
    d = rc*rc + ic*ic;
    r->real = (rs*rc+is*ic)/d;
    r->imag = (is*rc-rs*ic)/d;
    return;
}

#undef SERIES_HORNER_TERM@c@

/**end repeat**/
