//  Copyright (c) 2001-2009 Hartmut Kaiser
//  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)

#if !defined(SPIRIT_KARMA_ALTERNATIVE_MAR_01_2007_1124AM)
#define SPIRIT_KARMA_ALTERNATIVE_MAR_01_2007_1124AM

#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once      // MS compatible compilers support #pragma once
#endif

#include <boost/spirit/home/support/unused.hpp>
#include <boost/spirit/home/support/attribute_of.hpp>
#include <boost/spirit/home/karma/domain.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/mpl/find_if.hpp>
#include <boost/mpl/deref.hpp>
#include <boost/mpl/distance.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_convertible.hpp>
#include <boost/variant.hpp>

namespace boost { namespace spirit { namespace karma 
{
    ///////////////////////////////////////////////////////////////////////////
    struct sequence;    // forward declaration only
    
namespace detail
{
    ///////////////////////////////////////////////////////////////////////////
    //  A component is compatible to a given parameter type if the parameter
    //  is the same as the expected type of the component
    ///////////////////////////////////////////////////////////////////////////
    template <typename Expected, typename Parameter>
    struct compute_compatible_component
    {
        typedef typename Parameter::types types;
        typedef typename mpl::end<types>::type end;
        typedef typename mpl::begin<types>::type begin;

        typedef typename
            mpl::find_if<types, is_same<mpl::_1, Expected> >::type
        iter;

        typedef typename mpl::not_<is_same<iter, end> >::type type;
        enum { value = type::value };
    };

    template <typename Expected>
    struct compute_compatible_component<Expected, unused_type>
      : mpl::false_
    {};

    ///////////////////////////////////////////////////////////////////////////
    //  execute a generator if the given parameter type is compatible
    ///////////////////////////////////////////////////////////////////////////

    //  this get's instantiated if the parameter type is _not_ compatible with
    //  the generator
    template <typename Component, typename Parameter, typename Expected,
        typename Enable = void>
    struct alternative_generate
    {
        template <typename OutputIterator, typename Context, typename Delimiter>
        static bool
        call(Component const&, OutputIterator&, Context&, Delimiter const&, 
            Parameter const&)
        {
            return false;
        }
    };

    template <typename Component>
    struct alternative_generate<Component, unused_type, unused_type>
    {
        template <typename OutputIterator, typename Context, typename Delimiter>
        static bool
        call(Component const& component, OutputIterator& sink,
            Context& ctx, Delimiter const& delim, unused_type const&)
        {
            // return true if any of the generators succeed
            typedef typename Component::director director;
            return director::generate(component, sink, ctx, delim, unused);
        }
    };

    //  this get's instantiated if there is no parameter given for the
    //  alternative generator
    template <typename Component, typename Expected>
    struct alternative_generate<Component, unused_type, Expected>
      : alternative_generate<Component, unused_type, unused_type>
    {};

    //  this get's instantiated if the generator does not expect to receive a
    //  parameter (the generator is self contained).
    template <typename Component, typename Parameter>
    struct alternative_generate<Component, Parameter, unused_type>
      : alternative_generate<Component, unused_type, unused_type>
    {};

    //  this get's instantiated if the parameter type is compatible to the
    //  generator
    template <typename Component, typename Parameter, typename Expected>
    struct alternative_generate<
        Component, Parameter, Expected,
        typename enable_if<
            compute_compatible_component<Expected, Parameter>
        >::type
    >
    {
        // If this alternative is a sequence, we wrap the attribute into a 
        // fusion sequence.
        template <typename Parameter_>
        static fusion::vector<Parameter_ const&> 
        wrap_attribute(Parameter_ const& param, mpl::true_)
        {
            return fusion::vector<Parameter const&>(param);
        }

        // If this alternative is not a sequence the parameter is passed 
        // through unchanged.
        template <typename Parameter_>
        static Parameter_ const& 
        wrap_attribute(Parameter_ const& param, mpl::false_)
        {
            return param;
        }

        template <typename OutputIterator, typename Context, typename Delimiter>
        static bool
        call(Component const& component, OutputIterator& sink,
            Context& ctx, Delimiter const& delim, Parameter const& param)
        {
            typedef
                compute_compatible_component<Expected, Parameter>
            component_type;

            typedef typename
                mpl::distance<
                    typename component_type::begin,
                    typename component_type::iter
                >::type
            distance_type;

            // make sure, the content of the passed variant matches our
            // expectations
            if (param.which() != distance_type::value)
                return false;

            // returns true if any of the generators succeed
            typedef
                typename mpl::deref<
                    typename component_type::iter
                >::type
            compatible_type;

            typedef typename Component::director director;
            typedef typename is_same<director, sequence>::type is_sequence;
            return director::generate(component, sink, ctx, delim,
                wrap_attribute(get<compatible_type>(param), is_sequence()));
        }
    };

    ///////////////////////////////////////////////////////////////////////////
    //  alternative_generate_functor: a functor supplied to spirit::any which
    //  will be executed for every generator in a given alternative generator
    //  expression
    ///////////////////////////////////////////////////////////////////////////
    template <typename OutputIterator, typename Context, typename Delimiter,
        typename Parameter>
    struct alternative_generate_functor
    {
        alternative_generate_functor(OutputIterator& sink_, Context& ctx_,
              Delimiter const& d, Parameter const& p)
          : sink(sink_), ctx(ctx_), delim(d), param(p)
        {
        }

        template <typename Component>
        bool operator()(Component const& component)
        {
            typedef
                typename traits::attribute_of<
                    karma::domain, Component, Context>::type
            expected;
            typedef
                alternative_generate<Component, Parameter, expected>
            generate;

            return generate::call(component, sink, ctx, delim, param);
        }

        OutputIterator& sink;
        Context& ctx;
        Delimiter const& delim;
        Parameter const& param;
    };

}}}}

#endif
