package sudoku;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Scanner;

import sudoku.core.Console;
import sudoku.gui.ColorGenerator;

public class Grille {
    private final int taille;
    private final Case[][] cases;
    private final ArrayList<Bloc> blocs;
    private ArrayList<Symbole> symbolesPossibles;
    private final Sudoku sudoku;
    private List<String> generatedColors;

    public Grille(int taille, Sudoku sudoku) {
        this.taille = taille;
        this.cases = new Case[taille][taille];
        this.blocs = new ArrayList<>();
        this.symbolesPossibles = new ArrayList<>();
        this.sudoku = sudoku;
        // Ici, on génère une palette de couleurs (par exemple 4 couleurs).
        // Si vous souhaitez avoir autant de couleurs qu'il y a de blocs,
        // vous pouvez adapter le nombre passé à greatScheme/greatPalette.
        initColors();

        // Initialiser les cases
        for (int i = 0; i < taille; i++) {
            for (int j = 0; j < taille; j++) {
                cases[i][j] = new Case(i, j, null);
            }
        }
    }

    private void initColors() {
        int numberOfColors = 4; // ou un autre nombre souhaité
        List<ColorGenerator.Color> colors = ColorGenerator.greatScheme(numberOfColors);
        generatedColors = new ArrayList<>();
        for (ColorGenerator.Color color : colors) {
            generatedColors.add(convertToAnsi(color));
        }
    }

    private String convertToAnsi(ColorGenerator.Color color) {
        int r = Math.round(color.r * 255);
        int g = Math.round(color.g * 255);
        int b = Math.round(color.b * 255);
        return String.format("\u001B[38;2;%d;%d;%dm", r, g, b);
    }

    public void setCase(int ligne, int colonne, Symbole symbole) {
        try {
            if (symbole != null && !symbolesPossibles.contains(symbole)) {
                throw new IllegalArgumentException("Symbole non autorisé : " + symbole);
            }
            // Sauvegarder l'ancien symbole
            Symbole ancienSymbole = cases[ligne][colonne].getSymbole();

            // Affecter le nouveau symbole
            cases[ligne][colonne].setSymbole(symbole);

            // Vérifier les contraintes
            if (!sudoku.verifierToutesContraintes()) {
                // Revenir sur le changement
                cases[ligne][colonne].setSymbole(ancienSymbole);
                throw new IllegalArgumentException("SET CASE: Les contraintes ne sont pas respectées pour la case ("
                        + ligne + ", " + colonne + ")");
            }
        } catch (Exception e) {
            Console.errorln(e.getMessage());
        }
    }

    public Case getCase(int ligne, int colonne) {
        return cases[ligne][colonne];
    }

    /**
     * Crée un bloc personnalisé à partir des positions spécifiées.
     * La couleur du bloc est choisie de façon cyclique dans la palette générée.
     *
     * Exemple d'utilisation :
     * sudoku.getGrille().creerBlocPersonnalise(Arrays.asList(
     * new int[] { 0, 0 },
     * new int[] { 0, 1 },
     * new int[] { 0, 2 },
     * new int[] { 1, 0 },
     * new int[] { 1, 1 },
     * new int[] { 1, 2 },
     * new int[] { 2, 0 },
     * new int[] { 2, 1 },
     * new int[] { 2, 2 }
     * ));
     */
    public void creerBlocPersonnalise(List<int[]> positions) {
        try {
            // Validation des positions
            for (int[] pos : positions) {
                if (pos[0] < 0 || pos[0] >= taille || pos[1] < 0 || pos[1] >= taille) {
                    throw new IllegalArgumentException("Position invalide : (" + pos[0] + ", " + pos[1] + ")");
                }
            }

            // Choisir la couleur suivante en fonction du nombre de blocs déjà créés
            int couleurIndex = blocs.size() % generatedColors.size();
            Bloc bloc = new Bloc(generatedColors.get(couleurIndex), couleurIndex);

            for (int[] pos : positions) {
                bloc.ajouterCase(cases[pos[0]][pos[1]]);
            }
            blocs.add(bloc);

        } catch (IllegalArgumentException e) {
            Console.errorln(e.getMessage());
        }
    }

    /**
     * Crée des blocs carrés (par exemple pour un sudoku classique).
     */
    public void creerBlocCarre() {
        try {
            int blocSize = (int) Math.sqrt(taille);
            if (blocSize * blocSize != taille) {
                throw new IllegalArgumentException("La taille de la grille doit être un carré parfait.");
            }

            // Création des blocs en motif 3x3 (ou autre selon la taille)
            for (int blocRow = 0; blocRow < blocSize; blocRow++) {
                for (int blocCol = 0; blocCol < blocSize; blocCol++) {
                    List<int[]> positions = new ArrayList<>();

                    // Ajouter toutes les positions pour le bloc courant
                    for (int i = 0; i < blocSize; i++) {
                        for (int j = 0; j < blocSize; j++) {
                            positions.add(new int[] {
                                    blocRow * blocSize + i,
                                    blocCol * blocSize + j
                            });
                        }
                    }

                    // Affectation cyclique d'une couleur issue de la palette générée
                    int couleurIndex = blocs.size() % generatedColors.size();
                    Bloc bloc = new Bloc(generatedColors.get(couleurIndex), couleurIndex);

                    for (int[] pos : positions) {
                        bloc.ajouterCase(cases[pos[0]][pos[1]]);
                    }
                    blocs.add(bloc);
                }
            }
        } catch (IllegalArgumentException e) {
            Console.errorln(e.getMessage());
        }
    }

    /**
     * Crée des blocs rectangulaires automatiquement à partir de la taille de la
     * grille.
     * Ne fonctionne pas pour les grilles dont la taille est un carré parfait.
     */
    public void creerBlocRectangulaire(int blocHeight, int blocWidth) {
        try {
            if (taille % blocHeight != 0 || taille % blocWidth != 0) {
                throw new IllegalArgumentException(
                        "La taille de la grille doit être divisible par les dimensions des blocs.");
            }

            int blocsParLigne = taille / blocWidth;
            int blocsParColonne = taille / blocHeight;

            // Création des blocs en motif rectangulaire
            for (int blocRow = 0; blocRow < blocsParColonne; blocRow++) {
                for (int blocCol = 0; blocCol < blocsParLigne; blocCol++) {
                    List<int[]> positions = new ArrayList<>();

                    // Ajouter toutes les positions pour le bloc courant
                    for (int i = 0; i < blocHeight; i++) {
                        for (int j = 0; j < blocWidth; j++) {
                            positions.add(new int[] {
                                    blocRow * blocHeight + i,
                                    blocCol * blocWidth + j
                            });
                        }
                    }

                    int couleurIndex = blocs.size() % generatedColors.size();
                    Bloc bloc = new Bloc(generatedColors.get(couleurIndex), couleurIndex);

                    for (int[] pos : positions) {
                        bloc.ajouterCase(cases[pos[0]][pos[1]]);
                    }
                    blocs.add(bloc);
                }
            }
        } catch (IllegalArgumentException e) {
            Console.errorln(e.getMessage());
        }
    }

    public void printBlocs() {
        for (Bloc bloc : blocs) {
            System.out.println(bloc.toString());
        }
    }

    public static int choisirTypeSymbole(Scanner scanner) {
        while (true) {
            System.out.println("Choisissez le type de symbole :");
            System.out.println("1 : Entiers");
            System.out.println("2 : Lettres");
            System.out.println("3 : Chaînes de caractères / Emoji");

            String input = scanner.nextLine();
            try {
                int choix = Integer.parseInt(input);
                if (choix >= 1 && choix <= 3) {
                    return choix;
                } else {
                    System.out.println("Choix invalide. Veuillez entrer 1, 2 ou 3.");
                }
            } catch (NumberFormatException e) {
                System.out.println("Entrée invalide. Veuillez entrer un nombre.");
            }
        }
    }

    public void askSetSymbolesPossibles(int choix, Scanner scanner) {
        try {
            for (int i = 0; i < taille; i++) {
                System.out.println("Entrez le symbole " + (i + 1) + "/" + taille + " :");
                String input = scanner.nextLine();

                switch (choix) {
                    case 1: // Nombres
                        try {
                            Symbole intTemp = Symbole.of(Integer.parseInt(input));
                            if (symbolesPossibles.contains(intTemp)) {
                                Console.errorln("Ce symbole existe déjà, veuillez entrer un autre symbole");
                                i--;
                            } else {
                                symbolesPossibles.add(intTemp);
                            }
                        } catch (NumberFormatException e) {
                            Console.errorln("Veuillez entrer un nombre valide");
                            i--;
                        }
                        break;

                    case 2: // Lettres
                        if (input.length() == 1 && Character.isLetter(input.charAt(0))) {
                            Symbole charTemp = Symbole.of(input.charAt(0));
                            if (symbolesPossibles.contains(charTemp)) {
                                Console.errorln("Ce symbole existe déjà, veuillez entrer un autre symbole");
                                i--;
                            } else {
                                symbolesPossibles.add(charTemp);
                            }
                        } else {
                            Console.errorln("Veuillez entrer une lettre valide");
                            i--;
                        }
                        break;

                    case 3: // Texte/Emoji
                        Symbole stringTemp = Symbole.of(input);
                        if (symbolesPossibles.contains(stringTemp)) {
                            Console.errorln("Ce symbole existe déjà, veuillez entrer un autre symbole");
                            i--;
                        } else if (Objects.equals(input, "ESC")) {
                            Console.errorln("Ce symbole est interdit, veuillez entrer un autre symbole");
                            i--;
                        } else {
                            symbolesPossibles.add(stringTemp);
                        }
                        break;

                    default:
                        Console.errorln("Type non supporté");
                        return;
                }
            }
        } catch (Exception e) {
            Console.errorln("Une erreur est survenue : " + e.getMessage());
        }
    }

    public List<Symbole> getSymbolesPossibles() {
        return symbolesPossibles;
    }

    public void setSymbolesPossibles(ArrayList<Symbole> symbolesPossibles) {
        this.symbolesPossibles = symbolesPossibles;
    }

    public void printSymbolesPossibles() {
        StringBuilder sb = new StringBuilder();
        for (Symbole symbole : symbolesPossibles) {
            sb.append(symbole.toString()).append(" ");
        }
        System.out.println(sb.toString());
    }

    public int getTaille() {
        return taille;
    }

    private Bloc findBlocForCase(Case target) {
        for (Bloc bloc : blocs) {
            if (bloc.getCases().contains(target)) {
                return bloc;
            }
        }
        return null; // Ne devrait jamais arriver si la grille est bien construite
    }

    /**
     * Vérifie si toutes les contraintes sont respectées.
     * S'arrête dès qu'une contrainte n'est pas respectée.
     *
     * @param contraintes
     * @return
     */
    public boolean verifierToutesContraintes(List<Contrainte> contraintes) {
        // Vérifier chaque case de la grille
        for (int i = 0; i < taille; i++) {
            for (int j = 0; j < taille; j++) {
                Case currentCase = cases[i][j];
                // Ne vérifier que les cases qui ont un symbole
                if (currentCase.getSymbole() != null) {
                    // Vérifier toutes les contraintes pour cette case
                    for (Contrainte contrainte : contraintes) {
                        if (!contrainte.estRespectee(this, currentCase)) {
                            Console.errorln(
                                    "GRILLE: Contrainte non respectée à la position : ligne=" + i + ", colonne=" + j);
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }

    public Case getCaseLibre() {
        for (int i = 0; i < taille; i++) {
            for (int j = 0; j < taille; j++) {
                if (cases[i][j].getSymbole() == null) {
                    return cases[i][j];
                }
            }
        }
        return null;
    }

    public int getLongueurSymboleLePlusLong() {
        int max = 0;
        for (Symbole symbole : symbolesPossibles) {
            if (symbole.toString().length() > max) {
                max = symbole.toString().length();
            }
        }
        return max;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        int maxLen = getLongueurSymboleLePlusLong(); // longueur maximale des symboles

        for (int i = 0; i < taille; i++) {
            for (int j = 0; j < taille; j++) {
                Case currentCase = cases[i][j];
                String cellStr = currentCase.toString();
                // Calculer le nombre d'espaces à ajouter pour atteindre maxLen
                int pad = maxLen - cellStr.length();
                String padding = " ".repeat(pad); // nécessite Java 11 ou ultérieur

                Bloc bloc = findBlocForCase(currentCase);

                if (bloc != null) {
                    sb.append(bloc.getCouleur()) // couleur du bloc
                            .append(cellStr)
                            .append(padding)
                            .append("\u001B[0m ") // réinitialiser la couleur
                            .append(" ");
                } else {
                    sb.append(cellStr)
                            .append(padding)
                            .append(" ");
                }
            }
            sb.append("\n");
        }
        return sb.toString();
    }

}
