//  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_SYMBOLS_NOV_23_2009_1251PM)
#define BOOST_SPIRIT_KARMA_SYMBOLS_NOV_23_2009_1251PM

#include <boost/spirit/home/support/common_terminals.hpp>
#include <boost/spirit/home/support/info.hpp>
#include <boost/spirit/home/support/unused.hpp>
#include <boost/spirit/home/support/attributes_fwd.hpp>
#include <boost/spirit/home/support/detail/get_encoding.hpp>
#include <boost/spirit/home/karma/detail/attributes.hpp>
#include <boost/spirit/home/karma/detail/extract_from.hpp>
#include <boost/spirit/home/karma/domain.hpp>
#include <boost/spirit/home/karma/meta_compiler.hpp>
#include <boost/spirit/home/karma/reference.hpp>
#include <boost/spirit/home/karma/generate.hpp>
#include <boost/spirit/home/karma/delimit_out.hpp>
#include <boost/spirit/home/karma/detail/get_casetag.hpp>
#include <boost/spirit/home/karma/detail/string_generate.hpp>
#include <boost/config.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/mpl/if.hpp>
#include <map>
#include <set>

#if defined(BOOST_MSVC)
# pragma warning(push)
# pragma warning(disable: 4355) // 'this' : used in base member initializer list warning
#endif

///////////////////////////////////////////////////////////////////////////////
namespace boost { namespace spirit { namespace traits
{
    template <typename T, typename Attribute, typename Enable>
    struct symbols_lookup
    {
        typedef
            mpl::eval_if<fusion::traits::is_sequence<T>
              , traits::detail::value_at_c<T, 0>
              , detail::add_const_ref<T> > sequence_type;
        typedef typename
            mpl::eval_if<traits::is_container<T>
              , traits::container_value<T>
              , sequence_type>::type type;

        // fusion sequence
        template <typename T_>
        static type call(T_ const& t, mpl::false_, mpl::true_)
        {
            return fusion::at_c<0>(t);
        }

        // container
        template <typename T_, typename IsSequence>
        static type call(T_ const& t, mpl::true_, IsSequence)
        {
            return t[0];
        }

        // not a container and not a fusion sequence
        template <typename T_>
        static type call(T_ const& t, mpl::false_, mpl::false_)
        {
            return t;
        }

        static type call(T const& t)
        {
            typedef typename traits::is_container<T>::type is_container;
            typedef typename fusion::traits::is_sequence<T>::type is_sequence;

            return call(t, is_container(), is_sequence());
        }
    };

    template <typename Attribute>
    struct symbols_lookup<Attribute, Attribute>
    {
        typedef Attribute const& type;

        static type call(Attribute const& t)
        {
            return t;
        }
    };

    template <typename Attribute, typename T, typename Enable>
    struct symbols_value
    {
        typedef
            mpl::eval_if<fusion::traits::is_sequence<T>
              , traits::detail::value_at_c<T, 1>
              , mpl::identity<unused_type> > sequence_type;
        typedef typename
            mpl::eval_if<traits::is_container<T>
              , traits::container_value<T>
              , sequence_type>::type type;

        // fusion sequence
        template <typename T_>
        static type call(T_ const& t, mpl::false_, mpl::true_)
        {
            return fusion::at_c<1>(t);
        }

        // container
        template <typename T_, typename IsSequence>
        static type call(T_ const& t, mpl::true_, IsSequence)
        {
            return t[1];
        }

        // not a container nor a fusion sequence
        template <typename T_>
        static type call(T_ const&, mpl::false_, mpl::false_)
        {
            return unused;
        }

        static type call(T const& t)
        {
            typedef typename traits::is_container<T>::type is_container;
            typedef typename fusion::traits::is_sequence<T>::type is_sequence;

            return call(t, is_container(), is_sequence());
        }
    };

    template <typename Attribute>
    struct symbols_value<Attribute, Attribute>
    {
        typedef unused_type type;

        static type call(Attribute const&)
        {
            return unused;
        }
    };
}}}

///////////////////////////////////////////////////////////////////////////////
namespace boost { namespace spirit { namespace karma
{
    ///////////////////////////////////////////////////////////////////////////
    template <typename T, typename Attribute>
    struct symbols_lookup
      : mpl::if_<
            traits::not_is_unused<T>
          , std::map<Attribute, T>
          , std::set<Attribute>
        >
    {};

    ///////////////////////////////////////////////////////////////////////////
    namespace detail
    {
        ///////////////////////////////////////////////////////////////////////
        template <typename CharEncoding, typename Tag>
        struct generate_encoded
        {
            typedef typename
                proto::terminal<tag::char_code<Tag, CharEncoding> >::type
            encoding_type;

            template <typename OutputIterator, typename Expr, typename Attribute>
            static bool call(OutputIterator& sink, Expr const& expr
              , Attribute const& attr)
            {
                encoding_type const encoding = encoding_type();
                return karma::generate(sink, encoding[expr], attr);
            }
        };

        template <>
        struct generate_encoded<unused_type, unused_type>
        {
            template <typename OutputIterator, typename Expr, typename Attribute>
            static bool call(OutputIterator& sink, Expr const& expr
              , Attribute const& attr)
            {
                return karma::generate(sink, expr, attr);
            }
        };
    }

    template <
        typename Attribute = char, typename T = unused_type
      , typename Lookup = typename symbols_lookup<T, Attribute>::type
      , typename CharEncoding = unused_type, typename Tag = unused_type>
    struct symbols
      : proto::extends<
            typename proto::terminal<
                reference<symbols<Attribute, T, Lookup, CharEncoding, Tag> >
            >::type
          , symbols<Attribute, T, Lookup, CharEncoding, Tag> >
      , primitive_generator<
            symbols<Attribute, T, Lookup, CharEncoding, Tag> >
    {
        typedef T value_type;       // the value associated with each entry

        typedef reference<symbols> reference_;
        typedef typename proto::terminal<reference_>::type terminal;
        typedef proto::extends<terminal, symbols> base_type;

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

        symbols(std::string const& name = "symbols")
          : base_type(terminal::make(reference_(*this)))
          , add(*this)
          , remove(*this)
          , lookup(new Lookup())
          , name_(name)
        {}

        symbols(symbols const& syms)
          : base_type(terminal::make(reference_(*this)))
          , add(*this)
          , remove(*this)
          , lookup(syms.lookup)
          , name_(syms.name_)
        {}

        template <typename CharEncoding_, typename Tag_>
        symbols(symbols<Attribute, T, Lookup, CharEncoding_, Tag_> const& syms)
          : base_type(terminal::make(reference_(*this)))
          , add(*this)
          , remove(*this)
          , lookup(syms.lookup)
          , name_(syms.name_)
        {}

        template <typename Symbols, typename Data>
        symbols(Symbols const& syms, Data const& data
              , std::string const& name = "symbols")
          : base_type(terminal::make(reference_(*this)))
          , add(*this)
          , remove(*this)
          , lookup(new Lookup())
          , name_(name)
        {
            typename range_const_iterator<Symbols>::type si = boost::begin(syms);
            typename range_const_iterator<Data>::type di = boost::begin(data);
            while (si != boost::end(syms))
                add(*si++, *di++);
        }

        symbols&
        operator=(symbols const& rhs)
        {
            *lookup = *rhs.lookup;
            name_ = rhs.name_;
            return *this;
        }

        template <typename CharEncoding_, typename Tag_>
        symbols&
        operator=(symbols<Attribute, T, Lookup, CharEncoding_, Tag_> const& rhs)
        {
            *lookup = *rhs.lookup;
            name_ = rhs.name_;
            return *this;
        }

        void clear()
        {
            lookup->clear();
        }

        struct adder;
        struct remover;

        template <typename Attr, typename T_>
        adder const&
        operator=(std::pair<Attr, T_> const& p)
        {
            lookup->clear();
            return add(p.first, p.second);
        }

        template <typename Attr, typename T_>
        friend adder const&
        operator+= (symbols& sym, std::pair<Attr, T_> const& p)
        {
            return sym.add(p.first, p.second);
        }

        template <typename Attr>
        friend remover const&
        operator-= (symbols& sym, Attr const& attr)
        {
            return sym.remove(attr);
        }

#if defined(BOOST_NO_RVALUE_REFERENCES)
        // non-const version needed to suppress proto's += kicking in
        template <typename Attr, typename T_>
        friend adder const&
        operator+= (symbols& sym, std::pair<Attr, T_>& p)
        {
            return sym.add(p.first, p.second);
        }

        // non-const version needed to suppress proto's -= kicking in
        template <typename Attr>
        friend remover const&
        operator-= (symbols& sym, Attr& attr)
        {
            return sym.remove(attr);
        }
#else
        // for rvalue references
        template <typename Attr, typename T_>
        friend adder const&
        operator+= (symbols& sym, std::pair<Attr, T_>&& p)
        {
            return sym.add(p.first, p.second);
        }

        // for rvalue references
        template <typename Attr>
        friend remover const&
        operator-= (symbols& sym, Attr&& attr)
        {
            return sym.remove(attr);
        }
#endif
        template <typename F>
        void for_each(F f) const
        {
            std::for_each(lookup->begin(), lookup->end(), f);
        }

        template <typename Attr>
        value_type* find(Attr const& attr)
        {
            typename Lookup::iterator it = lookup->find(attr);
            return (it != lookup->end()) ? &(*it).second : 0;
        }

        template <typename Attr>
        value_type& at(Attr const& attr)
        {
            return (*lookup)[attr];
        }

        ///////////////////////////////////////////////////////////////////////
        template <typename OutputIterator, typename Context, typename Delimiter
          , typename Attr>
        bool generate(OutputIterator& sink, Context&, Delimiter const& d
          , Attr const& attr) const
        {
            typename Lookup::iterator it = lookup->find(
                traits::symbols_lookup<Attr, Attribute>::call(attr));
            if (it == lookup->end())
                return false;

            return karma::detail::generate_encoded<CharEncoding, Tag>::call(
                        sink, (*it).second
                      , traits::symbols_value<Attribute, Attr>::call(attr)) &&
                   karma::delimit_out(sink, d);
        }

        template <typename Context>
        info what(Context&) const
        {
            return info(name_);
        }

        void name(std::string const &str)
        {
            name_ = str;
        }
        std::string const &name() const
        {
            return name_;
        }

        ///////////////////////////////////////////////////////////////////////
        struct adder
        {
            template <typename, typename = unused_type>
            struct result { typedef adder const& type; };

            adder(symbols& sym)
              : sym(sym)
            {
            }

            template <typename Attr>
            adder const&
            operator()(Attr const& attr, T const& val = T()) const
            {
                sym.lookup->insert(typename Lookup::value_type(attr, val));
                return *this;
            }

            template <typename Attr>
            adder const&
            operator, (Attr const& attr) const
            {
                sym.lookup->insert(typename Lookup::value_type(attr, T()));
                return *this;
            }

            symbols& sym;

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

        struct remover
        {
            template <typename>
            struct result { typedef remover const& type; };

            remover(symbols& sym)
              : sym(sym)
            {
            }

            template <typename Attr>
            remover const&
            operator()(Attr const& attr) const
            {
                sym.lookup->erase(attr);
                return *this;
            }

            template <typename Attr>
            remover const&
            operator, (Attr const& attr) const
            {
                sym.lookup->erase(attr);
                return *this;
            }

            symbols& sym;

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

        adder add;
        remover remove;
        shared_ptr<Lookup> lookup;
        std::string name_;
    };

    ///////////////////////////////////////////////////////////////////////////
    // specialization for unused stored type
    template <
        typename Attribute, typename Lookup
      , typename CharEncoding, typename Tag>
    struct symbols<Attribute, unused_type, Lookup, CharEncoding, Tag>
      : proto::extends<
            typename proto::terminal<
                spirit::karma::reference<
                    symbols<Attribute, unused_type, Lookup, CharEncoding, Tag> >
            >::type
          , symbols<Attribute, unused_type, Lookup, CharEncoding, Tag>
        >
      , spirit::karma::generator<
            symbols<Attribute, unused_type, Lookup, CharEncoding, Tag> >
    {
        typedef unused_type value_type;  // the value associated with each entry

        typedef spirit::karma::reference<symbols> reference_;
        typedef typename proto::terminal<reference_>::type terminal;
        typedef proto::extends<terminal, symbols> base_type;

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

        symbols(std::string const& name = "symbols")
          : base_type(terminal::make(reference_(*this)))
          , add(*this)
          , remove(*this)
          , lookup(new Lookup())
          , name_(name)
        {}

        symbols(symbols const& syms)
          : base_type(terminal::make(reference_(*this)))
          , add(*this)
          , remove(*this)
          , lookup(syms.lookup)
          , name_(syms.name_)
        {}

        template <typename CharEncoding_, typename Tag_>
        symbols(symbols<Attribute, unused_type, Lookup, CharEncoding_, Tag_> const& syms)
          : base_type(terminal::make(reference_(*this)))
          , add(*this)
          , remove(*this)
          , lookup(syms.lookup)
          , name_(syms.name_)
        {}

        template <typename Symbols, typename Data>
        symbols(Symbols const& syms, Data const& data
              , std::string const& name = "symbols")
          : base_type(terminal::make(reference_(*this)))
          , add(*this)
          , remove(*this)
          , lookup(new Lookup())
          , name_(name)
        {
            typename range_const_iterator<Symbols>::type si = boost::begin(syms);
            typename range_const_iterator<Data>::type di = boost::begin(data);
            while (si != boost::end(syms))
                add(*si++, *di++);
        }

        symbols&
        operator=(symbols const& rhs)
        {
            *lookup = *rhs.lookup;
            name_ = rhs.name_;
            return *this;
        }

        template <typename CharEncoding_, typename Tag_>
        symbols&
        operator=(symbols<Attribute, unused_type, Lookup, CharEncoding_, Tag_> const& rhs)
        {
            *lookup = *rhs.lookup;
            name_ = rhs.name_;
            return *this;
        }

        void clear()
        {
            lookup->clear();
        }

        struct adder;
        struct remover;

        template <typename Attr>
        adder const&
        operator=(Attr const& attr)
        {
            lookup->clear();
            return add(attr);
        }

        template <typename Attr>
        friend adder const&
        operator+= (symbols& sym, Attr const& attr)
        {
            return sym.add(attr);
        }

        template <typename Attr>
        friend remover const&
        operator-= (symbols& sym, Attr const& attr)
        {
            return sym.remove(attr);
        }

        // non-const version needed to suppress proto's += kicking in
        template <typename Attr>
        friend adder const&
        operator+= (symbols& sym, Attr& attr)
        {
            return sym.add(attr);
        }

        // non-const version needed to suppress proto's -= kicking in
        template <typename Attr>
        friend remover const&
        operator-= (symbols& sym, Attr& attr)
        {
            return sym.remove(attr);
        }

        template <typename F>
        void for_each(F f) const
        {
            std::for_each(lookup->begin(), lookup->end(), f);
        }

        template <typename Attr>
        value_type const* find(Attr const& attr)
        {
            typename Lookup::iterator it = lookup->find(attr);
            return (it != lookup->end()) ? &unused : 0;
        }

        template <typename Attr>
        value_type at(Attr const& attr)
        {
            typename Lookup::iterator it = lookup->find(attr);
            if (it == lookup->end())
                add(attr);
            return unused;
        }

        ///////////////////////////////////////////////////////////////////////
        template <typename OutputIterator, typename Context, typename Delimiter
          , typename Attr>
        bool generate(OutputIterator& sink, Context&, Delimiter const& d
          , Attr const& attr) const
        {
            typename Lookup::iterator it = lookup->find(
                traits::symbols_lookup<Attr, Attribute>::call(attr));
            if (it == lookup->end())
                return false;

            return karma::detail::generate_encoded<CharEncoding, Tag>::
                      call(sink
                        , traits::symbols_lookup<Attr, Attribute>::call(attr)
                        , unused) &&
                   karma::delimit_out(sink, d);
        }

        template <typename Context>
        info what(Context&) const
        {
            return info(name_);
        }

        void name(std::string const &str)
        {
            name_ = str;
        }
        std::string const &name() const
        {
            return name_;
        }

        ///////////////////////////////////////////////////////////////////////
        struct adder
        {
            template <typename, typename = unused_type>
            struct result { typedef adder const& type; };

            adder(symbols& sym)
              : sym(sym)
            {
            }

            template <typename Attr>
            adder const&
            operator()(Attr const& attr) const
            {
                sym.lookup->insert(attr);
                return *this;
            }

            template <typename Attr>
            adder const&
            operator, (Attr const& attr) const
            {
                sym.lookup->insert(attr);
                return *this;
            }

            symbols& sym;

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

        struct remover
        {
            template <typename>
            struct result { typedef remover const& type; };

            remover(symbols& sym)
              : sym(sym)
            {
            }

            template <typename Attr>
            remover const&
            operator()(Attr const& attr) const
            {
                sym.lookup->erase(attr);
                return *this;
            }

            template <typename Attr>
            remover const&
            operator, (Attr const& attr) const
            {
                sym.lookup->erase(attr);
                return *this;
            }

            symbols& sym;

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

        adder add;
        remover remove;
        shared_ptr<Lookup> lookup;
        std::string name_;
    };

    ///////////////////////////////////////////////////////////////////////////
    // Generator generators: make_xxx function (objects)
    ///////////////////////////////////////////////////////////////////////////
    template <typename Attribute, typename T, typename Lookup
      , typename CharEnconding, typename Tag, typename Modifiers>
    struct make_primitive<
        reference<symbols<Attribute, T, Lookup, CharEnconding, Tag> >
      , Modifiers>
    {
        static bool const lower =
            has_modifier<Modifiers, tag::char_code_base<tag::lower> >::value;
        static bool const upper =
            has_modifier<Modifiers, tag::char_code_base<tag::upper> >::value;

        typedef reference<
            symbols<Attribute, T, Lookup, CharEnconding, Tag>
        > reference_;

        typedef typename mpl::if_c<
            lower || upper
          , symbols<
                Attribute, T, Lookup
              , typename spirit::detail::get_encoding_with_case<
                    Modifiers, unused_type, lower || upper>::type
              , typename detail::get_casetag<Modifiers, lower || upper>::type>
          , reference_>::type
        result_type;

        result_type operator()(reference_ ref, unused_type) const
        {
            return result_type(ref.ref.get());
        }
    };
}}}

namespace boost { namespace spirit { namespace traits
{
    ///////////////////////////////////////////////////////////////////////////
    template <typename Attribute, typename T, typename Lookup
      , typename CharEncoding, typename Tag
      , typename Attr, typename Context, typename Iterator>
    struct handles_container<karma::symbols<Attribute, T, Lookup, CharEncoding, Tag>
            , Attr, Context, Iterator>
      : traits::is_container<Attr> {};
}}}

#if defined(BOOST_MSVC)
# pragma warning(pop)
#endif

#endif

