/*=============================================================================
    Copyright (c) 2001-2011 Joel de Guzman
    Copyright (c) 2001-2011 Hartmut Kaiser
    Copyright (c)      2011 Thomas Heller

    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)
==============================================================================*/
#if !defined(BOOST_SPIRIT_TERMINAL_NOVEMBER_04_2008_0906AM)
#define BOOST_SPIRIT_TERMINAL_NOVEMBER_04_2008_0906AM

#if defined(_MSC_VER)
#pragma once
#endif

#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_function.hpp>
#include <boost/proto/proto.hpp>
#include <boost/spirit/home/support/meta_compiler.hpp>
#include <boost/spirit/home/support/detail/make_vector.hpp>
#include <boost/spirit/home/support/unused.hpp>
#include <boost/spirit/home/support/detail/is_spirit_tag.hpp>
#include <boost/preprocessor/tuple/elem.hpp>

#include <boost/spirit/home/support/terminal_expression.hpp>

namespace boost { namespace spirit
{
    template <typename Terminal, typename Args>
    struct terminal_ex
    {
        typedef Terminal terminal_type;
        typedef Args args_type;

        terminal_ex(Args const& args_)
          : args(args_) {}
        terminal_ex(Args const& args_, Terminal const& term_)
          : args(args_), term(term_) {}

        Args args;  // Args is guaranteed to be a fusion::vectorN so you
                    // can use that template for detection and specialization
        Terminal term;
    };

    template <typename Terminal, typename Actor, int Arity>
    struct lazy_terminal
    {
        typedef Terminal terminal_type;
        typedef Actor actor_type;
        static int const arity = Arity;

        lazy_terminal(Actor const& actor_)
          : actor(actor_) {}
        lazy_terminal(Actor const& actor_, Terminal const& term_)
          : actor(actor_), term(term_) {}

        Actor actor;
        Terminal term;
    };

    template <typename Domain, typename Terminal, int Arity, typename Enable = void>
    struct use_lazy_terminal : mpl::false_ {};

    template <typename Domain, typename Terminal, int Arity, typename Enable = void>
    struct use_lazy_directive : mpl::false_ {};

    template <typename Terminal>
    struct terminal;

    template <typename Domain, typename Terminal>
    struct use_terminal<Domain, terminal<Terminal> >
        : use_terminal<Domain, Terminal> {};

    template <typename Domain, typename Terminal, int Arity, typename Actor>
    struct use_terminal<Domain, lazy_terminal<Terminal, Actor, Arity> >
        : use_lazy_terminal<Domain, Terminal, Arity> {};

    template <typename Domain, typename Terminal, int Arity, typename Actor>
    struct use_directive<Domain, lazy_terminal<Terminal, Actor, Arity> >
        : use_lazy_directive<Domain, Terminal, Arity> {};

    template <
        typename F
      , typename A0 = unused_type
      , typename A1 = unused_type
      , typename A2 = unused_type
      , typename Unused = unused_type
    >
    struct make_lazy;

    template <typename F, typename A0>
    struct make_lazy<F, A0>
    {
        typedef typename
            proto::terminal<
                lazy_terminal<
                    typename F::terminal_type
                  , typename phoenix::detail::expression::function_eval<F, A0>::type
                  , 1 // arity
                >
            >::type
        result_type;
        typedef result_type type;

        result_type
        operator()(F f, A0 const& _0_) const
        {
            typedef typename result_type::proto_child0 child_type;
            return result_type::make(child_type(
                phoenix::detail::expression::function_eval<F, A0>::make(f, _0_)
              , f.proto_base().child0
            ));
        }
    };

    template <typename F, typename A0, typename A1>
    struct make_lazy<F, A0, A1>
    {
        typedef typename
            proto::terminal<
               lazy_terminal<
                    typename F::terminal_type
                  , typename phoenix::detail::expression::function_eval<F, A0, A1>::type
                  , 2 // arity
                >
            >::type
        result_type;
        typedef result_type type;

        result_type
        operator()(F f, A0 const& _0_, A1 const& _1_) const
        {
            typedef typename result_type::proto_child0 child_type;
            return result_type::make(child_type(
                phoenix::detail::expression::function_eval<F, A0, A1>::make(f, _0_, _1_)
              , f.proto_base().child0
            ));
        }
    };

    template <typename F, typename A0, typename A1, typename A2>
    struct make_lazy<F, A0, A1, A2>
    {
        typedef typename
            proto::terminal<
               lazy_terminal<
                    typename F::terminal_type
                  , typename phoenix::detail::expression::function_eval<F, A0, A1, A2>::type
                  , 3 // arity
                >
            >::type
        result_type;
        typedef result_type type;

        result_type
        operator()(F f, A0 const& _0_, A1 const& _1_, A2 const& _2_) const
        {
            typedef typename result_type::proto_child0 child_type;
            return result_type::make(child_type(
                phoenix::detail::expression::function_eval<F, A0, A1, A2>::make(f, _0_, _1_, _2_)
              , f.proto_base().child0
            ));
        }
    };

    namespace detail
    {
        // Helper struct for SFINAE purposes
        template <bool C> struct bool_;

        template <>
        struct bool_<true> : mpl::bool_<true>
        { 
            typedef bool_<true>* is_true; 
        };

        template <>
        struct bool_<false> : mpl::bool_<false>
        { 
            typedef bool_<false>* is_false; 
        };

        // Metafunction to detect if at least one arg is a Phoenix actor
        template <
            typename A0
          , typename A1 = unused_type
          , typename A2 = unused_type
        >
        struct contains_actor
            : bool_<
                  phoenix::is_actor<A0>::value
               || phoenix::is_actor<A1>::value
               || phoenix::is_actor<A2>::value
              >
        {};

        // to_lazy_arg: convert a terminal arg type to the type make_lazy needs
        template <typename A>
        struct to_lazy_arg
          : phoenix::as_actor<A> // wrap A in a Phoenix actor if not already one
        {};

        template <typename A>
        struct to_lazy_arg<const A>
          : to_lazy_arg<A>
        {};
        
        template <typename A>
        struct to_lazy_arg<A &>
          : to_lazy_arg<A>
        {};

        template <>
        struct to_lazy_arg<unused_type>
        {
            // unused arg: make_lazy wants unused_type
            typedef unused_type type;
        };

        // to_nonlazy_arg: convert a terminal arg type to the type make_vector needs
        template <typename A>
        struct to_nonlazy_arg
        {
            // identity
            typedef A type;
        };

        template <typename A>
        struct to_nonlazy_arg<const A>
          : to_nonlazy_arg<A>
        {};

        template <typename A>
        struct to_nonlazy_arg<A &>
          : to_nonlazy_arg<A>
        {};

        // incomplete type: should not be appeared unused_type in nonlazy arg.
        template <>
        struct to_nonlazy_arg<unused_type>;
    }

    template <typename Terminal>
    struct terminal
      : proto::extends<
            typename proto::terminal<Terminal>::type
          , terminal<Terminal>
        >
    {
        typedef terminal<Terminal> this_type;
        typedef Terminal terminal_type;

        typedef proto::extends<
            typename proto::terminal<Terminal>::type
          , terminal<Terminal>
        > base_type;

        terminal() {}

        terminal(Terminal const& t)
          : base_type(proto::terminal<Terminal>::type::make(t)) 
        {}

        template <
            bool Lazy
          , typename A0
          , typename A1 = unused_type
          , typename A2 = unused_type
        >
        struct result_helper;

        template <
            typename A0
        >
        struct result_helper<false, A0>
        {
            typedef typename
                proto::terminal<
                    terminal_ex<
                        Terminal
                      , typename detail::result_of::make_vector<
                            typename detail::to_nonlazy_arg<A0>::type>::type>
                >::type
            type;
        };

        template <
            typename A0
          , typename A1
        >
        struct result_helper<false, A0, A1>
        {
            typedef typename
                proto::terminal<
                    terminal_ex<
                        Terminal
                      , typename detail::result_of::make_vector<
                            typename detail::to_nonlazy_arg<A0>::type
                          , typename detail::to_nonlazy_arg<A1>::type>::type>
                >::type
            type;
        };

        template <
            typename A0
          , typename A1
          , typename A2
        >
        struct result_helper<false, A0, A1, A2>
        {
            typedef typename
                proto::terminal<
                    terminal_ex<
                        Terminal
                      , typename detail::result_of::make_vector<
                            typename detail::to_nonlazy_arg<A0>::type
                          , typename detail::to_nonlazy_arg<A1>::type
                          , typename detail::to_nonlazy_arg<A2>::type>::type>
                >::type
            type;
        };

        template <
            typename A0
          , typename A1
          , typename A2
        >
        struct result_helper<true, A0, A1, A2>
        {
            typedef typename
                make_lazy<this_type
                  , typename detail::to_lazy_arg<A0>::type
                  , typename detail::to_lazy_arg<A1>::type
                  , typename detail::to_lazy_arg<A2>::type>::type
            type;
        };

        // FIXME: we need to change this to conform to the result_of protocol
        template <
            typename A0
          , typename A1 = unused_type
          , typename A2 = unused_type      // Support up to 3 args
        >
        struct result
        {
            typedef typename
                result_helper<
                    detail::contains_actor<A0, A1, A2>::value
                  , A0, A1, A2
                >::type
            type;
        };

        template <typename This, typename A0>
        struct result<This(A0)>
        {
            typedef typename
                result_helper<
                    detail::contains_actor<A0, unused_type, unused_type>::value
                  , A0, unused_type, unused_type
                >::type
            type;
        };

        template <typename This, typename A0, typename A1>
        struct result<This(A0, A1)>
        {
            typedef typename
                result_helper<
                    detail::contains_actor<A0, A1, unused_type>::value
                  , A0, A1, unused_type
                >::type
            type;
        };


        template <typename This, typename A0, typename A1, typename A2>
        struct result<This(A0, A1, A2)>
        {
            typedef typename
                result_helper<
                     detail::contains_actor<A0, A1, A2>::value
                   , A0, A1, A2
                 >::type
                 type;
        };

        // Note: in the following overloads, SFINAE cannot
        // be done on return type because of gcc bug #24915:
        //   http://gcc.gnu.org/bugzilla/show_bug.cgi?id=24915
        // Hence an additional, fake argument is used for SFINAE,
        // using a type which can never be a real argument type.

        // Non-lazy overloads. Only enabled when all
        // args are immediates (no Phoenix actor).

        template <typename A0>
        typename result<A0>::type
        operator()(A0 const& _0_
          , typename detail::contains_actor<A0>::is_false = 0) const
        {
            typedef typename result<A0>::type result_type;
            typedef typename result_type::proto_child0 child_type;
            return result_type::make(
                child_type(
                    detail::make_vector(_0_)
                  , this->proto_base().child0)
            );
        }

        template <typename A0, typename A1>
        typename result<A0, A1>::type
        operator()(A0 const& _0_, A1 const& _1_
          , typename detail::contains_actor<A0, A1>::is_false = 0) const
        {
            typedef typename result<A0, A1>::type result_type;
            typedef typename result_type::proto_child0 child_type;
            return result_type::make(
                child_type(
                    detail::make_vector(_0_, _1_)
                  , this->proto_base().child0)
            );
        }

        template <typename A0, typename A1, typename A2>
        typename result<A0, A1, A2>::type
        operator()(A0 const& _0_, A1 const& _1_, A2 const& _2_
          , typename detail::contains_actor<A0, A1, A2>::is_false = 0) const
        {
            typedef typename result<A0, A1, A2>::type result_type;
            typedef typename result_type::proto_child0 child_type;
            return result_type::make(
                child_type(
                    detail::make_vector(_0_, _1_, _2_)
                  , this->proto_base().child0)
            );
        }

        // Lazy overloads. Enabled when at
        // least one arg is a Phoenix actor.
        template <typename A0>
        typename result<A0>::type
        operator()(A0 const& _0_
          , typename detail::contains_actor<A0>::is_true = 0) const
        {
            return make_lazy<this_type
              , typename phoenix::as_actor<A0>::type>()(*this
              , phoenix::as_actor<A0>::convert(_0_));
        }

        template <typename A0, typename A1>
        typename result<A0, A1>::type
        operator()(A0 const& _0_, A1 const& _1_
          , typename detail::contains_actor<A0, A1>::is_true = 0) const
        {
            return make_lazy<this_type
              , typename phoenix::as_actor<A0>::type
              , typename phoenix::as_actor<A1>::type>()(*this
              , phoenix::as_actor<A0>::convert(_0_)
              , phoenix::as_actor<A1>::convert(_1_));
        }

        template <typename A0, typename A1, typename A2>
        typename result<A0, A1, A2>::type
        operator()(A0 const& _0_, A1 const& _1_, A2 const& _2_
          , typename detail::contains_actor<A0, A1, A2>::is_true = 0) const
        {
            return make_lazy<this_type
              , typename phoenix::as_actor<A0>::type
              , typename phoenix::as_actor<A1>::type
              , typename phoenix::as_actor<A2>::type>()(*this
              , phoenix::as_actor<A0>::convert(_0_)
              , phoenix::as_actor<A1>::convert(_1_)
              , phoenix::as_actor<A2>::convert(_2_));
        }

    private:
        // silence MSVC warning C4512: assignment operator could not be generated
        terminal& operator= (terminal const&);
    };

    ///////////////////////////////////////////////////////////////////////////
    namespace result_of
    {
        // Calculate the type of the compound terminal if generated by one of
        // the spirit::terminal::operator() overloads above

        // The terminal type itself is passed through without modification
        template <typename Tag>
        struct terminal
        {
            typedef spirit::terminal<Tag> type;
        };

        template <typename Tag, typename A0>
        struct terminal<Tag(A0)>
        {
            typedef typename spirit::terminal<Tag>::
                template result<A0>::type type;
        };

        template <typename Tag, typename A0, typename A1>
        struct terminal<Tag(A0, A1)>
        {
            typedef typename spirit::terminal<Tag>::
                template result<A0, A1>::type type;
        };

        template <typename Tag, typename A0, typename A1, typename A2>
        struct terminal<Tag(A0, A1, A2)>
        {
            typedef typename spirit::terminal<Tag>::
                template result<A0, A1, A2>::type type;
        };
    }

    ///////////////////////////////////////////////////////////////////////////
    // support for stateful tag types
    namespace tag
    {
        template <
            typename Data, typename Tag
          , typename DataTag1 = unused_type, typename DataTag2 = unused_type>
        struct stateful_tag
        {
            BOOST_SPIRIT_IS_TAG()

            typedef Data data_type;

            stateful_tag() {}
            stateful_tag(data_type const& data) : data_(data) {}

            data_type data_;

        private:
            // silence MSVC warning C4512: assignment operator could not be generated
            stateful_tag& operator= (stateful_tag const&);
        };
    }

    template <
        typename Data, typename Tag
      , typename DataTag1 = unused_type, typename DataTag2 = unused_type>
    struct stateful_tag_type
      : spirit::terminal<tag::stateful_tag<Data, Tag, DataTag1, DataTag2> >
    {
        typedef tag::stateful_tag<Data, Tag, DataTag1, DataTag2> tag_type;

        stateful_tag_type() {}
        stateful_tag_type(Data const& data)
          : spirit::terminal<tag_type>(data) 
        {}

    private:
        // silence MSVC warning C4512: assignment operator could not be generated
        stateful_tag_type& operator= (stateful_tag_type const&);
    };

    namespace detail
    {
        // extract expression if this is a Tag
        template <typename StatefulTag>
        struct get_stateful_data
        {
            typedef typename StatefulTag::data_type data_type;

            // is invoked if given tag is != Tag
            template <typename Tag_>
            static data_type call(Tag_) { return data_type(); }

            // this is invoked if given tag is same as'Tag'
            static data_type const& call(StatefulTag const& t) { return t.data_; }
        };
    }

}}

namespace boost { namespace phoenix
{
    template <typename Tag>
    struct is_custom_terminal<Tag, typename Tag::is_spirit_tag>
      : mpl::true_
    {};

    template <typename Tag>
    struct custom_terminal<Tag, typename Tag::is_spirit_tag>
    {
#ifndef BOOST_PHOENIX_NO_SPECIALIZE_CUSTOM_TERMINAL
        typedef void _is_default_custom_terminal; // fix for #7730
#endif

        typedef spirit::terminal<Tag> result_type;

        template <typename Context>
        result_type operator()(Tag const & t, Context const &)
        {
            return spirit::terminal<Tag>(t);
        }
    };
}}

// Define a spirit terminal. This macro may be placed in any namespace.
// Common placeholders are placed in the main boost::spirit namespace
// (see common_terminals.hpp)

#define BOOST_SPIRIT_TERMINAL_X(x, y) ((x, y)) BOOST_SPIRIT_TERMINAL_Y
#define BOOST_SPIRIT_TERMINAL_Y(x, y) ((x, y)) BOOST_SPIRIT_TERMINAL_X
#define BOOST_SPIRIT_TERMINAL_X0
#define BOOST_SPIRIT_TERMINAL_Y0

#ifndef BOOST_SPIRIT_NO_PREDEFINED_TERMINALS

#define BOOST_SPIRIT_TERMINAL_NAME(name, type_name)                             \
    namespace tag { struct name { BOOST_SPIRIT_IS_TAG() }; }                    \
    typedef boost::proto::terminal<tag::name>::type type_name;                  \
    type_name const name = {{}};                                                \
    inline void BOOST_PP_CAT(silence_unused_warnings_, name)() { (void) name; } \
    /***/

#else

#define BOOST_SPIRIT_TERMINAL_NAME(name, type_name)                             \
    namespace tag { struct name { BOOST_SPIRIT_IS_TAG() }; }                    \
    typedef boost::proto::terminal<tag::name>::type type_name;                  \
    /***/

#endif

#define BOOST_SPIRIT_TERMINAL(name)                                             \
    BOOST_SPIRIT_TERMINAL_NAME(name, name ## _type)                             \
    /***/

#define BOOST_SPIRIT_DEFINE_TERMINALS_NAME_A(r, _, names)                       \
    BOOST_SPIRIT_TERMINAL_NAME(                                                 \
        BOOST_PP_TUPLE_ELEM(2, 0, names),                                       \
        BOOST_PP_TUPLE_ELEM(2, 1, names)                                        \
    )                                                                           \
    /***/

#define BOOST_SPIRIT_DEFINE_TERMINALS_NAME(seq)                                 \
    BOOST_PP_SEQ_FOR_EACH(BOOST_SPIRIT_DEFINE_TERMINALS_NAME_A, _,              \
        BOOST_PP_CAT(BOOST_SPIRIT_TERMINAL_X seq, 0))                           \
    /***/

// Define a spirit extended terminal. This macro may be placed in any namespace.
// Common placeholders are placed in the main boost::spirit namespace
// (see common_terminals.hpp)

#ifndef BOOST_SPIRIT_NO_PREDEFINED_TERMINALS

#define BOOST_SPIRIT_TERMINAL_NAME_EX(name, type_name)                          \
    namespace tag { struct name { BOOST_SPIRIT_IS_TAG() }; }                    \
    typedef boost::spirit::terminal<tag::name> type_name;                       \
    type_name const name = type_name();                                         \
    inline void BOOST_PP_CAT(silence_unused_warnings_, name)() { (void) name; } \
    /***/

#else

#define BOOST_SPIRIT_TERMINAL_NAME_EX(name, type_name)                          \
    namespace tag { struct name { BOOST_SPIRIT_IS_TAG() }; }                    \
    typedef boost::spirit::terminal<tag::name> type_name;                       \
    /***/

#endif

#define BOOST_SPIRIT_TERMINAL_EX(name)                                          \
    BOOST_SPIRIT_TERMINAL_NAME_EX(name, name ## _type)                          \
    /***/

#define BOOST_SPIRIT_DEFINE_TERMINALS_NAME_EX_A(r, _, names)                    \
    BOOST_SPIRIT_TERMINAL_NAME_EX(                                              \
        BOOST_PP_TUPLE_ELEM(2, 0, names),                                       \
        BOOST_PP_TUPLE_ELEM(2, 1, names)                                        \
    )                                                                           \
    /***/

#define BOOST_SPIRIT_DEFINE_TERMINALS_NAME_EX(seq)                              \
    BOOST_PP_SEQ_FOR_EACH(BOOST_SPIRIT_DEFINE_TERMINALS_NAME_EX_A, _,           \
        BOOST_PP_CAT(BOOST_SPIRIT_TERMINAL_X seq, 0))                           \
    /***/

#endif


