/*=============================================================================
    Copyright (c) 2001-2011 Joel de Guzman
    Copyright (c) 2011 Thomas Bernard

    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)
=============================================================================*/

#define FUSION_MAX_VECTOR_SIZE 50 
#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS 
#define BOOST_MPL_LIMIT_LIST_SIZE 50 
#define BOOST_MPL_LIMIT_VECTOR_SIZE 50

#include "../measure.hpp"
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_container.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi_permutation.hpp>
#include <boost/spirit/home/qi/string/tst_map.hpp>
#include <boost/spirit/repository/include/qi_kwd.hpp>
#include <boost/spirit/repository/include/qi_keywords.hpp>
#include <boost/optional.hpp>
#include <boost/spirit/home/phoenix/core/argument.hpp>
#include <boost/spirit/home/phoenix/bind/bind_member_variable.hpp>

#include <iostream>
#include <string>
#include <complex>
#include <vector>
#include <iterator>
#include <stdexcept>

#include <boost/preprocessor/control/if.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>

#define KEYS_5

#include "keywords.hpp"

#define declOptions(r, data, i, elem) boost::optional<int> BOOST_PP_CAT(option,i);
#define fusionOptions(r, data, i, elem) (boost::optional<int>, BOOST_PP_CAT(option,i))


namespace client
{
        namespace qi = boost::spirit::qi;
        namespace ascii = boost::spirit::ascii;

        ///////////////////////////////////////////////////////////////////////////
        //  Our parsedData struct
        ///////////////////////////////////////////////////////////////////////////
        //[tutorial_parsedData_struct
        struct parsedDataOptions
        {   
                BOOST_PP_SEQ_FOR_EACH_I(declOptions,_,keys)
        };
        struct parsedData
        {

                std::string name;
                parsedDataOptions options;        
                void clear()
                {
                        name.clear();            
                }
        };

        struct parsedData2
        {
                std::string name;
                BOOST_PP_SEQ_FOR_EACH_I(declOptions,_,keys)

                        void clear()
                        {
                                name.clear();            
                        }
        };
}

std::ostream &operator<<(std::ostream & os, client::parsedData &data)
{
        os << data.name <<std::endl;

#define generateOutput1(r, d, i, elem) if( BOOST_PP_CAT(data.options.option, i) ) os<< BOOST_PP_STRINGIZE( BOOST_PP_CAT(option,i)) <<" "<< * BOOST_PP_CAT(data.options.option , i)<<std::endl;
        BOOST_PP_SEQ_FOR_EACH_I(generateOutput1,_,keys)

                os<<std::endl;

        return os;
}

std::ostream &operator<<(std::ostream & os, client::parsedData2 &data)
{
        os << data.name <<std::endl;

#define generateOutput2(r, d, i, elem) if(BOOST_PP_CAT(data.option, i)) os<< BOOST_PP_STRINGIZE( BOOST_PP_CAT(option,i)) <<" "<< * BOOST_PP_CAT(data.option,i)<<std::endl;
        BOOST_PP_SEQ_FOR_EACH_I(generateOutput2,_,keys)

                os<<std::endl;

        return os;
}



BOOST_FUSION_ADAPT_STRUCT(
                client::parsedDataOptions,
                BOOST_PP_SEQ_FOR_EACH_I(fusionOptions,_,keys)
                )

BOOST_FUSION_ADAPT_STRUCT(
                client::parsedData,
                (std::string, name)
                (client::parsedDataOptions, options)    
                )

BOOST_FUSION_ADAPT_STRUCT(
                client::parsedData2,
                (std::string, name)
                BOOST_PP_SEQ_FOR_EACH_I(fusionOptions,_,keys)
                )
enum variation
{
        full,
        no_assign,
        assign
};
namespace client
{


        ///////////////////////////////////////////////////////////////////////////////
        //  Our parsedData parser
        ///////////////////////////////////////////////////////////////////////////////
        //[tutorial_parsedData_parser
        template <typename Iterator>
                struct permutation_parser : qi::grammar<Iterator, parsedData(), ascii::space_type>
        {
                permutation_parser() : permutation_parser::base_type(start)
                {
                        using qi::int_;
                        using qi::lit;
                        using qi::double_;
                        using qi::lexeme;
                        using ascii::char_;
                        using boost::phoenix::at_c;
                        using boost::phoenix::assign;
                        using qi::_r1;
                        using qi::_1;
                        using qi::_val;
                        using qi::omit;
                        using qi::repeat;


                        quoted_string %= lexeme[+(char_-' ')];

#define generateOptions1(r, data, i, elem) BOOST_PP_IF(i, ^(lit(elem) > int_) , (lit(elem) > int_))
                        options = (BOOST_PP_SEQ_FOR_EACH_I(generateOptions1,_,keys));

                        start %=
                                quoted_string 
                                >> options;                
                        ;
                        v_vals = repeat(1,2)[int_];
                }

                typedef parsedData parser_target_type;

                qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
                qi::rule<Iterator, parsedDataOptions(), ascii::space_type> options;
                qi::rule<Iterator, std::vector<int>(), ascii::space_type> v_vals;

                qi::rule<Iterator, parsedData(), ascii::space_type> start;
        };

        template <typename Iterator>
                struct alternative_parser : qi::grammar<Iterator, parsedData2(), ascii::space_type>
        {
                alternative_parser() : alternative_parser::base_type(start)
                {
                        using qi::int_;
                        using qi::lit;
                        using qi::double_;
                        using qi::lexeme;
                        using ascii::char_;
                        using boost::phoenix::at_c;                        
                        using qi::_r1;
                        using qi::_1;
                        using qi::_val;

                        quoted_string %= lexeme[+(char_-' ')];

#define generateOptions2(r, data, i, elem) BOOST_PP_IF(i, |(lit(elem) > int_[at_c<i+1>(_r1)=_1]) , (lit(elem) > int_[at_c<i+1>(_r1)=_1]))
                        options = (BOOST_PP_SEQ_FOR_EACH_I(generateOptions2,_,keys));

                        start =
                                quoted_string [at_c<0>(_val)=_1]
                                >> *options(_val);                
                        ;
                }

                typedef parsedData2 parser_target_type;

                qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
                qi::rule<Iterator, void(parsedData2 & ), ascii::space_type> options;
                qi::rule<Iterator, parsedData2(), ascii::space_type> start;
        };



        template <typename Iterator,typename variation>
                struct tst_parser : qi::grammar<Iterator, parsedData2(), ascii::space_type>
        {
                typedef variation variation_type;

                tst_parser() : tst_parser::base_type(startalias)
                {
                        namespace phx = boost::phoenix;
                        using qi::int_;
                        using qi::lit;
                        using qi::double_;
                        using qi::lexeme;
                        using ascii::char_;            
                        using boost::phoenix::at_c;                        
                        using qi::_r1;
                        using qi::_1;
                        using qi::_a;
                        using qi::_val;
                        using qi::locals;
                        using qi::parameterized_nonterminal;

                        startalias = start.alias();
                        quoted_string %= lexeme[+(char_-' ')];

#define generateRules(r, data, i, elem) BOOST_PP_CAT(rule,i) = int_[phx::at_c<i+1>(*phx::ref(currentObj))=_1];
                        BOOST_PP_SEQ_FOR_EACH_I(generateRules,_,keys)

#define generateOptions3(r, data, i, elem) (elem,& BOOST_PP_CAT(rule,i))


                                options.add BOOST_PP_SEQ_FOR_EACH_I(generateOptions3,_,keys);
                        switch(variation_type::value)
                        {
                                case full:
                                        {
                                                start =
                                                        quoted_string [at_c<0>(_val)=_1][phx::ref(currentObj)=&_val]
                                                        >> *( options [_a=_1] >> lazy(*_a));
                                                ;
                                                break;
                                        }
                                case no_assign:
                                        {
                                                start =
                                                        quoted_string [at_c<0>(_val)=_1][phx::ref(currentObj)=&_val]
                                                        >> *( options >> int_);
                                                ;
                                                break;
                                        }
                                case assign:
                                        {
                                                start =
                                                        quoted_string [at_c<0>(_val)=_1][phx::ref(currentObj)=&_val]
                                                        >> *( options [_a=_1] >> int_);
                                                ;
                                                break;
                                        }
                        }


                }

                parsedData2 *currentObj;

                typedef parsedData2 parser_target_type;

                qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
                typedef qi::rule<Iterator, ascii::space_type> optionsRule;
#define declareRules(r, data, i, elem) optionsRule BOOST_PP_CAT(rule,i);

                BOOST_PP_SEQ_FOR_EACH_I(declareRules,_,keys)

                        qi::symbols<char,optionsRule* > options;
                qi::rule<Iterator, parsedData2(), ascii::space_type> startalias;
                qi::rule<Iterator, parsedData2(), qi::locals<optionsRule*>, ascii::space_type> start;
        };



        template <typename Iterator,typename variation>
                struct tst_map_parser : qi::grammar<Iterator, parsedData2(), ascii::space_type>
        {
                typedef variation variation_type;
                tst_map_parser() : tst_map_parser::base_type(startalias)
                {
                        namespace phx = boost::phoenix;
                        using qi::int_;
                        using qi::lit;
                        using qi::double_;
                        using qi::lexeme;
                        using ascii::char_;            
                        using boost::phoenix::at_c;                        
                        using qi::_r1;
                        using qi::_1;
                        using qi::_a;
                        using qi::_val;
                        using qi::locals;
                        using qi::parameterized_nonterminal;

                        startalias = start.alias();
                        quoted_string %= lexeme[+(char_-' ')];

#define generateRules3(r, data, i, elem) BOOST_PP_CAT(rule,i) = int_[phx::at_c<i+1>(*phx::ref(currentObj))=_1];
                        BOOST_PP_SEQ_FOR_EACH_I(generateRules3,_,keys)

#define generateOptions3(r, data, i, elem) (elem,& BOOST_PP_CAT(rule,i))


                                options.add BOOST_PP_SEQ_FOR_EACH_I(generateOptions3,_,keys);

                        switch(variation_type::value)
                        {
                                case full:
                                        {
                                                start =
                                                        quoted_string [at_c<0>(_val)=_1][phx::ref(currentObj)=&_val]
                                                        >> *( options [_a=_1] >> lazy(*_a));
                                                ;
                                                break;
                                        }
                                case no_assign:
                                        {
                                                start =
                                                        quoted_string [at_c<0>(_val)=_1][phx::ref(currentObj)=&_val]
                                                        >> *( options >> int_);
                                                ;
                                                break;
                                        }
                                case assign:
                                        {
                                                start =
                                                        quoted_string [at_c<0>(_val)=_1][phx::ref(currentObj)=&_val]
                                                        >> *( options [_a=_1] >> int_);
                                                ;
                                                break;
                                        }
                        }
                }

                parsedData2 *currentObj;

                typedef parsedData2 parser_target_type;

                qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
                typedef qi::rule<Iterator, ascii::space_type> optionsRule;
#define declareRules(r, data, i, elem) optionsRule BOOST_PP_CAT(rule,i);

                BOOST_PP_SEQ_FOR_EACH_I(declareRules,_,keys)

                        qi::symbols<char,optionsRule*, boost::spirit::qi::tst_map<char,optionsRule*> > options;
                qi::rule<Iterator, parsedData2(), ascii::space_type> startalias;
                qi::rule<Iterator, parsedData2(), qi::locals<optionsRule*>, ascii::space_type> start;
        };


        template <typename Iterator>
                struct kwd_parser : qi::grammar<Iterator, parsedData(), ascii::space_type>
        {
                kwd_parser() : kwd_parser::base_type(start)
                {
                        using qi::int_;
                        using qi::lit;
                        using qi::double_;
                        using qi::lexeme;
                        using ascii::char_;
                        using qi::_r1;
                        using qi::_1;
                        using qi::_val;
                        using boost::spirit::repository::qi::kwd;

                        quoted_string %= lexeme[+(char_-' ')];

#define generateOptions4(r, data, i, elem) BOOST_PP_IF(i, / kwd( elem )[ int_ ] , kwd(  elem )[ int_ ] )
                        options = (BOOST_PP_SEQ_FOR_EACH_I(generateOptions4,_,keys));

                        start %=
                                quoted_string 
                                >> options;                
                        ;
                }

                typedef parsedData parser_target_type;

                qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
                qi::rule<Iterator, parsedDataOptions(), ascii::space_type> options;
                qi::rule<Iterator, boost::fusion::vector<boost::optional<int>,boost::optional<int> > () , ascii::space_type> v_vals;

                qi::rule<Iterator, parsedData(), ascii::space_type> start;
        };

}


template <typename parserType>
        struct timeParser : test::base{
                timeParser(const std::string & str) : str(str)
                {
                }
                parserType &get_parser(){
                        static parserType parser;
                        return parser;
                }    

                std::string str;

                void benchmark()    
                {

                        using boost::spirit::ascii::space;
                        bool r = false;
                        std::string::const_iterator end = str.end();
                        std::string::const_iterator iter = str.begin();


                        typename parserType::parser_target_type data;
                        r = phrase_parse(iter, end, get_parser(), space, data);

                        if (r && iter == end)
                        {
                                this->val += data.name.size();
                        }
                        else
                        {
                                throw std::runtime_error("Parsing failed");
                        }
                }

        };




typedef std::string::const_iterator iterator_type;
typedef client::permutation_parser<iterator_type> permutation_parser;
typedef client::kwd_parser<iterator_type> kwd_parser;
typedef client::alternative_parser<iterator_type> alternative_parser;
typedef client::tst_map_parser<iterator_type, boost::mpl::int_<full> > tst_map_parser;

struct permutation_timer_fwd : timeParser<permutation_parser>
{
        permutation_timer_fwd() : timeParser<permutation_parser>(fwd) {}
};

struct permutation_timer_back : timeParser<permutation_parser>
{
        permutation_timer_back() : timeParser<permutation_parser>(back) {}
};

struct alternative_timer_fwd : timeParser<alternative_parser>
{
        alternative_timer_fwd() : timeParser<alternative_parser>(fwd) {}
};

struct alternative_timer_back : timeParser<alternative_parser>
{
        alternative_timer_back() : timeParser<alternative_parser>(back) {}
};

struct tst_timer_fwd_full : timeParser< client::tst_parser<iterator_type, boost::mpl::int_<full> > >
{
        tst_timer_fwd_full() : timeParser< client::tst_parser<iterator_type, boost::mpl::int_<full> > >(fwd) {}
};

struct tst_timer_fwd_no_assign : timeParser< client::tst_parser<iterator_type, boost::mpl::int_<no_assign> > >
{
        tst_timer_fwd_no_assign() : timeParser< client::tst_parser<iterator_type,boost::mpl::int_<no_assign> > >(fwd) {}
};

struct tst_timer_fwd_assign : timeParser< client::tst_parser<iterator_type,boost::mpl::int_<assign> > >
{
        tst_timer_fwd_assign() : timeParser< client::tst_parser<iterator_type,boost::mpl::int_<assign> > >(fwd) {}
};



struct tst_timer_back : timeParser< client::tst_parser<iterator_type,boost::mpl::int_<full> > >
{
        tst_timer_back() : timeParser< client::tst_parser<iterator_type,boost::mpl::int_<full> > >(back) {}
};

struct tst_map_timer_fwd : timeParser<tst_map_parser>
{
        tst_map_timer_fwd() : timeParser<tst_map_parser>(fwd) {}
};

struct tst_map_timer_back : timeParser<tst_map_parser>
{
        tst_map_timer_back() : timeParser<tst_map_parser>(back) {}
};

struct kwd_timer_fwd : timeParser<kwd_parser>
{
        kwd_timer_fwd() : timeParser<kwd_parser>(fwd) {}
};

struct kwd_timer_back : timeParser<kwd_parser>
{
        kwd_timer_back() : timeParser<kwd_parser>(back) {}
};





////////////////////////////////////////////////////////////////////////////
//  Main program
////////////////////////////////////////////////////////////////////////////
        int
main()
{

        BOOST_SPIRIT_TEST_BENCHMARK(
                        10000000000,     // This is the maximum repetitions to execute
                        (permutation_timer_fwd)
                        (permutation_timer_back)
                        (alternative_timer_fwd)
                        (alternative_timer_back)
                        (tst_timer_fwd_full)
                        (tst_timer_fwd_no_assign)
                        (tst_timer_fwd_assign)
                        (tst_timer_back)
                        (tst_map_timer_fwd)
                        (tst_map_timer_back)
                        (kwd_timer_fwd)
                        (kwd_timer_back)
                        )

                // This is ultimately responsible for preventing all the test code
                // from being optimized away.  Change this to return 0 and you
                // unplug the whole test's life support system.
                return test::live_code != 0;
}


