# -*- coding: cp1252 -*-
from math import *

class Refs:
    pass

class Undefined(Exception):
    pass

class Suite:
    """Eine simple Klasse um Folgen zu berechnen.

    Es koennen sowohl Folgen die funktional als auch welche die rekursiv
    definiert sind berechnet werden.

    Die expression muss immer n beinhalten, sonst NUR Zahlen, *, +, - etc...
    first ist der erste Term bei einer Folge die rekursiv definiert ist.
    recursive ist False wenn die Folge funktional definiert ist, sonst True.
    Rekursive folgen verwenden bitte: self(n-1).
    Folgen die von anderen Folgen abhaengen benutzen andere_folge(n-5), und geben bei der Erstellung der Folge als Paramter refs={'andere_folge':abc}) an.

    Es ist moeglich alle Funktionen die in math.* sind zu benutzen.

    ACHTUNG: Es wird eval() verwendet!!!
    ACHTUNG: Folgen die rekursiv definiert sind...kann man zwar beliebig hohe Terme berechnen
    man muss aber aufpassen nicht ueber das recursion limit von python zu kommen.
    Da die Ergebnisse gecachet werden, kann man das uebergehen indem man
    die Berechnung von u(100) folgendermassen durchfuehrt:
    u(100);u(200);u(300);u(400);u(500)...

    Beispiele:
        u = Suite('self(n-1)+2', {0:0}, save_vals=True) # Geht
        v = Suite('5*n') # Geht
        w = Suite('n+v(n)', refs={'v':v})
    """
    def __init__(self, expression, val_dict={0:0}, save_vals=False, lovest_n=0, refs={}):
    	# expression -> Der Ausdruck der Folge
	# val_dict -> Vorgegebene Terme und deren Wer (z.B. der erste Term...)
	# save_vals -> Wenn True wird val_dict verwendet, sonst nicht.
	# 	(Resultate werden unabhaengig davon immer gecachet)
	# lovest_n -> Das niedrigste n das verwendet werden darf. 
	#	(lovest_n = 5 => u(4)=Undefined())
	# refs -> Dictionary mit den Referenzen zu anderen Folgen.
        self.expression = expression # A string like n^2
        self.refs = Refs()
        for key in refs:
            setattr(self.refs, key, refs[key])
        print dir(self.refs)
        if save_vals is True:
            self.value_dict = val_dict
        else:
            self.value_dict = {}

        self.lovest_n = lovest_n

    def calc(self, n):
        """Berechnet einen Term n"""
        if n < self.lovest_n: 
	    # Nur Terme berechnen die man auch berechnen kann
            raise Undefined
        if n in self.value_dict:
	    # Wenn schon ausgerechnet: dann aus dem Cache lesen.
            return self.value_dict[n]

        res = eval(self.expression, {'n':n, 'self':self, 'refs':self.refs})
        self.value_dict[n] = res # In den Cache speichern
        return res
        
    def __call__(self, n):
        """Enables u(500) instead of u.calc(500)"""
        return self.calc(n)

def test():
    """Some explame usage..."""
    u = Suite('self(n-1)+2', {0:0}, True)
    v = Suite('n+refs.u(n-1)', refs={'u':u})
    for i in range(0, 10):
        try:
            val_u = u(i)
        except Undefined:
            val_u = 'undefined'
        try:
            val_v = v(i)
        except Undefined:
            val_v = 'undefined'
        print 'u(%s)=' %(str(i), ) , val_u    
        print 'v(%s)=' %(str(i), ) , val_v
        
if __name__ == '__main__':
    test()

