

class AbacusCore:

    def __init__(self):
        self._input_string = ""
        self._vars = dict()
        self._funcs = dict()

        self._vars['result'] = 0

        self._operators = ['+', '-', '/', '*']
        self._delimiters = ['(', ')', '[', ']', ';', ' ']

    def get_vars(self):
        return self._vars

    def get_funcs(self):
        return self._funcs

    def add_var(self, name, value):
        name = name.strip()
        if not name:
            raise ValueError("Имя переменной не может быть пустым")
        self._vars[name] = value

    def add_func(self, name, value):
        name = name.strip()
        if not name:
            raise ValueError("Имя функции не может быть пустым")
        self._funcs[name] = value

    def parse_input(self, input_str):
        """
        Execute a multiline input sequentially, line by line.

        All lines share the same variable/function dictionaries, so a
        variable created on one line is immediately available on the next.
        """
        if input_str is None:
            raise ValueError("Leere Eingabe")

        if not input_str.strip():
            raise ValueError("Leere Eingabe")

        lines = input_str.splitlines()
        results = []
        last_comment = None
        last_name = None
        last_value = None

        for line in lines:
            if not line.strip():
                continue

            result = self._parse_line(line)

            if result is None:
                continue

            orig, name, value, comment = result
            results.append(result)
            last_name = name
            last_value = value
            last_comment = comment

        if not results:
            raise ValueError("Leere Eingabe")

        # The notebook displays the result of the last executable line.
        return input_str, last_name, last_value, last_comment

    def _parse_line(self, input_str):
        """Execute exactly one line."""
        orig_input_str = input_str
        input_str = input_str.strip()

        if not input_str:
            return None

        input_str, comment = self._get_input_wo_commentar(input_str)

        if input_str is None or not input_str.strip():
            return None

        input_str = input_str.strip()

        chunks = input_str.split("=")

        if len(chunks) > 2:
            raise ValueError("Mehrfachzuweisung wird nicht unterstützt")

        if len(chunks) == 2:
            name = chunks[0].strip()
            expression = chunks[1].strip()

            if not name:
                raise ValueError("Имя переменной не может быть пустым")
            if not expression:
                raise ValueError("Значение переменной не может быть пустым")

            value = self._with_variable_another_way(expression)
            self._vars[name] = value
            return orig_input_str, name, value, comment

        chunks = input_str.split(":", 1)

        if len(chunks) == 2:
            name = chunks[0].strip()
            expression = chunks[1].strip()

            if not name:
                raise ValueError("Имя функции не может быть пустым")
            if not expression:
                raise ValueError("Тело функции не может быть пустым")

            self._funcs[name] = expression
            return orig_input_str, name, expression, comment

        result = self._with_variable_another_way(input_str)
        self._vars["result"] = result
        return orig_input_str, None, result, comment

    def _with_variable_another_way(self, input_str):
        input_str, comment = self._get_input_wo_commentar(input_str)
        input_str = ''.join(input_str.split())  # entferne alle leerzeichen
        input_str = input_str.replace(',', '.')
        opdel = self._operators + self._delimiters
        chunks = list()
        last_index = 0
        for i, v in enumerate(input_str):
            if v in opdel:
                chunk = input_str[last_index:i]
                chunk = self._is_a_variable_or_func(chunk)
                chunks.append(chunk)
                chunks.append(input_str[i])
                last_index = i+1
            elif i == len(input_str)-1:
                chunk = input_str[last_index:i+1]
                chunk = self._is_a_variable_or_func(chunk)
                chunks.append(chunk)
        return eval(''.join(chunks))

    def _is_a_variable_or_func(self, chunk):
        chunk = chunk.strip()
        var = self._vars.get(chunk)
        if not var:
            var = self._funcs.get(chunk)
            if var:
                var = self._with_variable_another_way(var)
        if var:
            return str(var)
        else:
            return chunk




    def _replace_vars(self, input_str):
        '''
        try to find vars and replace them
        :param input_str:
        :return:
        '''
        first_position = None
        new_input_str = ''
        op_del = self._operators + self._delimiters
        if input_str[0] in self._operators:
            input_str = 'result' + input_str
        input_str_len = len(input_str)
        for i, c in enumerate(input_str):
            if c in op_del or i == input_str_len-1: # and first_position is not None:
                if first_position is not None:
                    last_position = i
                    if i == input_str_len-1:
                        last_position += 1
                    temp_var = input_str[first_position:last_position]
                    var = self._vars.get(temp_var)
                    if var is not None:
                        new_input_str += str(var)
                    else:
                        func = self._funcs.get(temp_var)
                        if func:
                            new_input_str += str(func)
                        else:
                            raise NotImplemented()
                    first_position = None
                    if i == input_str_len - 1:
                        continue
                new_input_str += input_str[i]
            elif c.isalpha() or c=='_':
                if first_position is None:
                    first_position = i
            else:
                new_input_str += input_str[i]
        return new_input_str

    def _get_input_wo_commentar(self, input_str):
        '''
        Zerlege input in input und kommentar
        :param input_str:
        :return:
        '''
        input_str = input_str.strip()
        comment_index = input_str.find('#')
        if comment_index == 0:  # die ganze zeile ist ein kommentar
            return None, input_str
        elif comment_index == -1:
            return input_str, None
        else:
            comment = input_str[comment_index + 1:]
            input_str = input_str[:comment_index]
            return input_str, comment


    def _is_new_variable(self, input_str):
        '''

        '''
        chunks = input_str.split('=')
        chunks_len = len(chunks)
        if chunks_len > 2:
            raise ValueError()
        # chunks.reverse()
        elif chunks_len == 2:
            return chunks[1].strip(), chunks[0].strip()
        else:
            return chunks[0].strip(), None