#!/usr/bin/env python3

def gcd(a, b):
    """
        This function calculate the GCD (Greatest Common Divisor of the number a of b
        Args:
            a (Integer): the number a
            b (integer): the number b

        Return:
            the GCD 
    """

    if b == 0:
        return a
    return gcd(b, a%b)

def egcd(a, b):
    """
        This function compute the Extended Euclidean algorithm
        https://user.eng.umd.edu/~danadach/Cryptography_20/ExtEuclAlg.pdf
        https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm

        Args:
            a (Integer): the number a
            b (Integer): the number b
    """
    if a == 0:
        return b, 0, 1
    gcd, x1, y1 = egcd(b % a, a)
    x = y1 - (b // a) * x1
    y = x1
    return gcd, x, y

def bin_expo(n, e) -> int:
    """
        This function perform an binary exponentiation, also known as Exponentiation squaring.
        A binary exponentiation is the process for computing an integer power of a number, such as a ** n.
        For doing that, the first step is to convert the exponent into a binary representation
        And for each 1 bit, we compute the exponent.

        Args:
            n (Integer): it's the base
            e (Integer): it's the exponent

        Returns:
            Return the result of the exponentation of n ** e

    """
    binary = bin(e)[2:] # Remove the prefix 0b

    r = 1
    exp = 1
    # We need to reverse, and to start from the right to left
    # Otherwise, we do on left to the right
    # It's dirty, maybe we can find another way to do that
    for b in binary[::-1]:
        if b == '1':
            r *= n ** exp
            print(n ** exp, exp)
        exp *= 2
    return r

def exponent_squaring(n, e):
    """
        This function perform an exponentiation squaring, which compute an integer power of a number based on the following algorithm:
            n ** e = {
                1                               # if n is 0
                (n ** (e / 2)) ** 2             # if n is even
                ((n ** (e - 1 / 2)) ** 2) * n   # if n is odd
            }

        Args:
            n (Integer): n is the base of n ** e
            e (Integer): e is the exponent
    """
    if e < 0:
        return exponent_squaring(1 / n, -e)
    elif e == 0:
        return 1
    elif e % 2 == 1: # n is odd
        return exponent_squaring(n * n, (e - 1) / 2) * n
    elif e % 2 == 0: # n is even
        return exponent_squaring(n * n, e / 2)
