#!/usr/bin/env python

# Projet de compilation Umons 2025
# Par Debucquoy Anthony (231687)

import argparse
import lark
import sys
from enum import Enum
from modules.Variables import Variables
from modules.errors import *

class SPFInterpreter(lark.visitors.Interpreter):
    def __init__(self, trace=False):
        super().__init__()
        self.variables = Variables(trace)

    def while_loop(self, el):
        old = self.variables.variables.copy()
        while self.visit_children(el.children[0])[0]:
            self.visit_children(el.children[1])
            self.variables.variables = old.copy()

    def for_loop(self, el):
        type = el.children[0].value
        name = el.children[1].value
        old = self.variables.variables.copy()
        try:
            self.variables.declare(type, name)
        except SPFException as e:
            e.errorline = el.meta.line
            raise e
        old_inloop = self.variables.variables.copy()

        target = self.visit_children(el.children[2])[0]
        for i in target:
            try:
                self.variables.assign(name, i)
            except SPFException as e:
                e.errorline = el.meta.line
                raise e
            self.visit_children(el.children[3])
            self.variables.variables = old_inloop.copy()
        self.variables.variables = old.copy()

    def afficher(self, el):
        ligne = ""
        for toprint in el.children[1:]:
            ligne += str(self.visit_children(toprint)[0]) + " "
        print(ligne)

    def append(self, el):
        (_, toadd, var) = self.visit_children(el);
        try:
            var_val = self.variables.get(var.value)
        except SPFException as e:
            e.errorline = el.meta.line
            raise e

        var_val.append(toadd)

    def declaration(self, el):
        type = el.children[0].value
        name = el.children[1].value
        value = self.visit_children(el.children[3])[0] if len(el.children) >= 3 else None
        try:
            self.variables.declare(type, name, value)
        except SPFException as e:
            e.errorline = el.meta.line
            raise e

    def assignation(self, el):
        name = el.children[0].value
        assert el.children[1].value == "=" and el.children[2].data == "expression", "Unexpected"
        value = self.visit_children(el.children[2])[0]
        try:
            self.variables.assign(name, value)
        except SPFException as e:
            e.errorline = el.meta.line
            raise e

    def expression(self, el):
        return self.visit_children(el)[0]

    def logical(self, el):
        result = self.visit_children(el)
        if len(result) < 2:
            return result[0]
        if result[1][0].type == "AND_OP":
            return result[0] and result[1][1]
        elif result[1][0].type == "OR_OP":
            return result[0] or result[1][1]
        assert "Unreachable"

    def comparison(self, el):
        result = self.visit_children(el)
        if len(result) < 2:
            return result[0]
        if result[1][0].type == "SAME_OP":
            return result[0] == result[1][1]
        elif result[1][0].type == "DIFF_OP":
            return result[0] != result[1][1]
        elif result[1][0].type == "LT_OP":
            return result[0] < result[1][1]
        elif result[1][0].type == "LE_OP":
            return result[0] <= result[1][1]
        elif result[1][0].type == "GT_OP":
            return result[0] > result[1][1]
        elif result[1][0].type == "GE_OP":
            return result[0] >= result[1][1]
        assert "Unreachable"

    def sumterm(self, el):
        result = self.visit_children(el)
        if len(result) < 2:
            return result[0]
        if result[1][0].type == "PLUS_OP":
            return result[0] + result[1][1]
        elif result[1][0].type == "MINUS_OP":
            return result[0] - result[1][1]
        assert "Unreachable"

    def multterm(self, el):
        result = self.visit_children(el)
        if len(result) < 2:
            return result[0]
        if result[1][0].type == "TIMES_OP":
            return result[0] * result[1][1]
        elif result[1][0].type == "DIVIDE_OP": # Division entière car nous ne gérons pas les flotants
            return result[0] // result[1][1]
        assert "Unreachable"

    def priority(self, el):
        result = self.visit_children(el)
        if len(result) < 2:
            return result[0]
        elif result[0].type == "SIZE_OP":
            return len(result[1])
        elif result[0].type == "NEG_OP":
            return -result[1]
        elif result[0].type == "NOT_OP":
            return not result[1]

    def list_get(self, el):
        result = self.visit_children(el)
        try:
            return result[0][result[1] - 1] # Index start at 1 (like lua)
        except IndexError:
            e = SPFIndexError(result[0], result[1])
            e.errorline = el.meta.line
            raise e

    def finalterm(self, el):
        return self.visit_children(el)[0]

    def variable(self, el):
        try:
            return self.variables.get(el.children[0].value)
        except SPFException as e:
            e.errorline = el.meta.line
            raise e

    def test(self,el):
        old = self.variables.variables.copy()
        if self.visit_children(el.children[0])[0]:
            self.visit_children(el.children[1])
        elif len(el.children) >= 3:
            self.visit_children(el.children[2])
        self.variables.variables = old.copy()


    # Literals
    string = lambda self, el: el.children[0][1:-1]
    entier = lambda self, el: int(el.children[0])
    true = lambda self, _: True
    false = lambda self, _: False

    def range(self, el):
        (left, right) = self.visit_children(el)
        return list(range(left, right+1))

    def dump(self):
        self.variables.dump()



def main():
    arg_parser = argparse.ArgumentParser()
    arg_parser.add_argument("spf_file", help="Fichier source à interpréter")
    arg_parser.add_argument("-d", "--dump",
                        help="affichage de la mémoire du programme",
                        action="store_true")
    arg_parser.add_argument("-t", "--trace",
                        help="affichage de la mémoire au cours du programme",
                        action="store_true")
    arg_parser.add_argument("-p", "--pretty",
                        help="affichage de l'arbre et quite",
                        action="store_true")
    args = arg_parser.parse_args()

    with open("spf.lark") as grammar:
        spf_parser = lark.Lark(grammar, parser="lalr", strict=True, debug=True, propagate_positions=True)

    with open(args.spf_file) as spf_input:
        program = spf_input.read()
        try:
            parsed = spf_parser.parse(program)
        except lark.UnexpectedInput as u:
            e = SPFSyntaxError()
            e.errorline = u.line
            raise e

    if args.pretty:
        print(parsed.pretty())
        return

    interpreter = SPFInterpreter(args.trace)
    interpreted = interpreter.visit(parsed)

    if args.dump:
        interpreter.dump()

if __name__ == "__main__":
    main()

