"""
    Nous ne pouvons utiliser que pop et append pour les lists
"""

import math


def map(fun, it):
    """execute la fonction sur chaques elements de la liste """
    ret = list()
    for i in it:
        ret.append(fun(i))
    return ret


def filter(test_fun, it):
    """return a list of all el of it that passes test fun """
    ret = list()
    for i in it:
        v = test_fun(i)
        if v:
            ret.append(i)
    return ret


def reduce(fun, it):
    ret = fun(it[0], it[1])
    for i in range(2, len(it)):
        ret = fun(ret, it[i])
    return ret


def prime_numbers(n):
    """make a list of n first prime elements """
    ret = list()
    cursor = 2
    while len(ret) < n:
        check = True
        for i in range(2, cursor):
            if cursor % i == 0:
                check = False
        if check:
            ret.append(cursor)
        cursor += 1
    return ret


def is_prime(n):
    if n == 0 or n == 1:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True


def insert(seq, n):
    """insert n in the seq at the right position
    :seq: List par ordre croissant
    """
    ret = list()
    cursor = None
    for i in range(len(seq)):
        if seq[i] < n:
            ret.append(seq[i])
        else:
            cursor = i
            break
    ret.append(n)
    if cursor is None:
        return ret
    for j in seq[cursor:]:
        ret.append(j)
    return ret


def produit_matriciel(matrice1, matrice2):
    """fait le produit matriciel de 2 matrices """
    if len(matrice1[0]) != len(matrice2):
        return None
    ret = list()
    for i in range(len(matrice1)):
        temp = list()
        for j in range(len(matrice1)):
            temp.append(sum_for_matrices(matrice1, i, matrice2, j))
        ret.append(temp)
    if len(ret) == 1:
        return ret[0][0]
    return ret


def sum_for_matrices(a, i, b, j):
    tot = 0
    for k in range(0, len(b)):
        tot += a[i][k] * b[k][j]
    return tot


def test(fun, *args, result):
    if len(args) == 1:
        test = fun(args[0])
        if test == result:
            print(f"✅{fun.__name__}({args[0]}) == {result} -- SUCCESS!")
        else:
            print(f"❌{fun.__name__}({args[0]}) == {result} -- ERROR! (returned {test})")
    elif len(args) == 2 and hasattr(args[0], '__name__'):
        test = fun(args[0], args[1])
        if test == result:
            print(f"✅{fun.__name__}({args[0].__name__}, {args[1]}) == {result} -- SUCCESS!")
        else:
            print(f"❌{fun.__name__}({args[0].__name__}, {args[1]}) == {result} -- ERROR! (returned {test})")
    elif len(args) == 2:
        test = fun(args[0], args[1])
        if test == result:
            print(f"✅{fun.__name__}({args[0]}, {args[1]}) == {result} -- SUCCESS!")
        else:
            print(f"❌{fun.__name__}({args[0]}, {args[1]}) == {result} -- ERROR! (returned {test})")


if __name__ == "__main__":
    test(map, math.sqrt, [], result=[])
    test(map, math.sqrt, [2.0, 4.0, 6.0, 100.0], result=[1.4142135623730951, 2.0, 2.449489742783178, 10.0])
    test(map, str.upper, list('hello'), result=['H', 'E', 'L', 'L', 'O'])

    test(filter, is_prime, range(20), result=[2, 3, 5, 7, 11, 13, 17, 19])
    test(filter, str.isalpha, list('r2d2'), result=['r', 'd'])

    test(reduce, math.pow, [2, 2], result=4.0)
    test(reduce, math.pow, [2, 3, 4], result=4096.0)

    test(prime_numbers, 1, result=[2])
    test(prime_numbers, 12, result=[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37])

    test(is_prime, 1, result=False)
    test(is_prime, 2, result=True)
    test(is_prime, 3, result=True)
    test(is_prime, 33, result=False)

    test(insert, [], 1, result=[1])
    test(insert, list(range(6)), -1, result=[-1, 0, 1, 2, 3, 4, 5])
    test(insert, list(range(6)), 3, result=[0, 1, 2, 3, 3, 4, 5])
    test(insert, list(range(6)), 10, result=[0, 1, 2, 3, 4, 5, 10])

    test(produit_matriciel, [[2, 0, 1], [3, 6, 2]], [[1, 5], [2, 6], [3, 7]], result=[[5, 17], [21, 65]])
    test(produit_matriciel, [[1, 5], [2, 6], [3, 7]], [[2, 0, 1], [3, 6, 2]], result=[[17, 30, 11], [22, 36, 14], [27, 42, 17]])
    test(produit_matriciel, [[1.0, 2.5]], [[3.0], [4.5]], result=14.25)
    test(produit_matriciel, [[1.0, 2.5]], [[3.0, 4.5]], result=None)
    test(produit_matriciel, [[1, 5], [2, 6], [3, 7]], [[1, 5], [2, 6], [3, 7]], result=None)
