class UebersetzerWhileList(object):
    def __init__(self, variablenzustand):
        self.quellcode = None
        self.programm = []
        self.variablenzustand = variablenzustand
        self.bonsai_code = []
        self.bonsai_text = ''
        self.i = 0

    def setQuellcode(self, q):
        self.quellcode = q

    def getProgramm(self):
        return self.programm

    def getBonsaiCode(self):
        return self.bonsai_code

    def getBonsaiText(self):
        return self.bonsai_text

    def neueVariable(self, bez):
        existiertBereits = False
        for eintrag in self.variablenzustand.variablen:
            if eintrag[0] == bez:
                existiertBereits = True
        if not existiertBereits:
            self.variablenzustand.variablen = self.variablenzustand.variablen + [[bez, 0]]

    def c_programm(self, p):
        'programm : anweisungsfolge'
        c = self.c_anweisungsfolge(p)
        c = c + [('hlt', '')]
        return c

    def c_anweisungsfolge(self, p):
        '''anweisungsfolge : anweisung anweisungsfolge
                           | anweisung'''
        c = []
        for anw in p:
            c_a = self.c_anweisung(anw)
            c = c + c_a
        return c

    def c_anweisung(self, p):
        '''anweisung : VAR ZUW term
                     | PASS
                     | WHILE bedingung DP anweisungsfolge END
                     | IF bedingung DP anweisungsfolge ELSE DP anweisungsfolge END'''
        #print(p[0])

        if p[0] == "=":
            # Bezeichner ggf in variablenzustand eintragen
            bez_a = p[1][1]
            self.neueVariable(bez_a)
            # Zuweisung vom Typ a = n
            if p[2][0][0] == 'ZAHL':            
                c = self.c_zuw_a_gleich_n(p)
            # Zuweisung vom Typ a = b           
            elif p[2][0][0] == 'VAR':
                c = self.c_zuw_a_gleich_b(p)
            # Zuweisung vom Typ a = b + n            
            elif p[2][0][0] == '+' and p[2][1][0] == 'VAR' and p[2][2][0] == 'ZAHL':
                c = self.c_zuw_a_gleich_b_plus_n(p)
            # Zuweisung vom Typ a = b - n            
            elif p[2][0][0] == '-' and p[2][1][0] == 'VAR' and p[2][2][0] == 'ZAHL':
                c = self.c_zuw_a_gleich_b_minus_n(p)
            # Zuweisung vom Typ a = b + c
            elif p[2][0][0] == '+' and p[2][1][0] == 'VAR' and p[2][2][0] == 'VAR':
                c = self.c_zuw_a_gleich_b_plus_c(p)
            # Zuweisung vom Typ a = b - c
            elif p[2][0][0] == '-' and p[2][1][0] == 'VAR' and p[2][2][0] == 'VAR':
                c = self.c_zuw_a_gleich_b_minus_c(p)
        elif p[0] == "if":
            c = self.c_if(p)
        elif p[0] == "while":
            c = self.c_while(p)
        elif p[0] == "pass":
            c = []
        return c

    def neueHilfsvariable(self):
        bez_h = '_h'+str(self.i)
        self.neueVariable(bez_h)
        self.i = self.i+1
        return bez_h


    def c_zuw_a_gleich_null(self, bez_a):
        """
        AZ {a: n}        
        EZ {a: 0}
        """
        c = \
          [('tst', bez_a),
           ('jmp', '(+2)'),
           ('jmp', '(+3)'),
           ('dec', bez_a),
           ('jmp', '(-4)')]
        return c

    def c_trans_a_nach_b_und_c(self, bez_a, bez_b, bez_c):
        """
        AZ {a: n; b: 0; c: 0}        
        EZ {a: 0; b: n; c: n}
        """
        c = \
          [('tst', bez_a),
           ('jmp', '(+2)'),
           ('jmp', '(+5)'),
           ('dec', bez_a),
           ('inc', bez_b),
           ('inc', bez_c),
           ('jmp', '(-6)')]
        return c

    def c_trans_a_nach_b(self, bez_a, bez_b):
        """
        AZ {a: n; b: 0}        
        EZ {a: 0; b: n}
        """
        c = \
          [('tst', bez_a),
           ('jmp', '(+2)'),
           ('jmp', '(+4)'),
           ('dec', bez_a),
           ('inc', bez_b),
           ('jmp', '(-5)')]
        return c

    def c_zuw_a_gleich_n(self, p):
        bez_a = p[1][1]
        wert_n = int(p[2][0][1])
        # a = 0
        c = self.c_zuw_a_gleich_null(bez_a)
        # n mal inc a
        for i in range(wert_n):
            c = c + [('inc', bez_a)]
        return c

    def c_zuw_a_gleich_b(self, p):
        bez_a = p[1][1]
        bez_b = p[2][0][1]
        # Hilfsvariable erzeugen
        bez_h = self.neueHilfsvariable()
        # a = 0
        c = self.c_zuw_a_gleich_null(bez_a)
        # b -> a, h 
        c = c + self.c_trans_a_nach_b_und_c(bez_b, bez_a, bez_h)
        # h -> b
        c = c + self.c_trans_a_nach_b(bez_h, bez_b)
        # Zähler für Hilfsvariablen zurücksetzen
        self.i = self.i-1
        return c

    def c_zuw_a_gleich_b_plus_n(self, p):
        bez_a = p[1][1]
        bez_b = p[2][1][1]
        wert_n = int(p[2][2][1])
        # Hilfsvariablen erzeugen
        bez_h_b = self.neueHilfsvariable()
        bez_h_k = self.neueHilfsvariable()
        # b -> h_b, h_k
        c = self.c_trans_a_nach_b_und_c(bez_b, bez_h_b, bez_h_k)
        # h_k -> b
        c = c + self.c_trans_a_nach_b(bez_h_k, bez_b)
        # n mal inc h_b
        for i in range(wert_n):
            c = c + [('inc', bez_h_b)]
        # a = 0
        c = c + self.c_zuw_a_gleich_null(bez_a)
        # h_b -> a
        c = c + self.c_trans_a_nach_b(bez_h_b, bez_a)
        # Zähler für Hilfsvariablen zurücksetzen
        self.i = self.i-1
        self.i = self.i-1        
        return c

    def c_zuw_a_gleich_b_minus_n(self, p):
        bez_a = p[1][1]
        bez_b = p[2][1][1]
        wert_n = int(p[2][2][1])
        # Hilfsvariablen erzeugen
        bez_h_b = self.neueHilfsvariable()
        bez_h_k = self.neueHilfsvariable()
        # b -> h_b, h_k
        c = self.c_trans_a_nach_b_und_c(bez_b, bez_h_b, bez_h_k)
        # h_k -> b
        c = c + self.c_trans_a_nach_b(bez_h_k, bez_b)
        # n mal dec h_b
        for i in range(wert_n):
            c = c + \
                [('dec', bez_h_b)]
        # a = 0
        c = c + self.c_zuw_a_gleich_null(bez_a)
        # h_b -> a
        c = c + self.c_trans_a_nach_b(bez_h_b, bez_a)
        # Zähler für Hilfsvariablen zurücksetzen
        self.i = self.i-1
        self.i = self.i-1        
        return c

    def c_zuw_a_gleich_b_plus_c(self, p):
        bez_a = p[1][1]
        bez_b = p[2][1][1]
        bez_c = p[2][2][1]
        # Hilfsvariablen erzeugen
        bez_h_e = self.neueHilfsvariable()
        bez_h_k = self.neueHilfsvariable()
        # b -> h_e, h_k
        c = self.c_trans_a_nach_b_und_c(bez_b, bez_h_e, bez_h_k)
        # h_k -> b
        c = c + self.c_trans_a_nach_b(bez_h_k, bez_b)
        # c -> h_e, h_k
        c = c + self.c_trans_a_nach_b_und_c(bez_c, bez_h_e, bez_h_k)
        # h_k -> c
        c = c + self.c_trans_a_nach_b(bez_h_k, bez_c)
        # a = 0
        c = c + self.c_zuw_a_gleich_null(bez_a)
        # h_e -> a
        c = c + self.c_trans_a_nach_b(bez_h_e, bez_a)
        # Zähler für Hilfsvariablen zurücksetzen
        self.i = self.i-1
        self.i = self.i-1 
        return c

    def c_zuw_a_gleich_b_minus_c(self, p):
        bez_a = p[1][1]
        bez_b = p[2][1][1]
        bez_c = p[2][2][1]
        # Hilfsvariablen erzeugen
        bez_h_b = self.neueHilfsvariable()
        bez_h_c = self.neueHilfsvariable()
        bez_h_k = self.neueHilfsvariable()
        # b -> h_b, h_k
        c = self.c_trans_a_nach_b_und_c(bez_b, bez_h_b, bez_h_k)
        # h_k -> b
        c = c + self.c_trans_a_nach_b(bez_h_k, bez_b)
        # c -> h_c, h_k
        c = c + self.c_trans_a_nach_b_und_c(bez_c, bez_h_c, bez_h_k)
        # h_k -> c
        c = c + self.c_trans_a_nach_b(bez_h_k, bez_c)
        # h_c mal h_b-- und h_c--
        c = c + \
            [('tst', bez_h_c),
             ('jmp', '(+2)'),
             ('jmp', '(+4)'),
             ('dec', bez_h_c),
             ('dec', bez_h_b),
             ('jmp', '(-5)')]    
        # a = 0
        c = c + self.c_zuw_a_gleich_null(bez_a)
        # h_b -> a
        c = c + self.c_trans_a_nach_b(bez_h_b, bez_a)
        # Zähler für Hilfsvariablen zurücksetzen
        self.i = self.i-1
        self.i = self.i-1
        self.i = self.i-1
        return c

    def c_bedingung(self, p, bez_h):
        if p[0] == "==" and p[1][0] == 'VAR' and p[2][0] == 'ZAHL' and p[2][1] == '0':
            c = self.c_bed_a_gleich_null(p, bez_h)
        elif p[0] == "!=" and p[1][0] == 'VAR' and p[2][0] == 'ZAHL' and p[2][1] == '0':
            c = self.c_bed_a_ungleich_null(p, bez_h)
        elif p[0] == "<=" and p[1][0] == 'VAR' and p[2][0] == 'VAR':
            c = self.c_bed_a_kleinergleich_b(p, bez_h)
        elif p[0] == "<" and p[1][0] == 'VAR' and p[2][0] == 'VAR':
            c = self.c_bed_a_kleiner_b(p, bez_h)
        elif p[0] == "==" and p[1][0] == 'VAR' and p[2][0] == 'VAR':
            c = self.c_bed_a_gleich_b(p, bez_h)
        elif p[0] == "!=" and p[1][0] == 'VAR' and p[2][0] == 'VAR':
            c = self.c_bed_a_ungleich_b(p, bez_h)
        return c

    def c_bed_a_gleich_null(self, p, bez_h):
        bez_a = p[1][1]
        # h = 0
        c = self.c_zuw_a_gleich_null(bez_h)
        # h ggf. auf 1 
        c = c + \
            [('tst', bez_a),
             ('jmp', '(+2)'),
             ('inc', bez_h),]
        return c

    def c_bed_a_ungleich_null(self, p, bez_h):
        bez_a = p[1][1]
        # h = 0
        c = self.c_zuw_a_gleich_null(bez_h)
        # h ggf. auf 1 
        c = c + \
            [('tst', bez_a),
             ('jmp', '(+2)'),
             ('jmp', '(+2)'),
             ('inc', bez_h)]
        return c

    def c_bed_a_gleich_b(self, p, bez_h):
        bez_a = p[1][1]
        bez_b = p[2][1]
        # Hilfsvariablen erzeugen
        bez_h_c = self.neueHilfsvariable()
        # h = 0
        c = self.c_zuw_a_gleich_null(bez_h)
        # 0 < a, b -> a--, b--  
        c = c + \
            [('tst', bez_a),
             ('jmp', '(+4)'),
             ('tst', bez_b),
             ('jmp', '(+10)'),
             ('jmp', '(+8)'),
             ('tst', bez_b),
             ('jmp', '(+2)'),
             ('jmp', '(+6)'),
             # a != 0 & b != 0
             ('dec', bez_a),
             ('dec', bez_b),
             ('inc', bez_h_c),
             ('jmp', '(-11)'),
             # a == 0 & b == 0 -> h = 1
             ('inc', bez_h)]
        # h_c -> a, b
        c = c + self.c_trans_a_nach_b_und_c(bez_h_c, bez_a, bez_b)
        return c

    def c_bed_a_ungleich_b(self, p, bez_h):
        bez_a = p[1][1]
        bez_b = p[2][1]
        # Hilfsvariablen erzeugen
        bez_h_c = self.neueHilfsvariable()
        # h = 0
        c = self.c_zuw_a_gleich_null(bez_h)
        # 0 < a, b -> a--, b--  
        c = c + \
            [('tst', bez_a),
             ('jmp', '(+4)'),
             ('tst', bez_b),
             ('jmp', '(+9)'),
             ('jmp', '(+9)'),
             ('tst', bez_b),
             ('jmp', '(+2)'),
             ('jmp', '(+5)'),
             # h_a != 0 & h_b != 0
             ('dec', bez_a),
             ('dec', bez_b),
             ('inc', bez_h_c),
             ('jmp', '(-11)'),
             # a == 0 & b >= 0 -> h = 1
             ('inc', bez_h)]
        # c -> a, b
        c = c + self.c_trans_a_nach_b_und_c(bez_h_c, bez_a, bez_b)
        return c

    def c_bed_a_kleinergleich_b(self, p, bez_h):
        bez_a = p[1][1]
        bez_b = p[2][1]
        # Hilfsvariablen erzeugen
        bez_h_c = self.neueHilfsvariable()
        # h = 0
        c = self.c_zuw_a_gleich_null(bez_h)
        # 0 < a, b -> a--, b--  
        c = c + \
            [('tst', bez_a),
             ('jmp', '(+4)'),
             ('tst', bez_b),
             ('jmp', '(+9)'),
             ('jmp', '(+8)'),
             ('tst', bez_b),
             ('jmp', '(+2)'),
             ('jmp', '(+6)'),
             # h_a != 0 & h_b != 0
             ('dec', bez_a),
             ('dec', bez_b),
             ('inc', bez_h_c),
             ('jmp', '(-11)'),
             # a == 0 & b >= 0 -> h = 1
             ('inc', bez_h)]
        # c -> a, b
        c = c + self.c_trans_a_nach_b_und_c(bez_h_c, bez_a, bez_b)
        return c

    def c_bed_a_kleiner_b(self, p, bez_h):
        bez_a = p[1][1]
        bez_b = p[2][1]
        # Hilfsvariablen erzeugen
        bez_h_c = self.neueHilfsvariable()
        # h = 0
        c = self.c_zuw_a_gleich_null(bez_h)
        # 0 < a, b -> a--, b--  
        c = c + \
            [('tst', bez_a),
             ('jmp', '(+4)'),
             ('tst', bez_b),
             ('jmp', '(+9)'),
             ('jmp', '(+9)'),
             ('tst', bez_b),
             ('jmp', '(+2)'),
             ('jmp', '(+6)'),
             # h_a != 0 & h_b != 0
             ('dec', bez_a),
             ('dec', bez_b),
             ('inc', bez_h_c),
             ('jmp', '(-11)'),
             # a == 0 & b >= 0 -> h = 1
             ('inc', bez_h)]
        # c -> a, b
        c = c + self.c_trans_a_nach_b_und_c(bez_h_c, bez_a, bez_b)
        return c


    def c_while(self, p):
        # Hilfsvariable fuer das Ergebnis bei der Auswertung der Bedingung erzeugen
        bez_h = self.neueHilfsvariable()
        # Code fuer die Bedingung erzeugen
        bed = p[1]
        c_bed = self.c_bedingung(bed, bez_h) 
        # Code fuer den Schleifenkoerper erzeugen
        anwf = p[2]
        c_anwf = self.c_anweisungsfolge(anwf) 
        # Laenge der relativen Spruenge festlegen
        m = len(c_anwf) + 2
        n = len(c_anwf) + len(c_bed) + 3
        jmp_m = '(+'+str(m)+')'
        jmp_n = '(-'+str(n)+')'
        # Code fuer die while-Anweisung erzeugen
        c = c_bed + \
            [('tst', bez_h),
             ('jmp', '(+2)'),
             ('jmp', jmp_m)] + \
             c_anwf + \
             [('jmp', jmp_n)]
        # Hilfsvariable wieder auf 0 setzen
        c = c + \
            [('tst', bez_h),
             ('jmp', '(+2)'),
             ('jmp', '(+3)'),
             ('dec', bez_h),
             ('jmp', '(-4)')]
        # Zähler für Hilfsvariablen zurücksetzen
        self.i = self.i-1
        return c

    def c_if(self, p):
        # Hilfsvariable fuer das Ergebnis bei der Auswertung der Bedingung erzeugen
        bez_h = self.neueHilfsvariable()
        # Code fuer die Bedingung erzeugen
        bed = p[1]
        c_bed = self.c_bedingung(bed, bez_h) 
        # Code fuer den True-Fall erzeugen
        anwTrue = p[2]
        c_anw_true = self.c_anweisungsfolge(anwTrue)
        # Code fuer den False-Fall erzeugen
        anwFalse = p[3]
        c_anw_false = self.c_anweisungsfolge(anwFalse)
        # Laenge der relativen Spruenge festlegen
        m = len(c_anw_true) + 2
        n = len(c_anw_false) + 1
        jmp_ueber_true = '(+'+str(m)+')'
        jmp_ueber_false = '(+'+str(n)+')'
        # Code fuer die while-Anweisung erzeugen
        c = c_bed + \
            [('tst', bez_h),
             ('jmp', '(+2)'),
             ('jmp', jmp_ueber_true)] + \
             c_anw_true + \
             [('jmp', jmp_ueber_false)] + \
             c_anw_false
        # Hilfsvariable wieder auf 0 setzen
        c = c + \
            [('tst', bez_h),
             ('jmp', '(+2)'),
             ('jmp', '(+3)'),
             ('dec', bez_h),
             ('jmp', '(-4)')]
        # Zähler für Hilfsvariablen zurücksetzen
        self.i = self.i-1
        return c

    def uebersetzen(self):
        self.i = 0
        if self.quellcode != None:
            c = self.c_programm(self.quellcode)
            self.programm = c
        else:
            self.programm = None

    def erzeugeBonsaiCode(self):
        self.bonsai_code = []
        laengeProgramm = len(self.programm)
        z = 0
        for anw in self.programm:
            if anw[0] == 'inc':
                var = anw[1]
                var_reg_nr = self.variablenzustand.getIndex(var)
                anwBonsai = ('inc', var_reg_nr)
            elif anw[0] == 'dec':
                var = anw[1]
                var_reg_nr = self.variablenzustand.getIndex(var)
                anwBonsai = ('dec', var_reg_nr)
            elif anw[0] == 'tst':
                var = anw[1]
                var_reg_nr = self.variablenzustand.getIndex(var)
                anwBonsai = ('tst', var_reg_nr)
            elif anw[0] == 'jmp':
                relAddr = eval(anw[1])
                absAddr = z + relAddr
                anwBonsai = ('jmp', absAddr)
            elif anw[0] == 'hlt':
                anwBonsai = ('hlt', None)
            self.bonsai_code = self.bonsai_code + [anwBonsai]
            z = z + 1
        for eintrag in self.variablenzustand.variablen:
            self.bonsai_code = self.bonsai_code + [(eintrag[0], 0)]
        
    def erzeugeBonsaiText(self):
        self.bonsai_text = ''
        laengeProgramm = len(self.programm)
        z = 0
        while z < laengeProgramm:
            if self.bonsai_code[z][0] == 'hlt':
                self.bonsai_text = self.bonsai_text + str(z) + ': ' + \
                                   self.bonsai_code[z][0] + '\n'
            else:
                self.bonsai_text = self.bonsai_text + str(z) + ': ' + \
                                   self.bonsai_code[z][0] + ' ' + str(self.bonsai_code[z][1]) + '\n'
            z = z + 1
        for v in self.variablenzustand.variablen:
            self.bonsai_text = self.bonsai_text + '#0' + '\n'

