/***************************************************************************
* Copyright (c) 2016, Johan Mabille and Sylvain Corlay                     *
*                                                                          *
* Distributed under the terms of the BSD 3-Clause License.                 *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#ifndef XSIMD_HYPERBOLIC_HPP
#define XSIMD_HYPERBOLIC_HPP

#include <type_traits>

#include "xsimd_exponential.hpp"
#include "xsimd_fp_sign.hpp"
#include "xsimd_logarithm.hpp"
#include "xsimd_power.hpp"

namespace xsimd
{

    template <class B>
    batch_type_t<B> average(const simd_base<B>& x1, const simd_base<B>& x2);

    /**
     * Computes the hyperbolic sine of the batch \c x.
     * @param x batch of floating point values.
     * @return the hyperbolic sine of \c x.
     */
    template <class B>
    batch_type_t<B> sinh(const simd_base<B>& x);

    /**
     * Computes the hyperbolic cosine of the batch \c x.
     * @param x batch of floating point values.
     * @return the hyperbolic cosine of \c x.
     */
    template <class B>
    batch_type_t<B> cosh(const simd_base<B>& x);

    /**
     * Computes the hyperbolic tangent of the batch \c x.
     * @param x batch of floating point values.
     * @return the hyperbolic tangent of \c x.
     */
    template <class B>
    batch_type_t<B> tanh(const simd_base<B>& x);

    /**
     * Computes the inverse hyperbolic sine of the batch \c x.
     * @param x batch of floating point values.
     * @return the inverse hyperbolic sine of \c x.
     */
    template <class B>
    batch_type_t<B> asinh(const simd_base<B>& x);

    /**
     * Computes the inverse hyperbolic cosine of the batch \c x.
     * @param x batch of floating point values.
     * @return the inverse hyperbolic cosine of \c x.
     */
    template <class B>
    batch_type_t<B> acosh(const simd_base<B>& x);

    /**
     * Computes the inverse hyperbolic tangent of the batch \c x.
     * @param x batch of floating point values.
     * @return the inverse hyperbolic tangent of \c x.
     */
    template <class B>
    batch_type_t<B> atanh(const simd_base<B>& x);

    /***************************
     * average  implementation *
     ***************************/

    namespace detail
    {
        /* origin: boost/simd/arch/common/simd/function/average.hpp */
        /*
         * ====================================================
         * copyright 2016 NumScale SAS
         *
         * Distributed under the Boost Software License, Version 1.0.
         * (See copy at http://boost.org/LICENSE_1_0.txt)
         * ====================================================
         */

        template <class B, bool cond = std::is_floating_point<typename B::value_type>::value>
        struct average_impl
        {
            static inline B compute(const B& x1, const B& x2)
            {
                return (x1 & x2) + ((x1 ^ x2) >> 1);
            }
        };

        template <class B>
        struct average_impl<B, true>
        {
            static inline B compute(const B& x1, const B& x2)
            {
                return fma(x1, B(0.5), x2 * B(0.5));
            }
        };
    }

    template <class B>
    inline batch_type_t<B> average(const simd_base<B>& x1, const simd_base<B>& x2)
    {
        return detail::average_impl<batch_type_t<B>>::compute(x1(), x2());
    }

    /***********************
     * sinh implementation *
     ***********************/

    namespace detail
    {
        /* origin: boost/simd/arch/common/detail/generic/sinh_kernel.hpp */
        /*
         * ====================================================
         * copyright 2016 NumScale SAS
         *
         * Distributed under the Boost Software License, Version 1.0.
         * (See copy at http://boost.org/LICENSE_1_0.txt)
         * ====================================================
         */

        template <class B, class T = typename B::value_type>
        struct sinh_kernel_impl;

        template <class B>
        struct sinh_kernel_impl<B, float>
        {
            static inline B compute(const B& x)
            {
                B sqrx = x * x;
                return horner<B,
                              0x3f800000,  // 1.0f
                              0x3e2aaacc,  // 1.66667160211E-1f
                              0x3c087bbe,  // 8.33028376239E-3f
                              0x39559e2f  // 2.03721912945E-4f
                              >(sqrx) *
                    x;
            }
        };

        template <class B>
        struct sinh_kernel_impl<B, double>
        {
            static inline B compute(const B& x)
            {
                B sqrx = x * x;
                return fma(x, (horner<B,
                                      0xc115782bdbf6ab05ull,  //  -3.51754964808151394800E5
                                      0xc0c694b8c71d6182ull,  //  -1.15614435765005216044E4,
                                      0xc064773a398ff4feull,  //  -1.63725857525983828727E2,
                                      0xbfe9435fe8bb3cd6ull  //  -7.89474443963537015605E-1
                                      >(sqrx) /
                               horner1<B,
                                       0xc1401a20e4f90044ull,  //  -2.11052978884890840399E6
                                       0x40e1a7ba7ed72245ull,  //   3.61578279834431989373E4,
                                       0xc0715b6096e96484ull  //  -2.77711081420602794433E2,
                                       >(sqrx)) *
                               sqrx,
                           x);
            }
        };

        template <class B>
        struct sinh_kernel
        {
            static inline B compute(const B& a)
            {
                using b_type = B;
                b_type half = b_type(0.5);
                b_type x = abs(a);
                auto lt1 = x < b_type(1.);
                b_type bts = bitofsign(a);
                b_type z(0.);
                if (any(lt1))
                {
                    z = sinh_kernel_impl<b_type>::compute(x);
                    if (all(lt1))
                        return z ^ bts;
                }
                auto test1 = x >(maxlog<b_type>() - log_2<b_type>());
                b_type fac = select(test1, half, b_type(1.));
                b_type tmp = exp(x * fac);
                b_type tmp1 = half * tmp;
                b_type r = select(test1, tmp1 * tmp, tmp1 - half / tmp);
                return select(lt1, z, r) ^ bts;
            }
        };
    }

    /* origin: boost/simd/arch/common/simd/function/sinh.hpp */
    /*
     * ====================================================
     * copyright 2016 NumScale SAS
     *
     * Distributed under the Boost Software License, Version 1.0.
     * (See copy at http://boost.org/LICENSE_1_0.txt)
     * ====================================================
     */
    template <class B>
    inline batch_type_t<B> sinh(const simd_base<B>& a)
    {
        return detail::sinh_kernel<batch_type_t<B>>::compute(a());
    }

    /***********************
     * cosh implementation *
     ***********************/

    /* origin: boost/simd/arch/common/simd/function/cosh.hpp */
    /*
      * ====================================================
      * copyright 2016 NumScale SAS
      *
      * Distributed under the Boost Software License, Version 1.0.
      * (See copy at http://boost.org/LICENSE_1_0.txt)
      * ====================================================
      */

    namespace detail
    {
        template <class B>
        struct cosh_kernel
        {
            static inline B compute(const B& a)
            {
                using b_type = B;
                b_type x = abs(a);
                auto test1 = x > (maxlog<b_type>() - log_2<b_type>());
                b_type fac = select(test1, b_type(0.5), b_type(1.));
                b_type tmp = exp(x * fac);
                b_type tmp1 = b_type(0.5) * tmp;
                return select(test1, tmp1 * tmp, average(tmp, b_type(1.) / tmp));
            }
        };
    }

    template <class B>
    inline batch_type_t<B> cosh(const simd_base<B>& a)
    {
        return detail::cosh_kernel<batch_type_t<B>>::compute(a());
    }

    /***********************
     * tanh implementation *
     ***********************/

    namespace detail
    {
        /* origin: boost/simd/arch/common/detail/generic/tanh_kernel.hpp */
        /*
         * ====================================================
         * copyright 2016 NumScale SAS
         *
         * Distributed under the Boost Software License, Version 1.0.
         * (See copy at http://boost.org/LICENSE_1_0.txt)
         * ====================================================
         */
        template <class B, class T = typename B::value_type>
        struct tanh_kernel_impl;

        template <class B>
        struct tanh_kernel_impl<B, float>
        {
            static inline B tanh(const B& x)
            {
                B sqrx = x * x;
                return fma(horner<B,
                                  0xbeaaaa99,  //    -3.33332819422E-1F
                                  0x3e088393,  //    +1.33314422036E-1F
                                  0xbd5c1e2d,  //    -5.37397155531E-2F
                                  0x3ca9134e,  //    +2.06390887954E-2F
                                  0xbbbaf0ea  //    -5.70498872745E-3F
                                  >(sqrx) *
                               sqrx,
                           x, x);
            }

            static inline B cotanh(const B& x)
            {
                return B(1.) / tanh(x);
            }
        };

        template <class B>
        struct tanh_kernel_impl<B, double>
        {
            static inline B tanh(const B& x)
            {
                B sqrx = x * x;
                return fma(sqrx * p(sqrx) / q(sqrx), x, x);
            }

            static inline B cotanh(const B& x)
            {
                B sqrx = x * x;
                B qval = q(sqrx);
                return qval / (x * fma(p(sqrx), sqrx, qval));
            }

            static inline B p(const B& x)
            {
                return horner<B,
                              0xc0993ac030580563,  // -1.61468768441708447952E3
                              0xc058d26a0e26682d,  // -9.92877231001918586564E1,
                              0xbfeedc5baafd6f4b  // -9.64399179425052238628E-1
                              >(x);
            }

            static inline B q(const B& x)
            {
                return horner1<B,
                               0x40b2ec102442040c,  //  4.84406305325125486048E3
                               0x40a176fa0e5535fa,  //  2.23548839060100448583E3,
                               0x405c33f28a581B86  //  1.12811678491632931402E2,
                               >(x);
            }
        };

        template <class B>
        struct tanh_kernel
        {
            static inline B compute(const B& a)
            {
                using b_type = B;
                b_type one(1.);
                b_type x = abs(a);
                auto test = x < (b_type(5.) / b_type(8.));
                b_type bts = bitofsign(a);
                b_type z = one;
                if (any(test))
                {
                    z = tanh_kernel_impl<b_type>::tanh(x);
                    if (all(test))
                        return z ^ bts;
                }
                b_type r = fma(b_type(-2.), one / (one + exp(x + x)), one);
                return select(test, z, r) ^ bts;
            }
        };
    }

    /* origin: boost/simd/arch/common/simd/function/tanh.hpp */
    /*
     * ====================================================
     * copyright 2016 NumScale SAS
     *
     * Distributed under the Boost Software License, Version 1.0.
     * (See copy at http://boost.org/LICENSE_1_0.txt)
     * ====================================================
     */

    template <class B>
    inline batch_type_t<B> tanh(const simd_base<B>& a)
    {
        return detail::tanh_kernel<batch_type_t<B>>::compute(a());
    }

    /***********************
     * sinh implementation *
     ***********************/

    namespace detail
    {
        /* origin: boost/simd/arch/common/simd/function/asinh.hpp */
        /*
         * ====================================================
         * copyright 2016 NumScale SAS
         *
         * Distributed under the Boost Software License, Version 1.0.
         * (See copy at http://boost.org/LICENSE_1_0.txt)
         * ====================================================
         */

        template <class B, class T = typename B::value_type>
        struct asinh_kernel;

        template <class B>
        struct asinh_kernel<B, float>
        {
            static inline B compute(const B& a)
            {
                B x = abs(a);
                auto lthalf = x < B(0.5);
                B x2 = x * x;
                B bts = bitofsign(a);
                B z(0.);
                if (any(lthalf))
                {
                    z = horner<B,
                               0x3f800000,
                               0xbe2aa9ad,
                               0x3d9949b1,
                               0xbd2ee581,
                               0x3ca4d6e6>(x2) *
                        x;
                    if (all(lthalf))
                        return z ^ bts;
                }
                B tmp = select(x > oneosqrteps<B>(), x, average(x, hypot(B(1.), x)));
#ifndef XSIMD_NO_NANS
                return select(xsimd::isnan(a), nan<B>(), select(lthalf, z, log(tmp) + log_2<B>()) ^ bts);
#else
                return select(lthalf, z, log(tmp) + log_2<B>()) ^ bts;
#endif
            }
        };

        template <class B>
        struct asinh_kernel<B, double>
        {
            static inline B compute(const B& a)
            {
                B x = abs(a);
                auto test = x > oneosqrteps<B>();
                B z = select(test, x - B(1.), x + x * x / (B(1.) + hypot(B(1.), x)));
#ifndef XSIMD_NO_INFINITIES
                z = select(x == infinity<B>(), x, z);
#endif
                B l1pz = log1p(z);
                z = select(test, l1pz + log_2<B>(), l1pz);
                return bitofsign(a) ^ z;
            }
        };
    }

    template <class B>
    inline batch_type_t<B> asinh(const simd_base<B>& x)
    {
        return detail::asinh_kernel<batch_type_t<B>>::compute(x());
    }

    /************************
     * acosh implementation *
     ************************/

    /* origin: boost/simd/arch/common/simd/function/acosh.hpp */
    /*
     * ====================================================
     * copyright 2016 NumScale SAS
     *
     * Distributed under the Boost Software License, Version 1.0.
     * (See copy at http://boost.org/LICENSE_1_0.txt)
     * ====================================================
     */

    namespace detail
    {
        template <class B>
        struct acosh_kernel
        {
            static inline B compute(const B& a)
            {
                using b_type = B;
                b_type x = a - b_type(1.);
                auto test = x > oneotwoeps<b_type>();
                b_type z = select(test, a, x + sqrt(x + x + x * x));
                b_type l1pz = log1p(z);
                return select(test, l1pz + log_2<b_type>(), l1pz);
            }
        };
    }

    template <class B>
    inline batch_type_t<B> acosh(const simd_base<B>& a)
    {
        return detail::acosh_kernel<batch_type_t<B>>::compute(a());
    }

    /************************
     * atanh implementation *
     ************************/

    /* origin: boost/simd/arch/common/simd/function/acosh.hpp */
    /*
     * ====================================================
     * copyright 2016 NumScale SAS
     *
     * Distributed under the Boost Software License, Version 1.0.
     * (See copy at http://boost.org/LICENSE_1_0.txt)
     * ====================================================
     */

    namespace detail
    {
        template <class B>
        struct atanh_kernel
        {
            static inline B compute(const B& a)
            {
                using b_type = B;
                b_type x = abs(a);
                b_type t = x + x;
                b_type z = b_type(1.) - x;
                auto test = x < b_type(0.5);
                b_type tmp = select(test, x, t) / z;
                return bitofsign(a) ^ (b_type(0.5) * log1p(select(test, fma(t, tmp, t), tmp)));
            }
        };
    }

    template <class B>
    inline batch_type_t<B> atanh(const simd_base<B>& a)
    {
        return detail::atanh_kernel<batch_type_t<B>>::compute(a());
    }
}

#endif
