// Evaluador de un lenguaje pequeño, dado. Ustedes solo escriben su tabla de variables.
//
//   x = 2 + 3 * 4      guarda el valor
//   print x - 1        imprime el valor de una expresión
//   del x              borra una variable
//   vars               imprime cuántas variables hay
//   # comentario       una línea vacía o un comentario no hace nada
//
// Los nombres admiten letras, dígitos, _ y bytes no ASCII: "año" es un nombre válido.
#ifndef EVALUATOR_H
#define EVALUATOR_H

#include <cctype>
#include <cstddef>
#include <ostream>
#include <stdexcept>
#include <string>

#include "var_table.h"

class Evaluator {
public:
    explicit Evaluator(std::ostream& out) : out_(out) {}

    // Ejecuta una línea. Si tiene un error, lanza std::runtime_error.
    void run_line(const std::string& line) {
        text_ = line;
        position_ = 0;
        skip_spaces();
        if (at_end() || peek() == '#') {
            return;
        }
        std::size_t start = position_;
        std::string word = is_name_char(peek()) ? read_name() : "";
        if (word == "print") {
            out_ << expression() << "\n";
        } else if (word == "del") {
            std::string name = read_name();
            if (!vars_.erase(name)) {
                throw std::runtime_error("no se puede borrar '" + name + "': no existe");
            }
        } else if (word == "vars") {
            out_ << vars_.count() << "\n";
        } else {
            skip_spaces();
            if (!word.empty() && !at_end() && peek() == '=') {
                position_++;
                long long value = expression();
                vars_.assign(word, value);
            } else {
                position_ = start;
                throw std::runtime_error("no entiendo la instrucción");
            }
        }
        skip_spaces();
        if (!at_end()) {
            throw std::runtime_error(std::string("sobra texto desde '") + peek() + "'");
        }
    }

private:
    // expresión: término (('+' | '-') término)*
    long long expression() {
        long long value = term();
        for (skip_spaces(); !at_end() && (peek() == '+' || peek() == '-'); skip_spaces()) {
            char op = text_[position_++];
            long long right = term();
            value = op == '+' ? value + right : value - right;
        }
        return value;
    }

    // término: factor (('*' | '/' | '%') factor)*
    long long term() {
        long long value = factor();
        for (skip_spaces(); !at_end() && (peek() == '*' || peek() == '/' || peek() == '%'); skip_spaces()) {
            char op = text_[position_++];
            long long right = factor();
            if (op != '*' && right == 0) {
                throw std::runtime_error("división entre cero");
            }
            value = op == '*' ? value * right : op == '/' ? value / right : value % right;
        }
        return value;
    }

    // factor: número | nombre | '(' expresión ')' | '-' factor
    long long factor() {
        skip_spaces();
        if (at_end()) {
            throw std::runtime_error("falta un valor al final");
        }
        if (peek() == '-') {
            position_++;
            return -factor();
        }
        if (peek() == '(') {
            position_++;
            long long value = expression();
            skip_spaces();
            if (at_end() || peek() != ')') {
                throw std::runtime_error("falta ')'");
            }
            position_++;
            return value;
        }
        if (std::isdigit(static_cast<unsigned char>(peek()))) {
            long long value = 0;
            while (!at_end() && std::isdigit(static_cast<unsigned char>(peek()))) {
                value = value * 10 + (text_[position_++] - '0');
            }
            return value;
        }
        if (is_name_char(peek())) {
            std::string name = read_name();
            long long value = 0;
            if (!vars_.lookup(name, value)) {
                throw std::runtime_error("variable '" + name + "' sin definir");
            }
            return value;
        }
        throw std::runtime_error(std::string("no esperaba '") + peek() + "'");
    }

    std::string read_name() {
        skip_spaces();
        std::string name;
        while (!at_end() && is_name_char(peek())) {
            name += text_[position_++];
        }
        return name;
    }

    static bool is_name_char(char c) {
        unsigned char byte = static_cast<unsigned char>(c);
        return std::isalnum(byte) || c == '_' || byte >= 0x80;
    }

    bool at_end() const { return position_ >= text_.size(); }
    char peek() const { return text_[position_]; }

    void skip_spaces() {
        while (!at_end() && std::isspace(static_cast<unsigned char>(peek()))) {
            position_++;
        }
    }

    std::ostream& out_;
    VarTable vars_;
    std::string text_;
    std::size_t position_ = 0;
};

#endif
