import lexer
from loopinterpreter import ErrorHandler
from whileinterpreter import Timeout
import re
import operator


class GOTOInterpreter:
    def __init__(self, timeout=60):
        self.values = {}
        self.lex = None
        self.error_handler = None
        self.regex_to_token = [(re.compile(r'\d+'), 'NUMBER'),
                               (re.compile(r'x\d+'), 'IDENTIFIER'),
                               (re.compile(r'M\d+'), 'MARKER'),
                               (re.compile(r'\+'), 'PLUS'),
                               (re.compile(r'[−-]'), 'MINUS'),
                               (re.compile(r':=|≔'), 'ALLOCATION'),
                               (re.compile(r'='), 'EQUALS'),
                               (re.compile(r'/=|≠|!='), 'NOTEQUALS'),
                               (re.compile(r'IF'), 'IF'),
                               (re.compile(r'THEN'), 'THEN'),
                               (re.compile(r'GOTO'), 'GOTO'),
                               (re.compile(r'HALT'), 'HALT'),
                               (re.compile(r';'), 'SEMICOLON'),
                               (re.compile(r':'), 'COLON'),
                               (re.compile(r'BREAK'), 'BREAK'),
                               (re.compile(r'\s+', re.MULTILINE), 'WHITESPACE'),
                               (re.compile(r'[^\n]*'), 'UNKNOWN')]
        self.marker_to_position = {}
        self.marker_to_line = {}
        self.timeout = timeout
        self.halted = False

    def next_token(self):
        new_token = self.lex.next()
        if new_token is None:
            return None
        elif new_token.k == 'BREAK':
            self.error_handler.handle_break()
            return self.next_token()
        elif new_token.k == 'WHITESPACE':
            if new_token.v.count('\n') > 0:
                self.error_handler.increase_line(new_token.v.count('\n'))
            return self.next_token()
        else:
            return new_token

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

    def process_assignment(self, identifier_token_1):
        identifier_1 = identifier_token_1.v
        if not self.next_nonempty_token('Zuweisung', ':=').k == 'ALLOCATION':
            self.error_handler.handle_error(':= in Zuweisung erwartet.')
        identifier_token_2 = self.next_nonempty_token('Zuweisung', 'IDENTIFIER (x0, x1, ...) oder NUMBER')
        if identifier_token_2.k == 'NUMBER':
            value_1 = int(identifier_token_2.v)
            self.values.update({identifier_token_1.v: value_1})
            if not self.next_nonempty_token('Zuweisung', 'SEMICOLON').k == 'SEMICOLON':
                self.error_handler.handle_error('SEMICOLON zum Abschluss einer Zuweisung erwartet.')
            return self.next_token()
        if not identifier_token_2.k == 'IDENTIFIER':
            self.error_handler.handle_error('IDENTIFIER in Zuweisung erwartet.')
        identifier_2 = identifier_token_2.v
        if identifier_2 in self.values:
            value_2 = self.values.get(identifier_2)
        else:
            value_2 = 0
        operator_token = self.next_nonempty_token('Zuweisung', '+ oder -')
        op = None
        if operator_token.k == 'PLUS':
            op = operator.__add__
        elif operator_token.k == 'MINUS':
            op = operator.__sub__
        else:
            self.error_handler.handle_error('+ oder - in Zuweisung erwartet.')
        number_token = self.next_nonempty_token('Zuweisung', 'NUMBER')
        if not number_token.k == 'NUMBER':
            self.error_handler.handle_error('NUMBER in Zuweisung erwartet.')
        value_1 = max(0, op(value_2, int(number_token.v)))
        self.values.update({identifier_1: value_1})
        if not self.next_nonempty_token('Zuweisung', 'SEMICOLON').k == 'SEMICOLON':
            self.error_handler.handle_error('SEMICOLON zum Abschluss einer Zuweisung erwartet.')
        return self.next_token()

    def verify_assignment(self, identifier_token_1):
        if not self.next_nonempty_token('Zuweisung', ':=').k == 'ALLOCATION':
            self.error_handler.handle_error(':= in Zuweisung erwartet.')

        identifier_token_2 = self.next_nonempty_token('Zuweisung', 'IDENTIFIER (x0, x1, ...) oder NUMBER')
        if identifier_token_2.k == 'NUMBER':
            if not self.next_nonempty_token('Zuweisung', 'SEMICOLON').k == 'SEMICOLON':
                self.error_handler.handle_error('SEMICOLON zum Abschluss einer Zuweisung erwartet.')
            return self.next_token()

        if not identifier_token_2.k == 'IDENTIFIER':
            self.error_handler.handle_error('IDENTIFIER in Zuweisung erwartet.')
        if self.next_nonempty_token('Zuweisung', '+ oder -').k not in ['PLUS', 'MINUS']:
            self.error_handler.handle_error('+ oder - in Zuweisung erwartet.')
        if not self.next_nonempty_token('Zuweisung', 'NUMBER').k == 'NUMBER':
            self.error_handler.handle_error('NUMBER in Zuweisung erwartet.')
        if not self.next_nonempty_token('Zuweisung', 'SEMICOLON').k == 'SEMICOLON':
            self.error_handler.handle_error('SEMICOLON zum Abschluss einer Zuweisung erwartet.')
        return self.next_token()

    def process_goto(self, goto_token):
        marker_token = self.next_nonempty_token('GOTO', 'MARKER (M1, M2, ...)')
        if not marker_token.k == 'MARKER':
            self.error_handler.handle_error('MARKER (M1, M2, ...) in GOTO erwartet.')
        if not self.next_nonempty_token('GOTO', 'SEMICOLON').k == 'SEMICOLON':
            self.error_handler.handle_error('SEMICOLON zum Abschluss eines GOTO erwartet')
        marker_number = int(marker_token.v[1:])
        current_token = self.next_token()
        while current_token is not None and max(self.marker_to_position.keys()) < int(marker_number):
            current_token = self.verify_line(current_token)
        self.lex.current_position = self.marker_to_position.get(marker_number)
        self.error_handler.line_number = self.marker_to_line.get(marker_number)
        if marker_number not in self.marker_to_position.keys():
            self.error_handler.handle_error('GOTO zu nicht vorhandener Markierung')
        return self.next_token()

    def verify_goto(self, goto_token):
        marker_token = self.next_nonempty_token('GOTO', 'MARKER (M1, M2, ...)')
        if not marker_token.k == 'MARKER':
            self.error_handler.handle_error('MARKER (M1, M2, ...) in GOTO erwartet.')
        if not self.next_nonempty_token('GOTO', 'SEMICOLON').k == 'SEMICOLON':
            self.error_handler.handle_error('SEMICOLON zum Abschluss eines GOTO erwartet')
        marker_number = int(marker_token.v[1:])
        saved_position = self.lex.current_position
        saved_line = self.error_handler.line_number
        current_token = self.next_token()
        while current_token is not None and max(self.marker_to_position.keys()) < int(marker_number):
            current_token = self.verify_line(current_token)
        self.lex.current_position = saved_position
        self.error_handler.line_number = saved_line
        if marker_number not in self.marker_to_position.keys():
            self.error_handler.handle_error('GOTO zu nicht vorhandener Markierung')
        return self.next_token()

    def process_if(self, if_token):
        identifier_token = self.next_nonempty_token('IF', 'IDENTIFIER')
        if not identifier_token.k == 'IDENTIFIER':
            self.error_handler.handle_error('IDENTIFIER in IF erwartet')
        if not self.next_nonempty_token('IF', '=').k == 'EQUALS':
            self.error_handler.handle_error('= in IF erwartet')
        number_token = self.next_nonempty_token('IF', 'NUMBER')
        if not number_token.k == 'NUMBER':
            self.error_handler.handle_error('NUMBER in IF erwartet')

        if not self.next_nonempty_token('IF', 'THEN').k == 'THEN':
            self.error_handler.handle_error('THEN in IF erwartet')
        current_token = self.next_nonempty_token('IF', 'GOTO')
        if not current_token.k == 'GOTO':
            self.error_handler.handle_error('GOTO in IF erwartet')
        if identifier_token.v in self.values.keys():
            identifier_value = self.values.get(identifier_token.v)
        else:
            identifier_value = 0
        if identifier_value == int(number_token.v):
            return self.process_goto(current_token)
        else:
            return self.verify_goto(current_token)

    def verify_if(self, if_token):
        if not self.next_nonempty_token('IF', 'IDENTIFIER').k == 'IDENTIFIER':
            self.error_handler.handle_error('IDENTIFIER in IF erwartet')
        if not self.next_nonempty_token('IF', '=').k == 'EQUALS':
            self.error_handler.handle_error('= in IF erwartet')
        if not self.next_nonempty_token('IF', 'NUMBER').k == 'NUMBER':
            self.error_handler.handle_error('NUMBER in IF erwartet')
        if not self.next_nonempty_token('IF', 'THEN').k == 'THEN':
            self.error_handler.handle_error('THEN in IF erwartet')
        current_token = self.next_nonempty_token('IF', 'GOTO')
        if not current_token.k == 'GOTO':
            self.error_handler.handle_error('GOTO in IF erwartet')
        return self.verify_goto(current_token)

    def process_halt(self, halt_token):
        if not self.next_nonempty_token('HALT', 'SEMICOLON').k == 'SEMICOLON':
            self.error_handler.handle_error('SEMICOLON zum Abschluss eines HALT erwartet')
        self.halted = True
        current_token = self.next_token()
        while current_token is not None:
            current_token = self.verify_line(current_token)
        return current_token

    def verify_halt(self, halt_token):
        if not self.next_nonempty_token('HALT', 'SEMICOLON').k == 'SEMICOLON':
            self.error_handler.handle_error('SEMICOLON zum Abschluss eines HALT erwartet')
        return self.next_token()

    def next_marker_number(self):
        if self.marker_to_position.keys():
            return max(self.marker_to_position.keys()) + 1
        else:
            return 1

    def process_line(self, current_token):
        if current_token is None or not current_token.k == 'MARKER':
            self.error_handler.handle_error('Keine passende Anweisung gefunden\n' +
                                            'Erwartet: MARKER (M1, M2, ...)')
        marker_number = int(current_token.v[1:])
        if not (marker_number == self.next_marker_number() or
                (marker_number in self.marker_to_position.keys() and
                 current_token.p == self.marker_to_position.get(marker_number))):
            self.error_handler.handle_error('Die Nummern der Anweisungen starten bei 1 und steigen fortlaufend.')
        self.marker_to_position.update({marker_number: current_token.p})
        self.marker_to_line.update({marker_number: self.error_handler.line_number})
        colon_token = self.next_nonempty_token('LINE', 'COLON')
        if not colon_token.k == 'COLON':
            self.error_handler.handle_error('DOPPELPUNKT nach MARKER (M1, M2, ...) erwartet.')

        current_token = self.next_nonempty_token('LINE', 'IDENTIFIER (x0, x1, ...), IF, GOTO oder HALT')
        if current_token.k not in ['IDENTIFIER', 'IF', 'GOTO', 'HALT']:
            self.error_handler.handle_error('Keine passende Anweisung gefunden\n' +
                                            'Erwartet: IDENTIFIER (x0, x1, ...), IF, GOTO oder HALT')
        elif current_token.k == 'IDENTIFIER':
            current_token = self.process_assignment(current_token)
        elif current_token.k == 'GOTO':
            current_token = self.process_goto(current_token)
        elif current_token.k == 'IF':
            current_token = self.process_if(current_token)
        elif current_token.k == 'HALT':
            current_token = self.process_halt(current_token)
        return current_token

    def verify_line(self, current_token):
        if current_token is None or not current_token.k == 'MARKER':
            self.error_handler.handle_error('Keine passende Anweisung gefunden\n' +
                                            'Erwartet: MARKER (M1, M2, ...)')
        marker_number = int(current_token.v[1:])
        if not (marker_number == self.next_marker_number() or
                (marker_number in self.marker_to_position.keys() and
                 current_token.p == self.marker_to_position.get(marker_number))):
            self.error_handler.handle_error('Die Nummern der Anweisungen starten bei 1 und steigen fortlaufend.')
        self.marker_to_position.update({marker_number: current_token.p})
        self.marker_to_line.update({marker_number: self.error_handler.line_number})
        if not self.next_nonempty_token('LINE', 'COLON').k == 'COLON':
            self.error_handler.handle_error('DOPPELPUNKT nach MARKER (M1, M2, ...) erwartet.')

        current_token = self.next_nonempty_token('LINE', 'IDENTIFIER (x0, x1, ...), IF, GOTO oder HALT')
        if current_token.k not in ['IDENTIFIER', 'IF', 'GOTO', 'HALT']:
            self.error_handler.handle_error('Keine passende Anweisung gefunden\n' +
                                            'Erwartet: IDENTIFIER (x0, x1, ...), IF, GOTO oder HALT')
        elif current_token.k == 'IDENTIFIER':
            current_token = self.verify_assignment(current_token)
        elif current_token.k == 'GOTO':
            current_token = self.verify_goto(current_token)
        elif current_token.k == 'IF':
            current_token = self.verify_if(current_token)
        elif current_token.k == 'HALT':
            current_token = self.verify_halt(current_token)
        return current_token

    def interpret(self, program):
        try:
            with Timeout(self.timeout):
                self.halted = False
                self.lex = lexer.Lexer(self.regex_to_token, program)
                self.error_handler = ErrorHandler(program, self)
                self.values = {}
                current_token = self.next_token()
                while current_token is not None:
                    current_token = self.process_line(current_token)
                if not self.halted:
                    self.error_handler.handle_error('Ende des Programms ohne HALT erreicht.')
                if 'x0' in self.values:
                    return self.values.get('x0')
                return 0
        except KeyboardInterrupt:
            print('Die Ausführung des Programms wurde unterbrochen.\n' +
                  'Daher ist der Rückgabewert des Programms nicht definiert.')
            return -1


def interpret(program, timeout=60):
    interpreter = GOTOInterpreter(timeout)
    return interpreter.interpret(program)