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

#ifndef XSIMD_MATH_COMPLEX_HPP
#define XSIMD_MATH_COMPLEX_HPP

#include "../types/xsimd_complex_base.hpp"
#include "xsimd_basic_math.hpp"
#include "xsimd_exponential.hpp"
#include "xsimd_hyperbolic.hpp"
#include "xsimd_logarithm.hpp"
#include "xsimd_power.hpp"
#include "xsimd_trigonometric.hpp"

namespace xsimd
{
    template <class X>
    real_batch_type_t<X> real(const simd_base<X>& z);

    template <class X>
    real_batch_type_t<X> imag(const simd_base<X>& z);

    template <class X>
    real_batch_type_t<X> arg(const simd_base<X>& z);

    template <class X>
    batch_type_t<X> conj(const simd_base<X>& z);

    template <class X>
    real_batch_type_t<X> norm(const simd_base<X>& rhs);

    template <class X>
    batch_type_t<X> proj(const simd_base<X>& rhs);

    namespace detail
    {
        /********
         * sign *
         ********/

        template <class T, std::size_t N>
        inline batch<T, N> sign_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            using r_type = typename b_type::real_batch;
            auto rz = z.real();
            auto iz = z.imag();
            return select(rz != r_type(0.),
                          b_type(sign(rz)),
                          b_type(sign(iz)));
        }

        template <class T, std::size_t N>
        struct sign_impl<batch<std::complex<T>, N>, false>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return sign_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct sign_impl<batch<xtl::xcomplex<T, T, i3ec>, N>, false>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return sign_complex_impl(z);
            }
        };
#endif

        /*******
         * exp *
         *******/

        template <class T, std::size_t N>
        inline batch<T, N> exp_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            using r_type = typename b_type::real_batch;
            r_type icos, isin;
            sincos(z.imag(), isin, icos);
            return exp(z.real()) * batch<T, N>(icos, isin);
        }

        template <class T, std::size_t N>
        struct exp_kernel<batch<std::complex<T>, N>, exp_tag, std::complex<T>>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return exp_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct exp_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>, exp_tag, xtl::xcomplex<T, T, i3ec>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return exp_complex_impl(z);
            }
        };
#endif

        /*********
         * expm1 *
         *********/

        template <class T, std::size_t N>
        inline batch<T, N> expm1_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            using r_type = typename b_type::real_batch;
            r_type isin = sin(z.imag());
            r_type rem1 = expm1(z.real());
            r_type re = rem1 + r_type(1.);
            r_type si = sin(z.imag() * r_type(0.5));
            return batch<T, N>(rem1 - r_type(2.) * re * si * si, re * isin);
        }

        template <class T, std::size_t N>
        struct expm1_kernel<batch<std::complex<T>, N>, std::complex<T>>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return expm1_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct expm1_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>, xtl::xcomplex<T, T, i3ec>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return expm1_complex_impl(z);
            }
        };
#endif

        /*******
         * log *
         *******/

        template <class T, std::size_t N>
        inline batch<T, N> log_complex_impl(const batch<T, N>& z)
        {
            return batch<T, N>(log(abs(z)), atan2(z.imag(), z.real()));
        }

        template <class T, std::size_t N>
        struct log_kernel<batch<std::complex<T>, N>, std::complex<T>>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return log_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct log_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>, xtl::xcomplex<T, T, i3ec>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return log_complex_impl(z);
            }
        };
#endif

        /*********************
         * logN_complex_impl *
         *********************/

        template <class T, std::size_t N>
        inline batch<T, N> logN_complex_impl(const batch<T, N>& z, typename batch<T, N>::real_value_type base)
        {
            using b_type = batch<T, N>;
            using rv_type = typename b_type::real_value_type;
            return log(z) / b_type(rv_type(base));
        }

        /********
         * log2 *
         ********/

        template <class T, std::size_t N>
        struct log2_kernel<batch<std::complex<T>, N>, std::complex<T>>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return logN_complex_impl(z, std::log(2));
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct log2_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>, xtl::xcomplex<T, T, i3ec>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return logN_complex_impl(z, std::log(2));
            }
        };
#endif

        /*********
         * log10 *
         *********/

        template <class T, std::size_t N>
        struct log10_kernel<batch<std::complex<T>, N>, std::complex<T>>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return logN_complex_impl(z, std::log(10));
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct log10_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>, xtl::xcomplex<T, T, i3ec>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return logN_complex_impl(z, std::log(10));
            }
        };
#endif

        /*********
         * log1p *
         *********/

        template <class T, std::size_t N>
        inline batch<T, N> log1p_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            using r_type = typename b_type::real_batch;
            b_type u = b_type(1.) + z;
            b_type logu = log(u);
            return select(u == b_type(1.),
                          z,
                          select(u.real() <= r_type(0.),
                                 logu,
                                 logu * z / (u - b_type(1.))));
        }

        template <class T, std::size_t N>
        struct log1p_kernel<batch<std::complex<T>, N>, std::complex<T>>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return log1p_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct log1p_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>, xtl::xcomplex<T, T, i3ec>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return log1p_complex_impl(z);
            }
        };
#endif

        /*******
         * pow *
         *******/

        template <class T, std::size_t N>
        inline batch<T, N> pow_complex_impl(const batch<T, N>& a, const batch<T, N>& z)
        {
            using cplx_batch = batch<T, N>;
            using real_batch = typename cplx_batch::real_batch;
            real_batch absa = abs(a);
            real_batch arga = arg(a);
            real_batch x = z.real();
            real_batch y = z.imag();
            real_batch r = pow(absa, x);
            real_batch theta = x * arga;
            real_batch ze = zero<real_batch>();
            auto cond = (y == ze);
            r = select(cond, r, r * exp(-y * arga));
            theta = select(cond, theta, theta + y * log(absa));
            return select(absa == ze, cplx_batch(ze), cplx_batch(r * cos(theta), r * sin(theta)));
        }

        template <class T, std::size_t N>
        struct pow_kernel<batch<std::complex<T>, N>>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& a, const batch_type& z)
            {
                return pow_complex_impl(a, z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct pow_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& a, const batch_type& z)
            {
                return pow_complex_impl(a, z);
            }
        };
#endif

        /*********
         * trigo *
         *********/

        template <class T, std::size_t N>
        inline void sincos_complex_impl(const batch<T, N>& z, batch<T, N>& si, batch<T, N>& co)
        {
            using b_type = batch<T, N>;
            using r_type = typename b_type::real_batch;
            r_type rcos = cos(z.real());
            r_type rsin = sin(z.real());
            r_type icosh = cosh(z.imag());
            r_type isinh = sinh(z.imag());
            si = b_type(rsin * icosh, rcos * isinh);
            co = b_type(rcos * icosh, -rsin * isinh);
        }

        template <class T, std::size_t N>
        inline batch<T, N> sin_complex_impl(const batch<T, N>& z)
        {
            return batch<T, N>(sin(z.real()) * cosh(z.imag()), cos(z.real()) * sinh(z.imag()));
        }

        template <class T, std::size_t N>
        inline batch<T, N> cos_complex_impl(const batch<T, N>& z)
        {
            return batch<T, N>(cos(z.real()) * cosh(z.imag()), -sin(z.real()) * sinh(z.imag()));
        }

        template <class T, std::size_t N>
        inline batch<T, N> tan_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            using r_type = typename b_type::real_batch;
            r_type d = cos(2 * z.real()) + cosh(2 * z.imag());
            b_type winf(infinity<r_type>(), infinity<r_type>());
            r_type wreal = sin(2 * z.real()) / d;
            r_type wimag = sinh(2 * z.imag());
            b_type wres = select(xsimd::isinf(wimag), b_type(wreal, r_type(1.)), b_type(wreal, wimag / d));
            return select(d == r_type(0.), winf, wres);
        }

        template <class T, std::size_t N>
        struct trigo_kernel<batch<std::complex<T>, N>>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type sin(const batch_type& z)
            {
                return sin_complex_impl(z);
            }

            static inline batch_type cos(const batch_type& z)
            {
                return cos_complex_impl(z);
            }

            static inline batch_type tan(const batch_type& z)
            {
                return tan_complex_impl(z);
            }

            static inline void sincos(const batch_type& z, batch_type& si, batch_type& co)
            {
                return sincos_complex_impl(z, si, co);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct trigo_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type sin(const batch_type& z)
            {
                return sin_complex_impl(z);
            }

            static inline batch_type cos(const batch_type& z)
            {
                return cos_complex_impl(z);
            }

            static inline batch_type tan(const batch_type& z)
            {
                return tan_complex_impl(z);
            }

            static inline void sincos(const batch_type& z, batch_type& si, batch_type& co)
            {
                return sincos_complex_impl(z, si, co);
            }
        };
#endif

        /************
         * invtrigo *
         ************/

        template <class T, std::size_t N>
        batch<T, N> asin_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            using r_type = typename b_type::real_batch;

            r_type x = z.real();
            r_type y = z.imag();

            b_type ct(-y, x);
            b_type zz(r_type(1.) - (x - y) * (x + y), -2 * x * y);
            zz = log(ct + sqrt(zz));
            b_type resg(zz.imag(), -zz.real());

            return select(y == r_type(0.),
                          select(fabs(x) > r_type(1.),
                                 b_type(pio2<r_type>(), r_type(0.)),
                                 b_type(asin(x), r_type(0.))),
                          resg);
        }

        template <class T, std::size_t N>
        batch<T, N> acos_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            using r_type = typename b_type::real_batch;
            b_type tmp = asin_complex_impl(z);
            return b_type(pio2<r_type>() - tmp.real(), -tmp.imag());
        }

        template <class T, std::size_t N>
        batch<T, N> atan_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            using r_type = typename b_type::real_batch;
            r_type x = z.real();
            r_type y = z.imag();
            r_type x2 = x * x;
            r_type one = r_type(1.);
            r_type a = one - x2 - (y * y);
            r_type w = 0.5 * atan2(2. * x, a);
            r_type num = y + one;
            num = x2 + num * num;
            r_type den = y - one;
            den = x2 + den * den;
            b_type res = select((x == r_type(0.)) && (y == r_type(1.)),
                                b_type(r_type(0.), infinity<r_type>()),
                                b_type(w, 0.25 * log(num / den)));
            return res;
        }

        template <class T, std::size_t N>
        struct invtrigo_kernel<batch<std::complex<T>, N>>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type asin(const batch_type& z)
            {
                return asin_complex_impl(z);
            }

            static inline batch_type acos(const batch_type& z)
            {
                return acos_complex_impl(z);
            }

            static inline batch_type atan(const batch_type& z)
            {
                return atan_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct invtrigo_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type asin(const batch_type& z)
            {
                return asin_complex_impl(z);
            }

            static inline batch_type acos(const batch_type& z)
            {
                return acos_complex_impl(z);
            }

            static inline batch_type atan(const batch_type& z)
            {
                return atan_complex_impl(z);
            }
        };
#endif

        /********
         * sinh *
         ********/

        template <class T, std::size_t N>
        inline batch<T, N> sinh_complex_impl(const batch<T, N>& z)
        {
            auto x = z.real();
            auto y = z.imag();
            return batch<T, N>(sinh(x) * cos(y), cosh(x) * sin(y));
        }

        template <class T, std::size_t N>
        struct sinh_kernel<batch<std::complex<T>, N>>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return sinh_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct sinh_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return sinh_complex_impl(z);
            }
        };
#endif

        /********
         * cosh *
         ********/

        template <class T, std::size_t N>
        inline batch<T, N> cosh_complex_impl(const batch<T, N>& z)
        {
            auto x = z.real();
            auto y = z.imag();
            return batch<T, N>(cosh(x) * cos(y), sinh(x) * sin(y));
        }

        template <class T, std::size_t N>
        struct cosh_kernel<batch<std::complex<T>, N >>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return cosh_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct cosh_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return cosh_complex_impl(z);
            }
        };
#endif

        /********
         * tanh *
         ********/

        template <class T, std::size_t N>
        inline batch<T, N> tanh_complex_impl(const batch<T, N>& z)
        {
            using rvt = typename batch<T, N>::real_value_type;
            using real_batch = typename batch<T, N>::real_batch;
            auto x = z.real();
            auto y = z.imag();
            real_batch two = real_batch(rvt(2));
            auto d = cosh(two * x) + cos(two * y);
            return batch<T, N>(sinh(two * x) / d, sin(two * y) / d);
        }

        template <class T, std::size_t N>
        struct tanh_kernel<batch<std::complex<T>, N >>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return tanh_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct tanh_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return tanh_complex_impl(z);
            }
        };
#endif

        /*********
         * asinh *
         *********/

        template <class T, std::size_t N>
        inline batch<T, N> asinh_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            b_type w = asin(b_type(-z.imag(), z.real()));
            w = b_type(w.imag(), -w.real());
            return w;
        }

        template <class T, std::size_t N>
        struct asinh_kernel<batch<std::complex<T>, N >>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return asinh_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct asinh_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return asinh_complex_impl(z);
            }
        };
#endif

        /*********
         * acosh *
         *********/

        template <class T, std::size_t N>
        inline batch<T, N> acosh_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            b_type w = acos(z);
            w = b_type(-w.imag(), w.real());
            return w;
        }

        template <class T, std::size_t N>
        struct acosh_kernel<batch<std::complex<T>, N >>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return acosh_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct acosh_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return acosh_complex_impl(z);
            }
        };
#endif

        /*********
         * atanh *
         *********/

        template <class T, std::size_t N>
        inline batch<T, N> atanh_complex_impl(const batch<T, N>& z)
        {
            using b_type = batch<T, N>;
            b_type w = atan(b_type(-z.imag(), z.real()));
            w = b_type(w.imag(), -w.real());
            return w;
        }

        template <class T, std::size_t N>
        struct atanh_kernel<batch<std::complex<T>, N >>
        {
            using batch_type = batch<std::complex<T>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return atanh_complex_impl(z);
            }
        };

#ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct atanh_kernel<batch<xtl::xcomplex<T, T, i3ec>, N>>
        {
            using batch_type = batch<xtl::xcomplex<T, T, i3ec>, N>;

            static inline batch_type compute(const batch_type& z)
            {
                return atanh_complex_impl(z);
            }
        };
#endif
    }

    namespace detail
    {
        template <class T>
        T csqrt_scale_factor() noexcept;

        template <class T>
        T csqrt_scale() noexcept;

        template <>
        inline float csqrt_scale_factor<float>() noexcept
        {
            return 6.7108864e7f;
        }

        template <>
        inline float csqrt_scale<float>() noexcept
        {
            return 1.220703125e-4f;
        }

        template <>
        inline double csqrt_scale_factor<double>() noexcept
        {
            return 1.8014398509481984e16;
        }

        template <>
        inline double csqrt_scale<double>() noexcept
        {
            return 7.450580596923828125e-9;
        }
    }

    namespace detail
    {
        template <class T, std::size_t N>
        struct complex_batch_kernel
        {
            using batch_type = batch<T, N>;
            using batch_bool_type = typename simd_batch_traits<batch_type>::batch_bool_type;
            using real_batch = typename batch_type::real_batch;

            static real_batch abs(const batch_type& z)
            {
                return hypot(z.real(), z.imag());
            }

            static batch_type sqrt(const batch_type& z)
            {
                using rvt = typename real_batch::value_type;
                real_batch x = z.real();
                real_batch y = z.imag();
                real_batch sqrt_x = xsimd::sqrt(fabs(x));
                real_batch sqrt_hy = xsimd::sqrt(0.5 * fabs(y));
                auto cond = (fabs(x) > real_batch(4.) || fabs(y) > real_batch(4.));
                x = select(cond, x * 0.25, x * detail::csqrt_scale_factor<rvt>());
                y = select(cond, y * 0.25, y * detail::csqrt_scale_factor<rvt>());
                real_batch scale = select(cond, real_batch(2.), real_batch(detail::csqrt_scale<rvt>()));
                real_batch r = abs(batch_type(x, y));

                auto condxp = x > real_batch(0.);
                real_batch t0 = select(condxp, xsimd::sqrt(0.5 * (r + x)), xsimd::sqrt(0.5 * (r - x)));
                real_batch r0 = scale * fabs((0.5 * y) / t0);
                t0 *= scale;
                real_batch t = select(condxp, t0, r0);
                r = select(condxp, r0, t0);
                batch_type resg = select(y < real_batch(0.), batch_type(t, -r), batch_type(t, r));
                real_batch ze(0.);

                return select(y == ze,
                              select(x == ze,
                                     batch_type(ze, ze),
                                     select(x < ze, batch_type(ze, sqrt_x), batch_type(sqrt_x, ze))),
                              select(x == ze,
                                     select(y > ze, batch_type(sqrt_hy, sqrt_hy), batch_type(sqrt_hy, -sqrt_hy)),
                                     resg));
            }

            static batch_bool_type isnan(const batch_type& z)
            {
                return batch_bool_type(xsimd::isnan(z.real()) || xsimd::isnan(z.imag()));
            }
        };

        template <class T, std::size_t N>
        struct batch_kernel<std::complex<T>, N>
             : public complex_batch_kernel<std::complex<T>, N>
        {
        };

        #ifdef XSIMD_ENABLE_XTL_COMPLEX
        template <class T, bool i3ec, std::size_t N>
        struct batch_kernel<xtl::xcomplex<T, T, i3ec>, N>
             : public complex_batch_kernel<xtl::xcomplex<T, T, i3ec>, N>
        {
        };
        #endif
    }

    namespace detail
    {
        template <class B, bool is_complex>
        struct real_imag_kernel
        {
            using return_type = typename B::real_batch;

            static return_type real(const B& z)
            {
                return z.real();
            }

            static return_type imag(const B& z)
            {
                return z.imag();
            }
        };

        template <class B>
        struct real_imag_kernel<B, false>
        {
            using return_type = B;

            static return_type real(const B& z)
            {
                return z;
            }

            static return_type imag(const B&)
            {
                return B(typename B::value_type(0));
            }
        };
    }

    template <class X>
    inline real_batch_type_t<X> real(const simd_base<X>& z)
    {
        using batch_type = batch_type_t<X>;
        constexpr bool is_cplx = detail::is_complex<typename batch_type::value_type>::value;
        return detail::real_imag_kernel<batch_type, is_cplx>::real(z());
    }

    template <class X>
    inline real_batch_type_t<X> imag(const simd_base<X>& z)
    {
        using batch_type = batch_type_t<X>;
        constexpr bool is_cplx = detail::is_complex<typename batch_type::value_type>::value;
        return detail::real_imag_kernel<batch_type, is_cplx>::imag(z());
    }

    template <class X>
    inline real_batch_type_t<X> arg(const simd_base<X>& z)
    {
        return atan2(imag(z), real(z));
    }

    template <class X>
    inline batch_type_t<X> conj(const simd_base<X>& z)
    {
        return X(z().real(), -z().imag());
    }

    template <class X>
    inline real_batch_type_t<X> norm(const simd_base<X>& rhs)
    {
        return real(rhs) * real(rhs) + imag(rhs) * imag(rhs);
    }

    template <class X>
    inline batch_type_t<X> proj(const simd_base<X>& rhs)
    {
        using batch_type = batch_type_t<X>;
        using real_batch = typename simd_batch_traits<X>::real_batch;
        auto cond = xsimd::isinf(rhs().real()) || xsimd::isinf(rhs().imag());
        return select(cond, batch_type(infinity<real_batch>(), copysign(real_batch(0.), rhs().imag())), rhs());
    }
}

#endif
