##
# @package terminal
# @brief   Very simple terminal emulator
# @author  Henry Daitx
# @date    Apr 2017
# @copyright Racelogic Ltd., 2017
#

"""
This module supplies a simple terminal emulator. The only available control
character is newline (0x0a).
"""

class Terminal:
    """
    Create a terminal object.

        Parameters:
            x   window top-left x coordinate [pixels]
            y   window top-left y coordinate [pixels]
            w   window width [chars]
            h   window height [chars]
        Optional parameters:
            zoom    magnification level (default 1)
            mempos  position of the frame buffer in the FT8xx's RAM_G
            transp  use transparent background (default False)
    """
    def __init__(self, x, y, w, h, zoom=1, mempos=0, transp=False):
        import ft8xx as ft
        import gui
        self.x = x
        self.y = y
        self.w = int(w)
        self.h = int(h)
        self.zoom = zoom
        self.line_len_bytes = self.w << 1
        self.scr_len_bytes = (self.line_len_bytes * self.h) // self.zoom
        if transp:
            blend_func = gui.DL_BLEND_FUNC(
                ft.BLENDFUNC_SRC_ALPHA, ft.BLENDFUNC_ONE_MINUS_SRC_ALPHA)
        else:
            blend_func = gui.DL_BLEND_FUNC(
                ft.BLENDFUNC_ONE, ft.BLENDFUNC_ZERO)
        self.gui_l = [
            gui.DL_SAVE_CONTEXT(),
            gui.DL_BITMAP_TRANSFORM_A(int(256 / self.zoom)),
            gui.DL_BITMAP_TRANSFORM_E(int(256 / self.zoom)),
            gui.DL_MACRO(0),
            blend_func,
            gui.DL_BITMAP_LAYOUT(ft.BMPLAYOUT_TEXTVGA, self.line_len_bytes, self.h),
            gui.DL_BITMAP_SIZE(
                1,
                0,
                0,
                int((self.w << 3) * self.zoom) & 0x1ff,
                int((self.h << 4) * self.zoom)),
            gui.DL_BITMAP_SIZE_H(int(self.w * self.zoom) >> 6, 0),
            gui.DL_BEGIN(ft.BITMAPS),
            gui.DL_VERTEX2F(self.x, self.y),
            gui.DL_END(),
            gui.DL_RESTORE_CONTEXT(),
        ]
        # This module uses (w * h * 4) bytes in total for the buffers
        self.origin = ft.RAM_G + mempos
        # Clear buffers
        ft.cpcmd_memzero(self.origin, self.scr_len_bytes << 1)
        self.pos = 0
        # Set window origin
        self.win_origin = 0
        ft.wr32(ft.REG_MACRO_0, gui.DL_BITMAP_SOURCE(self.origin + self.win_origin))
        self.attr = 0x07
        # Draw cursor (green block)
        ft.wr16(self.origin + self.pos, 0x2000)

    def move(self, x=None, y=None, zoom=None):
        """
        Move terminal window and modify zoom level.

            Optional parameters:
                x       window top-left x coordinate [pixels]
                y       window top-left y coordinate [pixels]
                zoom    magnification level
        """
        import gui
        if x is not None:
            self.x = x
        if y is not None:
            self.y = y
        if zoom is not None:
            self.zoom = zoom
        self.gui_l[1] = gui.DL_BITMAP_TRANSFORM_A(int(256 / self.zoom))
        self.gui_l[2] = gui.DL_BITMAP_TRANSFORM_E(int(256 / self.zoom))
        self.gui_l[6] = gui.DL_BITMAP_SIZE(
            1,
            0,
            0,
            int((self.w << 3) * self.zoom) & 0x1ff,
            int((self.h << 4) * self.zoom))
        self.gui_l[7] = gui.DL_BITMAP_SIZE_H(int(self.w * self.zoom) >> 6, 0)
        self.gui_l[9] = gui.DL_VERTEX2F(self.x, self.y)
        gui.redraw()

    def set_attr(self, attr=0x07):
        """
        Set character attributes (colors).

            Optional parameters:
                attr    attribute (one byte, upper nibble is background color,
                        lower nibble is foreground color) (default 0x07: black
                        background, white foreground)

        Color nibble:
            bit 0  blue
            bit 1  green
            bit 2  red
            bit 3  bright
        """
        self.attr = attr

    def gui_list(self):
        """
        Return the GUI list for drawing the window.
        """
        return self.gui_l

    def frame_switch(self, open_newline=False):
        """
        Switch frame buffer. Normally does not need to be called directly by the
        user.

            Optional parameters:
                open_newline    clear room for a newline at the bottom (bool,
                                default False)
        """
        import ft8xx as ft
        import gui
        orig = self.origin
        scrlen = self.scr_len_bytes
        linelen = self.line_len_bytes
        if self.win_origin < scrlen:
            raise Exception('cannot frame switch')
        if open_newline:
            # Copy whole screen but the first line and open a newline at the
            # bottom
            ft.cpcmd_memcpy(orig, orig + self.win_origin + linelen, scrlen - linelen)
            self.pos -= scrlen + linelen
        else:
            # Copy whole screen
            ft.cpcmd_memcpy(orig, orig + self.win_origin, scrlen)
            self.pos -= scrlen
        ft.wr32(ft.REG_MACRO_0, gui.DL_BITMAP_SOURCE(orig))
        # Clear buffer after current position
        ft.cpcmd_memzero(orig + self.pos, (self.scr_len_bytes << 1) - self.pos)
        self.win_origin = 0

    def scroll(self):
        """
        Scroll screen one line down.
        """
        import ft8xx as ft
        import gui
        orig = self.origin
        scrlen = self.scr_len_bytes
        linelen = self.line_len_bytes
        if self.win_origin >= scrlen:
            self.frame_switch(open_newline=True)
        else:
            self.win_origin += linelen
            ft.wr32(ft.REG_MACRO_0, gui.DL_BITMAP_SOURCE(orig + self.win_origin))

    def newline(self):
        """
        Set a newline (position cursor at start of new line, scroll if needed).
        """
        win_end = self.win_origin + self.scr_len_bytes
        if win_end - self.line_len_bytes <= self.pos < win_end:
            self.scroll()
        linelen = self.line_len_bytes
        self.pos += linelen - (self.pos % linelen)

    def write(self, s, blockwrite=True):
        """
        Write a string to the terminal.

            Parameters:
                s           string to be shown
            Optional parameters:
                blockwrite  use a block writing algorithm -the default- or write
                            to the frame buffer character by character (bool)
        """
        import ft8xx as ft
        attr = self.attr
        scrlen = self.scr_len_bytes
        linelen = self.line_len_bytes
        if not s:
            return
        if not isinstance(s, str):
            raise TypeError('only str can be printed')
        # Erase cursor
        ft.wr16(self.origin + self.pos, attr << 8)
        # Filter only useful chars
        s = ''.join([x for x in s if (x == '\n' or ord(x) >= 32)])
        if blockwrite:
            # Split by lines and show one at a time
            ls = s.split('\n')
            for li, l in enumerate(ls):
                if len(l) > (scrlen >> 1):
                    l = l[-(scrlen >> 1):]
                if self.pos + (len(l) << 1) > self.origin + (scrlen << 1):
                    # This write will cause a frame switch, so preempt it
                    self.win_origin = self.origin + scrlen
                    self.frame_switch()
                final_l = []
                for c in l:
                    final_l.append(ord(c))
                    final_l.append(attr)
                ft.wrbuf(self.origin + self.pos, bytearray(final_l))
                self.pos += len(l) << 1
                while self.pos >= (self.win_origin + scrlen) - self.line_len_bytes:
                    self.scroll()
                if li < len(ls) - 1:
                    self.newline()
        else:
            # Simpler (slower) char-by-char writing
            for c in s:
                if c == '\n':
                    self.newline()
                else:
                    ft.wr16(self.origin + self.pos, ord(c) | (attr << 8))
                    self.pos += 2
                if self.pos >= self.win_origin + scrlen:
                    self.scroll()
        # Draw cursor (green block)
        ft.wr16(self.origin + self.pos, 0x2000)


class NullTerminal:
    def __init__(self, *args, **kwargs):
        pass
    def move(self, *args, **kwargs):
        pass
    def set_attr(self, *args, **kwargs):
        pass
    def gui_list(self, *args, **kwargs):
        import gui
        return [gui.DL_NOP()]
    def frame_switch(self, *args, **kwargs):
        pass
    def scroll(self, *args, **kwargs):
        pass
    def newline(self, *args, **kwargs):
        pass
    def write(self, *args, **kwargs):
        pass

if __name__ == "__main__":
    import gui
    term = Terminal(0, 0, 100, 30, 1)
    gui.show([term.gui_l])