#ifndef PARSER_HPP
#define PARSER_HPP

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

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) {
        error = std::bind(&math_parser_impl::error, &impl, std::placeholders::_1);
        warning = std::bind(&math_parser_impl::warning, &impl, std::placeholders::_1);
        note = std::bind(&math_parser_impl::note, &impl, std::placeholders::_1);

        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_string = std::bind(&math_parser_impl::make_string, &impl, std::placeholders::_1);
        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);

        assign_begin_no_eval = std::bind(&math_parser_impl::assign_begin_no_eval, &impl);
        assign_end_no_eval = std::bind(&math_parser_impl::assign_end_no_eval, &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]);

        escaped_character = qi::as_string[qi::hold[
            // An escaped character stars with a backslash,
            qi::char_('\\') >>
            // followed by any character.
            qi::char_]];

        string = qi::as_string[
            // A string starts with a double quote,
            qi::omit[*ascii::space] >> '"' >> *(
                // followed by either an escaped character (line jumps, ...),
                escaped_character |
                // or any other character that is not a double quote,
                qi::as_string[qi::char_ - '"']
            ) >>
            // and ends with a double quote.
            '"'];

        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 is a 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 =
            // A leaf is either a 'string',
            string[make_string] |
            // a 'function_call',
            function_call |
            // an 'identifier',
            identifier[make_variable] |
            // an 'array',
            array |
            // or a 'number'.
            number[make_scalar];

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

        access_operator =
            // An access operator is a 'factor',
            factor >> *(
                // followed by an opening braket,
                SKIP(ascii::space, '[') >>
                // an epression,
                expression >>
                // and a closing braket.
                SKIP(ascii::space, ']') >> qi::eps[std::bind(binary_operator, "[]")]
            );

        unary =
            // An unary is an unary operator applied to an 'access_operator'
            access_operator | (
                SKIP(ascii::space, '-') >> unary[std::bind(unary_operator, "-")] |
                SKIP(ascii::space, '+') >> unary[std::bind(unary_operator, "+")] |
                SKIP(ascii::space, '!') >> unary[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, "%")]
            );

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

        expression =
            // An expression is either an assignment,
            (
                // (prevent early matches)
                &(identifier >> SKIP(ascii::space, (qi::lit('=') | ":="))) >>
                // composed of an 'identifier',
                identifier[assign_begin] >> (
                    // followed by an '=' sign,
                    SKIP(ascii::space, '=') >>
                    // and an 'expression'.
                    expression[assign_end] |
                    // Or a ':=' sign,
                    SKIP(ascii::space, ":=") >>
                    qi::eps[assign_begin_no_eval] >>
                    // and an 'expression'.
                    expression[assign_end_no_eval]
                )
            ) |
            // or simply a mathematical formula.
            sum;

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

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

        start = *ascii::space >> (
                // A command is either a 'destruction',
                destruction |
                // a 'question',
                question |
                // or an 'expression' (prints output, except if the command ends with ';').
                expression >> *(qi::lit(' ') | qi::lit('\t')) >> (
                    qi::eoi[print_expression] |
                    qi::lit(';')
                )
            );

        start_eval = *ascii::space >> (
                // A command is either a 'destruction',
                destruction |
                // a 'question',
                question |
                // or an 'expression' (prints output, except if the command ends with ';').
                expression >> end
            );

        end = qi::omit[*(qi::lit(' ') | qi::lit('\t')) >> (qi::lit(';') | qi::eoi)];

        impl.set_parse_function([this](const std::string& str) {
            auto first = std::begin(str);
            auto last = std::end(str);

            while (first != last) {
                if (!qi::parse(first, last, start_eval))
                    return false;
            }

            return true;
        });
    }

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

        while (first != last) {
            bool res = false;

            try {
                res = qi::parse(first, last, *this);
            } catch (parser_exception&) {
                finish();
                break;
            }

            finish();

            if (!res) {
                std::cout << "error: unhandled error while parsing" << std::endl;
                break;
            }
        }
    }

private :
    std::function<void(const std::string&)> error, warning, note;
    std::function<void()>                   array_begin, array_push, array_end;
    std::function<void(const std::string&)> make_variable, make_scalar, make_string;
    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()>                   assign_begin_no_eval;
    std::function<void()>                   assign_end_no_eval;
    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> start_eval;
    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> sum;
    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> access_operator;
    qi::rule<iterator_type> function_call;
    qi::rule<iterator_type> array;
    qi::rule<iterator_type, std::string()> identifier;
    qi::rule<iterator_type, std::string()> string;
    qi::rule<iterator_type, std::string()> escaped_character;
    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
