// Copyright Louis Dionne 2013-2022
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)

#ifndef BOOST_HANA_TEST_LAWS_BASE_HPP
#define BOOST_HANA_TEST_LAWS_BASE_HPP

#include <boost/hana/and.hpp>
#include <boost/hana/bool.hpp>
#include <boost/hana/core/when.hpp>
#include <boost/hana/detail/wrong.hpp>
#include <boost/hana/equal.hpp>
#include <boost/hana/eval_if.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/functional/compose.hpp>
#include <boost/hana/functional/infix.hpp>
#include <boost/hana/functional/partial.hpp>
#include <boost/hana/fwd/concept/integral_constant.hpp>
#include <boost/hana/fwd/core/to.hpp>
#include <boost/hana/fwd/less.hpp>
#include <boost/hana/not.hpp>
#include <boost/hana/or.hpp>
#include <boost/hana/tuple.hpp>

#include <support/tracked.hpp>

#include <type_traits>
#include <utility>


namespace boost { namespace hana {
    //////////////////////////////////////////////////////////////////////////
    // Misc
    //////////////////////////////////////////////////////////////////////////
    namespace test {
        struct laws;

        template <int i>
        struct for_each_n_t {
            static_assert(i > 0, "can't use for_each_n with i < 0");

            template <typename Xs, typename F>
            constexpr auto operator()(Xs const& xs, F const& f) const {
                hana::for_each(xs,
                    hana::compose(
                        hana::partial(for_each_n_t<i - 1>{}, xs),
                        hana::partial(hana::partial, f)
                    )
                );
            }
        };

        template <>
        struct for_each_n_t<1> {
            template <typename Xs, typename F>
            constexpr auto operator()(Xs const& xs, F const& f) const {
                hana::for_each(xs, f);
            }
        };

        template <int i>
        constexpr for_each_n_t<i> for_each_n{};

        auto foreach = hana::for_each;
        constexpr auto foreach3 = for_each_n<3>;
        constexpr auto foreach2 = for_each_n<2>;

        struct implies_t {
            template <typename P, typename Q>
            constexpr decltype(auto) operator()(P&& p, Q&& q) const {
                return hana::or_(hana::not_(static_cast<P&&>(p)),
                                 static_cast<Q&&>(q));
            }
        };
        constexpr auto implies = hana::infix(implies_t{});

        struct iff_t {
            template <typename P, typename Q>
            constexpr decltype(auto) operator()(P&& p, Q&& q) const {
                return hana::and_(implies(p, q), implies(q, p));
            }
        };
        constexpr auto iff = hana::infix(iff_t{});

        template <typename Cond, typename F>
        constexpr decltype(auto) only_when_(Cond cond, F f) {
            return hana::eval_if(cond, f, [](auto){ });
        }

        // A type with a constructor that must not be instantiated.
        // This is to make sure we don't instantiate something else than
        // the copy-constructor of the elements inside a container when we
        // copy the container.
        struct trap_construct {
            trap_construct() = default;
            trap_construct(trap_construct const&) = default;
#ifndef BOOST_HANA_WORKAROUND_MSVC_MULTIPLECTOR_106654
            trap_construct(trap_construct&) = default;
#endif
            trap_construct(trap_construct&&) = default;

            template <typename X>
            trap_construct(X&&) {
                static_assert(detail::wrong<X>{},
                "this constructor must not be instantiated");
            }
        };

        // A move-only type. Useful for testing containers.
        struct move_only {
            move_only() = default;
            move_only(move_only const&) = delete;
            move_only(move_only&&) = default;
        };

        //////////////////////////////////////////////////////////////////////
        // InjectionResult
        //////////////////////////////////////////////////////////////////////
        struct InjectionResult { };

        template <int i, typename ...X>
        struct injection_result {
            using hana_tag = InjectionResult;
            static constexpr int injection_id = i;
            hana::tuple<X...> args;
            Tracked tracker;

            template <typename ...Y, typename = decltype(tuple<X...>{std::declval<Y>()...})>
            constexpr explicit injection_result(Y&& ...y)
                : args{static_cast<Y&&>(y)...}, tracker{i}
            { }
        };

        //! A monotonic injective function.
        //!
        //! This is used in the unit tests, where we often just need a function
        //! which preserves equality and order, but which also satisfies the
        //! following law for all `Injection`s `f` and `g`:
        //! @code
        //!     f(x) == g(x) if and only if f === g
        //! @endcode
        //! where `===` means _was created by the same call to `injection`_.
        //! This allows creating several such functions in the unit tests while
        //! conserving precious information such as the fact that
        //! `f(g(x)) != g(f(x))`.
        template <int i>
        struct _injection {
            template <typename ...X>
            constexpr auto operator()(X&& ...x) const {
                return injection_result<i,
                    typename std::decay<X>::type...
                >{static_cast<X&&>(x)...};
            }
        };
    } // end namespace test

    template <>
    struct equal_impl<test::InjectionResult, test::InjectionResult> {
        template <typename X, typename Y>
        static constexpr auto apply(X x, Y y) {
            return hana::and_(
                hana::bool_c<X::injection_id == Y::injection_id>,
                hana::equal(x.args, y.args)
            );
        }
    };

    template <>
    struct less_impl<test::InjectionResult, test::InjectionResult> {
        template <typename X, typename Y>
        static constexpr auto apply(X x, Y y) {
            static_assert(X::injection_id == Y::injection_id,
            "can't order the result of two different injections");
            return hana::less(x.args, y.args);
        }
    };


    //////////////////////////////////////////////////////////////////////////
    // Integer
    //////////////////////////////////////////////////////////////////////////
    namespace test {
        enum class Policy : int {
            // One of those is mandatory
              Constant   = 1 << 0
            , Constexpr  = 1 << 1
            , Runtime    = 1 << 2

            // Those are optional
            , Tracked    = 1 << 3
            , Comparable = 1 << 4
            , Orderable  = 1 << 5
        };

        constexpr bool operator&&(Policy a, Policy b) {
            return static_cast<int>(a) && static_cast<int>(b);
        }

        constexpr bool operator&&(Policy a, bool b) {
            return static_cast<int>(a) && b;
        }

        constexpr bool operator&&(bool a, Policy b) {
            return a && static_cast<int>(b);
        }

        constexpr bool operator||(Policy a, Policy b) {
            return static_cast<int>(a) || static_cast<int>(b);
        }

        constexpr bool operator||(Policy a, bool b) {
            return static_cast<int>(a) || b;
        }

        constexpr bool operator||(bool a, Policy b) {
            return a || static_cast<int>(b);
        }

        constexpr bool operator!(Policy a) {
            return !static_cast<int>(a);
        }

        constexpr Policy operator|(Policy a, Policy b) {
            return static_cast<Policy>(static_cast<int>(a) | static_cast<int>(b));
        }

        constexpr Policy operator&(Policy a, Policy b) {
            return static_cast<Policy>(static_cast<int>(a) & static_cast<int>(b));
        }

        template <Policy policy, typename = void>
        struct Integer { };

        template <Policy policy>
        struct Integer<policy, std::enable_if_t<!!(policy & Policy::Constant)>> {
            using value_type = int;
        };

        template <int i, Policy policy, typename = void>
        struct integer {
            static_assert(
            !!(policy & (Policy::Constant | Policy::Constexpr | Policy::Runtime))
            , "You must choose at least one of Constant, Constexpr and Runtime.");

            static constexpr int value = i;
            constexpr operator int() const { return value; }
            using hana_tag = Integer<policy>;
            Tracked tracker{i};
        };

        template <int i, Policy policy>
        struct integer <i, policy, std::enable_if_t<!!(policy & Policy::Constexpr)>> {
            static constexpr int value = i;
            constexpr operator int() const { return value; }
            using hana_tag = Integer<policy>;
        };

        template <int i>
        struct eq : integer<i, Policy::Comparable | Policy::Runtime> { };

        template <int i>
        struct ct_eq : integer<i, Policy::Comparable | Policy::Constant> { };

        template <int i>
        struct cx_eq : integer<i, Policy::Comparable | Policy::Constexpr> { };

        template <int i>
        struct ord : integer<i, Policy::Orderable | Policy::Runtime> { };

        template <int i>
        struct ct_ord : integer<i, Policy::Orderable | Policy::Constant> { };

        template <int i>
        struct cx_ord : integer<i, Policy::Orderable | Policy::Constexpr> { };

        template <int i>
        struct _constant
            : integer<i, Policy::Constant | Policy::Comparable | Policy::Orderable>
        { };
    }

    //////////////////////////////////////////////////////////////////////////
    // Model of Constant/IntegralConstant
    //////////////////////////////////////////////////////////////////////////
    template <test::Policy policy>
    struct IntegralConstant<test::Integer<policy>> {
        static constexpr bool value = static_cast<bool>(policy & test::Policy::Constant);
    };

    template <test::Policy policy, typename C>
    struct to_impl<test::Integer<policy>, C, when<
        (policy & test::Policy::Constant) &&
        hana::IntegralConstant<C>::value
    >>
        : embedding<is_embedded<typename C::value_type, int>::value>
    {
        template <typename N>
        static constexpr auto apply(N const&) {
            return test::integer<N::value, policy>{};
        }
    };

    //////////////////////////////////////////////////////////////////////////
    // Model of Comparable
    //////////////////////////////////////////////////////////////////////////
    template <test::Policy p1, test::Policy p2>
    struct equal_impl<test::Integer<p1>, test::Integer<p2>, when<
        // both Comparable or Orderable
        (p1 & (test::Policy::Comparable | test::Policy::Orderable)) &&
        (p2 & (test::Policy::Comparable | test::Policy::Orderable)) &&

        // one Constexpr and the other Constant, or both Constexpr
        (((p1 & test::Policy::Constant) && (p2 & test::Policy::Constexpr)) ||
        ((p1 & test::Policy::Constexpr) && (p2 & test::Policy::Constant))  ||
        ((p1 & test::Policy::Constexpr) && (p2 & test::Policy::Constexpr)))
    >> {
        template <typename X, typename Y>
        static constexpr bool apply(X const&, Y const&)
        { return X::value == Y::value; }
    };

    template <test::Policy p1, test::Policy p2>
    struct equal_impl<test::Integer<p1>, test::Integer<p2>, when<
        // both Comparable or Orderable
        (p1 & (test::Policy::Comparable | test::Policy::Orderable)) &&
        (p2 & (test::Policy::Comparable | test::Policy::Orderable)) &&

        // either one is Runtime
        ((p1 & test::Policy::Runtime) || (p2 & test::Policy::Runtime))
    >> {
        template <typename X, typename Y>
        static bool apply(X const&, Y const&)
        { return X::value == Y::value; }
    };


    //////////////////////////////////////////////////////////////////////////
    // Model of Orderable
    //////////////////////////////////////////////////////////////////////////
    template <test::Policy p1, test::Policy p2>
    struct less_impl<test::Integer<p1>, test::Integer<p2>, when<
        // both Orderable
        (p1 & test::Policy::Orderable) && (p2 & test::Policy::Orderable) &&

        // one Constexpr and the other Constant, or both Constexpr
        (((p1 & test::Policy::Constant) && (p2 & test::Policy::Constexpr)) ||
        ((p1 & test::Policy::Constexpr) && (p2 & test::Policy::Constant))  ||
        ((p1 & test::Policy::Constexpr) && (p2 & test::Policy::Constexpr)))
    >> {
        template <typename X, typename Y>
        static constexpr bool apply(X const&, Y const&)
        { return X::value < Y::value; }
    };

    template <test::Policy p1, test::Policy p2>
    struct less_impl<test::Integer<p1>, test::Integer<p2>, when<
        // both Orderable
        (p1 & test::Policy::Orderable) && (p2 & test::Policy::Orderable) &&

        // either one is Runtime
        ((p1 & test::Policy::Runtime) || (p2 & test::Policy::Runtime))
    >> {
        template <typename X, typename Y>
        static bool apply(X const&, Y const&)
        { return X::value < Y::value; }
    };
}} // end namespace boost::hana

#endif // !BOOST_HANA_TEST_LAWS_BASE_HPP
