#!/usr/bin/env python3

class Group:
    """
        This class generate a group self._g based on the operation (here denoted +)
        with the function ope: (a ** b) % n

        In group theory, any group has an identity element (e), which with the binary operation, do not change the value and must satisfy the condition: $a + e = 0$

        Attributes:
            n (Integer): It's the G_n elements in the Group
            g (List): The set of the Group
            operation (Function): Function for generating the Group
            identity (Integer): The identity of the group.
            reverse (Dict): For each elements of the Group of n, we have the reverse value
    """
    def __init__(self, n, g, ope):
        self._n = n
        self._g = g
        self._operation = ope
        self._identity = 0
        self._reverse = dict()
       
    def getG(self) -> list():
        """
            This function return the Group

            Returns:
                List of all elements in the Group
        """
        return self._g

    def closure(self) -> bool:
        """
            Check the closure law
            In a group, each element a, b belongs to G, such as a + b belongs to G

            Returns:
                Return a Boolean if the closure law is respected. True if yes otherwise it's False
        """
        for e1 in self._g:
            for e2 in self._g:
                res = self._operation(e1, e2, self._n)
                if  not res in self._g:
                    # raise Exception(f"{res} not in g. g is not a group")
                    return False
        return True 

    def associative(self) -> bool:
        """
            Check the associative law. 
            In a group, for any a, b and c belongs to G,
            they must respect this condition: (a + b) + c = a + (a + b)

            Returns:
                Return a boolean if the Associative law is respected. True if yes otherwise it's False
        """
        a = self._g[0]
        b = self._g[1]
        c = self._g[2]
        
        res_ope = self._operation(a, b, self._n)
        res1 = self._operation(res_ope, c, self._n)
        
        res_ope = self._operation(b, c, self._n)
        res2 = self._operation(a, res_ope, self._n)
        if res1 != res2:
            # raise Exception(f"{res1} is different from {res2}. g is not a group")
            return False
        return True

    def identity(self) -> bool:
        """
            Check the identity law.
            In a group, an identity element exist and must be uniq

            Returns:
                Return a Boolean if the identity elements has been found. True if found otherwise it's False
        """
        for a in self._g:
            for b in self._g:
                if not self._operation(a, b, self._n) == b:
                    break

                self._identity = a
                return True
        return False

    def getIdentity(self) -> int:
        """
            Return the identity element. The function identitu() must be called before.

            Returns:
                Return the identity element if it has been found
        """
        return self._identity

    def reverse(self) -> bool:
        """
            Check the inverse law
            In a group, for each element belongs to G
            they must have an inverse a ^ (-1) = e (identity)

            Returns:
                Return a Boolean if the all elements ha a reverse element. True if yes otherwise it's False
        """
        reverse = False
        for a in self._g:
            for b in self._g:
                if self._operation(a, b, self._n) == self._identity:
                    self._reverse[a] = b
                    reverse = True
                    break
        return reverse        

    def getReverses(self) -> dict:
        """
            This function return the dictionary of all reverses elements. The key is the element in G and the value is the reverse element

            Returns:
                Return the reverse dictionary

        """
        return self._reverse
