#pragma once
#ifndef PARSODUS_PARSER_{{name}}_H
#define PARSODUS_PARSER_{{name}}_H

#include <cassert>
#include <deque>
#include <stack>

template <typename Value>
class {{name}} {
    public:
        {{name}}() {}
        virtual ~{{name}}() {}

        /**
         * Parse it
         */
        Value parse();


    protected:
        /**
         * Represents the type of the symbol (both terminals and nonterminals)
         */
        enum Symbol {
            {{#symbols}}
            {{symbol}},
            {{/symbols}}
        }

        /**
         * A token, consisting of a Symbol type (should be a terminal) and a Value
         */
        struct Token {
            Symbol symbol,
            Value value
        };


        /******************************************
        *  Functions to be supplied by the user  *
        ******************************************/

        /**
         * Get the next token from the lexer
         */
        virtual Token lex() = 0;
        
        /**
         * Apply a reduction (a grammar rule in reverse)
         */
        {{#rules}}
        {{#name}}virtual Value reduce_{{name}}(std::deque<Token> subparts) = 0;{{/name}}
        {{^name}}virtual Value reduce_{{index}}(std::deque<Token> subparts) = 0;{{/name}}
        {{/rules}}

    private:
};

#define TABLE {{name}}___Table___{{name}}
#define REDUCE_COUNT {{name}}___Num_Reduces___{{name}}
// Not a static member because the table should not be replicated for different instantiations of the parser
extern const std::uint64_t TABLE[{{num_states}}][{{num_symbols}}];
extern const unsigned char REDUCE_COUNT[{{num_rules}}];

enum Action {
    ERROR = 0,
    SHIFT = 1,
    REDUCE = 2,
    ACCEPT = 3
};


/***************************
*  Parser implementation  *
***************************/
template <typename Value>
Value {{name}}::parse() {
    std::stack<Token> valueStack;
    std::stack<std::uint64_t> stateStack;

    stateStack.push(0);
    Token tok = lex();

    while (true) {
        std::uint64_t act = TABLE[stateStack.top()][tok.symbol];

       switch (act & 0x3) {
            case ERROR:
                //TODO: error handling
                assert(false);
                break;
            case SHIFT:
                valueStack.emplace(std::move(tok));
                stateStack.push(act >> 2);
                tok = lex();
                break;
            case REDUCE:
                {
                    std::uint64_t tmp = act >> 2;
                    Symbol symbol = tmp >> 31;
                    std::uint32_t rule = tmp & ((1ull << 32) - 1);

                    std::deque<Token> dq;
                    for (decltype(RECUDE_COUNT[rule]) i = 0; i < REDUCE_COUNT[rule]; i++) {
                        dq.emplace_front(std::move(valueStack.top()));
                        valueStack.pop();
                        stateStack.pop();
                    }

                    switch (rule) {
                        {{#rules}}
                        case {{index}}:
                            {{#name}}valueStack.emplace({symbol, reduce_{{name}}(std::move(dq))}){{/name}}
                            {{^name}}valueStack.emplace({symbol, reduce_{{index}}(std::move(dq))}){{/name}}
                            break;
                        {{/rules}}
                        default:
                            assert(false); //There should be no such rule
                            break;
                    }

                    stateStack.push(TABLE[stateStack.top()][valueStack.top().symbol] >> 2);
                }
                break;
            case ACCEPT:
                assert(stateStack.size() == 2);
                assert(valueStack.size() == 1);
                return valueStack.top().value;
            default:
                //IMPOSSIBLE
                break;
        }
    }
}
#undef REDUCE_COUNT
#undef TABLE

#endif /* PARSODUS_PARSER_{{name}}_H */