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

#ifndef XTL_XMASKED_VALUE_HPP
#define XTL_XMASKED_VALUE_HPP

#include "xmasked_value_meta.hpp"
#include "xtype_traits.hpp"

namespace xtl
{
    template <class T>
    inline xmasked_value<T, bool> masked() noexcept
    {
        return xmasked_value<T, bool>(T(0), false);
    }

    /****************************
    * xmasked_value declaration *
    *****************************/

    template <class T, class B>
    class xmasked_value
    {
    public:

        using self_type = xmasked_value<T, B>;

        using value_type = T;
        using flag_type = B;

        template <class T1, class B1>
        constexpr xmasked_value(T1&& value, B1&& flag);

        template <class T1>
        constexpr xmasked_value(T1&& value);

        explicit constexpr xmasked_value();

        inline operator value_type() {
            return m_value;
        }

        std::add_lvalue_reference_t<T> value() & noexcept;
        std::add_lvalue_reference_t<std::add_const_t<T>> value() const & noexcept;
        std::conditional_t<std::is_reference<T>::value, apply_cv_t<T, std::decay_t<T>>&, std::decay_t<T>> value() && noexcept;
        std::conditional_t<std::is_reference<T>::value, const std::decay_t<T>&, std::decay_t<T>> value() const && noexcept;

        std::add_lvalue_reference_t<B> visible() & noexcept;
        std::add_lvalue_reference_t<std::add_const_t<B>> visible() const & noexcept;
        std::conditional_t<std::is_reference<B>::value, apply_cv_t<B, std::decay_t<B>>&, std::decay_t<B>> visible() && noexcept;
        std::conditional_t<std::is_reference<B>::value, const std::decay_t<B>&, std::decay_t<B>> visible() const && noexcept;

        template <class T1, class B1>
        bool equal(const xmasked_value<T1, B1>& rhs) const noexcept;

        template <class T1, XTL_DISALLOW(is_xmasked_value<T1>)>
        bool equal(const T1& rhs) const noexcept;

        template <class T1, class B1>
        void swap(xmasked_value<T1, B1>& other);

#define DEFINE_ASSIGN_OPERATOR(OP)                                                            \
        template <class T1>                                                                   \
        inline xmasked_value& operator OP(const T1& rhs)                                      \
        {                                                                                     \
            if (m_visible)                                                                    \
            {                                                                                 \
                m_value OP rhs;                                                               \
            }                                                                                 \
            return *this;                                                                     \
        }                                                                                     \
                                                                                              \
        template <class T1, class B1>                                                         \
        inline xmasked_value& operator OP(const xmasked_value<T1, B1>& rhs)                   \
        {                                                                                     \
            m_visible = m_visible && rhs.visible();                                           \
            if (m_visible)                                                                    \
            {                                                                                 \
                m_value OP rhs.value();                                                       \
            }                                                                                 \
            return *this;                                                                     \
        }

        DEFINE_ASSIGN_OPERATOR(=);
        DEFINE_ASSIGN_OPERATOR(+=);
        DEFINE_ASSIGN_OPERATOR(-=);
        DEFINE_ASSIGN_OPERATOR(*=);
        DEFINE_ASSIGN_OPERATOR(/=);
        DEFINE_ASSIGN_OPERATOR(%=);
        DEFINE_ASSIGN_OPERATOR(&=);
        DEFINE_ASSIGN_OPERATOR(|=);
        DEFINE_ASSIGN_OPERATOR(^=);
#undef DEFINE_ASSIGN_OPERATOR

    private:

        value_type m_value;
        flag_type m_visible;
    };

    /********************************
     * xmasked_value implementation *
     ********************************/

    template <class T, class B>
    template <class T1, class B1>
    inline constexpr xmasked_value<T, B>::xmasked_value(T1&& value, B1&& flag)
        : m_value(std::forward<T1>(value)), m_visible(std::forward<B1>(flag))
    {
    }

    template <class T, class B>
    template <class T1>
    inline constexpr xmasked_value<T, B>::xmasked_value(T1&& value)
        : m_value(std::forward<T1>(value)), m_visible(true)
    {
    }

    template <class T, class B>
    inline constexpr xmasked_value<T, B>::xmasked_value()
        : m_value(0), m_visible(true)
    {
    }

    template <class T>
    inline auto masked_value(T&& val)
    {
        return xmasked_value<T>(std::forward<T>(val));
    }

    template <class T, class B>
    inline auto masked_value(T&& val, B&& mask)
    {
        return xmasked_value<T, B>(std::forward<T>(val), std::forward<B>(mask));
    }

    template <class T, class B>
    inline auto xmasked_value<T, B>::value() & noexcept -> std::add_lvalue_reference_t<T>
    {
        return m_value;
    }

    template <class T, class B>
    inline auto xmasked_value<T, B>::value() const & noexcept -> std::add_lvalue_reference_t<std::add_const_t<T>>
    {
        return m_value;
    }

    template <class T, class B>
    inline auto xmasked_value<T, B>::value() && noexcept -> std::conditional_t<std::is_reference<T>::value, apply_cv_t<T, std::decay_t<T>>&, std::decay_t<T>>
    {
        return m_value;
    }

    template <class T, class B>
    inline auto xmasked_value<T, B>::value() const && noexcept -> std::conditional_t<std::is_reference<T>::value, const std::decay_t<T>&, std::decay_t<T>>
    {
        return m_value;
    }

    template <class T, class B>
    inline auto xmasked_value<T, B>::visible() & noexcept -> std::add_lvalue_reference_t<B>
    {
        return m_visible;
    }

    template <class T, class B>
    inline auto xmasked_value<T, B>::visible() const & noexcept -> std::add_lvalue_reference_t<std::add_const_t<B>>
    {
        return m_visible;
    }

    template <class T, class B>
    inline auto xmasked_value<T, B>::visible() && noexcept -> std::conditional_t<std::is_reference<B>::value, apply_cv_t<B, std::decay_t<B>>&, std::decay_t<B>>
    {
        return m_visible;
    }

    template <class T, class B>
    inline auto xmasked_value<T, B>::visible() const && noexcept -> std::conditional_t<std::is_reference<B>::value, const std::decay_t<B>&, std::decay_t<B>>
    {
        return m_visible;
    }

    template <class T, class B>
    template <class T1, class B1>
    inline bool xmasked_value<T, B>::equal(const xmasked_value<T1, B1>& rhs) const noexcept
    {
        return (!m_visible && !rhs.visible()) || (m_value == rhs.value() && (m_visible && rhs.visible()));
    }

    template <class T, class B>
    template <class T1, check_disallow<is_xmasked_value<T1>>>
    inline bool xmasked_value<T, B>::equal(const T1& rhs) const noexcept
    {
        return m_visible && m_value == rhs;
    }

    template <class T, class B>
    template <class T1, class B1>
    inline void xmasked_value<T, B>::swap(xmasked_value<T1, B1>& other)
    {
        using std::swap;
        swap(m_value, other.m_value);
        swap(m_visible, other.m_visible);
    }

    template <class T1, class B1, class T2, class B2>
    inline bool operator==(const xmasked_value<T1, B1>& lhs, const xmasked_value<T2, B2>& rhs) noexcept
    {
        return lhs.equal(rhs);
    }

    template <class T1, class T2, class B2, XTL_REQUIRES(negation<is_xmasked_value<T1>>)>
    inline bool operator==(const T1& lhs, const xmasked_value<T2, B2>& rhs) noexcept
    {
        return rhs.equal(lhs);
    }

    template <class T1, class B1, class T2, XTL_REQUIRES(negation<is_xmasked_value<T2>>)>
    inline bool operator==(const xmasked_value<T1, B1>& lhs, const T2& rhs) noexcept
    {
        return lhs.equal(rhs);
    }

    template <class T1, class B1, class T2, class B2>
    inline bool operator!=(const xmasked_value<T1, B1>& lhs, const xmasked_value<T2, B2>& rhs) noexcept
    {
        return !lhs.equal(rhs);
    }

    template <class T1, class T2, class B2, XTL_REQUIRES(negation<is_xmasked_value<T1>>)>
    inline bool operator!=(const T1& lhs, const xmasked_value<T2, B2>& rhs) noexcept
    {
        return !rhs.equal(lhs);
    }

    template <class T1, class B1, class T2, XTL_REQUIRES(negation<is_xmasked_value<T2>>)>
    inline bool operator!=(const xmasked_value<T1, B1>& lhs, const T2& rhs) noexcept
    {
        return !lhs.equal(rhs);
    }

    template <class T, class B>
    inline auto operator+(const xmasked_value<T, B>& e) noexcept
        -> xmasked_value<std::decay_t<T>, std::decay_t<B>>
    {
        return xmasked_value<std::decay_t<T>, std::decay_t<B>>(e.value(), e.visible());
    }

    template <class T, class B>
    inline auto operator-(const xmasked_value<T, B>& e) noexcept
        -> xmasked_value<std::decay_t<T>, std::decay_t<B>>
    {
        return xmasked_value<std::decay_t<T>, std::decay_t<B>>(-e.value(), e.visible());
    }

    template <class T, class B>
    inline auto operator~(const xmasked_value<T, B>& e) noexcept
        -> xmasked_value<std::decay_t<T>>
    {
        using value_type = std::decay_t<T>;
        return e.visible() ? masked_value(~e.value()) : masked<value_type>();
    }

    template <class T, class B>
    inline auto operator!(const xmasked_value<T, B>& e) noexcept -> xmasked_value<decltype(!e.value())>
    {
        using return_type = xmasked_value<decltype(!e.value())>;
        using value_type = typename return_type::value_type;
        return e.visible() ? return_type(!e.value()) : masked<value_type>();
    }

    template <class T, class B, class OC, class OT>
    inline std::basic_ostream<OC, OT>& operator<<(std::basic_ostream<OC, OT>& out, xmasked_value<T, B> v)
    {
        if (v.visible())
        {
            out << v.value();
        }
        else
        {
            out << "masked";
        }
        return out;
    }

    template <class T1, class B1, class T2, class B2>
    inline void swap(xmasked_value<T1, B1>& lhs, xmasked_value<T2, B2>& rhs)
    {
        lhs.swap(rhs);
    }

#define DEFINE_OPERATOR(OP)                                                                                   \
    template <class T1, class B1, class T2, class B2>                                                         \
    inline auto operator OP(const xmasked_value<T1, B1>& e1, const xmasked_value<T2, B2>& e2) noexcept        \
        -> xmasked_value<promote_type_t<std::decay_t<T1>, std::decay_t<T2>>>                                  \
    {                                                                                                         \
        using value_type = promote_type_t<std::decay_t<T1>, std::decay_t<T2>>;                                \
        return e1.visible() && e2.visible() ? masked_value(e1.value() OP e2.value()) : masked<value_type>();  \
    }                                                                                                         \
                                                                                                              \
    template <class T1, class B1, class T2, XTL_REQUIRES(negation<is_xmasked_value<T2>>)>                     \
    inline auto operator OP(const xmasked_value<T1, B1>& e1, const T2& e2) noexcept                           \
        -> xmasked_value<promote_type_t<std::decay_t<T1>, std::decay_t<T2>>>                                  \
    {                                                                                                         \
        using value_type = promote_type_t<std::decay_t<T1>, std::decay_t<T2>>;                                \
        return e1.visible() ? masked_value(e1.value() OP e2) : masked<value_type>();                          \
    }                                                                                                         \
                                                                                                              \
    template <class T1, class T2, class B2, XTL_REQUIRES(negation<is_xmasked_value<T1>>)>                     \
    inline auto operator OP(const T1& e1, const xmasked_value<T2, B2>& e2) noexcept                           \
        -> xmasked_value<promote_type_t<std::decay_t<T1>, std::decay_t<T2>>>                                  \
    {                                                                                                         \
        using value_type = promote_type_t<std::decay_t<T1>, std::decay_t<T2>>;                                \
        return e2.visible() ? masked_value(e1 OP e2.value()) : masked<value_type>();                          \
    }

#define DEFINE_BOOL_OPERATOR(OP)                                                                           \
    template <class T1, class B1, class T2, class B2>                                                      \
    inline auto operator OP(const xmasked_value<T1, B1>& e1, const xmasked_value<T2, B2>& e2) noexcept     \
        -> xmasked_value<decltype(e1.value() OP e2.value())>                                               \
    {                                                                                                      \
        return e1.visible() && e2.visible() ?                                                              \
            masked_value(e1.value() OP e2.value()) :                                                       \
            masked<decltype(e1.value() OP e2.value())>();                                                  \
    }                                                                                                      \
                                                                                                           \
    template <class T1, class B1, class T2, XTL_REQUIRES(negation<is_xmasked_value<T2>>)>                  \
    inline auto operator OP(const xmasked_value<T1, B1>& e1, const T2& e2) noexcept                        \
        -> xmasked_value<decltype(e1.value() OP e2)>                                                       \
    {                                                                                                      \
        return e1.visible() ? masked_value(e1.value() OP e2) : masked<decltype(e1.value() OP e2)>();       \
    }                                                                                                      \
                                                                                                           \
    template <class T1, class T2, class B2, XTL_REQUIRES(negation<is_xmasked_value<T1>>)>                  \
    inline auto operator OP(const T1& e1, const xmasked_value<T2, B2>& e2) noexcept                        \
        -> xmasked_value<decltype(e1 OP e2.value())>                                                       \
    {                                                                                                      \
        return e2.visible() ? masked_value(e1 OP e2.value()) : masked<decltype(e1 OP e2.value())>();       \
    }

#define DEFINE_UNARY_OPERATOR(OP)                                                     \
    template <class T, class B>                                                       \
    inline xmasked_value<std::decay_t<T>> OP(const xmasked_value<T, B>& e)            \
    {                                                                                 \
        using std::OP;                                                                \
        return e.visible() ? masked_value(OP(e.value())) : masked<std::decay_t<T>>(); \
    }

#define DEFINE_UNARY_BOOL_OPERATOR(OP)                                                        \
    template <class T, class B>                                                               \
    inline auto OP(const xmasked_value<T, B>& e)                                              \
    {                                                                                         \
        using std::OP;                                                                        \
        return e.visible() ? masked_value(OP(e.value())) : masked<decltype(OP(e.value()))>(); \
    }

#define DEFINE_BINARY_OPERATOR(OP)                                                                       \
    template <class T1, class B1, class T2, class B2>                                                    \
    inline auto OP(const xmasked_value<T1, B1>& e1, const xmasked_value<T2, B2>& e2)                     \
    {                                                                                                    \
        using std::OP;                                                                                   \
        return e1.visible() && e2.visible() ?                                                            \
            masked_value(OP(e1.value(), e2.value())) :                                                   \
            masked<decltype(OP(e1.value(), e2.value()))>();                                              \
    }                                                                                                    \
                                                                                                         \
    template <class T1, class B1, class T2>                                                              \
    inline auto OP(const xmasked_value<T1, B1>& e1, const T2& e2)                                        \
    {                                                                                                    \
        using std::OP;                                                                                   \
        return e1.visible() ? masked_value(OP(e1.value(), e2)) : masked<decltype(OP(e1.value(), e2))>(); \
    }                                                                                                    \
                                                                                                         \
    template <class T1, class T2, class B2>                                                              \
    inline auto OP(const T1& e1, const xmasked_value<T2, B2>& e2)                                        \
    {                                                                                                    \
        using std::OP;                                                                                   \
        return e2.visible() ? masked_value(OP(e1, e2.value())) : masked<decltype(OP(e1, e2.value()))>(); \
    }

#define DEFINE_TERNARY_OPERATOR_MMM(OP)                                                                               \
    template <class T1, class B1, class T2, class B2, class T3, class B3>                                             \
    inline auto OP(const xmasked_value<T1, B1>& e1, const xmasked_value<T2, B2>& e2, const xmasked_value<T3, B3>& e3) \
    {                                                                                                                 \
        using std::OP;                                                                                                \
        return (e1.visible() && e2.visible() && e3.visible()) ?                                                       \
                masked_value(OP(e1.value(), e2.value(), e3.value())) :                                                \
                masked<decltype(OP(e1.value(), e2.value(), e3.value()))>();                                           \
    }

#define DEFINE_TERNARY_OPERATOR_MMT(OP)                                                              \
    template <class T1, class B1, class T2, class B2, class T3>                                      \
    inline auto OP(const xmasked_value<T1, B1>& e1, const xmasked_value<T2, B2>& e2, const T3& e3)   \
    {                                                                                                \
        using std::OP;                                                                               \
        return (e1.visible() && e2.visible()) ?                                                      \
                masked_value(OP(e1.value(), e2.value(), e3)) :                                       \
                masked<decltype(OP(e1.value(), e2.value(), e3))>();                                  \
    }

#define DEFINE_TERNARY_OPERATOR_MTM(OP)                                                            \
    template <class T1, class B1, class T2, class T3, class B3>                                    \
    inline auto OP(const xmasked_value<T1, B1>& e1, const T2& e2, const xmasked_value<T3, B3>& e3) \
    {                                                                                              \
        using std::OP;                                                                             \
        return (e1.visible() && e3.visible()) ?                                                    \
                masked_value(OP(e1.value(), e2, e3.value())) :                                     \
                masked<decltype(OP(e1.value(), e2, e3.value()))>();                                \
    }

#define DEFINE_TERNARY_OPERATOR_TMM(OP)                                                            \
    template <class T1, class T2, class B2, class T3, class B3>                                    \
    inline auto OP(const T1& e1, const xmasked_value<T2, B2>& e2, const xmasked_value<T3, B3>& e3) \
    {                                                                                              \
        using std::OP;                                                                             \
        return (e2.visible() && e3.visible()) ?                                                    \
                masked_value(OP(e1, e2.value(), e3.value())) :                                     \
                masked<decltype(OP(e1, e2.value(), e3.value()))>();                                \
    }

#define DEFINE_TERNARY_OPERATOR_TTM(OP)                                         \
    template <class T1, class T2, class T3, class B3>                           \
    inline auto OP(const T1& e1, const T2& e2, const xmasked_value<T3, B3>& e3) \
    {                                                                           \
        using std::OP;                                                          \
        return e3.visible() ?                                                   \
            masked_value(OP(e1, e2, e3.value())) :                              \
            masked<decltype(OP(e1, e2, e3.value()))>();                         \
    }

#define DEFINE_TERNARY_OPERATOR_TMT(OP)                                         \
    template <class T1, class T2, class B2, class T3>                           \
    inline auto OP(const T1& e1, const xmasked_value<T2, B2>& e2, const T3& e3) \
    {                                                                           \
        using std::OP;                                                          \
        return e2.visible() ?                                                   \
            masked_value(OP(e1, e2.value(), e3)) :                              \
            masked<decltype(OP(e1, e2.value(), e3))>();                         \
    }

#define DEFINE_TERNARY_OPERATOR_MTT(OP)                                         \
    template <class T1, class B1, class T2, class T3>                           \
    inline auto OP(const xmasked_value<T1, B1>& e1, const T2& e2, const T3& e3) \
    {                                                                           \
        using std::OP;                                                          \
        return e1.visible() ?                                                   \
            masked_value(OP(e1.value(), e2, e3)) :                              \
            masked<decltype(OP(e1.value(), e2, e3))>();                         \
    }

#define DEFINE_TERNARY_OPERATOR(OP) \
    DEFINE_TERNARY_OPERATOR_MMM(OP) \
                                    \
    DEFINE_TERNARY_OPERATOR_MMT(OP) \
    DEFINE_TERNARY_OPERATOR_MTM(OP) \
    DEFINE_TERNARY_OPERATOR_TMM(OP) \
    DEFINE_TERNARY_OPERATOR_TTM(OP) \
    DEFINE_TERNARY_OPERATOR_TMT(OP) \
    DEFINE_TERNARY_OPERATOR_MTT(OP)

    DEFINE_OPERATOR(+);
    DEFINE_OPERATOR(-);
    DEFINE_OPERATOR(*);
    DEFINE_OPERATOR(/);
    DEFINE_OPERATOR(%);
    DEFINE_BOOL_OPERATOR(||);
    DEFINE_BOOL_OPERATOR(&&);
    DEFINE_OPERATOR(&);
    DEFINE_OPERATOR(|);
    DEFINE_OPERATOR(^);
    DEFINE_BOOL_OPERATOR(<);
    DEFINE_BOOL_OPERATOR(<=);
    DEFINE_BOOL_OPERATOR(>);
    DEFINE_BOOL_OPERATOR(>=);
    DEFINE_UNARY_OPERATOR(abs)
    DEFINE_UNARY_OPERATOR(fabs)
    DEFINE_UNARY_OPERATOR(exp)
    DEFINE_UNARY_OPERATOR(exp2)
    DEFINE_UNARY_OPERATOR(expm1)
    DEFINE_UNARY_OPERATOR(log)
    DEFINE_UNARY_OPERATOR(log10)
    DEFINE_UNARY_OPERATOR(log2)
    DEFINE_UNARY_OPERATOR(log1p)
    DEFINE_UNARY_OPERATOR(sqrt)
    DEFINE_UNARY_OPERATOR(cbrt)
    DEFINE_UNARY_OPERATOR(sin)
    DEFINE_UNARY_OPERATOR(cos)
    DEFINE_UNARY_OPERATOR(tan)
    DEFINE_UNARY_OPERATOR(acos)
    DEFINE_UNARY_OPERATOR(asin)
    DEFINE_UNARY_OPERATOR(atan)
    DEFINE_UNARY_OPERATOR(sinh)
    DEFINE_UNARY_OPERATOR(cosh)
    DEFINE_UNARY_OPERATOR(tanh)
    DEFINE_UNARY_OPERATOR(acosh)
    DEFINE_UNARY_OPERATOR(asinh)
    DEFINE_UNARY_OPERATOR(atanh)
    DEFINE_UNARY_OPERATOR(erf)
    DEFINE_UNARY_OPERATOR(erfc)
    DEFINE_UNARY_OPERATOR(tgamma)
    DEFINE_UNARY_OPERATOR(lgamma)
    DEFINE_UNARY_OPERATOR(ceil)
    DEFINE_UNARY_OPERATOR(floor)
    DEFINE_UNARY_OPERATOR(trunc)
    DEFINE_UNARY_OPERATOR(round)
    DEFINE_UNARY_OPERATOR(nearbyint)
    DEFINE_UNARY_OPERATOR(rint)
    DEFINE_UNARY_BOOL_OPERATOR(isfinite)
    DEFINE_UNARY_BOOL_OPERATOR(isinf)
    DEFINE_UNARY_BOOL_OPERATOR(isnan)
    DEFINE_BINARY_OPERATOR(fmod)
    DEFINE_BINARY_OPERATOR(remainder)
    DEFINE_BINARY_OPERATOR(fmax)
    DEFINE_BINARY_OPERATOR(fmin)
    DEFINE_BINARY_OPERATOR(fdim)
    DEFINE_BINARY_OPERATOR(pow)
    DEFINE_BINARY_OPERATOR(hypot)
    DEFINE_BINARY_OPERATOR(atan2)
    DEFINE_TERNARY_OPERATOR(fma)

#undef DEFINE_TERNARY_OPERATOR
#undef DEFINE_TERNARY_OPERATOR_MMM
#undef DEFINE_TERNARY_OPERATOR_MMT
#undef DEFINE_TERNARY_OPERATOR_MTM
#undef DEFINE_TERNARY_OPERATOR_TMM
#undef DEFINE_TERNARY_OPERATOR_TTM
#undef DEFINE_TERNARY_OPERATOR_TMT
#undef DEFINE_TERNARY_OPERATOR_MTT
#undef DEFINE_BINARY_OPERATOR
#undef DEFINE_UNARY_OPERATOR
#undef DEFINE_UNARY_BOOL_OPERATOR
#undef DEFINE_OPERATOR
#undef DEFINE_BOOL_OPERATOR
}

#endif
