#ifndef PARSER_HPP
#define PARSER_HPP

#include "parser_impl.hpp"
#include <boost/spirit/include/qi.hpp>
#include <boost/tuple/tuple.hpp>

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

#define SKIP(delim, parser) (qi::omit[*(delim)] >> (parser) >> qi::omit[*(delim)])

template<typename iterator_type>
struct math_parser_t : qi::grammar<iterator_type> {
    math_parser_t(math_parser_impl& impl) : math_parser_t::base_type(start), impl(impl) {
        function_begin = std::bind(&math_parser_impl::function_begin, &impl, std::placeholders::_1);
        function_param = std::bind(&math_parser_impl::function_param, &impl);
        function_end = std::bind(&math_parser_impl::function_end, &impl);

        array_begin = std::bind(&math_parser_impl::array_begin, &impl);
        array_push = std::bind(&math_parser_impl::array_push, &impl);
        array_end = std::bind(&math_parser_impl::array_end, &impl);

        make_variable = std::bind(&math_parser_impl::make_variable, &impl, std::placeholders::_1);
        make_scalar = std::bind(&math_parser_impl::make_scalar, &impl, std::placeholders::_1);

        binary_operator = std::bind(&math_parser_impl::binary_operator, &impl, std::placeholders::_1);
        unary_operator = std::bind(&math_parser_impl::unary_operator, &impl, std::placeholders::_1);

        assign_begin = std::bind(&math_parser_impl::assign_begin, &impl, std::placeholders::_1);
        assign_end = std::bind(&math_parser_impl::assign_end, &impl);

        destroy = std::bind(&math_parser_impl::destroy, &impl, std::placeholders::_1);
        detroy_all = std::bind(&math_parser_impl::destroy_all, &impl);

        print_identifier = std::bind(&math_parser_impl::print_identifier, &impl, std::placeholders::_1);
        print_help = std::bind(&math_parser_impl::print_help, &impl);
        print_expression = std::bind(&math_parser_impl::print_expression, &impl);

        finish = std::bind(&math_parser_impl::finish, &impl);

        function_call =
            // Prevent early matches :
            &(
                identifier >>
                SKIP(ascii::space, qi::char_('('))
            ) >>
            // A function call is an 'identifier',
            identifier[function_begin] >>
            // followed by a comma separated list of 'expressions' surrounded
            // by parenthesis.
            SKIP(ascii::space, qi::char_('(')) >>
                -(
                    expression[function_param] >>
                    *(
                        SKIP(ascii::space, qi::char_(',')) >>
                        expression[function_param]
                    )
                ) >>
            SKIP(ascii::space, qi::char_(')')[function_end]);

        identifier = qi::as_string[
            // An identifier is an alphanumeric string that
            // cannot start by a digit.
            (qi::alpha | qi::char_('_')) >> *(qi::alnum | qi::char_('_'))];

        array =
            // An array is a comma separated list of 'expressions' surrounded
            // by parenthesis.
            SKIP(ascii::space, qi::char_('(')[array_begin]) >>
                expression[array_push] >>
                +(
                    SKIP(ascii::space, qi::char_(',')) >>
                    expression[array_push]
                ) >>
            SKIP(ascii::space, qi::char_(')')[array_end]);

        litteral_exponent = qi::as_string[
            // A litteral exponent starts with 'e' or 'E',
            (qi::char_('e') | qi::char_('E')) >>
            // followed by an optional '+' or '-' sign,
            -(qi::char_('+') | qi::char_('-')) >>
            // followed by any digit.
            +qi::digit];

        decimal_number = qi::as_string[
            // A decimal_number can start by a '.' then any digit,
            qi::char_('.') >> +qi::digit |
            // or any digit, an optional '.', followed by any digit.
            +qi::digit >> -(qi::char_('.') >> +qi::digit)];

        number = qi::as_string[
            // A number can start with an unary '+' or '-' operator,
            //-(qi::char_('+') | qi::char_('-')) >>
            // followed by a 'decimal_number',
            decimal_number >>
            // an optional 'litteral_exponent',
            -litteral_exponent >>
            // and if complex numbers are enabled,
            qi::repeat(impl.allow_complex())[
            // an optional 'i' to indicate imaginary numbers.
            -qi::char_('i')]];

        leaf =
            function_call |
            identifier[make_variable] |
            array |
            number[make_scalar];

        factor =
            // A factor is a 'leaf',
            leaf |
            // or another 'expression' surrounded by parenthesis.
            SKIP(ascii::space, '(') >> expression >> SKIP(ascii::space, ')');

        unary =
            // An unary is an unary operator applied to a 'factor'
            factor |
            (
                SKIP(ascii::space, '-') >> factor[std::bind(unary_operator, "-")] |
                SKIP(ascii::space, '+') >> factor[std::bind(unary_operator, "+")] |
                SKIP(ascii::space, '!') >> factor[std::bind(unary_operator, "!")]
            );

        exponent =
            // An exponent is an exponentiation of 'unary'
            unary >>
            *(
                SKIP(ascii::space, '^') >>
                unary[std::bind(binary_operator, "^")]
            );

        term =
            // A term is a multiplication, division or remainder of 'exponent'.
            exponent >>
            *(
                SKIP(ascii::space, '*') >> exponent[std::bind(binary_operator, "*")] |
                SKIP(ascii::space, '/') >> exponent[std::bind(binary_operator, "/")] |
                SKIP(ascii::space, '%') >> exponent[std::bind(binary_operator, "%")]
            );

        expression =
            // An expression is an addition or substraction of 'terms'.
            term >>
            *(
                SKIP(ascii::space, '+') >> term[std::bind(binary_operator, "+")] |
                SKIP(ascii::space, '-') >> term[std::bind(binary_operator, "-")]
            );

        assignment =
            // An assignment is composed of an 'identifier',
            identifier[assign_begin] >>
            // an '=' sign,
            SKIP(ascii::space, '=') >>
            // and an 'expression'.
            expression[assign_end];

        destruction =
            // A destruction is a tilda '~',
            SKIP(ascii::space, '~') >>
            (
                // followed by an 'identifier',
                identifier[destroy] |
                // or nothing.
                end[detroy_all]
            );

        question =
            // A question is a question mark '?',
            SKIP(ascii::space, '?') >>
            (
                // followed by an 'identifier',
                identifier[print_identifier] |
                // or nothing.
                end[print_help]
            );

        start = *ascii::space >>
            (
                destruction >> end |
                question >> end |
                assignment >> end |
                expression >> end[print_expression]
            )[finish];

        end = qi::omit[+ascii::space | qi::eoi];
    }

    template<typename S>
    void parse(const S& str) {
        auto first = std::begin(str);
        auto last = std::end(str);

        while (first != last) {
            if (!qi::parse(first, last, *this))
                break;
        }
    }

private :
    std::function<void()>                   array_begin, array_push, array_end;
    std::function<void(const std::string&)> make_variable, make_scalar;
    std::function<void(const std::string&)> function_begin;
    std::function<void()>                   function_param, function_end;
    std::function<void(const std::string&)> binary_operator;
    std::function<void(const std::string&)> unary_operator;
    std::function<void(const std::string&)> assign_begin;
    std::function<void()>                   assign_end;
    std::function<void(const std::string&)> destroy;
    std::function<void()>                   detroy_all;
    std::function<void(const std::string&)> print_identifier;
    std::function<void()>                   print_help, print_expression;
    std::function<void()>                   finish;

    qi::rule<iterator_type> start;
    qi::rule<iterator_type> end;
    qi::rule<iterator_type> assignment;
    qi::rule<iterator_type> destruction;
    qi::rule<iterator_type> question;
    qi::rule<iterator_type> expression;
    qi::rule<iterator_type> term;
    qi::rule<iterator_type> factor;
    qi::rule<iterator_type> exponent;
    qi::rule<iterator_type> unary;
    qi::rule<iterator_type> leaf;
    qi::rule<iterator_type> function_call;
    qi::rule<iterator_type> array;
    qi::rule<iterator_type, std::string()> identifier;
    qi::rule<iterator_type, std::string()> number;
    qi::rule<iterator_type, std::string()> decimal_number;
    qi::rule<iterator_type, std::string()> litteral_exponent;

    math_parser_impl& impl;
};

typedef math_parser_t<std::string::const_iterator> math_parser;

#endif
