/*=============================================================================
    Copyright (c) 2002 2004 2006 Joel de Guzman
    Copyright (c) 2004 Eric Niebler
    http://spirit.sourceforge.net/

    Use, modification and distribution is subject to 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)
=============================================================================*/

#include <map>
#include <boost/foreach.hpp>
#include <boost/spirit/include/classic_core.hpp>
#include <boost/spirit/include/classic_actor.hpp>
#include <boost/spirit/include/classic_loops.hpp>
#include <boost/spirit/include/classic_symbols.hpp>
#include <boost/spirit/include/classic_chset.hpp>
#include <boost/spirit/include/classic_numerics.hpp>
#include <boost/spirit/include/phoenix1_primitives.hpp>
#include "grammar_impl.hpp"
#include "actions_class.hpp"
#include "doc_info_tags.hpp"
#include "phrase_tags.hpp"

namespace quickbook
{
    namespace cl = boost::spirit::classic;

    struct attribute_info
    {
        attribute_info(value::tag_type t, cl::rule<scanner>* r)
            : tag(t), rule(r)
        {}

        value::tag_type tag;
        cl::rule<scanner>* rule;
    };

    struct doc_info_grammar_local
    {
        struct assign_attribute_type
        {
            assign_attribute_type(doc_info_grammar_local& l)
                : l(l)
            {}

            void operator()(value::tag_type& t) const {
                l.attribute_rule = *l.attribute_rules[t];
                l.attribute_tag = t;
            }
            
            doc_info_grammar_local& l;
        };
        
        struct fallback_attribute_type
        {
            fallback_attribute_type(doc_info_grammar_local& l)
                : l(l)
            {}

            void operator()(iterator, iterator) const {
                l.attribute_rule = l.doc_fallback;
                l.attribute_tag = value::default_tag;
            }
            
            doc_info_grammar_local& l;
        };

        cl::rule<scanner>
                        doc_title, doc_simple, doc_phrase, doc_fallback,
                        doc_authors, doc_author,
                        doc_copyright, doc_copyright_holder,
                        doc_source_mode, doc_biblioid,
                        quickbook_version, char_;
        cl::uint_parser<int, 10, 4, 4> doc_copyright_year;
        cl::symbols<> doc_types;
        cl::symbols<value::tag_type> doc_attributes;
        std::map<value::tag_type, cl::rule<scanner>* > attribute_rules;
        value::tag_type attribute_tag;
        cl::rule<scanner> attribute_rule;
        assign_attribute_type assign_attribute;
        fallback_attribute_type fallback_attribute;
        
        doc_info_grammar_local()
            : assign_attribute(*this)
            , fallback_attribute(*this)
        {}
    };

    void quickbook_grammar::impl::init_doc_info()
    {
        doc_info_grammar_local& local = cleanup_.add(
            new doc_info_grammar_local);

        typedef cl::uint_parser<int, 10, 1, 2>  uint2_t;

        local.doc_types =
            "book", "article", "library", "chapter", "part"
          , "appendix", "preface", "qandadiv", "qandaset"
          , "reference", "set"
        ;

        BOOST_FOREACH(value::tag_type t, doc_info_attributes::tags()) {
            local.doc_attributes.add(doc_info_attributes::name(t), t);
        }
        
        doc_info_details =
            space
            >> '[' >> space
            >> (local.doc_types >> cl::eps_p)
                                            [actions.values.reset()]
                                            [actions.values.entry(ph::arg1, ph::arg2, doc_info_tags::type)]
            >> hard_space
            >>  (  *(~cl::eps_p(cl::ch_p('[') | ']' | cl::eol_p) >> local.char_)
                )                           [actions.docinfo_value(ph::arg1, ph::arg2, doc_info_tags::title)]
            >>  !(
                    space >> '[' >>
                        local.quickbook_version
                    >> space >> ']'
                )
            >>  *(
                    space
                >>  '['
                >>  space
                >>  (   local.doc_attributes
                                            [local.assign_attribute]
                    |   (+(cl::alnum_p | '_' | '-'))
                                            [local.fallback_attribute]
                                            [actions.error("Unrecognized document attribute: '%s'.")]
                    )
                >>  hard_space
                >>  actions.values.list(ph::var(local.attribute_tag))
                    [local.attribute_rule]
                >>  space
                >>  ']'
                >>  +cl::eol_p
                )
            >>  space                       [actions.values.sort()]
            >>  ']'
            >>  +cl::eol_p
            ;

        local.quickbook_version =
            actions.values.list(doc_info_tags::qbk_version)
            [   "quickbook"
            >>  hard_space
            >>  (   cl::uint_p              [actions.values.entry(ph::arg1)]
                    >> '.' 
                    >>  uint2_t()           [actions.values.entry(ph::arg1)]
                )
            ]
            ;

        // TODO: Clear phrase afterwards?

        local.doc_fallback = (*(~cl::eps_p(']') >> local.char_));

        // TODO: Restrictions on doc_id and doc_dirname?

        local.doc_simple = (*(~cl::eps_p(']') >> local.char_)) [actions.docinfo_value(ph::arg1, ph::arg2)];
        local.attribute_rules[doc_info_attributes::version] = &local.doc_simple;
        local.attribute_rules[doc_info_attributes::id] = &local.doc_simple;
        local.attribute_rules[doc_info_attributes::dirname] = &local.doc_simple;
        local.attribute_rules[doc_info_attributes::category] = &local.doc_simple;
        local.attribute_rules[doc_info_attributes::last_revision] = &local.doc_simple;
        local.attribute_rules[doc_info_attributes::lang] = &local.doc_simple;

        local.doc_copyright_holder
            =   *(  ~cl::eps_p
                    (   ']'
                    |   ',' >> space >> local.doc_copyright_year
                    )
                >>  local.char_
                );

        local.doc_copyright =
            *(  +(  local.doc_copyright_year
                                            [actions.values.entry(ph::arg1, doc_info_tags::copyright_year)]
                >>  space
                >>  !(  '-'
                    >>  space
                    >>  local.doc_copyright_year
                                            [actions.values.entry(ph::arg1, doc_info_tags::copyright_year_end)]
                    >>  space
                    )
                >>  !cl::ch_p(',')
                >>  space
                )
            >>  local.doc_copyright_holder  [actions.docinfo_value(ph::arg1, ph::arg2, doc_info_tags::copyright_name)]
            >>  !cl::ch_p(',')
            >>  space
            )
            ;

        local.attribute_rules[doc_info_attributes::copyright] = &local.doc_copyright;

        local.doc_phrase = simple_phrase [actions.docinfo_value(ph::arg1, ph::arg2)];
        local.attribute_rules[doc_info_attributes::purpose] = &local.doc_phrase;
        local.attribute_rules[doc_info_attributes::license] = &local.doc_phrase;

        local.doc_author =
                '['
            >>   space
            >>  (*(~cl::eps_p(',') >> local.char_))
                                            [actions.docinfo_value(ph::arg1, ph::arg2, doc_info_tags::author_surname)]
            >>  ',' >> space
            >>  (*(~cl::eps_p(']') >> local.char_))
                                            [actions.docinfo_value(ph::arg1, ph::arg2, doc_info_tags::author_first)]
            >>  ']'
            ;

        local.doc_authors =
            *(  local.doc_author
            >>  space
            >>  !(cl::ch_p(',') >> space)
            )
            ;

        local.attribute_rules[doc_info_attributes::authors] = &local.doc_authors;

        local.doc_source_mode =
                (
                   cl::str_p("c++") 
                |  "python"
                |  "teletype"
                )                           [cl::assign_a(actions.source_mode)]
            ;

        local.attribute_rules[doc_info_attributes::source_mode] = &local.doc_source_mode;

        local.doc_biblioid =
                (+cl::alnum_p)              [actions.values.entry(ph::arg1, ph::arg2, doc_info_tags::biblioid_class)]
            >>  hard_space
            >>  (+(~cl::eps_p(']') >> local.char_))
                                            [actions.docinfo_value(ph::arg1, ph::arg2, doc_info_tags::biblioid_value)]
            ;

        local.attribute_rules[doc_info_attributes::biblioid] = &local.doc_biblioid;

        local.char_ =
                cl::str_p("\\n")            [actions.break_]
            |   "\\ "                       // ignore an escaped space
            |   '\\' >> cl::punct_p         [actions.raw_char]
            |   "\\u" >> cl::repeat_p(4)
                    [cl::chset<>("0-9a-fA-F")]
                                            [actions.escape_unicode]
            |   "\\U" >> cl::repeat_p(8)
                    [cl::chset<>("0-9a-fA-F")]
                                            [actions.escape_unicode]
            |   ("'''" >> !eol)
            >>  actions.values.save()
                [  (*(cl::anychar_p - "'''"))
                                            [actions.values.entry(ph::arg1, ph::arg2, phrase_tags::escape)]
                >>  cl::str_p("'''")        [actions.element]
                ]
            |   cl::anychar_p               [actions.plain_char]
            ;
    }
}
