/*****************************************************************************
 * 
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2011 Artem Pavlenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

#ifndef MAPNIK_WKT_GRAMMAR_HPP
#define MAPNIK_WKT_GRAMMAR_HPP

#include <boost/assert.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
// spirit::qi
#include <boost/spirit/include/qi.hpp>
// spirit::phoenix
#include <boost/spirit/include/phoenix_function.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/home/phoenix/object/new.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
// mapnik
#include <mapnik/geometry.hpp>

namespace mapnik { namespace wkt {

using namespace boost::spirit;
using namespace boost::fusion;
using namespace boost::phoenix;

struct push_vertex
{
    template <typename T0,typename T1, typename T2, typename T3>
    struct result
    {
        typedef void type;
    };
    
    template <typename T0,typename T1, typename T2, typename T3>
    void operator() (T0 c, T1 path, T2 x, T3 y) const
    {
        BOOST_ASSERT( path!=0 );
        path->push_vertex(x,y,c);        
    }
};

struct cleanup
{
    template <typename T0>
    struct result
    {
        typedef void type;
    };
    
    template <typename T0>
    void operator() (T0 & path) const
    {        
        if (path)  delete path,path=0;
    }
};

template <typename Iterator>
struct wkt_grammar : qi::grammar<Iterator,  boost::ptr_vector<mapnik::geometry_type>() , ascii::space_type>
{
    wkt_grammar()
        : wkt_grammar::base_type(geometry_tagged_text)
    {       
        using qi::no_case;   
        using boost::phoenix::push_back;
        
        geometry_tagged_text = point_tagged_text
            | linestring_tagged_text
            | polygon_tagged_text
            | multipoint_tagged_text 
            | multilinestring_tagged_text 
            | multipolygon_tagged_text 
            ;
        
        // <point tagged text> ::= point <point text>
        point_tagged_text = no_case[lit("POINT")] [ _a = new_<geometry_type>(Point) ]
            >> ( point_text(_a) [push_back(_val,_a)] 
                 | eps[cleanup_(_a)][_pass = false])
            ;
        
        // <point text> ::= <empty set> | <left paren> <point> <right paren>
        point_text = (lit("(") >> point(SEG_MOVETO,_r1) >> lit(')')) 
            | empty_set
            ;
        
        // <linestring tagged text> ::= linestring <linestring text>
        linestring_tagged_text = no_case[lit("LINESTRING")] [ _a = new_<geometry_type>(LineString) ]  
            >> (linestring_text(_a)[push_back(_val,_a)] 
                | eps[cleanup_(_a)][_pass = false])
            ;
                
        // <linestring text> ::= <empty set> | <left paren> <point> {<comma> <point>}* <right paren>
        linestring_text = points(_r1) | empty_set
            ;
        
        // <polygon tagged text> ::= polygon <polygon text>
        polygon_tagged_text = no_case[lit("POLYGON")] [ _a = new_<geometry_type>(Polygon) ]
            >> ( polygon_text(_a)[push_back(_val,_a)] 
                 | eps[cleanup_(_a)][_pass = false])
            ;
        
        // <polygon text> ::= <empty set> | <left paren> <linestring text> {<comma> <linestring text>}* <right paren>
        polygon_text = (lit('(') >> linestring_text(_r1) % lit(',') >> lit(')')) | empty_set;
        
        
        //<multipoint tagged text> ::= multipoint <multipoint text>
        multipoint_tagged_text = no_case[lit("MULTIPOINT")] 
            >>  multipoint_text
            ;
        
        // <multipoint text> ::= <empty set> | <left paren> <point text> {<comma> <point text>}* <right paren>
        multipoint_text = (lit('(') 
                           >> ((eps[_a = new_<geometry_type>(Point)] 
                                >> (point_text(_a) | empty_set) [push_back(_val,_a)] 
                                | eps[cleanup_(_a)][_pass = false]) % lit(',')) 
                           >> lit(')')) | empty_set
            ;
        
        // <multilinestring tagged text> ::= multilinestring <multilinestring text>
        multilinestring_tagged_text = no_case[lit("MULTILINESTRING")] 
            >> multilinestring_text ;
        
        // <multilinestring text> ::= <empty set> | <left paren> <linestring text> {<comma> <linestring text>}* <right paren>
        multilinestring_text = (lit('(') 
                                >> ((eps[_a = new_<geometry_type>(LineString)] 
                                    >> ( points(_a)[push_back(_val,_a)] 
                                         | eps[cleanup_(_a)][_pass = false]))
                                    % lit(',')) 
                                >> lit(')')) | empty_set;
        
        // <multipolygon tagged text> ::= multipolygon <multipolygon text>
        multipolygon_tagged_text = no_case[lit("MULTIPOLYGON")] 
            >> multipolygon_text ;
        
        // <multipolygon text> ::= <empty set> | <left paren> <polygon text> {<comma> <polygon text>}* <right paren>
        
        multipolygon_text = (lit('(') 
                             >> ((eps[_a = new_<geometry_type>(Polygon)] 
                                 >> ( polygon_text(_a)[push_back(_val,_a)] 
                                      | eps[cleanup_(_a)][_pass = false]))
                                 % lit(',')) 
                             >> lit(')')) | empty_set;
        
        // points
        points = lit('(')[_a = SEG_MOVETO] >> point (_a,_r1) % lit(',') [_a = SEG_LINETO]  >> lit(')');
        // point 
        point = (double_ >> double_) [push_vertex_(_r1,_r2,_1,_2)];
        
        // <empty set>
        empty_set = no_case[lit("EMPTY")];

    }

    // start
    qi::rule<Iterator,boost::ptr_vector<geometry_type>(),ascii::space_type> geometry_tagged_text;
    
    qi::rule<Iterator,qi::locals<geometry_type*>,boost::ptr_vector<geometry_type>(),ascii::space_type> point_tagged_text;
    qi::rule<Iterator,qi::locals<geometry_type*>,boost::ptr_vector<geometry_type>(),ascii::space_type> linestring_tagged_text;
    qi::rule<Iterator,qi::locals<geometry_type*>,boost::ptr_vector<geometry_type>(),ascii::space_type> polygon_tagged_text;
    qi::rule<Iterator,boost::ptr_vector<geometry_type>(),ascii::space_type> multipoint_tagged_text;
    qi::rule<Iterator,boost::ptr_vector<geometry_type>(),ascii::space_type> multilinestring_tagged_text;
    qi::rule<Iterator,boost::ptr_vector<geometry_type>(),ascii::space_type> multipolygon_tagged_text;
    // 
    qi::rule<Iterator,void(geometry_type*),ascii::space_type> point_text;
    qi::rule<Iterator,void(geometry_type*),ascii::space_type> linestring_text;
    qi::rule<Iterator,void(geometry_type*),ascii::space_type> polygon_text;
    qi::rule<Iterator, qi::locals<geometry_type*>, boost::ptr_vector<geometry_type>(),ascii::space_type> multipoint_text;
    qi::rule<Iterator, qi::locals<geometry_type*>, boost::ptr_vector<geometry_type>(),ascii::space_type> multilinestring_text;
    qi::rule<Iterator, qi::locals<geometry_type*>, boost::ptr_vector<geometry_type>(),ascii::space_type> multipolygon_text;
    //
    qi::rule<Iterator,void(CommandType,geometry_type*),ascii::space_type> point;
    qi::rule<Iterator,qi::locals<CommandType>,void(geometry_type*),ascii::space_type> points;
    qi::rule<Iterator,ascii::space_type> empty_set;
    boost::phoenix::function<push_vertex> push_vertex_;
    boost::phoenix::function<cleanup> cleanup_;
};


template <typename Iterator>
struct wkt_collection_grammar : qi::grammar<Iterator, boost::ptr_vector<mapnik::geometry_type>(), ascii::space_type>
{
    wkt_collection_grammar()
        :  wkt_collection_grammar::base_type(start)
    {
        using qi::_1;   
        using qi::_val;
        using qi::no_case;
        using boost::phoenix::push_back;
        start = wkt | no_case[lit("GEOMETRYCOLLECTION")] 
            >> (lit("(") >> wkt % lit(",") >> lit(")"));
    }
    
    qi::rule<Iterator,boost::ptr_vector<mapnik::geometry_type>(),ascii::space_type> start;
    wkt_grammar<Iterator> wkt;
};

template <typename Iterator>
struct wkt_stream_grammar : qi::grammar<Iterator, boost::ptr_vector<mapnik::geometry_type>(), ascii::space_type>
{
    wkt_stream_grammar()
        :  wkt_stream_grammar::base_type(start)
    {
        start = *wkt_collection;
    }
    
    qi::rule<Iterator,boost::ptr_vector<mapnik::geometry_type>(),ascii::space_type> start;
    wkt_collection_grammar<Iterator> wkt_collection;
};

}}

#endif // MAPNIK_WKT_GRAMMAR_HPP
