#!/usr/bin/env python3

from asciimatics.widgets import Frame, TextBox, Layout, Widget
from asciimatics.effects import Background
from asciimatics.scene import Scene
from asciimatics.screen import Screen
from asciimatics.exceptions import ResizeScreenError
from asciimatics.parsers import AnsiTerminalParser
from asciimatics.event import KeyboardEvent
import sys
import subprocess
import threading
import logging
try:
    import select
    import pty
    import os
    import fcntl
    import curses
except Exception:
    print("This demo only runs on Unix systems.")
    sys.exit(0)


logging.basicConfig(filename="term.log", level=logging.DEBUG)


class Terminal(TextBox):
    def __init__(self, height):
        super(Terminal, self).__init__(height, line_wrap=True, parser=AnsiTerminalParser())

        #Key definitions
        self._map = {}
        for k, v in [
            (Screen.KEY_LEFT, "kcub1"),
            (Screen.KEY_RIGHT, "kcuf1"),
            (Screen.KEY_UP, "kcuu1"),
            (Screen.KEY_DOWN, "kcud1"),
            (Screen.KEY_HOME, "khome"),
            (Screen.KEY_END, "kend"),
            (Screen.KEY_BACK, "kbs"),
        ]:
            self._map[k] = curses.tigetstr(v)
        self._map[Screen.KEY_TAB] = "\t".encode()

        # Open a pseudo TTY to control the interactive session.  Make it non-blocking.
        self._master, slave = pty.openpty()
        fl = fcntl.fcntl(self._master, fcntl.F_GETFL)
        fcntl.fcntl(self._master, fcntl.F_SETFL, fl | os.O_NONBLOCK)

        # Start the shell and thread to pull data from it.
        self._shell = subprocess.Popen(["bash", "-i"], preexec_fn=os.setsid, stdin=slave, stdout=slave, stderr=slave)
        self._lock = threading.Lock()
        self._thread = threading.Thread(target=self._background)
        self._thread.daemon = True
        self._thread.start()

    def update(self, frame_no):
        # Don't allow background thread to update values mid screen refresh.
        with self._lock:
            super(Terminal, self).update(frame_no)

    def process_event(self, event):
        if isinstance(event, KeyboardEvent):
            if event.key_code > 0:
                os.write(self._master, chr(event.key_code).encode())
                return
            elif event.key_code in self._map:
                os.write(self._master, self._map[event.key_code])
                return
        return event

    def _background(self):
        while True:
            ready, _, _ = select.select([self._master], [], [])
            for stream in ready:
                value = ""
                while True:
                    try:
                        data = os.read(stream, 102400)
                        data = data.decode("utf8", "replace")
                        value += data
                    # Python 2 and 3 raise different exceptions when they would block
                    except Exception:
                        with self._lock:
                            value = value.split("\n")
                            if len(self.value) > 0:
                                value = self.value[:-1] + ["".join([self.value[-1].raw_text, value[0]])] + value[1:]
                            self.value = value[-self._h:]
                            cursor = self.value[-1:][0]._cursor
                            if cursor > 0:
                                self._column -= cursor
                            self._frame.screen.force_update()
                        break

class DemoFrame(Frame):
    def __init__(self, screen):
        super(DemoFrame, self).__init__(screen, screen.height, screen.width)

        # Create the widgets for the demo.
        layout = Layout([100], fill_frame=True)
        self.add_layout(layout)
        layout.add_widget(Terminal(Widget.FILL_FRAME))
        self.fix()
        self.set_theme("monochrome")

def demo(screen, scene):
    screen.play([Scene([
        Background(screen),
        DemoFrame(screen)
    ], -1)], stop_on_resize=True, start_scene=scene, allow_int=True)


last_scene = None
while True:
    try:
        Screen.wrapper(demo, catch_interrupt=False, arguments=[last_scene])
        sys.exit(0)
    except ResizeScreenError as e:
        last_scene = e.scene