package sudoku;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;

import sudoku.core.Console;

public class Sudoku {
    private final Grille grille;
    private final List<Contrainte> contraintes;

    public Sudoku(int taille) {
        this.grille = new Grille(taille, this);
        this.contraintes = new ArrayList<>();
    }

    public void ajouterContrainte(Contrainte contrainte) {
        contraintes.add(contrainte);
    }

    // public void creerBloc(List<int[]> positions, int blocHeight, int blocWidth) {
    // grille.creerBloc(positions, blocHeight, blocWidth);
    // }

    public boolean estValide(Case c) {
        for (Contrainte contrainte : contraintes) {
            if (!contrainte.estRespectee(grille, c)) {
                return false;
            }
        }
        return true;
    }

    public boolean verifierToutesContraintes() {
        return grille.verifierToutesContraintes(contraintes);
    }

    public Grille getGrille() {
        return grille;
    }

    public static Sudoku menuGrille() {
        Scanner scanner = new Scanner(System.in);
        // ETAPE 1 : TAILLE GRILLE
        System.out.println("ETAPE 1 : Choisir la taille de la grille");
        int tailleGrille = setTailleGrille(scanner); // récpère la taille de la grille
        Sudoku sudoku = new Sudoku(tailleGrille);
        // ETAPE 2 : SYMBOLE POSSIBLE
        System.out.println("ETAPE 2 : Choisir les symboles possibles");
        int typeSymbole = Grille.choisirTypeSymbole(scanner);
        sudoku.getGrille().askSetSymbolesPossibles(typeSymbole, scanner); // demande à l'utilisateur de saisir ses
                                                                          // symboles
        // ETAPE 3 : REMPLIR LA GRILLE
        System.out.println("ETAPE 3 : Remplir la grille");
        setValeursGrille(sudoku, scanner, tailleGrille, typeSymbole);
        System.out.println("Voici votre sudoku rempli :");
        System.out.println(sudoku.getGrille().toString());
        // ETAPE 4 : CHOIX DES CONTRAINTES
        choixContraintes(sudoku, scanner);
        // ETAPE 5 : RESOLUTION
        System.out.println("ETAPE 5 : Résolution du sudoku");
        choixResolution(sudoku, scanner);

        return sudoku;
    }

    private static void choixContraintes(Sudoku sudoku, Scanner scanner) {
        System.out.println("Choisissez une combinaison entre 0 et 3 contraintes parmi les suivantes :");
        System.out.println("1: Contrainte Bloc, 2: Contrainte Ligne, 3: Contrainte Colonne");
        System.out.println(
                "Entrez les numéros des contraintes séparés par des virgules (par exemple, 1,2,3) ou appuyez sur Entrée pour aucune contrainte :");

        String input = scanner.nextLine();
        if (input.trim().isEmpty()) {
            System.out.println("Aucune contrainte sélectionnée.");
            return;
        }

        String[] choix = input.split(",");
        Set<Integer> contraintesChoisies = new HashSet<>();
        for (String choixStr : choix) {
            try {
                int choixInt = Integer.parseInt(choixStr.trim());
                if (choixInt >= 1 && choixInt <= 3) {
                    contraintesChoisies.add(choixInt);
                } else {
                    System.out.println("Choix invalide : " + choixInt);
                }
            } catch (NumberFormatException e) {
                System.out.println("Entrée invalide : " + choixStr);
            }
        }

        for (int choixContrainte : contraintesChoisies) {
            switch (choixContrainte) {
                case 1:
                    sudoku.ajouterContrainte(new ContrainteBloc());
                    System.out.println("Contrainte Bloc ajoutée.");
                    break;
                case 2:
                    sudoku.ajouterContrainte(new ContrainteLigne());
                    System.out.println("Contrainte Ligne ajoutée.");
                    break;
                case 3:
                    sudoku.ajouterContrainte(new ContrainteColonne());
                    System.out.println("Contrainte Colonne ajoutée.");
                    break;
                default:
                    System.out.println("Choix invalide : " + choixContrainte);
            }
        }
    }

    private void resolutionSudoku(Sudoku sudoku, boolean afficherEtape) {
        ResolveurBacktraceSimple resolveur = new ResolveurBacktraceSimple(sudoku);
        resolveur.resoudre(sudoku, afficherEtape);
        System.out.println("Sudoku résolu :");
        System.out.println(sudoku.getGrille().toString());
    }

    private static void choixResolution(Sudoku sudoku, Scanner scanner) {
        System.out.println("Voulez-vous résoudre le sudoku ? (O/N)");
        String choix = scanner.nextLine();
        if (choix.equalsIgnoreCase("O")) {
            System.out.println("Résolution du sudoku.");
            // resolutionSudoku(sudoku);
            System.out.println("Voulez-vous afficher les étapes de la résolution ? (O/N)");
            String choixAffichageEtapes = scanner.nextLine();

            if (choixAffichageEtapes.equalsIgnoreCase("O")) {
                sudoku.resolutionSudoku(sudoku, true);
            } else if (choixAffichageEtapes.equalsIgnoreCase("N")) {
                sudoku.resolutionSudoku(sudoku, false);
            } else {
                System.err.println("Choix invalide.");
            }
        } else if (choix.equalsIgnoreCase("N")) {
            System.err.println("Fin du jeu.");
        } else {
            System.out.println("Choix invalide.");
            choixResolution(sudoku, scanner);
        }
    }

    private static int setTailleGrille(Scanner scanner) {
        int tailleGrille = -1;
        while (tailleGrille <= 0) {
            try {
                tailleGrille = Integer.parseInt(scanner.nextLine());
                if (tailleGrille <= 0) {
                    System.out.println("Erreur : Veuillez entrer un entier positif.");
                }
            } catch (NumberFormatException e) {
                System.out.println("Erreur : Entrée invalide. Veuillez entrer un nombre.");
            }
        }
        return tailleGrille;
    }

    private static Sudoku setValeursGrille(Sudoku sudoku, Scanner scanner, int tailleGrille, int typeSymbole) {
        // Etude de la taille de la grille pour choisir le type de génération
        if (carreParfait(tailleGrille)) {
            System.out.println(tailleGrille + " est un carré parfait.");
            sudoku.getGrille().creerBlocCarre();
        } else {
            while (true) {
                System.out.println("Veuillez faire votre choix");
                System.out.println("1 : Entrer les blocs manuellement");
                System.out.println("2 : Entrer les blocs à l'aide de la longueur et de la largeur");

                int choixGenerationBloc = Integer.parseInt(scanner.nextLine());

                switch (choixGenerationBloc) {
                    case 1:
                        // Entrer les blocs manuellement
                        System.out.println("Entrez les blocs manuellement.");
                        creationBlocMannuel(sudoku, scanner, tailleGrille);
                        break;
                    case 2:
                        // Entrer les blocs à l'aide de la longueur et de la largeur
                        boolean validDimensions = false;
                        while (!validDimensions) {
                            try {
                                System.out.println("Entrez les blocs à l'aide de la longueur et de la largeur.");
                                System.out.println("Entrez la longueur du bloc : ");
                                int longueurBloc = Integer.parseInt(scanner.nextLine());
                                System.out.println("Entrez la largeur du bloc :");
                                int largeurBloc = Integer.parseInt(scanner.nextLine());
                                sudoku.getGrille().creerBlocRectangulaire(longueurBloc, largeurBloc);
                                validDimensions = true; // Sortir de la boucle si aucune exception n'est lancée
                            } catch (IllegalArgumentException e) {
                                Console.errorln("Erreur : " + e.getMessage());
                                Console.errorln("Veuillez entrer des dimensions valides.");
                            }
                        }
                        break;
                    default:
                        Console.errorln("Choix invalide.");
                        continue;
                }
                break;
            }
        }

        // DEBUT DU REMPLISSAGE DU SUDOKU
        String input;
        while (true) {
            // Demander et vérifier la ligne
            int ligne = -1;
            while (true) {
                System.out.println("Pour arrêter la saisie, tapez \"esc\".");
                System.out.println(
                        "Entrez le numéro de ligne :");
                input = scanner.nextLine(); // Lire la ligne
                if (input.equalsIgnoreCase("ESC")) {
                    break; // Sortie de la boucle si l'utilisateur tape ESC
                }
                if (input.isEmpty()) {
                    System.out.println("Veuillez entrer un numéro de ligne valide.");
                    continue; // Recommencer la saisie de la ligne si l'entrée est vide
                }
                try {
                    ligne = Integer.parseInt(input); // Convertir la ligne en entier
                    break; // Sortir de la boucle si la ligne est valide
                } catch (NumberFormatException e) {
                    System.out.println("Veuillez entrer un numéro de ligne valide (un nombre entier).");
                }
            }
            // ARRET DE LA BOUCLE SI SAISIE DE "ESC"
            if (input.equalsIgnoreCase("ESC")) {
                break; // Sortie de la boucle principale si l'utilisateur tape ESC
            }

            // Demander et vérifier la colonne
            int colonne = -1;
            while (true) {
                System.out.println("Entrez le numéro de colonne :");
                input = scanner.nextLine(); // Lire la colonne
                if (input.isEmpty()) {
                    System.out.println("Veuillez entrer un numéro de colonne valide.");
                    continue; // Recommencer la saisie de la colonne si l'entrée est vide
                }
                try {
                    colonne = Integer.parseInt(input); // Convertir la colonne en entier
                    break; // Sortir de la boucle si la colonne est valide
                } catch (NumberFormatException e) {
                    System.out.println("Veuillez entrer un numéro de colonne valide (un nombre entier).");
                }
            }

            // Demander et vérifier le symbole
            String symbole = "";
            while (true) {
                System.out.println("Entrez le symbole :");
                symbole = scanner.nextLine(); // Lire le symbole
                if (symbole.isEmpty()) {
                    System.out.println("Veuillez entrer un symbole valide.");
                    continue; // Recommencer la saisie du symbole si l'entrée est vide
                }
                try {
                    Symbole s = null;
                    switch (typeSymbole) {
                        case 1:
                            // Entiers
                            s = Symbole.of(Integer.parseInt(symbole));
                            break;
                        case 2:
                            // Lettres
                            if (symbole.length() == 1 && Character.isLetter(symbole.charAt(0))) {
                                s = Symbole.of(symbole.charAt(0));
                            } else {
                                throw new IllegalArgumentException("Veuillez entrer une seule lettre.");
                            }
                            break;
                        case 3:
                            // Chaînes de caractères
                            s = Symbole.of(symbole);
                            break;
                        default:
                            throw new IllegalArgumentException("Type de symbole invalide.");
                    }
                    sudoku.getGrille().setCase(ligne, colonne, s); // Ajouter le symbole à la grille

                    System.out.println(sudoku.getGrille().toString());
                    break; // Sortir de la boucle si le symbole est valide
                } catch (IllegalArgumentException e) {
                    System.out.println("Symbole non valide. " + e.getMessage());
                }
            }
        }
        return sudoku;
    }

    private static boolean carreParfait(int nombre) {
        if (nombre < 0) {
            return false;
        }
        int racine = (int) Math.sqrt(nombre);
        return racine * racine == nombre;
    }

    private static Sudoku creationBlocMannuel(Sudoku sudoku, Scanner scanner, int tailleGrille) {
        int nombreBloc = tailleGrille; // Nombre de blocs dans la grille

        Set<String> toutesLesCoordonnees = new HashSet<>(); // Stocke toutes les coordonnées utilisées

        while (nombreBloc != 0) {
            List<int[]> listeCases = new ArrayList<>(); // Liste des cases du bloc
            System.out.println("Remplissage du bloc " + nombreBloc);

            for (int i = 1; i <= tailleGrille; i++) {
                int choixLigne, choixColonne;
                boolean coordonneesValides;

                do {
                    coordonneesValides = true; // Par défaut, on suppose que la coordonnée est valide

                    // Demande de saisie
                    System.out.println("Entrez la ligne de la case " + i + " dans le bloc " + nombreBloc + " (0 à "
                            + (tailleGrille - 1) + "):");
                    choixLigne = scanner.nextInt();
                    System.out.println("Entrez la colonne de la case " + i + " dans le bloc " + nombreBloc + " (0 à "
                            + (tailleGrille - 1) + "):");
                    choixColonne = scanner.nextInt();

                    // Vérification des limites
                    if (choixLigne < 0 || choixLigne >= tailleGrille || choixColonne < 0
                            || choixColonne >= tailleGrille) {
                        System.out.println("Erreur : Coordonnées hors limites ! Veuillez réessayer.");
                        coordonneesValides = false;
                        continue;
                    }

                    // Vérification si la coordonnée existe déjà dans TOUTE la grille
                    String coordonneeStr = choixLigne + "-" + choixColonne;
                    if (toutesLesCoordonnees.contains(coordonneeStr)) {
                        System.out.println(
                                "Erreur : Ces coordonnées sont déjà utilisées dans un autre bloc ! Veuillez en entrer une nouvelle.");
                        coordonneesValides = false;
                        continue;
                    }

                } while (!coordonneesValides); // Tant que la saisie est invalide, on redemande

                // Ajout des coordonnées validées
                listeCases.add(new int[] { choixLigne, choixColonne });
                toutesLesCoordonnees.add(choixLigne + "-" + choixColonne); // Ajout dans l'ensemble global
            }

            // Création du bloc après la saisie complète
            try {
                sudoku.getGrille().creerBlocPersonnalise(listeCases);
                System.out.println("Bloc " + nombreBloc + " enregistré avec succès !");
            } catch (IllegalArgumentException e) {
                System.out.println("Erreur lors de la création du bloc : " + e.getMessage());
                System.out.println("Veuillez recommencer la saisie de ce bloc.");
                continue; // Recommence le bloc en cours
            }

            nombreBloc--; // Passer au bloc suivant
        }
        return sudoku;
    }
}