# !/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Anthony Labarre (c) 2016

Programme utilisant les décorateurs pour illustrer les appels récursifs. Pour
l'appliquer à vos fonctions, importez ce module, ou copiez et collez le code
nécessaire et suivez les instructions données dans la docstring de
montrer_recursivite.
"""
# Imports ---------------------------------------------------------------------
from inspect import getfullargspec


def montrer_recursivite(fonction):
    """Illustre les appels imbriqués effectués par une fonction récursive, en
    montrant le nom de cette fonction, ses paramètres avec leurs valeurs, les
    valeurs renvoyées par chaque appel, ainsi que le "niveau de profondeur" des
    appels à l'aide d'une indentation correspondant à ce niveau.
    
    L'utilisation est simple: si l'on a défini une fonction récursive:

    def mafonction(param1, param2, ...):
    # du code ...

    on peut visualiser son exécution en rajoutant @montrer_recursivite sur la
    ligne précédant la définition de la fonction:

    @montrer_recursivite
    def mafonction(param1, param2, ...):
    # du code ...

    Il n'y a rien d'autre à changer dans le code, ni dans les appels à cette
    fonction. On obtiendra par exemple le résultat suivant, pour une fonction
    calculant la factorielle de 5 récursivement:

    Exécution de factorielle_recursive avec {'n': 5}
      Exécution de factorielle_recursive avec {'n': 4}
        Exécution de factorielle_recursive avec {'n': 3}
          Exécution de factorielle_recursive avec {'n': 2}
            Exécution de factorielle_recursive avec {'n': 1}
            Retour de factorielle_recursive avec {'n': 1}, qui renvoie 1
          Retour de factorielle_recursive avec {'n': 2}, qui renvoie 2
        Retour de factorielle_recursive avec {'n': 3}, qui renvoie 6
      Retour de factorielle_recursive avec {'n': 4}, qui renvoie 24
    Retour de factorielle_recursive avec {'n': 5}, qui renvoie 120
    """
    numero_d_appel = 0

    def wrapper(*args, **kwargs):
        nonlocal numero_d_appel
        parametres_et_valeurs = dict(zip(getfullargspec(fonction)[0], args))
        print(
            "   " * numero_d_appel + "Exécution de " + fonction.__name__
            + " avec", parametres_et_valeurs
        )
        numero_d_appel += 1
        resultat = fonction(*args, **kwargs)
        numero_d_appel -= 1
        print(
            "   " * numero_d_appel + "Retour de " + fonction.__name__ + " avec",
            parametres_et_valeurs, "\b, qui renvoie", resultat
        )
        return resultat

    return wrapper


@montrer_recursivite  # commentez cette ligne pour cacher la récursivité
def factorielle_recursive(n):
    if n <= 1:
        return 1
    return n * factorielle_recursive(n-1)


@montrer_recursivite  # commentez cette ligne pour cacher la récursivité
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)


@montrer_recursivite  # commentez cette ligne pour cacher la récursivité
def puissance_recursive(a, n):
    if not n:
        return 1
    return a * puissance_recursive(a, n-1)


def main():
    a, n = 3, 5
    print("Test de factorielle_recursive:")
    print(n, "! = ", factorielle_recursive(n), sep="")
    print()
    print("Test de fibonacci:")
    print("F_", n, " = ", fibonacci(n), sep="")
    print()
    print("Test de puissance_recursive:")
    print(a, "**", n, " = ", puissance_recursive(a, n))
    print()


if __name__ == "__main__":
    main()
