/*=============================================================================
    Copyright (c) 2001-2014 Joel de Guzman
    Copyright (c) 2013 Agustin Berge
    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)
=============================================================================*/
#if !defined(BOOST_SPIRIT_X3_MOVE_TO_JAN_17_2013_0859PM)
#define BOOST_SPIRIT_X3_MOVE_TO_JAN_17_2013_0859PM

#include <boost/spirit/home/x3/support/traits/attribute_category.hpp>
#include <boost/spirit/home/x3/support/traits/tuple_traits.hpp>
#include <boost/spirit/home/x3/support/traits/variant_has_substitute.hpp>
#include <boost/fusion/include/is_sequence.hpp>
#include <boost/fusion/include/front.hpp>
#include <boost/fusion/include/size.hpp>
#include <boost/fusion/include/move.hpp>
#include <boost/fusion/include/is_sequence.hpp>
#include <utility>

namespace boost { namespace spirit { namespace x3 { namespace traits
{
    template <typename Source, typename Dest>
    inline void move_to(Source&& src, Dest& dest);

    template <typename T>
    inline void move_to(T& src, T& dest);

    template <typename T>
    inline void move_to(T const& src, T& dest);

    template <typename T>
    inline void move_to(T&& src, T& dest);

    template <typename Iterator, typename Dest>
    inline void move_to(Iterator first, Iterator last, Dest& dest);

    template <typename Dest>
    inline void move_to(unused_type, Dest&) {}

    template <typename Source>
    inline void move_to(Source&, unused_type) {}

    inline void move_to(unused_type, unused_type) {}

    template <typename Iterator>
    inline void
    move_to(Iterator, Iterator, unused_type) {}

    namespace detail
    {
        template <typename Source, typename Dest>
        inline void
        move_to(Source&&, Dest&, unused_attribute) {}
        
        template <typename Source, typename Dest>
        inline void
        move_to_plain(Source&& src, Dest& dest, mpl::false_) // src is not a single-element tuple
        {
            dest = std::move(src);
        }
        
        template <typename Source, typename Dest>
        inline void
        move_to_plain(Source&& src, Dest& dest, mpl::true_) // src is a single-element tuple
        {
            dest = std::move(fusion::front(src));
        }

        template <typename Source, typename Dest>
        inline void
        move_to(Source&& src, Dest& dest, plain_attribute)
        {
            typename mpl::and_<
                fusion::traits::is_sequence<Source>,
                is_size_one_sequence<Source> >
            is_single_element_sequence;
        
            move_to_plain(std::forward<Source>(src), dest, is_single_element_sequence);
        }

        template <typename Source, typename Dest>
        inline typename enable_if<is_container<Source>>::type
        move_to(Source&& src, Dest& dest, container_attribute)
        {
            traits::move_to(src.begin(), src.end(), dest);
        }

        template <typename Source, typename Dest>
        inline typename enable_if<
            mpl::and_<
                is_same_size_sequence<Dest, Source>,
                mpl::not_<is_size_one_sequence<Dest> > >
        >::type
        move_to(Source&& src, Dest& dest, tuple_attribute)
        {
            fusion::move(std::forward<Source>(src), dest);
        }

        template <typename Source, typename Dest>
        inline typename enable_if<
            is_size_one_sequence<Dest>
        >::type
        move_to(Source&& src, Dest& dest, tuple_attribute)
        {
            traits::move_to(std::forward<Source>(src), fusion::front(dest));
        }

        template <typename Source, typename Dest>
        inline void
        move_to(Source&& src, Dest& dest, variant_attribute, mpl::false_)
        {
            dest = std::move(src);
        }
        
        template <typename Source, typename Dest>
        inline void
        move_to_variant_from_single_element_sequence(Source&& src, Dest& dest, mpl::false_)
        {
            // dest is a variant, src is a single element fusion sequence that the variant
            // cannot directly hold. We'll try to unwrap the single element fusion sequence.
            
            // Make sure that the Dest variant can really hold Source
            static_assert(variant_has_substitute<Dest, typename fusion::result_of::front<Source>::type>::value,
                "Error! The destination variant (Dest) cannot hold the source type (Source)");
            
            dest = std::move(fusion::front(src));
        }
        
        template <typename Source, typename Dest>
        inline void
        move_to_variant_from_single_element_sequence(Source&& src, Dest& dest, mpl::true_)
        {
            // dest is a variant, src is a single element fusion sequence that the variant
            // *can* directly hold.
            dest = std::move(src);
        }

        template <typename Source, typename Dest>
        inline void
        move_to(Source&& src, Dest& dest, variant_attribute, mpl::true_)
        {
            move_to_variant_from_single_element_sequence(src, dest, variant_has_substitute<Dest, Source>());
        }

        template <typename Source, typename Dest>
        inline void
        move_to(Source&& src, Dest& dest, variant_attribute tag)
        {
            move_to(src, dest, tag, is_size_one_sequence<Source>());
        }

        template <typename Source, typename Dest>
        inline void
        move_to(Source&& src, Dest& dest, optional_attribute)
        {
            dest = std::move(src);
        }

        template <typename Iterator>
        inline void
        move_to(Iterator, Iterator, unused_type, unused_attribute) {}

        template <typename Iterator, typename Dest>
        inline void
        move_to(Iterator first, Iterator last, Dest& dest, container_attribute)
        {
            if (is_empty(dest))
                dest = Dest(first, last);
            else
                append(dest, first, last);
        }

        template <typename Iterator, typename Dest>
        inline typename enable_if<
            is_size_one_sequence<Dest>
        >::type
        move_to(Iterator first, Iterator last, Dest& dest, tuple_attribute)
        {
            traits::move_to(first, last, fusion::front(dest));
        }
        
        template <typename Iterator>
        inline void
        move_to(Iterator first, Iterator last, boost::iterator_range<Iterator>& rng, range_attribute)
        {
            rng = {first, last};
        }
    }

    template <typename Source, typename Dest>
    inline void move_to(Source&& src, Dest& dest)
    {
        detail::move_to(std::move(src), dest
          , typename attribute_category<Dest>::type());
    }

    template <typename T>
    inline void move_to(T& src, T& dest)
    {
        if (boost::addressof(src) != boost::addressof(dest))
            dest = std::move(src);
    }

    template <typename T>
    inline void move_to(T const& src, T& dest)
    {
        if (boost::addressof(src) != boost::addressof(dest))
            dest = std::move(src);
    }

    template <typename T>
    inline void move_to(T&& src, T& dest)
    {
        if (boost::addressof(src) != boost::addressof(dest))
            dest = std::move(src);
    }

    template <typename Iterator, typename Dest>
    inline void move_to(Iterator first, Iterator last, Dest& dest)
    {
        // $$$ Use std::move_iterator when iterator is not a const-iterator $$$
        detail::move_to(first, last, dest, typename attribute_category<Dest>::type());
    }
}}}}

#endif
