import lexer
import sys
import operator
import re

regex_to_token = [(re.compile(r'\d+'), 'NUMBER'),
                  (re.compile(r'x\d+'), 'IDENTIFIER'),
                  (re.compile(r'\+'), 'PLUS'),
                  (re.compile(r'-'), 'MINUS'),
                  (re.compile(r':=|≔'), 'EQUALS'),
                  (re.compile(r'LOOP'), 'LOOP'),
                  (re.compile(r'DO'), 'DO'),
                  (re.compile(r'END'), 'END'),
                  (re.compile(r';'), 'SEMICOLON'),
                  (re.compile(r'BREAK'), 'BREAK'),
                  (re.compile(r'\n', re.MULTILINE), 'LINEBREAK'),
                  (re.compile(r'\s+'), 'WHITESPACE'),
                  (re.compile(r'[^\n]*'), 'UNKNOWN')]

global error_handler, lex, values


class ErrorHandler:
    def __init__(self, program):
        sys.tracebacklimit = 0
        self.program = program
        self.line_number = 0

    def handle_error(self, message):
        msg = ["Fehler in Zeile " + str(self.line_number + 1),
               self.program.split("\n")[self.line_number],
               message]
        raise SyntaxError("\n".join(msg)) from None

    def increase_line(self):
        self.line_number += 1

    def handle_break(self):
        print("BREAK in Zeile " + str(self.line_number))
        print("Aktueller Zustand:")
        for k,v in values.items():
            print("Variable " + k + ": " + str(v))
        user_input = input("Drücke ENTER zum Fotfahren oder schreibe EXIT zum Beenden:")
        if user_input.lower() == 'exit':
            raise KeyboardInterrupt


def process_assignment(value_list, forbidden_identifiers, identifier_token_1):
    identifier_1 = identifier_token_1.v
    if identifier_1 in forbidden_identifiers:
        error_handler.handle_error("Identifier " + identifier_1 +
                                   " ist bereits in Loop vorhanden und darf nicht verwendet werden.")

    if next_nonempty_token("Zuweisung", ":=") == 'EQUALS':
        error_handler.handle_error(":= in Zuweisung erwartet.")

    identifier_token_2 = next_nonempty_token("Zuweisung", "IDENTIFIER (x0, x1, ...) oder NUMBER")
    if identifier_token_2.k == 'NUMBER':
        value_1 = int(identifier_token_2.v)
        value_list.update({identifier_token_1.v: value_1})
        return next_token(), value_list

    if not identifier_token_2.k == 'IDENTIFIER':
        error_handler.handle_error("IDENTIFIER in Zuweisung erwartet.")
    identifier_2 = identifier_token_2.v
    if identifier_2 in forbidden_identifiers:
        error_handler.handle_error("Identifier " + identifier_2 +
                                   " ist bereits in Loop vorhanden und darf nicht verwendet werden.")

    if identifier_2 in value_list:
        value_2 = value_list.get(identifier_2)
    else:
        value_2 = 0

    operator_token = next_nonempty_token("Zuweisung", "+ oder -")
    op = None
    if operator_token.k == 'PLUS':
        op = operator.__add__
    elif operator_token.k == 'MINUS':
        op = operator.__sub__
    else:
        error_handler.handle_error("+ oder - in Zuweisung erwartet.")

    number_token = next_nonempty_token("Zuweisung", "NUMBER")

    if not number_token.k == 'NUMBER':
        error_handler.handle_error("NUMBER in Zuweisung erwartet.")

    value_1 = max(0, op(value_2, int(number_token.v)))
    value_list.update({identifier_1: value_1})
    return next_token(), value_list


def verify_assignment(forbidden_identifiers, identifier_token_1):
    identifier_1 = identifier_token_1.v
    if identifier_1 in forbidden_identifiers:
        error_handler.handle_error("Identifier " + identifier_1 +
                                   " ist bereits in Loop vorhanden und darf nicht verwendet werden.")
    if not next_nonempty_token("Zuweisung", ":=").k == 'EQUALS':
        error_handler.handle_error(":= in Zuweisung erwartet.")

    identifier_token_2 = next_nonempty_token("Zuweisung", "IDENTIFIER (x0, x1, ...) oder NUMBER")
    if identifier_token_2.k == 'NUMBER':
        return next_token()

    if not identifier_token_2.k == 'IDENTIFIER':
        error_handler.handle_error("IDENTIFIER in Zuweisung erwartet.")
    identifier_2 = identifier_token_2.v
    if identifier_2 in forbidden_identifiers:
        error_handler.handle_error("Identifier " + identifier_2 +
                                   " ist bereits in Loop vorhanden und darf nicht verwendet werden.")

    if next_nonempty_token("Zuweisung", "+ oder -").k not in ['PLUS', 'MINUS']:
        error_handler.handle_error("+ oder - in Zuweisung erwartet.")
    if not next_nonempty_token("Zuweisung", "NUMBER").k == 'NUMBER':
        error_handler.handle_error("NUMBER in Zuweisung erwartet.")

    return next_token()


def process_loop(value_list, forbidden_identifiers, loop_token):
    identifier_token = next_nonempty_token('LOOP', 'IDENTIFIER (x0, x1, ...)')
    if not identifier_token.k == 'IDENTIFIER':
        error_handler.handle_error('IDENTIFIER in LOOP erwartet.')
    if identifier_token.v in forbidden_identifiers:
        error_handler.handle_error('Identifier ' + identifier_token.v +
                                   ' ist bereits in Loop vorhanden und darf nicht verwendet werden.')
    if not next_nonempty_token("LOOP", "DO").k == 'DO':
        error_handler.handle_error('DO in LOOP erwartet.')

    if identifier_token.v in value_list:
        number_of_loops = int(value_list.get(identifier_token.v))
    else:
        number_of_loops = 0

    saved_position = lex.current_position
    saved_line = error_handler.line_number
    forbidden_identifiers.append(identifier_token.v)

    if number_of_loops == 0:
        end_found = False

        while not end_found:
            token = verify_program(forbidden_identifiers, next_token())
            if token is None or token.k not in ['SEMICOLON', 'END']:
                error_handler.handle_error("SEMICOLON oder END in LOOP erwartet.")
            elif token.k == 'SEMICOLON':
                continue
            elif token.k == 'END':
                end_found = True

    for index in range(number_of_loops):
        lex.current_position = saved_position
        error_handler.line_number = saved_line
        end_found = False
        while not end_found:
            token, value_list = process_program(value_list, forbidden_identifiers, next_token())
            if token is None or token.k not in ['SEMICOLON', 'END']:
                error_handler.handle_error("SEMICOLON oder END in LOOP erwartet.")
            elif token.k == 'SEMICOLON':
                continue
            elif token.k == 'END':
                end_found = True

    forbidden_identifiers.remove(identifier_token.v)
    return next_token(), value_list


def verify_loop(forbidden_identifiers, loop_token):
    identifier_token = next_nonempty_token("LOOP", "IDENTIFIER")
    if not identifier_token.k == 'IDENTIFIER':
        error_handler.handle_error('IDENTIFIER in LOOP erwartet.')
    if identifier_token.v in forbidden_identifiers:
        error_handler.handle_error("Identifier " + identifier_token.v +
                                   " ist bereits in Loop vorhanden und darf nicht verwendet werden.")
    if not next_nonempty_token("LOOP", "DO").k == 'DO':
        error_handler.handle_error('DO in LOOP erwartet.')

    forbidden_identifiers.append(identifier_token.v)

    end_found = False
    while not end_found:
        token = verify_program(forbidden_identifiers, next_token())
        if token is None or token.k not in ['SEMICOLON', 'END']:
            error_handler.handle_error("SEMICOLON oder END in LOOP erwartet.")
        elif token.k == 'SEMICOLON':
            continue
        elif token.k == 'END':
            end_found = True

    forbidden_identifiers.remove(identifier_token.v)
    return next_token()


def process_program(value_list, forbidden_identifiers, current_token):
    values = value_list
    if current_token is None or current_token.k not in ['IDENTIFIER', 'LOOP']:
        error_handler.handle_error("Keine passende Anweisung gefunden\n" +
                                   "Erwartet: IDENTIFIER (x0, x1, ...) oder LOOP")
    elif current_token.k == 'IDENTIFIER':
        current_token, values = process_assignment(value_list, forbidden_identifiers, current_token)
    elif current_token.k == 'LOOP':
        current_token, values = process_loop(value_list, forbidden_identifiers, current_token)
    return current_token, values


def verify_program(forbidden_identifiers, current_token):
    if current_token is None or current_token.k not in ['IDENTIFIER', 'LOOP']:
        error_handler.handle_error("Keine passende Anweisung gefunden\n" +
                                   "Erwartet: IDENTIFIER (x0, x1, ...) oder LOOP")
    elif current_token.k == 'IDENTIFIER':
        current_token = verify_assignment(forbidden_identifiers, current_token)
    elif current_token.k == 'LOOP':
        current_token = verify_loop(forbidden_identifiers, current_token)
    return current_token


def next_token():
    new_token = lex.next()
    if new_token is None:
        return None
    elif new_token.k == 'BREAK':
        error_handler.handle_break()
        return next_token()
    elif new_token.k == 'LINEBREAK':
        error_handler.increase_line()
        return next_token()
    elif new_token.k == 'WHITESPACE':
        return next_token()
    else:
        return new_token


def next_nonempty_token(current_function, expected_token):
    token = next_token()
    if token is None:
        error_handler.handle_error("Frühzeitiges Ende von " + current_function + "\n" + "Erwartet: " + expected_token)
    return token


def interpret(program):
    try:
        global error_handler, lex, values
        lex = lexer.Lexer(regex_to_token, program)
        error_handler = ErrorHandler(program)
        values = {}
        forbidden_identifiers = []
        current_token = next_token()
        while current_token is not None:
            current_token, values = process_program(values, forbidden_identifiers, current_token)
            if current_token is not None:
                if not current_token.k == 'SEMICOLON':
                    error_handler.handle_error("Semicolon erwartet")
                current_token = next_token()
                if current_token is None:
                    error_handler.handle_error("Semikolons werden nur zur Trennung und nicht zum " +
                                               "Abschluss von Programmen verwendet")
        if "x0" in values:
            return values.get("x0")
        return 0
    except KeyboardInterrupt:
        return -1