/*=============================================================================
    Copyright (c) 2001-2014 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(BOOST_SPIRIT_X3_PARSE_INTO_CONTAINER_JAN_15_2013_0957PM)
#define BOOST_SPIRIT_X3_PARSE_INTO_CONTAINER_JAN_15_2013_0957PM

#include <type_traits>

#include <boost/spirit/home/x3/support/traits/container_traits.hpp>
#include <boost/spirit/home/x3/support/traits/value_traits.hpp>
#include <boost/spirit/home/x3/support/traits/attribute_of.hpp>
#include <boost/spirit/home/x3/support/traits/handles_container.hpp>
#include <boost/spirit/home/x3/support/traits/has_attribute.hpp>
#include <boost/spirit/home/x3/support/traits/is_substitute.hpp>
#include <boost/spirit/home/x3/support/traits/move_to.hpp>
#include <boost/mpl/and.hpp>
#include <boost/fusion/include/at_key.hpp>
#include <boost/fusion/include/front.hpp>
#include <boost/fusion/include/back.hpp>
#include <boost/variant/apply_visitor.hpp>

namespace boost { namespace spirit { namespace x3 { namespace detail
{
    template <typename Attribute, typename Value>
    struct saver_visitor;

    // save to associative fusion container where Key is simple type
    template <typename Key, typename Enable = void>
    struct save_to_assoc_attr
    {
        template <typename Value, typename Attribute>
        static void call(const Key, Value& value, Attribute& attr)
        {
            traits::move_to(value, fusion::at_key<Key>(attr));
        }
    };

    // save to associative fusion container where Key
    // is variant over possible keys
    template <typename ...T>
    struct save_to_assoc_attr<variant<T...> >
    {
        typedef variant<T...> variant_t;

        template <typename Value, typename Attribute>
        static void call(const variant_t key, Value& value, Attribute& attr)
        {
            apply_visitor(saver_visitor<Attribute, Value>(attr, value), key);
        }
    };

    template <typename Attribute, typename Value>
    struct saver_visitor  : boost::static_visitor<void>
    {
        saver_visitor(Attribute& attr, Value& value)
            : attr(attr), value(value) {};

        Attribute& attr;
        Value& value;

        template <typename Key>
        void operator()(Key) const
        {
            save_to_assoc_attr<Key>::call(Key(), value,attr);
        }
    };

    template <typename Parser, typename Container, typename Context>
    struct parser_accepts_container
        : traits::is_substitute<
                typename traits::attribute_of<Parser, Context>::type
              , Container
            >
    {};

    template <typename Parser>
    struct parse_into_container_base_impl
    {
    private:

        // Parser has attribute (synthesize; Attribute is a container)
        template <typename Iterator, typename Context
          , typename RContext, typename Attribute>
        static bool call_synthesize_x(
            Parser const& parser
          , Iterator& first, Iterator const& last
          , Context const& context, RContext& rcontext, Attribute& attr, mpl::false_)
        {
            // synthesized attribute needs to be value initialized
            typedef typename
                traits::container_value<Attribute>::type
            value_type;
            value_type val = traits::value_initialize<value_type>::call();

            if (!parser.parse(first, last, context, rcontext, val))
                return false;

            // push the parsed value into our attribute
            traits::push_back(attr, val);
            return true;
        }

        // Parser has attribute (synthesize; Attribute is a container)
        template <typename Iterator, typename Context
          , typename RContext, typename Attribute>
        static bool call_synthesize_x(
            Parser const& parser
          , Iterator& first, Iterator const& last
          , Context const& context, RContext& rcontext, Attribute& attr, mpl::true_)
        {
            return parser.parse(first, last, context, rcontext, attr);
        }

        // Parser has attribute (synthesize; Attribute is a container)
        template <typename Iterator, typename Context
          , typename RContext, typename Attribute>
        static bool call_synthesize(
            Parser const& parser
          , Iterator& first, Iterator const& last
          , Context const& context, RContext& rcontext, Attribute& attr)
        {
            typedef
                parser_accepts_container<Parser, Attribute, Context>
            parser_accepts_container;

            return call_synthesize_x(parser, first, last, context, rcontext, attr
                , parser_accepts_container());
        }

        // Parser has attribute (synthesize; Attribute is a single element fusion sequence)
        template <typename Iterator, typename Context
          , typename RContext, typename Attribute>
        static bool call_synthesize_into_fusion_seq(Parser const& parser
          , Iterator& first, Iterator const& last, Context const& context
          , RContext& rcontext, Attribute& attr, mpl::false_ /* is_associative */)
        {
            static_assert(traits::has_size<Attribute, 1>::value,
                "Expecting a single element fusion sequence");
            return call_synthesize(parser, first, last, context, rcontext,
                fusion::front(attr));
        }

        // Parser has attribute (synthesize; Attribute is fusion map sequence)
        template <typename Iterator, typename Context, typename RContext, typename Attribute>
        static bool call_synthesize_into_fusion_seq(
            Parser const& parser
          , Iterator& first, Iterator const& last, Context const& context
          , RContext& rcontext, Attribute& attr, mpl::true_ /*is_associative*/)
        {
            using attribute_type = typename traits::attribute_of<Parser, Context>::type;
            static_assert(traits::has_size<attribute_type, 2>::value,
                "To parse directly into fusion map parser must produce 2 element attr");

            // use type of  first element of attribute as key
            using key = typename std::remove_reference<
            typename fusion::result_of::front<attribute_type>::type>::type;

            attribute_type attr_;
            if (!parser.parse(first, last, context, rcontext, attr_))
                return false;

            save_to_assoc_attr<key>::call(fusion::front(attr_), fusion::back(attr_), attr);
            return true;
        }

        template <typename Iterator, typename Context, typename RContext, typename Attribute>
        static bool call_synthesize_dispatch_by_seq(Parser const& parser
          , Iterator& first, Iterator const& last, Context const& context
          , RContext& rcontext, Attribute& attr, mpl::true_ /*is_sequence*/)
        {
            return call_synthesize_into_fusion_seq(
                parser, first, last, context, rcontext, attr
              , fusion::traits::is_associative<Attribute>());
        }

        template <typename Iterator, typename Context, typename RContext, typename Attribute>
        static bool call_synthesize_dispatch_by_seq(Parser const& parser
          , Iterator& first, Iterator const& last, Context const& context
          , RContext& rcontext, Attribute& attr, mpl::false_ /*is_sequence*/)
        {
            return call_synthesize(parser, first, last, context, rcontext, attr);
        }

        // Parser has attribute (synthesize)
        template <typename Iterator, typename Context, typename RContext, typename Attribute>
        static bool call(Parser const& parser
          , Iterator& first, Iterator const& last, Context const& context
          , RContext& rcontext, Attribute& attr, mpl::true_)
        {
            return call_synthesize_dispatch_by_seq(parser, first, last, context, rcontext, attr
                , fusion::traits::is_sequence<Attribute>());
        }

        // Parser has no attribute (pass unused)
        template <typename Iterator, typename Context, typename RContext, typename Attribute>
        static bool call(
            Parser const& parser
          , Iterator& first, Iterator const& last, Context const& context
          , RContext& rcontext, Attribute& /* attr */, mpl::false_)
        {
            return parser.parse(first, last, context, rcontext, unused);
        }


    public:

        template <typename Iterator, typename Context, typename RContext, typename Attribute>
        static bool call(Parser const& parser
          , Iterator& first, Iterator const& last, Context const& context
          , RContext& rcontext, Attribute& attr)
        {
            return call(parser, first, last, context, rcontext, attr
              , mpl::bool_<traits::has_attribute<Parser, Context>::value>());
        }
    };

    template <typename Parser, typename Context, typename RContext, typename Enable = void>
    struct parse_into_container_impl : parse_into_container_base_impl<Parser> {};

    template <typename Parser, typename Container, typename Context>
    struct parser_attr_is_substitute_for_container_value
        : traits::is_substitute<
            typename traits::attribute_of<Parser, Context>::type
          , typename traits::container_value<Container>::type
        >
    {};

    template <typename Parser, typename Context, typename RContext>
    struct parse_into_container_impl<Parser, Context, RContext,
        typename enable_if<traits::handles_container<Parser, Context>>::type>
    {
        template <typename Iterator, typename Attribute>
        static bool call(
            Parser const& parser
          , Iterator& first, Iterator const& last
          , Context const& context, RContext& rcontext, Attribute& attr, mpl::false_)
        {
            return parse_into_container_base_impl<Parser>::call(
                parser, first, last, context, rcontext, attr);
        }

        template <typename Iterator>
        static bool call(
            Parser const& parser
          , Iterator& first, Iterator const& last
          , Context const& context, RContext& rcontext, unused_type attr, mpl::true_)
        {
            return parser.parse(first, last, context, rcontext, attr);
        }

        template <typename Iterator, typename Attribute>
        static bool call(
            Parser const& parser
          , Iterator& first, Iterator const& last
          , Context const& context, RContext& rcontext, Attribute& attr, mpl::true_)
        {
            if (traits::is_empty(attr))
                return parser.parse(first, last, context, rcontext, attr);
            Attribute rest;
            bool r = parser.parse(first, last, context, rcontext, rest);
            if (r)
                traits::append(attr, rest.begin(), rest.end());
            return r;
        }

        template <typename Iterator, typename Attribute>
        static bool call(Parser const& parser
          , Iterator& first, Iterator const& last
          , Context const& context, RContext& rcontext, Attribute& attr)
        {
            typedef parser_accepts_container<
                    Parser, Attribute, Context>
            parser_accepts_container;

            typedef parser_attr_is_substitute_for_container_value<
                    Parser, Attribute, Context>
            parser_attr_is_substitute_for_container_value;

            typedef mpl::or_<
                parser_accepts_container
              , mpl::not_<parser_attr_is_substitute_for_container_value>>
            pass_attibute_as_is;

            return call(parser, first, last, context, rcontext, attr,
                pass_attibute_as_is());
        }
    };

    template <typename Parser, typename Iterator, typename Context
      , typename RContext, typename Attribute>
    bool parse_into_container(
        Parser const& parser
      , Iterator& first, Iterator const& last, Context const& context
      , RContext& rcontext, Attribute& attr)
    {
        return parse_into_container_impl<Parser, Context, RContext>::call(
            parser, first, last, context, rcontext, attr);
    }

}}}}

#endif
