# -*- coding:utf-8 -*-

__nom_fichier__ = "region"
__author__ = "tinet & Fay-Varnier"
__date__ = "avril 2018"

"""
Exercices récapitulatifs

Le Sudoku

Classe Region

"""
# Comptage de lignes
# Région : 2 (__init__) + 5 (__str__) + 5 (getEtat) + 5 (dataRegion) + 8 (doubleValeur) + 18 (placeUnique) + 12 (Couples)
# Ligne : 3; Colonne : 3; Carre : 3
# Total : 69 lignes

from case import Case


class Region:

    def __init__(self, liste_cases):
        """
        Constructeur de la classe région.
        :param liste_cases: liste de Case
        """
        self.lcases_ = liste_cases

    def __str__(self):
        """
        Surcharge de print et de str
        :return: str, ligne à afficher
        """
        txt = ""

        for case in self.lcases_:
            txt += str(case) + '  '

        return txt

    def get_etat(self):
        """
        Indique si la region est connue, cad si toutes les cases sont connues
        :return: True si la région est complètement remplie False sinon
        """
        for case in self.lcases_:
            # on regarde si l'état de la case est connue
            # si ce n'est pas le cas c'est que l'état de la region ne l'est pas non plus
            if not case.get_etat():
                return False

        return True

    def data_region(self):
        """
        :return: liste des valeurs (int) déjà associées à une case,
                liste des valeurs disponibles
                et liste des cases vides
        """
        lvides = [case for case in self.lcases_ if not case.get_etat()]
        # dans case.getdomaine() on a une seule case quand la case est connue
        lassociees = [case.get_domaine()[0] for case in self.lcases_ if case.get_etat()]
        ldisponible = [v for v in range(1, 10) if v not in lassociees]

        return lvides, lassociees, ldisponible

    def exclut_double_valeur(self):
        """
        Implémente la règle de résolution 1
        Exclut du domaine des possibles des cases vides les valeurs déjà associées.
        :return: True si la règle a permis de modifier le domaine des possibles de au moins 1 case False sinon
        """

        # Si la région est connue, on ne fait rien
        if self.get_etat():
            return False

        lvides, lassociees, ldisponible = self.data_region()
        flag = False    # permet de retourner s'il y a eu un changement ou pas

        # On ne travaille que sur les cases vides
        for case in lvides:
            # Réduction du domaine et prise en compte d'une éventuelle modification
            # Il faut d'abord mettre l'appel de la fonction pour être sûrs qu'elle soit faite
            flag = case.reduit_domaine(lassociees) or flag

        return flag

    def place_unique(self):
        """
        Implémente la règle de résolution 2
        Isole les valeurs n'ayant qu'une seule position possible. Modifie la liste de cases.
        :return: True si la règle a permis de modifier le domaine des possibles de au moins 1 case False sinon
        """

        # Si la région est connue, on ne fait rien
        if self.get_etat():
            return False

        lvides, lassociees, ldisponible = self.data_region()
        flag = False

        for valeur in ldisponible:
            # Cherche si la valeur a une case unique
            # on compte le nombre de fois où la valeur est trouvé dans les domaines des possibles des cases vides
            caseUnique = None   # permet de repérer si la valeur a été trouvé
                                # et de stocker la case dans laquelle elle a été trouvé
            for case in lvides:
                if valeur in case.get_domaine():
                    if caseUnique == None:
                        # la valeur apparaissait pour la 1ère fois on mémorise la case sur laquelle elle apparaît
                        caseUnique = case
                    else:
                        # la valeur a déjà été trouvée dans une autre case
                        # Il y a au moins 2 cases contenant la valeur, il n'est pas nécessaire de continuer la recherche
                        caseUnique = None
                        break

            # Fixe la valeur de la case unique
            if caseUnique != None:
                # print("valeur fixée : ", valeur)
                caseUnique.fixe_valeur(valeur)
                flag = True

        return flag

    def couple_de_valeurs(self):
        """
        Implémente la règle de résolution 3
        Retire des cases vides des doublets de valeurs présents à l'identique dans deux cases vides.
        :return: True si la règle a permis de modifier le domaine des possibles de au moins 1 case False sinon
        """
        if self.get_etat():
            return False

        lvides, lassociees, ldisponible = self.data_region()
        flag = False

        # liste des cases dont le domaine est réduit à deux valeurs
        lcouples = [c.get_domaine() for c in lvides if len(c.get_domaine()) == 2]

        # Liste pour ne contenant que les couples avec un couple identique
        lsosies = [lcouples[i] for i in range(len(lcouples)) if lcouples[i] in lcouples[i + 1:]]

        # On retire les valeurs 'sosies' des autres cases vides:
        for case in lvides:
            for doublet in lsosies:
                if case.get_domaine() != doublet:
                    flag = case.reduit_domaine(doublet) or flag

        return flag


class Ligne(Region):

    def __init__(self, matrice, loc):
        """
        Constructeur de la sous-classe Ligne.
        :param matrice: Liste de listes de Case
        :param loc: numéro de la Ligne (de 1 à 9)
        """

        liste_case = matrice[loc]
        Region.__init__(self, liste_case)


class Colonne(Region):

    def __init__(self, matrice, loc):
        """
        Constructeur de la sous-classe Colonne.
        :param matrice: Liste de listes de Case
        :param loc: numéro de la Colonne (de 1 à 9)
        """

        liste_case = [matrice[i][loc] for i in range(9)]
        Region.__init__(self, liste_case)


class Carre(Region):

    def __init__(self, matrice, loc):
        """
        Constructeur de la sous-classe Carre.
        :param matrice: Liste de listes de Case
        :param loc: numéro du Carre (de 1 à 9)
        """

        liste_case = [matrice[3 * (loc // 3) + k // 3][3 * (loc % 3) + k % 3] for k in range(9)]
        Region.__init__(self, liste_case)


if __name__ == "__main__":
    caz0 = Case(0)
    caz1 = Case(4)
    caz2 = Case(5)
    caz3 = Case(1)
    caz4 = Case(0)
    caz5 = Case(8)
    caz6 = Case(3)
    caz7 = Case(2)
    caz8 = Case(0)

    lcases = [caz0, caz1, caz2, caz3, caz4, caz5, caz6, caz7, caz8]
    region = Region(lcases)
    print(region)
    print(region.get_etat())

    region.exclut_double_valeur()
    print(region)
    print(region.get_etat())

    caz0.reduit_domaine([2, 9])
    caz4.reduit_domaine([2, 9])
    caz8.reduit_domaine([3, 1, 6])
    region.exclut_double_valeur()
    print(region)

    caz0.fixe_valeur(6)
    region.exclut_double_valeur()
    print(region)
    print(region.get_etat())

    region.exclut_double_valeur()
    print(region)
    print(region.get_etat())
