import syntax
from vistram.tram import *

label_counter = 0

def new_label():
    global label_counter
    label_counter += 1
    return Label(text=f"L{label_counter}")

# Rules E1, E2
def elab_def(decls, rho, nl):
    r = dict(rho)
    for d in decls:
        r = d.elab(r, nl)
    return r

# Rule K6
class CONST(syntax.CONST):
    def code(self, rho, nl):
        return [const(self.value)]

# Rule K3
class ID(syntax.ID):
    def code(self, rho, nl):
        k, nl_def = rho[self.name]
        return [load(k, nl - nl_def)]

# Rule K7
class AOP(syntax.AOP):
    def code(self, rho, nl):
        c1 = self.arg1.code(rho, nl)
        c2 = self.arg2.code(rho, nl)

        match self.operator:
            case "+":
                return c1 + c2 + [add()]
            case "-":
                return c1 + c2 + [sub()]
            case "*":
                return c1 + c2 + [mul()]
            case "/":
                return c1 + c2 + [div()]
            case _:
                raise Exception(f"Unknown AOP operator.")

# Rule K5
class COMP(syntax.COMP):
    def code(self, rho, nl):
        c1 = self.arg1.code(rho, nl)
        c2 = self.arg2.code(rho, nl)

        match self.operator:
            case "<":
                return c1 + c2 + [lt()]
            case ">":
                return c1 + c2 + [gt()]
            case "<=":
                # leq is not implemented in vistra.tram
                return LOP("||",  COMP("<", self.arg1, self.arg2), EQOP("==", self.arg1, self.arg2)).code(rho, nl)
            case ">=":
                # geq is not implemented in vistra.tram
                return LOP("||",  COMP(">", self.arg1, self.arg2), EQOP("==", self.arg1, self.arg2)).code(rho, nl)
            case _:
                raise ValueError(f"Unknown COMP operator.")

# Rule K5
class EQOP(syntax.EQOP):
    def code(self, rho, nl):
        c1 = self.arg1.code(rho, nl)
        c2 = self.arg2.code(rho, nl)

        match self.operator:
            case "==":
                return c1 + c2 + [eq()]
            case "!=":
                return c1 + c2 + [neq()]
            case _:
                raise ValueError(f"Unknown EQOP operator.")

# see lecture code generation AND/OR
class LOP(syntax.LOP):
    def code(self, rho, nl):
        c1 = self.arg1.code(rho, nl)
        c2 = self.arg2.code(rho, nl)

        l1 = new_label()
        l2 = new_label()
        match self.operator:
            case "&&":
                return c1 + [ifzero(l1)] + [const(1)] +  c2 +  [mul()] +  [goto(l2)] + [nop(l1)] + [const(0)] + [nop(l2)]
            case "||":
                return c1 + [ifzero(l1)] + [const(1)] + [goto(l2)] +  [nop(l1)] + c2 + [nop(l2)]
            case _:
                raise ValueError("Unknown LOP operator.")

# Rule K4
class ASSIGN(syntax.ASSIGN):
    def code(self, rho, nl):
        c = self.expr.code(rho, nl)
        k, nl_def = rho[self.var.name]
        dif = nl - nl_def
        return c + [store(k, dif), load(k, dif)]

# Rule K9
class SEQ(syntax.SEQ):
    def code(self, rho, nl):
        c1 = self.exp1.code(rho, nl)
        c2 = self.exp2.code(rho, nl)
        return c1 + [pop()] + c2

# Rule K10
class IF(syntax.IF):
    def code(self, rho, nl):
        l1 = new_label()
        l2 = new_label()

        c1 = self.cond.code(rho, nl)
        c2 = self.exp1.code(rho, nl)
        c3 = self.exp2.code(rho, nl)

        return c1 + [ifzero(l1)] + c2 + [goto(l2)] + [nop(assigned_label=l1)] + c3 + [nop(assigned_label=l2)]

# Rule K11
class WHILE(syntax.WHILE):
    def code(self, rho, nl):
        l1 = new_label()
        l2 = new_label()
        l3 = new_label()
        l4 = new_label()

        c1 = self.cond.code(rho, nl)
        c2 = self.body.code(rho, nl)

        return c1 + [ifzero(l3), goto(l4)] + [nop(l1)] + c1 + [ifzero(l2), pop()] + [nop(l4)] + c2 + [goto(l1)] + [nop(l3), const(None), nop(l2)]

# Rule K2
class LET(syntax.LET):
    def code(self, rho, nl):
        l = new_label()

        # Normalize to list
        decls = self.decl if isinstance(self.decl, list) else [self.decl]

        shared_rho = elab_def(decls, rho, nl)
        ds = []
        for d in decls:
            ds += d.code(shared_rho, nl)

        es = self.body.code(shared_rho, nl)
        return [goto(l)] + ds + [nop(assigned_label=l)] + es

# Rule K13
class DECL(syntax.DECL):
    def elab(self, rho, nl):
        nested_rho = dict(rho)
        nested_rho[self.f_name] = (new_label(), nl)
        return nested_rho

    def code(self, rho, nl):
        l, _ = rho[self.f_name]
        nested_rho = dict(rho)
        for i, p in enumerate(self.params):
            nested_rho[p] = (i, nl + 1)
        c = self.body.code(nested_rho, nl + 1)
        return [nop(assigned_label=l)] + c + [ireturn()]

# Rule K12
class CALL(syntax.CALL):
    def code(self, rho, nl):
        c = []
        for a in self.arg:
            c += a.code(rho, nl)
        l, nl_def = rho[self.f_name]
        return c + [invoke(len(self.arg), l, nl - nl_def)]
