package procesador;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
import org.jsoup.*;
import org.jsoup.nodes.*;
import org.jsoup.select.*;
import org.jsoup.select.Elements;

//Analizador Sintactico Descendente por Tablas 
public class ASint {

    //declaraciones para el análisis sintáctico
    private static BufferedWriter ptwParse;
    static final public Stack<ItemPila> stackP = new Stack<>();
    static final public Stack<ItemPila> stackAux = new Stack<>();
    private static Token<?> tokenActual;

    //declaraciones para procesar la TablaAccSint sintáctica
    final static private ArrayList<String> token_names = new ArrayList<>();
    private static String[] cabecera;
    final static private HashMap<String, HashMap<String, List<ItemPila>>> TablaAccSint = new HashMap<>();
    final static private Map<String, Runnable> mapaAcciones = new HashMap<>();

    private static boolean debug = false;


    /* Inicia el análisis sintáctico.
     * Es el punto de entrada del analizador sintáctico.
     * Se encarga de:
     *  - Insertar el símbolo de fin de fichero (EOF) y el símbolo inicial de la gramática (P) en la pila.
     *  - Comenzar a procesar la pila
     */
    public static void analizar() {

        printParse("descendente");
        // Añadir EOF y Axioma inicial a la pila
        stackP.push(new Terminal(ALex.tok_id.get("EOF")));
        stackP.push(new NoTerminal("P"));

        ASem.iniciarASem();

        tokenActual = ALex.generarToken();
        //   topP = new Terminal("OEF");
        try {
            procesarPila();
        } catch (ErrorSintacticoException e) {

        }

    }

    private static void procesarPila() {
        ItemPila topP;
        while (!stackP.isEmpty()) {

            topP = stackP.pop();
            if (tokenActual == null) {
                throw new ErrorSintacticoException("");
            }

            if (debug) {
                System.out.println("Token actual: " + ALex.id_tok.get(tokenActual.getId()));
                System.out.println("Tope de pila: " + topP.toString());
                System.out.println("Pila antes de procesar: " + stackP);
                System.out.println("Pila Auxiliar antes de procesar: " + stackAux);

            }

            switch (topP.getTipo()) {

                case TERMINAL -> {
                    if (debug) {
                        System.out.println("Procesando Terminal: " + ((Terminal) topP).getTokenLexema());
                    }
                    Terminal topPT = (Terminal) topP;

                    // finalización del programa
                    if (Objects.equals(topPT.getTokenId(), ALex.tok_id.get("EOF")) && Objects.equals(tokenActual.getId(), ALex.tok_id.get("EOF"))) {
                        if (debug) {
                            System.out.println("Análisis sintáctico finalizado correctamente.");
                        }
                        return;
                    }

                    int res = match(topPT);
                    if (res >= 1) {
                        if (res == 2) {
                            topPT.getAtributos().setPos((Integer) tokenActual.getAtributo());
                        }
                        if (res == 3) {
                            topPT.getAtributos().setVal((Integer) tokenActual.getAtributo());
                        }
                        if (res == 4) {
                            topPT.getAtributos().setLex((String) tokenActual.getAtributo());
                        }
                        stackAux.push(topPT.clone());
                        tokenActual = ALex.generarToken();
                    } else {
                        GestorError.writeError("sintactico", "Se esperaba " + topPT.getTokenLexema() + " y se leyó " + ALex.id_tok.get(tokenActual.getId()));
                    }
                }

                case NOTERMINAL -> {
                    if (debug) {

                        //System.out.println("Desplegando NoTerminal: " + ((NoTerminal) topP).getNombre());
                        // System.out.println("tablaAccSint" + TablaAccSint.get(((NoTerminal) topP).getNombre()));
                        //System.out.println("Token actual: " + ALex.id_tok.get(tokenActual.getId()));
                    }

                    stackAux.push(topP);

                    //conseguir la producción a aplicar
                    List<ItemPila> produccion = TablaAccSint
                            .get(((NoTerminal) topP).getNombre().toUpperCase())
                            .get(ALex.id_tok.get(tokenActual.getId()));

                    //error si no hay producción
                    if (produccion == null) {
                        StringBuilder sb = new StringBuilder();
                        sb.append("La lectura de ")
                                .append(tokenActual).append(" no respeta las reglas sintacticas.\n")
                                .append("Posibles tokens aceptados:\n");
                        List<String> TokensPosibles = new ArrayList<>(TablaAccSint.get(((NoTerminal) topP).getNombre().toUpperCase()).keySet());
                        for (String t : TokensPosibles) {
                            sb.append("\t - ").append(t).append("\n");
                        }
                        GestorError.writeError("sintáctico", sb.toString());
                        throw new ErrorSintacticoException("");
                    }

                    //añadir la producción a la pila (en orden inverso)
                    for (int i = produccion.size() - 1; i >= 0; i--) {
                        stackP.push(produccion.get(i).clone());
                    }

                    //imprimir la producción aplicada
                    String produccionAplicada = ((AccionSemantica) produccion.get(produccion.size() - 1)).getAccion();   //devuelve _accXX
                    produccionAplicada = produccionAplicada.replaceAll("_ACC(\\d+).*", "$1");
                    printParse(produccionAplicada);

                }

                case ACCIONSEMANTICA -> {
                    if (debug) {
                        System.out.println("Ejecutando acción semántica: " + ((AccionSemantica) topP).getAccion());
                    }
                    Runnable accion = mapaAcciones.get(((AccionSemantica) topP).getAccion());
                    if (accion == null) {
                        System.out.println("Acción semántica no encontrada: " + ((AccionSemantica) topP).getAccion());
                        throw new ErrorSintacticoException("");
                    }
                    accion.run();

                }
            }
        }
    }

    // Métodos para cargar la tabla sintáctica desde un archivo (CSV o HTML)
    public static void cargarTablaAccSint(String filename) {
        if (filename.endsWith(".csv")) {
            cargarTablaAccSintCSV(filename);
        } else if (filename.endsWith(".htm") || filename.endsWith(".html")) {
            cargarTablaAccSintHTML(filename);
        } else {
            System.err.println("Formato de archivo no soportado para la tabla sintáctica: " + filename);
            return;
        }

        if (debug) {
            // printTablaAccSint();
            // System.out.println("Mapa de Acciones Semánticas:");
            // System.out.println(mapaAcciones.keySet());
        }
    }

    // Función auxiliar para cargar la tabla sintáctica desde un archivo CSV
    private static void cargarTablaAccSintCSV(String filename) {
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            if (debug) {
                System.out.println("Cargando Tabla de Acciones Sintácticas desde: " + filename);
            }

            // ---- 1. Leer cabecera ----
            String linea = reader.readLine();
            cabecera = linea.split(",");
            procesar_cabecera(cabecera);

            // ---- 2. Leer líneas siguientes ----
            while ((linea = reader.readLine()) != null) {

                String[] partes = linea.split(",");

                if (partes.length != cabecera.length) {
                    System.err.println("Error al generar la tabla, la fila correspondiente a " + partes[0] + " tiene un número incorrecto de columnas.");
                    throw new ErrorSintacticoException("Error en la tabla sintáctica: número incorrecto de columnas");
                }

                if (partes[0].startsWith("_ACC")) {
                    procesar_acciones(partes[0]);
                }
                String noTerminal = partes[0].toUpperCase();

                HashMap<String, List<ItemPila>> fila = new HashMap<>();

                for (int i = 1; i < partes.length; i++) {

                    String celda = partes[i];
                    String tokenColumna = cabecera[i].toUpperCase();

                    if (celda.equals("--") || celda.equals("LAMBDA")) {
                        continue;
                    }

                    // Construir la lista de ItemPila
                    List<ItemPila> items = new ArrayList<>();
                    String[] acciones = celda.split(" ");
                    if (!acciones[0].toUpperCase().equals(noTerminal)) {
                        System.err.println("Error al generar la tabla, en la tabla correspondiente a " + noTerminal + " se ha encontrado un token que coincide con el no terminal: " + acciones[0]);
                        throw new ErrorSintacticoException("Error en la tabla sintáctica: token coincide con no terminal");
                    }

                    String elemento;
                    for (int j = 2; j < acciones.length; j++) {
                        elemento = acciones[j].toUpperCase();

                        if (elemento.startsWith("_ACC")) {
                            // acción semántica
                            items.add(new AccionSemantica(elemento));
                        } else if (token_names.contains(elemento)) {
                            // terminal
                            if (elemento.contains("final de cadena") || elemento.contains("FINAL DE CADENA")) {
                                elemento = "EOF";
                            }
                            items.add(new Terminal((elemento)));
                        } else {
                            // no terminal
                            items.add(new NoTerminal(elemento));
                        }
                    }
                    fila.put(tokenColumna.toUpperCase(), items);
                }

                TablaAccSint.put(noTerminal, fila);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void procesar_cabecera(String[] cabecera) {
        for (String token : cabecera) {
            if (!token.startsWith("_ACC")) {
                if (token.contains("final de cadena") || token.contains("FINAL DE CADENA")) {
                    token = "EOF";
                }
                token_names.add(token.toUpperCase());
            }
        }
    }

    // Función auxiliar para cargar la tabla sintáctica desde un archivo HTML
    private static void cargarTablaAccSintHTML(String filename) {
        try {
            File input = new File(filename);
            Document doc = Jsoup.parse(input, "UTF-8");

            Element tabla = doc.select("table").first();
            org.jsoup.select.Elements filas = tabla.select("tr");

            // ---- 1. Leer cabecera ----
            org.jsoup.select.Elements ths = filas.get(0).select("th");
            cabecera = new String[ths.size()];
            for (int i = 0; i < ths.size(); i++) {
                cabecera[i] = ths.get(i).text().toUpperCase();
                if (cabecera[i].contains("final de cadena") || cabecera[i].contains("FINAL DE CADENA")) {
                    cabecera[i] = "EOF";
                }
                if (!cabecera[i].startsWith("_ACC") && !cabecera[i].equals("")) {
                    token_names.add(cabecera[i]);
                }
            }

            // ---- 2. Leer filas de no terminales ----
            for (int f = 1; f < filas.size(); f++) {
                Elements celdas = filas.get(f).children();
                if (celdas.isEmpty()) {
                    continue;
                }

                String noTerminal = celdas.get(0).text().toUpperCase();
                if (noTerminal.isEmpty()) {
                    continue;
                }

                //no es un no terminal, es una acción semántica
                if (noTerminal.startsWith("_ACC")) {
                    procesar_acciones(noTerminal);
                    continue;
                }

                HashMap<String, List<ItemPila>> fila = new HashMap<>();

                if (celdas.size() != cabecera.length + 1) {
                    System.err.println("Error al generar la tabla, la fila correspondiente a " + noTerminal + " tiene un número incorrecto de columnas.");
                    throw new ErrorSintacticoException("Error en la tabla sintáctica: número incorrecto de columnas");
                }

                // Recorrer columnas (tokens)
                for (int i = 1; i < celdas.size(); i++) {
                    String celda = celdas.get(i).text().trim().toUpperCase();

                    if (celda.equals("--") || celda.equals("LAMBDA") || celda.isEmpty()) {
                        continue;
                    }

                    String tokenColumna = cabecera[i - 1];

                    // Ejemplo de celda:  "A → PARENT_ABRIR X id DOSPUNTOS T _acc16_1 AA PARENT_CERRAR _acc16_2"
                    String[] partesRegla = celda.split("\\s+");

                    if (!partesRegla[0].equals(noTerminal)) {
                        System.err.println("Error al generar la tabla, en la tabla correspondiente a " + noTerminal + " se ha encontrado un token que coincide con el no terminal: " + partesRegla[0]);
                        throw new ErrorSintacticoException("Error en la tabla sintáctica: token coincide con no terminal");
                    }

                    String rhs = celda.split("→")[1].trim();
                    String[] partes = rhs.split("\\s+");

                    List<ItemPila> items = new ArrayList<>();

                    for (String p : partes) {
                        p = p.toUpperCase();

                        if (p.startsWith("_ACC")) {
                            items.add(new AccionSemantica(p));
                        } else if (token_names.contains(p)) {
                            items.add(new Terminal(p));
                        } else {
                            items.add(new NoTerminal(p));
                        }
                    }

                    fila.put(tokenColumna, items);
                }

                TablaAccSint.put(noTerminal, fila);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void printTablaAccSint() {
        for (String nt : TablaAccSint.keySet()) {
            System.out.println("NoTerminal: " + nt);
            HashMap<String, List<ItemPila>> fila = TablaAccSint.get(nt);

            for (String token : fila.keySet()) {
                System.out.println("  Token " + token + " -> " + fila.get(token));
            }
            System.out.println();
        }
    }

    // Función auxiliar para asociar el nombre de las acciones semánticas con los métodos correspondientes en ASem
    private static void procesar_acciones(String accionNombre) {

        // convertir "_ACC1_1" a "acc_1_1"
        String nombreMetodo = accionNombre.substring(1).toLowerCase(); // quita la barra baja inicial → "acc1_1"

        try {
            Method m = ASem.class.getMethod(nombreMetodo);

            Runnable r = () -> {
                try {
                    m.invoke(null); // null porque es un método estático
                } catch (Exception e) {
                    System.err.println("Error al invocar el método: " + nombreMetodo);
                    e.printStackTrace();
                    throw new ErrorSintacticoException(
                            "");
                }
            };

            mapaAcciones.put(accionNombre.toUpperCase(), r);

        } catch (NoSuchMethodException e) {
            System.err.println("Método no encontrado: " + nombreMetodo);
        }
    }

    // Función auxiliar para comparar el token actual con el token esperado.
    // Devuelve:
    //     - 0 -> no coincide
    //     - 1 -> coincide
    //     - 2 -> coincide y es ID
    //     - 3 -> coincide y es ENTERO
    //     - 4 -> coincide y es CADENA
    private static int match(Terminal tokenEsperado) {

        int esperadoInt = tokenEsperado.getTokenId();
        int result = 0;
        if (debug) {
            System.out.println("token actual " + tokenActual.toString() + " esperado " + tokenEsperado.getTokenLexema());
        }

        if (tokenActual.getId() == esperadoInt) {
            result = 1;
            if (Objects.equals(tokenActual.getId(), ALex.tok_id.get("ID"))) {
                result = 2;
            }
            if (Objects.equals(tokenActual.getId(), ALex.tok_id.get("ENTERO"))) {
                result = 3;
            }
            if (Objects.equals(tokenActual.getId(), ALex.tok_id.get("CADENA"))) {
                result = 4;
            }

        } else {
            //   hayError = true;
            result = 0;
            GestorError.writeError("sintáctico",
                    "Se esperaba " + tokenEsperado.getTokenLexema()
                    + " pero se encontró " + ALex.id_tok.get(tokenActual.getId())
            );
            throw new ErrorSintacticoException("");
        }

        return result;

    }

    public static void activarDebug() {
        debug = true;
    }

    public static void desactivarDebug() {
        debug = false;
    }

    public static void setOutputParseFile(BufferedWriter parsePtr) {
        ptwParse = parsePtr;
    }

    @SuppressWarnings("CallToPrintStackTrace")
    private static void printParse(String ReglaSintactica) {
        try {
            // System.out.println(ReglaSintactica + "\n");
            ptwParse.write(ReglaSintactica + " ");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
