#include "sipi_electron.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* ---------- estado del parser ---------- */

typedef struct {
    Lexer lx;
    Token actual;
    Token siguiente;
    int hay_siguiente;
} Parser;

static Nodo *nodo_nuevo(TipoNodo tipo, int linea) {
    Nodo *n = calloc(1, sizeof(Nodo));
    n->tipo = tipo;
    n->linea = linea;
    return n;
}

static void error_parser(Parser *p, const char *msg) {
    fprintf(stderr, "Error de sintaxis (linea %d): %s\n", p->actual.linea, msg);
    exit(1);
}

static void avanzar_token(Parser *p) {
    if (p->hay_siguiente) {
        p->actual = p->siguiente;
        p->hay_siguiente = 0;
    } else {
        p->actual = lexer_siguiente(&p->lx);
    }
}

static void saltar_newlines(Parser *p) {
    while (p->actual.tipo == TOK_NEWLINE) avanzar_token(p);
}

static int coincide(Parser *p, TipoToken tipo) { return p->actual.tipo == tipo; }

static void esperar(Parser *p, TipoToken tipo, const char *msg) {
    if (!coincide(p, tipo)) error_parser(p, msg);
    avanzar_token(p);
}

/* ---------- expresiones (precedencia ascendente) ---------- */

static Nodo *expr_or(Parser *p);

static Nodo *expr_primaria(Parser *p) {
    int linea = p->actual.linea;
    if (coincide(p, TOK_NUMERO)) {
        Nodo *n = nodo_nuevo(N_NUMERO, linea);
        n->numero = p->actual.numero;
        avanzar_token(p);
        return n;
    }
    if (coincide(p, TOK_TEXTO)) {
        Nodo *n = nodo_nuevo(N_TEXTO, linea);
        strncpy(n->texto, p->actual.texto, sizeof(n->texto) - 1);
        avanzar_token(p);
        return n;
    }
    if (coincide(p, TOK_VERDADERO) || coincide(p, TOK_FALSO)) {
        Nodo *n = nodo_nuevo(N_BOOL, linea);
        n->numero = coincide(p, TOK_VERDADERO) ? 1 : 0;
        avanzar_token(p);
        return n;
    }
    if (coincide(p, TOK_MENOS) || coincide(p, TOK_NO)) {
        TipoToken op = p->actual.tipo;
        avanzar_token(p);
        Nodo *n = nodo_nuevo(N_UNARIA, linea);
        n->operador = op;
        n->izq = expr_primaria(p);
        return n;
    }
    if (coincide(p, TOK_PAREN_ABRE)) {
        avanzar_token(p);
        Nodo *n = expr_or(p);
        esperar(p, TOK_PAREN_CIERRA, "se esperaba ')'");
        return n;
    }
    if (coincide(p, TOK_IDENT)) {
        char nombre[128];
        strncpy(nombre, p->actual.texto, sizeof(nombre) - 1);
        nombre[sizeof(nombre) - 1] = '\0';
        avanzar_token(p);
        if (coincide(p, TOK_PAREN_ABRE)) {
            avanzar_token(p);
            Nodo *n = nodo_nuevo(N_LLAMADA_EXPR, linea);
            strncpy(n->texto, nombre, sizeof(n->texto) - 1);
            Nodo *cabeza = NULL, *cola = NULL;
            while (!coincide(p, TOK_PAREN_CIERRA)) {
                Nodo *arg = expr_or(p);
                if (!cabeza) cabeza = cola = arg;
                else { cola->siguiente = arg; cola = arg; }
                if (coincide(p, TOK_COMA)) avanzar_token(p);
            }
            esperar(p, TOK_PAREN_CIERRA, "se esperaba ')' tras los argumentos");
            n->args = cabeza;
            return n;
        }
        Nodo *n = nodo_nuevo(N_IDENT, linea);
        strncpy(n->texto, nombre, sizeof(n->texto) - 1);
        return n;
    }
    error_parser(p, "se esperaba una expresion");
    return NULL;
}

static Nodo *bin(Nodo *izq, TipoToken op, Nodo *der, int linea) {
    Nodo *n = nodo_nuevo(N_BINARIA, linea);
    n->operador = op; n->izq = izq; n->der = der;
    return n;
}

static Nodo *expr_multiplicativa(Parser *p) {
    Nodo *n = expr_primaria(p);
    while (coincide(p, TOK_POR) || coincide(p, TOK_DIV) || coincide(p, TOK_MOD)) {
        TipoToken op = p->actual.tipo; int linea = p->actual.linea;
        avanzar_token(p);
        n = bin(n, op, expr_primaria(p), linea);
    }
    return n;
}

static Nodo *expr_aditiva(Parser *p) {
    Nodo *n = expr_multiplicativa(p);
    while (coincide(p, TOK_MAS) || coincide(p, TOK_MENOS)) {
        TipoToken op = p->actual.tipo; int linea = p->actual.linea;
        avanzar_token(p);
        n = bin(n, op, expr_multiplicativa(p), linea);
    }
    return n;
}

static Nodo *expr_comparacion(Parser *p) {
    Nodo *n = expr_aditiva(p);
    while (coincide(p, TOK_MENOR) || coincide(p, TOK_MAYOR) ||
           coincide(p, TOK_MENOR_IGUAL) || coincide(p, TOK_MAYOR_IGUAL) ||
           coincide(p, TOK_IGUAL_IGUAL) || coincide(p, TOK_DISTINTO)) {
        TipoToken op = p->actual.tipo; int linea = p->actual.linea;
        avanzar_token(p);
        n = bin(n, op, expr_aditiva(p), linea);
    }
    return n;
}

static Nodo *expr_and(Parser *p) {
    Nodo *n = expr_comparacion(p);
    while (coincide(p, TOK_Y)) {
        int linea = p->actual.linea; avanzar_token(p);
        n = bin(n, TOK_Y, expr_comparacion(p), linea);
    }
    return n;
}

static Nodo *expr_or(Parser *p) {
    Nodo *n = expr_and(p);
    while (coincide(p, TOK_O)) {
        int linea = p->actual.linea; avanzar_token(p);
        n = bin(n, TOK_O, expr_and(p), linea);
    }
    return n;
}

/* ---------- sentencias y bloques ---------- */

static Nodo *analizar_bloque(Parser *p);

static int es_fin_de_bloque(Parser *p) {
    return coincide(p, TOK_FIN) || coincide(p, TOK_SINO) || coincide(p, TOK_EOF);
}

static Nodo *analizar_sentencia(Parser *p) {
    int linea = p->actual.linea;

    if (coincide(p, TOK_DECIR)) {
        avanzar_token(p);
        Nodo *n = nodo_nuevo(N_DECIR, linea);
        n->izq = expr_or(p);
        return n;
    }
    if (coincide(p, TOK_VARIABLE) || coincide(p, TOK_CONST)) {
        int es_const = coincide(p, TOK_CONST);
        avanzar_token(p);
        if (!coincide(p, TOK_IDENT)) error_parser(p, "se esperaba un nombre de variable");
        Nodo *n = nodo_nuevo(es_const ? N_CONST : N_VARIABLE, linea);
        strncpy(n->texto, p->actual.texto, sizeof(n->texto) - 1);
        avanzar_token(p);
        if (coincide(p, TOK_DOSPUNTOS)) { /* anotacion de tipo, se ignora */
            avanzar_token(p);
            if (coincide(p, TOK_IDENT)) avanzar_token(p);
        }
        esperar(p, TOK_IGUAL, "se esperaba '=' en la declaracion");
        n->izq = expr_or(p);
        return n;
    }
    if (coincide(p, TOK_SI)) {
        avanzar_token(p);
        Nodo *n = nodo_nuevo(N_SI, linea);
        n->izq = expr_or(p);
        n->cuerpo = analizar_bloque(p);
        if (coincide(p, TOK_SINO)) {
            avanzar_token(p);
            n->cuerpo2 = analizar_bloque(p);
        }
        esperar(p, TOK_FIN, "se esperaba 'fin' para cerrar el 'si'");
        return n;
    }
    if (coincide(p, TOK_MIENTRAS)) {
        avanzar_token(p);
        Nodo *n = nodo_nuevo(N_MIENTRAS, linea);
        n->izq = expr_or(p);
        n->cuerpo = analizar_bloque(p);
        esperar(p, TOK_FIN, "se esperaba 'fin' para cerrar el 'mientras'");
        return n;
    }
    if (coincide(p, TOK_REPETIR)) {
        avanzar_token(p);
        Nodo *n = nodo_nuevo(N_REPETIR, linea);
        n->izq = expr_or(p);
        esperar(p, TOK_VECES, "se esperaba 'veces' tras la cantidad de repeticiones");
        n->cuerpo = analizar_bloque(p);
        esperar(p, TOK_FIN, "se esperaba 'fin' para cerrar el 'repetir'");
        return n;
    }
    if (coincide(p, TOK_FUNCION)) {
        avanzar_token(p);
        if (!coincide(p, TOK_IDENT)) error_parser(p, "se esperaba un nombre de funcion");
        Nodo *n = nodo_nuevo(N_FUNCION, linea);
        strncpy(n->texto, p->actual.texto, sizeof(n->texto) - 1);
        avanzar_token(p);
        esperar(p, TOK_PAREN_ABRE, "se esperaba '(' tras el nombre de la funcion");
        Nodo *cabeza = NULL, *cola = NULL;
        while (!coincide(p, TOK_PAREN_CIERRA)) {
            if (!coincide(p, TOK_IDENT)) error_parser(p, "se esperaba un parametro");
            Nodo *par = nodo_nuevo(N_IDENT, p->actual.linea);
            strncpy(par->texto, p->actual.texto, sizeof(par->texto) - 1);
            avanzar_token(p);
            if (coincide(p, TOK_DOSPUNTOS)) { avanzar_token(p); if (coincide(p, TOK_IDENT)) avanzar_token(p); }
            if (!cabeza) cabeza = cola = par; else { cola->siguiente = par; cola = par; }
            if (coincide(p, TOK_COMA)) avanzar_token(p);
        }
        esperar(p, TOK_PAREN_CIERRA, "se esperaba ')' tras los parametros");
        if (coincide(p, TOK_FLECHA)) { avanzar_token(p); if (coincide(p, TOK_IDENT)) avanzar_token(p); }
        n->args = cabeza;
        n->cuerpo = analizar_bloque(p);
        esperar(p, TOK_FIN, "se esperaba 'fin' para cerrar la funcion");
        return n;
    }
    if (coincide(p, TOK_DEVOLVER)) {
        avanzar_token(p);
        Nodo *n = nodo_nuevo(N_DEVOLVER, linea);
        if (!coincide(p, TOK_NEWLINE) && !es_fin_de_bloque(p))
            n->izq = expr_or(p);
        return n;
    }
    if (coincide(p, TOK_ROMPER)) { avanzar_token(p); return nodo_nuevo(N_ROMPER, linea); }
    if (coincide(p, TOK_CONTINUAR)) { avanzar_token(p); return nodo_nuevo(N_CONTINUAR, linea); }

    if (coincide(p, TOK_LLAMAR_VALOR) || coincide(p, TOK_LLAMAR)) {
        int con_valor = coincide(p, TOK_LLAMAR_VALOR);
        avanzar_token(p);
        Nodo *llamada = expr_primaria(p); /* debe resultar en N_LLAMADA_EXPR */
        Nodo *n = nodo_nuevo(con_valor ? N_LLAMAR_VALOR : N_LLAMAR, linea);
        n->izq = llamada;
        if (con_valor) {
            esperar(p, TOK_FLECHA, "se esperaba '->' tras 'llamar_valor'");
            if (!coincide(p, TOK_IDENT)) error_parser(p, "se esperaba una variable destino");
            strncpy(n->texto, p->actual.texto, sizeof(n->texto) - 1);
            avanzar_token(p);
        }
        return n;
    }

    /* asignacion: IDENT = expr, o llamada suelta como sentencia: IDENT(args) */
    if (coincide(p, TOK_IDENT)) {
        char nombre[128];
        strncpy(nombre, p->actual.texto, sizeof(nombre) - 1);
        nombre[sizeof(nombre) - 1] = '\0';
        avanzar_token(p);
        if (coincide(p, TOK_IGUAL)) {
            avanzar_token(p);
            Nodo *n = nodo_nuevo(N_ASIGNAR, linea);
            strncpy(n->texto, nombre, sizeof(n->texto) - 1);
            n->izq = expr_or(p);
            return n;
        }
        if (coincide(p, TOK_PAREN_ABRE)) {
            avanzar_token(p);
            Nodo *llamada = nodo_nuevo(N_LLAMADA_EXPR, linea);
            strncpy(llamada->texto, nombre, sizeof(llamada->texto) - 1);
            Nodo *cabeza = NULL, *cola = NULL;
            while (!coincide(p, TOK_PAREN_CIERRA)) {
                Nodo *arg = expr_or(p);
                if (!cabeza) cabeza = cola = arg; else { cola->siguiente = arg; cola = arg; }
                if (coincide(p, TOK_COMA)) avanzar_token(p);
            }
            esperar(p, TOK_PAREN_CIERRA, "se esperaba ')' tras los argumentos");
            llamada->args = cabeza;
            Nodo *n = nodo_nuevo(N_LLAMAR, linea);
            n->izq = llamada;
            /* '-> variable' opcional para llamadas sueltas */
            if (coincide(p, TOK_FLECHA)) {
                avanzar_token(p);
                if (!coincide(p, TOK_IDENT)) error_parser(p, "se esperaba una variable destino");
                n->tipo = N_LLAMAR_VALOR;
                strncpy(n->texto, p->actual.texto, sizeof(n->texto) - 1);
                avanzar_token(p);
            }
            return n;
        }
        error_parser(p, "sentencia invalida tras identificador");
    }

    error_parser(p, "sentencia no reconocida");
    return NULL;
}

static Nodo *analizar_bloque(Parser *p) {
    saltar_newlines(p);
    Nodo *cabeza = NULL, *cola = NULL;
    while (!es_fin_de_bloque(p)) {
        Nodo *s = analizar_sentencia(p);
        if (!cabeza) cabeza = cola = s; else { cola->siguiente = s; cola = s; }
        saltar_newlines(p);
    }
    return cabeza;
}

Nodo *analizar_programa(const char *fuente_completa) {
    Parser p;
    p.hay_siguiente = 0;
    lexer_iniciar(&p.lx, fuente_completa);
    avanzar_token(&p);

    Nodo *raiz = nodo_nuevo(N_PROGRAMA, 1);
    saltar_newlines(&p);
    if (coincide(&p, TOK_PROGRAMA)) {
        avanzar_token(&p);
        if (coincide(&p, TOK_TEXTO)) {
            strncpy(raiz->texto, p.actual.texto, sizeof(raiz->texto) - 1);
            avanzar_token(&p);
        }
    }
    raiz->cuerpo = analizar_bloque(&p);
    if (!coincide(&p, TOK_EOF)) error_parser(&p, "se esperaba el fin del archivo");
    return raiz;
}
