"""
@Copyright: (C) 1989-2020 Lauterbach GmbH, licensed for use with TRACE32(R) only

$Id: python_interactive.py 125771 2020-09-09 13:52:23Z jvogl $
"""

import argparse
import code
import re
import sys
import traceback
import platform
import rlcompleter
import os
import copy

import lauterbach.trace32.rcl as t32


class T32TerminalInterpreter(code.InteractiveInterpreter):
    def __init__(self, locals=None, filename="<term>"):
        """Constructor.

        The optional locals argument will be passed to the
        InteractiveInterpreter base class.

        The optional filename argument should specify the (file)name
        of the input stream; it will show up in tracebacks.

        """
        super().__init__(locals)
        self.filename = filename
        self.buffer = []
        self.resetbuffer()
        self.conn = None
        self.encoding2 = None
        self.set_encode_decode()

    def set_encode_decode(self):
        if (platform.system() == 'Windows') or (platform.system()[0:6] == 'CYGWIN'):
            #   Windows
            self.encoding2 = 'Windows-1252'
        else:
            self.encoding2 = 'UTF-8'

    def showsyntaxerror(self, filename=None):
        """Display the syntax error that just occurred.

        This doesn't display a stack trace because there isn't one.

        If a filename is given, it is stuffed in the exception instead
        of what was there before (because Python's parser always uses
        "<string>" when reading from a string).

        The output is written by self.write(), below.

        """
        type, value, tb = sys.exc_info()
        sys.last_type = type
        sys.last_value = value
        sys.last_traceback = tb
        if filename and type is SyntaxError:
            # Work hard to stuff the correct filename in the exception
            try:
                msg, (dummy_filename, lineno, offset, line) = value.args
            except ValueError:
                # Not the format we expect; leave it alone
                pass
            else:
                # Stuff in the right filename
                value = SyntaxError(msg, (filename, lineno, offset, line))
                sys.last_value = value
        if sys.excepthook is sys.__excepthook__:
            lines = traceback.format_exception_only(type, value)
            self.write(''.join(lines), file=sys.stderr)
        else:
            # If someone has set sys.excepthook, we let that take precedence
            # over self.write
            sys.excepthook(type, value, tb)

    def showtraceback(self):
        """Display the exception that just occurred.

        We remove the first stack item because it is our own code.

        The output is written by self.write(), below.

        """
        sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
        sys.last_traceback = last_tb
        try:
            lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
            if sys.excepthook is sys.__excepthook__:
                self.write(''.join(lines), file=sys.stderr)
            else:
                # If someone has set sys.excepthook, we let that take precedence
                # over self.write
                sys.excepthook(ei[0], ei[1], last_tb)
        finally:
            last_tb = ei = None

    # def write(self, *args, sep=' ', end='\n', file=None):
    def write(self, data, *, file=None):
        """Overrides code.InteractiveInterpreter.write

        """
        if file == sys.stderr:
            data = '\x1b[1;31m' + data + '\x1b[0m'
        data = data.replace('\n', '\r\n')  # necessary for VT100
        sys.stdout.write(data)

    def resetbuffer(self):
        """Reset the input buffer."""
        self.buffer = []

    def push(self, line):
        """Push a line to the interpreter.

        The line should not have a trailing newline; it may have
        internal newlines.  The line is appended to a buffer and the
        interpreter's runsource() method is called with the
        concatenated contents of the buffer as source.  If this
        indicates that the command was executed or invalid, the buffer
        is reset; otherwise, the command is incomplete, and the buffer
        is left as it was after the line was appended.  The return
        value is 1 if more input is required, 0 if the line was dealt
        with in some way (this is the same as runsource()).

        """
        more = self.runsource(line, self.filename)
        return more


class T32TerminalConsole(T32TerminalInterpreter):
    def __init__(self, locals=None, filename="<term>"):
        """Constructor.

        The optional locals argument will be passed to the
        InteractiveInterpreter base class.

        The optional filename argument should specify the (file)name
        of the input stream; it will show up in tracebacks.

        """
        super().__init__(locals, filename)
        self.blacklist = ['\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x0b', '\x0c', '\x0e', '\x0f', '\x10',
                          '\x11', '\x12', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a']
        self.char_handler = CharHandler(self.encoding2, self)
        self.ps = None

    def interact(self, pipename):
        """Closely emulate the interactive Python console.

        The optional banner argument specifies the banner to print
        before the first interaction; by default it prints a banner
        similar to the one printed by the real Python interpreter,
        followed by the current class name in parentheses (so as not
        to confuse this with the real interpreter -- since it's so
        close!).

        """

        try:
            # noinspection PyUnresolvedReferences
            self.ps = sys.ps1
        except AttributeError:
            self.ps = sys.ps1 = '>>> '
        try:
            # noinspection PyUnresolvedReferences
            sys.ps2
        except AttributeError:
            sys.ps2 = '... '

        # print banner
        self.write('Python {} on {}\n'.format(sys.version, sys.platform))
        self.write('Type "help", "copyright", "credits" or "license" for more information.\n')

        # write prompt
        self.init_line()
        while True:
            # process data from connection
            data = sys.stdin.read(1)
            if data:
                ch = data
                if not ch == '\r' and ch not in self.blacklist:
                    self.char_handler.handler(ch, data, self.ps)
                elif ch == '\x03':
                    # ctrl+c
                    print('\x1b[2K\rClosing and finishing')
                    break
                elif ch == '\x04':
                    # ctrl+d
                    print('\x1b[2K\rClosing and finishing')
                    break
                elif ch == '\r':
                    self.char_handler.maybe_mult_line_cop = False
                    self.char_handler.move_cursor_end()
                    sys.stdout.write('\n\r')
                    line = self.char_handler.line_generator(False)
                    more = self.push(line)
                    if more:
                        self.ps = sys.ps2
                        self.char_handler.extend_line()
                    else:
                        self.ps = sys.ps1
                        self.char_handler.reset_line()
                    self.init_line()
                    sys.stdout.write(' ' * 4 * self.char_handler.tabcounter)
                    self.char_handler.line_pos_x = 4 * self.char_handler.tabcounter
            else:
                if not os.path.exists(pipename[0]) or not os.path.exists(pipename[1]):
                    break
                continue
        self.conn = None

    def init_line(self):
        """
        writes >>> or ... to make it look like the real interactive interpreter
        """
        sys.stdout.write(self.ps)


class History:
    """
    Implements the history of the Interpreter
    compiled lines get added to the History list and then accessed over their respective index
    """

    def __init__(self):
        self.history_list = []
        self.history_count = 0

    def add_history(self, line):
        """
        Adds the previously entered statement into the List of
        all History items
        """
        if line != ["\n"] and line != []:
            historyline = copy.deepcopy(line)
            if historyline[-1] == '\n':
                historyline.pop(-1)
            if self.history_count != 0:
                if historyline == self.history_list[-1]:
                    return
            self.history_list.append(historyline)
            self.history_count += 1
        # self.print_history()

    def get_history_item(self, pos):
        """
        returns the item in the history list on position pos
        pos begins at 1
        """
        if pos > self.history_count:
            return None
        return copy.deepcopy(self.history_list[pos - 1])

    def print_history(self):
        """
        prints all the complete History
        just for Debug purposes
        """
        for i in self.history_list:
            print(i)


class CharHandler:
    """
    is meant to handle chars that won't lead to a special event like breaking or compiling
    the line_generator gives you the final string to compile or return or whatever as string
    """

    def __init__(self, encoding2, console):
        """
        the name line is misleading since it's a list containing several lines
        if it's a multiline statement like "for i in range:"
        """
        self.line = ['']
        self.line_pos_x = 0
        self.line_pos_y = 0
        self.mode = None
        self.data = None
        self.history_count = 0
        self.readline = History()
        self.encoding2 = encoding2
        self.compl = rlcompleter.Completer()
        self.console = console
        self.termwidth = 80
        self.tabcounter = 0
        self.maybe_mult_line_cop = False

    def line_generator(self, reset_after):
        """
        generates a compilable line out of all the entries
        by adding a newline at the end of each line
        """

        # the following if statement deletes spaces in the last line
        # so the compiler doesn't always think that more is coming
        if self.line:
            if self.line[-1]:
                self.line[-1] = list(self.line[-1])
                while self.line[-1][-1] == ' ':
                    self.line[-1].pop(-1)
                    if not self.line[-1]:
                        self.line[-1] = ''
                        break
                if self.line[-1] != '':
                    self.line[-1] = ''.join(self.line[-1])
        # the final string is assembled in sendline
        sendline = []
        begin = True
        for step in self.line:
            if begin:
                sendline.append(step)
                begin = False
            else:
                sendline.append('\n' + step)
        self.line_pos_x = 0
        # reset_after is a workaround for license and help since they both do different and strange things
        if reset_after:
            line = self.line[0]
            self.reset_line()
            if line != '\n':
                if line == 'q\n':
                    return 'q'
                return line + '\n'
            else:
                return '\n'
        else:
            return ''.join(sendline)

    def reset_line(self):
        """
        resets the current line and all its properties
        """
        if not self.line == ['']:
            while not self.line[-1]:
                self.line.pop(-1)
            self.readline.add_history(self.line)
        self.line = ['']
        self.history_count = 0
        self.line_pos_y = 0
        self.tabcounter = 0

    def extend_line(self):
        """
        extends the line if more input is expected by the compiler
        the extra code implements the automated more depending on ":"
        """
        searchpos = len(self.line[self.line_pos_y]) - 1
        if self.line[self.line_pos_y]:
            while self.line[self.line_pos_y][searchpos] == ' ' and searchpos:
                searchpos -= 1
            if self.line[self.line_pos_y][searchpos] == ':':
                self.tabcounter += 1
        if self.tabcounter:
            self.line.append(' ' * 4 * self.tabcounter)
            # print(self.line)
        else:
            self.line.append('')
        self.line_pos_y += 1

    def handler(self, ch, data, ps):
        """
        here all the chars get handled that aren't triggering compiling
        """
        # the completer needs the current locals to autocomplete
        self.compl = rlcompleter.Completer(self.console.locals)
        self.data = data
        # print("von handler", ch, '\n\r')
        if self.mode is None:
            if self.maybe_mult_line_cop:
                pass
                # self.line.append('')
                # self.line_pos_y += 1
                # self.line_pos_x = len(self.line[self.line_pos_y])
                # sys.stdout.write("\n\r... ")
            self.maybe_mult_line_cop = False
            if ch == '\x1b':
                # escape triggers ansiescape in the next step
                self.mode = '\x1b'
            elif ch == '\b':
                # backspace is not as easy as you might think
                if self.line_pos_x:
                    # this statement handles the backspace if something has been entered into the current line
                    self.line[self.line_pos_y] = list(self.line[self.line_pos_y])
                    if self.line_pos_x == len(self.line[self.line_pos_y]):
                        # if the cursor is at the end we just need to pop the last element and overwrite
                        # the last letter with a space
                        self.line[self.line_pos_y] = self.line[self.line_pos_y][:-1]
                        sys.stdout.write('\b \b')
                    else:
                        # if the cursor is somewhere in the middle we need to pop the respective element,
                        # overwrite in the terminal everything written after the element with spaces
                        # and reprint the line from there
                        lendiff = len(self.line[self.line_pos_y]) - self.line_pos_x
                        # clears to one before cursor
                        oldpos = self.line_pos_x
                        self.move_cursor_line_end()
                        self.line_pos_x = oldpos
                        sys.stdout.write('\b \b' * (lendiff + 1))
                        del self.line[self.line_pos_y][self.line_pos_x - 1]
                        # reprints new string
                        sys.stdout.write((''.join(self.line[self.line_pos_y]
                                                  [(self.line_pos_x - 1):len(
                            self.line[self.line_pos_y])])) + '\b' * lendiff)
                    self.line_pos_x -= 1
                    self.line[self.line_pos_y] = ''.join(self.line[self.line_pos_y])
                elif self.line_pos_y:
                    # if we are at the beginning of a line after the first line
                    # we put the content of this line in the line before
                    holder_y = self.line_pos_y
                    cursor_pos = len(self.line[holder_y - 1])
                    self.clear_all_lines()
                    self.line[holder_y - 1] += self.line[holder_y]
                    del self.line[holder_y]
                    self.line_pos_y = holder_y - 1
                    self.print_complete_line_content()
                    while self.line_pos_y != (holder_y - 1):
                        self.move_one_up()
                    self.move_cursor_line_beginning()
                    sys.stdout.write("\x1b[C" * cursor_pos)
                    self.line_pos_x = cursor_pos
                    self.tabcounter -= 1
            elif ch == "\x09":
                # tab is used for formatting and autocompletion
                self.line[self.line_pos_y] = list(self.line[self.line_pos_y])
                if self.line_pos_x == 0:
                    # this if else statement creates the word that might be autocompleted
                    to_complete_string = ''
                else:
                    cut_start_pos = self.line_pos_x - 1
                    while True:
                        # print(self.line, "\n\r")
                        if cut_start_pos == 0 or self.line[self.line_pos_y][cut_start_pos] == ' ' or \
                                self.line[self.line_pos_y][cut_start_pos] == '(':
                            if self.line[self.line_pos_y][cut_start_pos] == ' ' or self.line[self.line_pos_y][
                                    cut_start_pos] == '(':
                                cut_start_pos += 1
                            to_complete_string = ''.join(self.line[self.line_pos_y][cut_start_pos:self.line_pos_x])
                            if to_complete_string == '':
                                to_complete_string = '('
                            break
                        else:
                            cut_start_pos -= 1
                if not self.line[self.line_pos_y]:
                    # here the line is empty therefore it will never need autocompletion
                    # that means we just do a regular tab containing 4 spaces
                    i = 0
                    while i < 4:
                        self.line[self.line_pos_y].append(' ')
                        self.line_pos_x += 1
                        i += 1
                    sys.stdout.write((' ' * 4))
                else:
                    # if the line is not empty we need to check if we are in a situation that might
                    # need autocompletion
                    if self.line_pos_x == 0:
                        # this is the beginning of the line
                        # here is nothing that could be completed so it is just used for formatting
                        sys.stdout.write((' ' * 4))
                        sys.stdout.write(''.join(self.line[self.line_pos_y]))
                        sys.stdout.write(('\b' * len(self.line[self.line_pos_y])))

                        i = 0
                        while i < 4:
                            self.line[self.line_pos_y].insert(0, ' ')
                            self.line_pos_x += 1
                            i += 1
                    elif self.line[self.line_pos_y][self.line_pos_x - 1] == ' ':
                        # if a space is at the current position a tab will be placed
                        # i mean it's weird formatting but should be possible
                        sys.stdout.write(' ')
                        self.line[self.line_pos_y].insert(self.line_pos_x - 1, ' ')
                        self.line_pos_x += 1
                        while not (self.line_pos_x % 4) == 0:
                            sys.stdout.write(' ')
                            self.line[self.line_pos_y].insert(self.line_pos_x - 1, ' ')
                            self.line_pos_x += 1
                        sys.stdout.write(('\b' * len(self.line[self.line_pos_y]) + ''.join(
                            self.line[self.line_pos_y]) + '\b' * (len(self.line[self.line_pos_y]) - self.line_pos_x)))
                    elif not self.compl.complete(to_complete_string, 0) is None and ps != 'input':
                        # All the situaions where we might need a tab are checked
                        # so here the Autocompletion part starts
                        if self.compl.complete(to_complete_string, 1) is None:
                            # if there is just one possibility the completion
                            # gets added to our current position
                            self.clear_line()
                            self.line[self.line_pos_y].insert(self.line_pos_x, ''.join(
                                list(self.compl.complete(to_complete_string, 0))[len(to_complete_string):]))
                            self.line[self.line_pos_y] = list(''.join(self.line[self.line_pos_y]))
                            self.line_pos_x += len(self.compl.complete(to_complete_string, 0)) - len(to_complete_string)
                            sys.stdout.write(''.join(self.line[self.line_pos_y]))
                            sys.stdout.write('\b' * (len(self.line[self.line_pos_y]) - self.line_pos_x))
                        else:
                            # if there is more than one possibility to complete the statement
                            # they all get printed
                            # the holder_x and y help to put the cursor into the original position after everthing is done
                            holder_y = self.line_pos_y
                            holder_x = self.line_pos_x
                            self.move_cursor_end()
                            sys.stdout.write('\n\r')
                            i = biggest = 0
                            while not self.compl.complete(to_complete_string, i) is None:
                                if biggest < len(self.compl.complete(to_complete_string, i)):
                                    biggest = len(self.compl.complete(to_complete_string, i))
                                i += 1
                            biggest += 5
                            i = curr_pos = 0
                            while not self.compl.complete(to_complete_string, i) is None:
                                curr_pos += biggest
                                if curr_pos >= self.termwidth:
                                    sys.stdout.write('\n\r')
                                    curr_pos = 0
                                else:
                                    sys.stdout.write((self.compl.complete(to_complete_string, i)))
                                    sys.stdout.write((biggest - len(self.compl.complete(to_complete_string, i))) * ' ')
                                    i += 1
                            sys.stdout.write('\n\r')
                            sys.stdout.write(ps)
                            self.line[holder_y] = ''.join(self.line[holder_y])
                            self.print_complete_line_content()
                            while self.line_pos_y != holder_y:
                                self.move_one_up()
                            self.move_cursor_line_beginning()
                            sys.stdout.write('\x1b[C' * holder_x)
                            self.line_pos_x = holder_x
                            self.line[holder_y] = list(self.line[holder_y])
                self.line[self.line_pos_y] = ''.join(self.line[self.line_pos_y])
            elif ch == "\n":
                # Problem: wenn eine Zeile kompiliert werden soll endet sie mit \n\r
                self.maybe_mult_line_cop = True
            else:
                # if it's no char that needs special handling it is just printed
                self.line[self.line_pos_y] = list(self.line[self.line_pos_y])
                if self.line_pos_x == len(self.line[self.line_pos_y]):
                    # if we are at the end of the current line it gets just printed at the end
                    self.line[self.line_pos_y].append(ch)
                    sys.stdout.write(data)
                else:
                    # if we are inside a line it's inserted and handled like in backspace
                    lendiff = len(self.line[self.line_pos_y]) - self.line_pos_x
                    # clears everything after the cursor
                    sys.stdout.write(((' ' * (lendiff + 1)) + '\b' * (lendiff + 1)))
                    self.line[self.line_pos_y].insert(self.line_pos_x, ch)
                    # inserts the new string
                    sys.stdout.write(
                        (''.join(self.line[self.line_pos_y][
                                 self.line_pos_x:len(self.line[self.line_pos_y])]) + '\b' * lendiff))

                self.line_pos_x += 1
                self.line[self.line_pos_y] = ''.join(self.line[self.line_pos_y])
        else:
            # if we entered an escape sequence we go into ansiescape or do nothing
            if self.mode == '\x1b':
                if ch == '[':
                    self.mode += ch
                # else:
                # TODO ESC key is not handled properly right now
                # raise ValueError(ch.encode(self.encoding2))
            elif self.mode.startswith('\x1b['):
                if ch.isalpha():
                    self.mode += ch
                    self.ansiescape(self.mode, ps)
                    self.mode = None
                elif ch.isdigit():
                    self.mode += ch
                # else:
                # raise ValueError(ch)

    def ansiescape(self, code, ps):
        """
        escapesequences(like up down left right) are handled here
        These are (ab)used for moving inside a line and accessing the History
        """
        re_ansi_escape = re.compile('\\x1b\[[0-9]*[a-zA-Z]$')
        match = re_ansi_escape.match(code)
        if match is None:
            raise ValueError(code)
        if code[-1] == 'A' and ps != 'input':
            # arrow up
            # goes up inside the statement or in the history if it is already at the most upper point
            if self.line_pos_y == 0:
                self.history_count += 1
                if self.history_count <= self.readline.history_count:
                    self.clear_all_lines()
                    self.line = self.readline.get_history_item(
                        self.readline.history_count - self.history_count + 1)
                    self.print_complete_line_content()
                else:
                    self.history_count -= 1
            else:
                self.move_one_up()

        elif code[-1] == 'B' and ps != 'input':
            # arrow down
            # goes down inside the statement or in the history if it is already at the lowest point
            if self.line_pos_y == (len(self.line) - 1):
                self.history_count -= 1
                if self.history_count > 0:
                    self.clear_all_lines()
                    self.line = self.readline.get_history_item(
                        self.readline.history_count - self.history_count + 1)
                    self.print_complete_line_content()
                elif self.history_count == 0:
                    self.clear_all_lines()
                    self.line = ['']
                    self.line_pos_x = 0
                    self.line_pos_y = 0
                else:
                    self.history_count += 1
            else:
                self.move_one_down()

        elif code[-1] == 'C':
            # arrow right
            # moves the cursor right as long as we are in the line
            if self.line_pos_x < len(self.line[self.line_pos_y]):
                self.line_pos_x += 1
                sys.stdout.write(code)
        elif code[-1] == 'D':
            # arrow left
            # moves the cursor left until the beginning of the line
            if self.line_pos_x > 0:
                sys.stdout.write('\b')
                self.line_pos_x -= 1

    def print_complete_line_content(self):
        """
        prints the line/s
        is used if the complete line needs to be printed (e.g. after multiple possible autocompletions)
        """
        begin = True
        for step in self.line:
            if begin:
                sys.stdout.write(step)
                begin = False
            else:
                sys.stdout.write('\n\r... ' + step)
        self.line_pos_y = len(self.line) - 1
        self.line_pos_x = len(self.line[-1])

    def move_cursor_end(self):
        """
        moves the cursor to the end of the current line
        """
        self.move_cursor_line_beginning()
        while self.line_pos_y != (len(self.line) - 1):
            self.move_one_down()
        self.move_cursor_line_end()

    def clear_line(self):
        """
        clears the current line
        """
        self.move_cursor_end()
        sys.stdout.write('\b \b' * len(self.line[self.line_pos_y]))

    def clear_all_lines(self):
        """
        clears all lines
        """
        self.move_cursor_end()
        while self.line_pos_y != 0:
            sys.stdout.write('\b \b' * (len(self.line[self.line_pos_y]) + 4) + '\x1b[C' * 4)
            self.line_pos_x = 0
            self.move_one_up()
            self.move_cursor_line_end()
        sys.stdout.write('\b \b' * len(self.line[self.line_pos_y]))

    def move_cursor_line_beginning(self):
        """
        moves the cursor to position 0 of the current line
        """
        sys.stdout.write('\b' * self.line_pos_x)
        self.line_pos_x = 0

    def move_cursor_line_end(self):
        """
        moves the cursor to the last position of the current line
        """
        sys.stdout.write('\x1b[C' * (len(self.line[self.line_pos_y]) - self.line_pos_x))
        self.line_pos_x = len(self.line[self.line_pos_y])

    def move_one_up(self):
        """
        moves the cursor to the previous line(if one exists)
        standard is that the cursor is at the same x position, but if the line is to long it's just moved to the end
        """
        orig_pos = self.line_pos_x
        self.move_cursor_line_beginning()
        sys.stdout.write('\x1b[A')
        self.line_pos_y -= 1
        if len(self.line[self.line_pos_y]) > (self.termwidth - 4):
            line_len = len(self.line[self.line_pos_y - 1])
            while line_len > 0:
                sys.stdout.write('\x1b[A')
                line_len -= self.termwidth
        self.line_pos_x = orig_pos
        if len(self.line[self.line_pos_y]) < self.line_pos_x:
            self.line_pos_x = len(self.line[self.line_pos_y])
        sys.stdout.write('\x1b[C' * self.line_pos_x)

    def move_one_down(self):
        """
        moves the cursor to the next line(if one exists)
        standard is that the cursor is at the same x position, but if the line is to long it's just moved to end
        """
        orig_pos = self.line_pos_x
        self.move_cursor_line_beginning()
        sys.stdout.write('\x1b[B')
        if len(self.line[self.line_pos_y]) > (self.termwidth - 4):
            line_len = len(self.line[self.line_pos_y - 1])
            while line_len > 0:
                sys.stdout.write('\x1b[B')
                line_len -= self.termwidth
        self.line_pos_y += 1
        self.line_pos_x = orig_pos
        if len(self.line[self.line_pos_y]) < self.line_pos_x:
            self.line_pos_x = len(self.line[self.line_pos_y])
        sys.stdout.write('\x1b[C' * self.line_pos_x)


class PipeStdout(object):
    """
    used to write on the pipe
    replaces sys.stdout and sys.stderr
    """

    def __init__(self, pipe, stream, encoding, should_color):
        self.stream = stream
        self.pipe = pipe
        self.encoding2 = encoding
        self.memory = ''
        self.color = should_color

    def __getattr__(self, attr_name):
        """
        returns the inputted attribute
        used to closely emulate sys.stdout/sys.stderr
        """
        return getattr(self.stream, attr_name)

    def write(self, data):
        """
        adapts data to the VT100 protocol and writes it on the pipe
        """
        data_len = len(data)
        # empties data if it's just a newline
        if self.memory != data:
            self.memory = data
        elif self.memory == '\n':
            data = ''
        # self.stream.write(data)
        data = list(data)
        buffer = []
        for i in data:
            if str(i) == '\n':
                buffer.append('\r')  # necessary for VT100
            buffer.append(str(i))
        # text is printed in red if it should be colored
        if self.color:
            buffer.insert(0, '\x1b[1;31m')
            buffer.append('\x1b[0m')

        data = buffer

        data = ''.join(data)
        self.pipe.write(data.encode(self.encoding2))
        self.pipe.flush()
        return data_len

    def flush(self):
        """
        flushes pipe
        """
        self.stream.flush()


class PipeStdin(object):
    """
    used to read from the pipe
    replaces sys.stdin
    """

    def __init__(self, pipe, stream, encoding, ch_handler):
        self.blacklist = ['\x01',  # Strg + A
                          '\x02',  # Strg + B
                          '\x03',  # Strg + C
                          '\x04',  # Strg + D
                          '\x05',  # Strg + E
                          '\x06',  # Strg + F
                          '\x0b',  # Strg + K
                          '\x0c',  # Strg + L
                          '\x0e',  # Strg + N
                          '\x0f',  # Strg + O
                          '\x10',  # Strg + P
                          '\x11',  # Strg + Q
                          '\x12',  # Strg + R
                          '\x14',  # Strg + T
                          '\x15',  # Strg + U
                          '\x16',  # Strg + V
                          '\x17',  # Strg + W
                          '\x18',  # Strg + X
                          '\x19',  # Strg + Y
                          '\x1a',  # Strg + Z
                          ]  # Strg + J
        self.stream = stream
        self.pipe = pipe
        self.encoding2 = encoding
        self.char_handler = ch_handler
        self.ps = 'input'

    def isatty(self):
        """
        isatty returns if the stream is connected to a terminal
        since we don't want to output anything in a real terminal it is set to false
        needed for proper handling of functions like help()
        """
        return False

    def __getattr__(self, attr_name):
        """
        returns the inputted attribute and flushed the pipe
        used to closely emulate sys.stdin
        """
        sys.stdout.flush()
        return getattr(self.pipe, attr_name)

    def read(self, data):
        """
        reads data(int) characters form the input pipes
        """
        # sys.stdout.flush()
        return self.pipe.read(data).decode(self.encoding2)

    def readline(self):
        """
        reads the current line
        used in help and license()
        """
        buf = []  # storage buffer
        handle = self.pipe
        chunk_size = 1
        while not handle.closed:  # while our handle is open
            data = handle.read(chunk_size).decode(self.encoding2)
            ch = data  # read `chunk_size` sized data from the passed handle
            if not data == '\r' and data not in self.blacklist:
                self.char_handler.handler(ch, data, self.ps)
            elif data == '\r':
                # buf += '\n'
                # sys.stdout.write('\n\r')
                return self.char_handler.line_generator(True)


def pipeconnect(console):
    """
    formerly called autoconnect
    reads args and links input and output to the pipe
    returns the pipes, port and system directory of T32
    """

    parser = argparse.ArgumentParser()
    parser.add_argument("--node", help="Remote API node.", default=t32.__DEFAULT_NODE)
    parser.add_argument("--packlen", help="Remote API packet length.", default=t32.__DEFAULT_PACKLEN)
    parser.add_argument("--port", help="Remote API port.", default=t32.__DEFAULT_PORT)
    parser.add_argument(
        "--protocol", type=str, choices=("UDP", "TCP"), help="Remote API protocol.", default=t32.__DEFAULT_PROTOCOL
    )
    parser.add_argument("--pipe_in", help="Input redirection pipe name.")
    parser.add_argument("--pipe_out", help="Output redirection pipe name.")
    args = parser.parse_args()

    encoding = "ansi"
    try:
        x = "Hello!".encode(encoding)
    except LookupError:  # TODO PEP 8: do not use bare 'except'
        encoding = "utf8"

    char_handler = CharHandler(encoding, console)

    if args.pipe_in is not None and args.pipe_out is not None:
        # print("Redirecting output to debugger window using pipes {} and  and {} encoding.".format(input_pipe_name,
        #                                                                                          output_pipe_name,
        #                                                                                          encoding))
        input_pipe = open(args.pipe_in, "rb")
        output_pipe = open(args.pipe_out, "wb")
        sys.stdin = PipeStdin(input_pipe, sys.stdin, encoding, char_handler)
        sys.stdout = PipeStdout(output_pipe, sys.stdout, encoding, False)
        sys.stderr = PipeStdout(output_pipe, sys.stderr, encoding, True)
        return input_pipe, output_pipe, args.port, args.node, args.packlen, args.protocol
    else:
        return '', '', None, None, None, None


if __name__ == '__main__':
    console = T32TerminalConsole()
    contFlag = True
    inputpipename, outputpipe, port, node, packlen, protocol = pipeconnect(console)
    console.runsource('import lauterbach.trace32.rcl as t32\n')
    if contFlag:
        if port != 0:
            console.runsource("dbg = t32.connect(port={port})\n".format(port=port))
        else:
            print("\x1b[1;43mWarning: dbg interface not available (API Port not open)\x1b[0m")
    pipename = inputpipename, outputpipe
    console.interact(pipename)
