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

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

/**
 * Represents the type of the symbol (both terminals and nonterminals)
 */
enum class {{name}}_Symbol : std::uint64_t {
    {{#symbols}}
    {{symbol}},
    {{/symbols}}
};

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

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


    protected:
        /**
         * A token, consisting of a Symbol type (should be a terminal) and a Value
         */
        struct Token {
            Token(const {{name}}_Symbol& sym, const Value& val) : symbol(sym), value(val) {}
            {{name}}_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)
         */
        {{#rulenames}}
        virtual Value reduce_{{rname}}(std::deque<Token> subparts) = 0;
        {{/rulenames}}

    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}}<Value>::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()][static_cast<std::uint64_t>(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;
                    {{name}}_Symbol symbol = static_cast<{{name}}_Symbol>(tmp >> 31);
                    std::uint32_t rule = tmp & ((1ull << 31) - 1);

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

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

                    stateStack.push(TABLE[stateStack.top()][static_cast<std::uint64_t>(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 */