# L-41 MCS 260 Fri 22 Apr 2016 : factorial.py
"""
Our first example of a recursive function computes the factorial.
We provide several definitions of the factorial below.
"""
def factorial(k):
    """
    computes the factorial of k recursively
    """
    if k <= 1:
        return 1
    else:
        return k*factorial(k-1)

def factexcept(k):
    """
    when the recursion depth is exceeded
    the factorial of k is computed iteratively
    """
    if k <= 1:
        return 1
    else:
        try:
            return k*factexcept(k-1)
        except RuntimeError:
            print('run time error raised')
            result = 1
            for i in range(2, k+1):
                result = result*i
            return result

def factotrace(nbr, k):
    """
    prints out trace information in call k
    the initial value for k should be zero
    """
    prt = k*' '
    prt = prt + 'factotrace(%d,%d):' % (nbr, k)
    if nbr <= 1:
        print(prt + ' base case, return 1')
        return 1
    else:
        print(prt + ' call for n-1 = ' + str(nbr-1))
        result = nbr*factotrace(nbr-1, k+1)
        print(prt + ' returning %d' % result)
        return result

def factaccu(nbr, acc):
    """
    accumulates the factorial in acc
    call factaccu initially with acc = 1
    """
    if nbr <= 1:
        return acc
    else:
        return factaccu(nbr-1, acc*nbr)

def factatrace(nbr, acc, k):
    """
    accumulates the factorial in acc,
    k is used to trace the calls
    initialize acc to 1 and k to 0
    """
    prt = k*' '
    prt = prt + 'factatrace(%d,%d,%d)' % (nbr, acc, k)
    if nbr <= 1:
        print(prt + ' returning ' + str(acc))
        return acc
    else:
        print(prt + ' call for n-1 = ' + str(nbr-1))
        result = factatrace(nbr-1, acc*nbr, k+1)
        print(prt + ' returning %d' % result)
        return result

def main():
    """
    prompts the user for a number
    and returns the factorial of it
    """
    nbr = int(input('give a natural number n : '))
    if nbr < 998:
        fac = factorial(nbr)
        print('n! = ', fac)
        fac = factaccu(nbr, 1)
        print('n! = ', fac)
    else:
        fac = factexcept(nbr)
        print('n! = ', fac)
    print('len(n!) = ', len(str(fac)))
    if nbr < 21:
        print('tracing the basic factorial :')
        fac = factotrace(nbr, 0)
        print('tracing factorial with accumulator :')
        fac = factatrace(nbr, 1, 0)

if __name__ == "__main__":
    main()
