/*=============================================================================
    Copyright (c) 2001-2007 Joel de Guzman

    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 PHOENIX_STATEMENT_DETAIL_SWITCH_HPP
#define PHOENIX_STATEMENT_DETAIL_SWITCH_HPP

#include <boost/spirit/home/phoenix/core/nothing.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/as_vector.hpp>
#include <boost/fusion/include/push_back.hpp>
#include <boost/fusion/include/push_front.hpp>
#include <boost/fusion/include/begin.hpp>
#include <boost/fusion/include/size.hpp>
#include <boost/fusion/include/value_of.hpp>
#include <boost/fusion/include/is_sequence.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/if.hpp>

namespace boost { namespace phoenix
{

    template <typename Actor, typename K, K Value>
    struct switch_case;

    template <typename Actor>
    struct default_case;

    namespace detail
    {
        template <typename T>
        struct is_default_case : mpl::bool_<T::is_default> {};

        template <typename A0, typename A1>
        struct compose_case_a
        {
            // here, A0 and A1 are both switch cases
            typedef typename
                mpl::if_<
                    is_default_case<A1>
                  , fusion::vector<actor<A1>, actor<A0> >
                  , fusion::vector<actor<A0>, actor<A1> >
                >::type
            type;

            static type
            eval(A0 const& _0, A1 const& _1, mpl::false_)
            {
                return type(_0, _1);
            }

            static type
            eval(A0 const& _0, A1 const& _1, mpl::true_)
            {
                return type(_1, _0);
            }

            static type
            eval(A0 const& _0, A1 const& _1)
            {
                return eval(_0, _1, is_default_case<A1>());
            }
        };

        template <typename Seq, typename Case>
        struct compose_case_b
        {
            typedef typename fusion::result_of::as_vector<
                typename mpl::eval_if<
                    is_default_case<Case>
                  , fusion::result_of::push_front<Seq const, actor<Case> >
                  , fusion::result_of::push_back<Seq const, actor<Case> >
            >::type>::type
            type;

            static type
            eval(Seq const& seq, Case const& case_, mpl::false_)
            {
                return fusion::as_vector(
                    fusion::push_back(seq, actor<Case>(case_)));
            }

            static type
            eval(Seq const& seq, Case const& case_, mpl::true_)
            {
                return fusion::as_vector(
                    fusion::push_front(seq, actor<Case>(case_)));
            }

            static type
            eval(Seq const& seq, Case const& case_)
            {
                return eval(seq, case_, is_default_case<Case>());
            }
        };

        template <typename Cases>
        struct ensure_default
        {
            typedef
                is_default_case<
                    typename fusion::result_of::value_of<
                        typename fusion::result_of::begin<Cases>::type
                    >::type
                >
            is_default_case_;

            typedef typename
                mpl::eval_if<
                    is_default_case_
                  , mpl::identity<Cases>
                  , fusion::result_of::push_front<
                        Cases const, actor<default_case<actor<null_actor> > > >
                >::type
            type;

            static type
            eval(Cases const& cases, mpl::false_)
            {
                actor<default_case<actor<null_actor> > > default_
                    = default_case<actor<null_actor> >(nothing);
                return fusion::push_front(cases, default_);
            }

            static type
            eval(Cases const& cases, mpl::true_)
            {
                return cases;
            }

            static type
            eval(Cases const& cases)
            {
                return eval(cases, is_default_case_());
            }
        };

        template <typename Cond, typename Cases>
        struct switch_composite
        {
            BOOST_STATIC_ASSERT(fusion::traits::is_sequence<Cases>::value);
            typedef ensure_default<Cases> ensure_default_;

            typedef typename
                fusion::result_of::as_vector<
                    typename fusion::result_of::push_front<
                        typename ensure_default_::type, Cond>::type
                    >::type
            tuple_type;

            typedef
                composite<
                    detail::switch_eval<fusion::result_of::size<tuple_type>::value-2>
                  , tuple_type>
            type;

            static type
            eval(Cond const& cond, Cases const& cases)
            {
                return fusion::as_vector(
                    fusion::push_front(ensure_default_::eval(cases), cond));
            }
        };

        template <typename Cond, typename Cases>
        struct switch_composite_actor
        {
            typedef actor<typename switch_composite<Cond, Cases>::type> type;
        };
    }
}}

#endif
