//  Copyright (c) 2001-2011 Hartmut Kaiser
//
//  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_KARMA_AUTO_NOV_29_2009_0339PM)
#define BOOST_SPIRIT_KARMA_AUTO_NOV_29_2009_0339PM

#if defined(_MSC_VER)
#pragma once
#endif

#include <boost/spirit/home/support/common_terminals.hpp>
#include <boost/spirit/home/support/info.hpp>
#include <boost/spirit/home/support/container.hpp>
#include <boost/spirit/home/support/assert_msg.hpp>
#include <boost/spirit/home/support/detail/hold_any.hpp>
#include <boost/spirit/home/karma/domain.hpp>
#include <boost/spirit/home/karma/meta_compiler.hpp>
#include <boost/spirit/home/karma/delimit_out.hpp>
#include <boost/spirit/home/karma/generator.hpp>
#include <boost/spirit/home/karma/auto/create_generator.hpp>
#include <boost/mpl/bool.hpp>

///////////////////////////////////////////////////////////////////////////////
namespace boost { namespace spirit
{
    ///////////////////////////////////////////////////////////////////////////
    // Enablers
    ///////////////////////////////////////////////////////////////////////////
    template <>
    struct use_terminal<karma::domain, tag::auto_>     // enables auto_
      : mpl::true_ {};

    template <typename A0>
    struct use_terminal<karma::domain                   // enables auto_(...)
      , terminal_ex<tag::auto_, fusion::vector1<A0> >
    > : mpl::true_ {};

    template <>                                         // enables auto_(f)
    struct use_lazy_terminal<
        karma::domain, tag::auto_, 1   /*arity*/
    > : mpl::true_ {};

}}

///////////////////////////////////////////////////////////////////////////////
namespace boost { namespace spirit { namespace karma
{
    using spirit::auto_;
    using spirit::auto__type;

    ///////////////////////////////////////////////////////////////////////////
    template <typename Modifiers>
    struct auto_generator
      : generator<auto_generator<Modifiers> >
    {
        typedef mpl::int_<generator_properties::all_properties> properties;

        template <typename Context, typename Unused>
        struct attribute
        {
            typedef spirit::hold_any type;
        };

        auto_generator(Modifiers const& modifiers)
          : modifiers_(modifiers) {}

        // auto_generator has an attached attribute 
        template <
            typename OutputIterator, typename Context, typename Delimiter
          , typename Attribute>
        bool generate(OutputIterator& sink, Context& context
          , Delimiter const& d, Attribute const& attr) const
        {
            return compile<karma::domain>(create_generator<Attribute>(), modifiers_)
                      .generate(sink, context, d, attr);
        }

        // this auto_generator has no attribute attached, it needs to have been
        // initialized from a value/variable
        template <typename OutputIterator, typename Context
          , typename Delimiter>
        static bool
        generate(OutputIterator&, Context&, Delimiter const&, unused_type)
        {
            // It is not possible (doesn't make sense) to use auto_ generators 
            // without providing any attribute, as the generator doesn't 'know' 
            // what to output. The following assertion fires if this situation
            // is detected in your code.
            BOOST_SPIRIT_ASSERT_MSG(false, auto_not_usable_without_attribute, ());
            return false;
        }

        template <typename Context>
        info what(Context& /*context*/) const
        {
            return info("auto_");
        }

        Modifiers modifiers_;
    };

    ///////////////////////////////////////////////////////////////////////////
    template <typename T, typename Modifiers>
    struct lit_auto_generator
      : generator<lit_auto_generator<T, Modifiers> >
    {
        typedef mpl::int_<generator_properties::all_properties> properties;

        template <typename Context, typename Unused>
        struct attribute
        {
            typedef unused_type type;
        };

        lit_auto_generator(typename add_reference<T>::type t, Modifiers const& modifiers)
          : t_(t)
          , generator_(compile<karma::domain>(create_generator<T>(), modifiers)) 
        {}

        // auto_generator has an attached attribute 
        template <
            typename OutputIterator, typename Context, typename Delimiter
          , typename Attribute>
        bool generate(OutputIterator& sink, Context& context
          , Delimiter const& d, Attribute const&) const
        {
            return generator_.generate(sink, context, d, t_);
        }

        template <typename Context>
        info what(Context& /*context*/) const
        {
            return info("auto_");
        }

        typedef typename spirit::result_of::create_generator<T>::type 
            generator_type;

        typedef typename spirit::result_of::compile<
            karma::domain, generator_type, Modifiers>::type generator_impl_type;

        T t_;
        generator_impl_type generator_;

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

    ///////////////////////////////////////////////////////////////////////////
    // Generator generators: make_xxx function (objects)
    ///////////////////////////////////////////////////////////////////////////

    // auto_
    template <typename Modifiers>
    struct make_primitive<tag::auto_, Modifiers> 
    {
        typedef auto_generator<Modifiers> result_type;

        result_type operator()(unused_type, Modifiers const& modifiers) const
        {
            return result_type(modifiers);
        }
    };

    // auto_(...)
    template <typename Modifiers, typename A0>
    struct make_primitive<
            terminal_ex<tag::auto_, fusion::vector1<A0> >, Modifiers>
    {
        typedef typename add_const<A0>::type const_attribute;

        typedef lit_auto_generator<const_attribute, Modifiers> result_type;

        template <typename Terminal>
        result_type operator()(Terminal const& term, Modifiers const& modifiers) const
        {
            return result_type(fusion::at_c<0>(term.args), modifiers);
        }
    };

}}}

#endif
