/*=============================================================================
  Copyright (c) 2001-2011 Joel de Guzman
  http://spirit.sourceforge.net/

  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_SPIRIT_MAKE_COMPONENT_OCTOBER_16_2008_1250PM
#define BOOST_SPIRIT_MAKE_COMPONENT_OCTOBER_16_2008_1250PM

#if defined(_MSC_VER)
#pragma once
#endif

#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/proto/proto.hpp>
#include <boost/spirit/home/support/detail/make_cons.hpp>
#include <boost/spirit/home/support/modify.hpp>

namespace boost { namespace spirit
{
    // There is no real "component" class. Each domain is responsible
    // for creating its own components. You need to specialize this for
    // each component in your domain. Use this as a guide.

    template <typename Domain, typename Tag, typename Enable = void>
    struct make_component
    {
        template <typename Sig>
        struct result;

        template <typename This, typename Elements, typename Modifiers>
        struct result<This(Elements, Modifiers)>;

        template <typename Elements, typename Modifiers>
        typename result<make_component(Elements, Modifiers)>::type
        operator()(Elements const& elements, Modifiers const& modifiers) const;
    };

    namespace tag
    {
        // Normally, we use proto tags as-is to distinguish operators.
        // The special case is proto::tag::subscript. Spirit uses this
        // as either sementic actions or directives. To distinguish between
        // the two, we use these special tags below.

        struct directive;
        struct action;
    }

    template <typename Domain, typename T, typename Enable = void>
    struct flatten_tree;
}}

namespace boost { namespace spirit { namespace detail
{
    template <typename Expr, typename State, typename Data, typename Domain>
    struct make_terminal_impl
      : proto::transform_impl<Expr, State, Data>
    {
        typedef typename
            proto::result_of::value<Expr>::type 
        value;

        typedef typename result_of::make_cons<value>::type elements;

        typedef
            make_component<Domain, proto::tag::terminal>
        make_component_;

        typedef typename
            make_component_::template
                result<make_component_(elements, Data)>::type
        result_type;

        result_type operator()(
            typename make_terminal_impl::expr_param expr
          , typename make_terminal_impl::state_param /*state*/
          , typename make_terminal_impl::data_param data
        ) const
        {
            return typename make_terminal_impl::make_component_()(
                detail::make_cons(proto::value(expr))
              , data
            );
        }
    };

    template <typename Expr, typename State, typename Data, typename Domain>
    struct make_terminal_impl<phoenix::actor<Expr>, State, Data, Domain>
      : proto::transform_impl<phoenix::actor<Expr>, State, Data>
    {
        typedef phoenix::actor<Expr> value;
        typedef typename result_of::make_cons<value>::type elements;
        typedef make_component<Domain, proto::tag::terminal> make_component_;

        typedef typename
            make_component_::template
                result<make_component_(elements, Data)>::type
        result_type;

        result_type operator()(
            typename make_terminal_impl::expr_param expr
          , typename make_terminal_impl::state_param /*state*/
          , typename make_terminal_impl::data_param data
        ) const
        {
            return typename make_terminal_impl::make_component_()(
                detail::make_cons(expr)
              , data
            );
        }
    };

    template <typename Expr, typename State, typename Data, typename Domain>
    struct make_terminal_impl<phoenix::actor<Expr> &, State, Data, Domain>
        : make_terminal_impl<phoenix::actor<Expr>, State, Data, Domain>
    {};

    template <typename Expr, typename State, typename Data, typename Domain>
    struct make_terminal_impl<phoenix::actor<Expr> const &, State, Data, Domain>
        : make_terminal_impl<phoenix::actor<Expr>, State, Data, Domain>
    {};

    template <typename Domain>
    struct make_terminal : proto::transform<make_terminal<Domain> >
    {
        template<typename Expr, typename State, typename Data>
        struct impl : make_terminal_impl<Expr, State, Data, Domain> {};
    };

    template <typename Domain, typename Tag, typename Grammar>
    struct make_unary : proto::transform<make_unary<Domain, Tag, Grammar> >
    {
        template<typename Expr, typename State, typename Data>
        struct impl : proto::transform_impl<Expr, State, Data>
        {
            typedef typename
                proto::result_of::child_c<Expr, 0>::type
            child;

            typedef typename Grammar::
                template result<Grammar(child, State, Data)>::type
            child_component;

            typedef typename
                result_of::make_cons<child_component>::type
            elements;

            typedef make_component<Domain, Tag> make_component_;

            typedef typename
                make_component_::template
                    result<make_component_(elements, Data)>::type
            result_type;

            result_type operator()(
                typename impl::expr_param expr
              , typename impl::state_param state
              , typename impl::data_param data
            ) const
            {
                return typename impl::make_component_()(
                    detail::make_cons(
                        Grammar()(proto::child(expr), state, data))
                  , data
                );
            }
        };
    };

    // un-flattened version
    template <typename Domain, typename Tag, typename Grammar,
        bool flatten = flatten_tree<Domain, Tag>::value>
    struct make_binary
    {
        template<typename Expr, typename State, typename Data>
        struct impl : proto::transform_impl<Expr, State, Data>
        {
            typedef typename Grammar::
                template result<Grammar(
                    typename proto::result_of::child_c<Expr, 0>::type
                  , State, Data)>::type
            lhs_component;

            typedef typename Grammar::
                template result<Grammar(
                    typename proto::result_of::child_c<Expr, 1>::type
                  , State, Data)>::type
            rhs_component;

            typedef typename
                result_of::make_cons<
                    lhs_component
                  , typename result_of::make_cons<rhs_component>::type
                >::type
            elements_type;

            typedef make_component<Domain, Tag> make_component_;

            typedef typename
                make_component_::template
                    result<make_component_(elements_type, Data)>::type
            result_type;

            result_type operator()(
                typename impl::expr_param expr
              , typename impl::state_param state
              , typename impl::data_param data
            ) const
            {
                elements_type elements =
                    detail::make_cons(
                        Grammar()(
                            proto::child_c<0>(expr), state, data)       // LHS
                      , detail::make_cons(
                            Grammar()(
                                proto::child_c<1>(expr), state, data)   // RHS
                        )
                    );

                return make_component_()(elements, data);
            }
        };
    };

    template <typename Grammar>
    struct make_binary_helper : proto::transform<make_binary_helper<Grammar> >
    {
        template<typename Expr, typename State, typename Data>
        struct impl : proto::transform_impl<Expr, State, Data>
        {
            typedef typename Grammar::
                template result<Grammar(Expr, State, Data)>::type
            lhs;

            typedef typename result_of::make_cons<lhs, State>::type result_type;

            result_type operator()(
                typename impl::expr_param expr
              , typename impl::state_param state
              , typename impl::data_param data
            ) const
            {
                return detail::make_cons(Grammar()(expr, state, data), state);
            }
        };
    };

    // Flattened version
    template <typename Domain, typename Tag, typename Grammar>
    struct make_binary<Domain, Tag, Grammar, true>
      : proto::transform<make_binary<Domain, Tag, Grammar> >
    {
        template<typename Expr, typename State, typename Data>
        struct impl : proto::transform_impl<Expr, State, Data>
        {
            typedef typename
                proto::reverse_fold_tree<
                    proto::_
                  , proto::make<fusion::nil>
                  , make_binary_helper<Grammar>
                >::template impl<Expr, State, Data>
            reverse_fold_tree;

            typedef typename reverse_fold_tree::result_type elements;
            typedef make_component<Domain, Tag> make_component_;

            typedef typename
                make_component_::template
                    result<make_component_(elements, Data)>::type
            result_type;

            result_type operator()(
                typename impl::expr_param expr
              , typename impl::state_param state
              , typename impl::data_param data
            ) const
            {
                return make_component_()(
                    reverse_fold_tree()(expr, state, data), data);
            }
        };
    };

    template <typename Domain, typename Grammar>
    struct make_directive : proto::transform<make_directive<Domain, Grammar> >
    {
        template<typename Expr, typename State, typename Data>
        struct impl : proto::transform_impl<Expr, State, Data>
        {
            typedef typename
                proto::result_of::child_c<Expr, 0>::type
            lhs;

            typedef typename
                proto::result_of::value<lhs>::type
            tag_type;

            typedef typename modify<Domain>::
                template result<modify<Domain>(tag_type, Data)>::type
            modifier_type;

            typedef typename Grammar::
                template result<Grammar(
                    typename proto::result_of::child_c<Expr, 1>::type
                  , State
                  , modifier_type
                )>::type
            rhs_component;

            typedef typename
                result_of::make_cons<
                    tag_type
                  , typename result_of::make_cons<rhs_component>::type
                >::type
            elements_type;

            typedef make_component<Domain, tag::directive> make_component_;

            typedef typename
                make_component_::template
                    result<make_component_(elements_type, Data)>::type
            result_type;

            result_type operator()(
                typename impl::expr_param expr
              , typename impl::state_param state
              , typename impl::data_param data
            ) const
            {
                tag_type tag = proto::value(proto::child_c<0>(expr));
                typename remove_reference<modifier_type>::type
                    modifier = modify<Domain>()(tag, data);

                elements_type elements =
                    detail::make_cons(
                        tag                                 // LHS
                      , detail::make_cons(
                            Grammar()(
                                proto::child_c<1>(expr)     // RHS
                              , state, modifier)
                        )
                    );

                return make_component_()(elements, data);
            }
        };
    };

    template <typename Domain, typename Grammar>
    struct make_action : proto::transform<make_action<Domain, Grammar> >
    {
        template<typename Expr, typename State, typename Data>
        struct impl : proto::transform_impl<Expr, State, Data>
        {
            typedef typename Grammar::
                template result<Grammar(
                    typename proto::result_of::child_c<Expr, 0>::type
                  , State
                  , Data
                )>::type
            lhs_component;

#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
            typedef typename
                proto::result_of::value<
                    typename proto::result_of::child_c<Expr, 1>::type
                >::type
            rhs_component;
#else
            typedef
                typename mpl::eval_if_c<
                    phoenix::is_actor<
                        typename proto::result_of::child_c<Expr, 1>::type
                    >::type::value
                  , proto::result_of::child_c<Expr, 1>
                  , proto::result_of::value<
                        typename proto::result_of::child_c<Expr, 1>::type
                    >
                >::type
                rhs_component;
#endif

            typedef typename
                result_of::make_cons<
                    lhs_component
                  , typename result_of::make_cons<rhs_component>::type
                >::type
            elements_type;

            typedef make_component<Domain, tag::action> make_component_;

            typedef typename
                make_component_::template
                    result<make_component_(elements_type, Data)>::type
            result_type;

#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
            result_type operator()(
                typename impl::expr_param expr
              , typename impl::state_param state
              , typename impl::data_param data
            ) const
            {
                elements_type elements =
                    detail::make_cons(
                        Grammar()(
                            proto::child_c<0>(expr), state, data)   // LHS
                      , detail::make_cons(
                            proto::value(proto::child_c<1>(expr)))  // RHS
                    );

                return make_component_()(elements, data);
            }
#else
            result_type operator()(
                typename impl::expr_param expr
              , typename impl::state_param state
              , typename impl::data_param data
            ) const
            {
                return
                    (*this)(
                        expr
                      , state
                      , data
                      , typename phoenix::is_actor<
                            typename proto::result_of::child_c<Expr, 1>::type
                        >::type()
                    );
            }
            
            result_type operator()(
                typename impl::expr_param expr
              , typename impl::state_param state
              , typename impl::data_param data
              , mpl::false_
            ) const
            {
                elements_type elements =
                    detail::make_cons(
                        Grammar()(
                            proto::child_c<0>(expr), state, data)   // LHS
                      , detail::make_cons(
                            proto::value(proto::child_c<1>(expr)))  // RHS
                    );

                return make_component_()(elements, data);
            }

            result_type operator()(
                typename impl::expr_param expr
              , typename impl::state_param state
              , typename impl::data_param data
              , mpl::true_
            ) const
            {
                elements_type elements =
                    detail::make_cons(
                        Grammar()(
                            proto::child_c<0>(expr), state, data)   // LHS
                      , detail::make_cons(
                            proto::child_c<1>(expr))               // RHS
                    );

                return make_component_()(elements, data);
            }
#endif
        };
    };
}}}

#endif
