// Copyright (C) 2016-2018 T. Zachary Laine
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_YAP_ALGORITHM_FWD_HPP_INCLUDED
#define BOOST_YAP_ALGORITHM_FWD_HPP_INCLUDED

#include <boost/yap/config.hpp>

#include <boost/hana/integral_constant.hpp>
#include <boost/hana/tuple.hpp>
#include <boost/hana/core/is_a.hpp>


namespace boost { namespace yap {

    /** The enumeration representing all the kinds of expressions supported in
        YAP.
    */
    enum class expr_kind {
        expr_ref =
            0, ///< A (possibly \c const) reference to another expression.

        terminal = 1, ///< A terminal expression.

        // unary
        unary_plus = 2,  ///< \c +
        negate = 3,      ///< \c -
        dereference = 4, ///< \c *
        complement = 5,  ///< \c ~
        address_of = 6,  ///< \c &
        logical_not = 7, ///< \c !
        pre_inc = 8,     ///< \c ++
        pre_dec = 9,     ///< \c \-\-
        post_inc = 10,   ///< \c ++(int)
        post_dec = 11,   ///< \c \-\-(int)

        // binary
        shift_left = 12,         ///< \c <<
        shift_right = 13,        ///< \c >>
        multiplies = 14,         ///< \c *
        divides = 15,            ///< \c /
        modulus = 16,            ///< \c %
        plus = 17,               ///< \c +
        minus = 18,              ///< \c -
        less = 19,               ///< \c <
        greater = 20,            ///< \c >
        less_equal = 21,         ///< \c <=
        greater_equal = 22,      ///< \c >=
        equal_to = 23,           ///< \c ==
        not_equal_to = 24,       ///< \c !=
        logical_or = 25,         ///< \c ||
        logical_and = 26,        ///< \c &&
        bitwise_and = 27,        ///< \c &
        bitwise_or = 28,         ///< \c |
        bitwise_xor = 29,        ///< \c ^
        comma = 30,              ///< \c ,
        mem_ptr = 31,            ///< \c ->*
        assign = 32,             ///< \c =
        shift_left_assign = 33,  ///< \c <<=
        shift_right_assign = 34, ///< \c >>=
        multiplies_assign = 35,  ///< \c *=
        divides_assign = 36,     ///< \c /=
        modulus_assign = 37,     ///< \c %=
        plus_assign = 38,        ///< \c +=
        minus_assign = 39,       ///< \c -=
        bitwise_and_assign = 40, ///< \c &=
        bitwise_or_assign = 41,  ///< \c |=
        bitwise_xor_assign = 42, ///< \c ^=
        subscript = 43,          ///< \c []

        // ternary
        if_else = 44, ///< Analogous to \c ?: .

        // n-ary
        call = 45 ///< \c ()
    };

    /** The type used to represent the index of a placeholder terminal. */
    template<long long I>
    struct placeholder : hana::llong<I>
    {
    };

#ifdef BOOST_YAP_DOXYGEN

    /** A metafunction that evaluates to std::true_type if \a Expr is an
        Expression, and std::false_type otherwise. */
    template<typename Expr>
    struct is_expr;

#else

    template<expr_kind Kind, typename Tuple>
    struct expression;

    namespace detail {

        // void_t

        template<class...>
        using void_t = void;

        // remove_cv_ref

        template<typename T>
        struct remove_cv_ref : std::remove_cv<std::remove_reference_t<T>>
        {
        };

        template<typename T>
        using remove_cv_ref_t = typename remove_cv_ref<T>::type;
    }

    template<
        typename Expr,
        typename = detail::void_t<>,
        typename = detail::void_t<>>
    struct is_expr : std::false_type
    {
    };

    template<typename Expr>
    struct is_expr<
        Expr,
        detail::void_t<decltype(detail::remove_cv_ref_t<Expr>::kind)>,
        detail::void_t<decltype(std::declval<Expr>().elements)>>
        : std::integral_constant<
              bool,
              std::is_same<
                  std::remove_cv_t<decltype(
                      detail::remove_cv_ref_t<Expr>::kind)>,
                  expr_kind>{} &&
                  hana::is_a<
                      hana::tuple_tag,
                      decltype(std::declval<Expr>().elements)>()>
    {
    };

#endif // BOOST_YAP_DOXYGEN

    /** A convenience alias for a terminal expression holding a \a T,
        instantiated from expression template \a expr_template. */
    template<template<expr_kind, class> class expr_template, typename T>
    using terminal = expr_template<expr_kind::terminal, hana::tuple<T>>;

    /** A convenience alias for a reference expression holding an expression
        \a T, instantiated from expression template \a expr_template. */
    template<template<expr_kind, class> class expr_template, typename T>
    using expression_ref = expr_template<
        expr_kind::expr_ref,
        hana::tuple<std::remove_reference_t<T> *>>;

#ifndef BOOST_YAP_DOXYGEN

    template<typename Expr, typename... T>
    decltype(auto) evaluate(Expr && expr, T &&... t);

    template<typename Expr, typename Transform, typename... Transforms>
    constexpr decltype(auto) transform(
        Expr && expr, Transform && transform, Transforms &&... transforms);

    template<typename Expr, typename Transform, typename... Transforms>
    decltype(auto) transform_strict(
        Expr && expr, Transform && transform, Transforms &&... transforms);

    template<typename T>
    decltype(auto) deref(T && x);

    template<typename Expr>
    decltype(auto) value(Expr && expr);

#endif // BOOST_YAP_DOXYGEN

    namespace literals {

        /** Creates literal placeholders.  Placeholder indices are 1-based. */
        template<char... c>
        constexpr auto operator"" _p()
        {
            using i = hana::llong<hana::ic_detail::parse<sizeof...(c)>({c...})>;
            static_assert(1 <= i::value, "Placeholders must be >= 1.");
            return expression<
                expr_kind::terminal,
                hana::tuple<placeholder<i::value>>>{};
        }
    }

    /** Used as the tag-type passed to a transform function written in the
        tag-transform form. */
    template<expr_kind Kind>
    struct expr_tag
    {
        static const expr_kind kind = Kind;
    };

    /** Used as the expression template returned by some operations inside YAP
        when YAP does not have an expression template it was told to use.  For
        instance, if transform() creates a new expression by transforming an
        existing expression's elements, it will attempt to create the new
        expression using the existing one's expression template.  If no such
        template exists because the existing expression was not made from an
        expression template, minimal_expr is used. */
    template<expr_kind Kind, typename Tuple>
    struct minimal_expr
    {
        static expr_kind const kind = Kind;
        Tuple elements;
    };

}}

#endif
