import curses
from time import sleep

class Pen:
    def __init__(self):
        self.screen = curses.initscr()
        self.x = 0
        self.y = 0

    def up(self, c=1):
        self.y -= c

    def down(self, c=1):
        self.y += c

    def left(self, c=1):
        self.x -= c

    def right(self, c=1):
        self.x += c

    def top(self):
        self.y = 0

    def reset(self):
        self.top()
        self.clear()

    def clear(self, height=None):
        max_height, max_width = self.screen.getmaxyx()

        # use max heigth by default
        if (height == None or height > (max_height - 1)):
            height = (max_height - 1) - self.y
       
        # draw the empty line
        if height == 1:
            self.screen.addstr(self.y, 0, ' ' * (max_width-1))
        else:
            # clear multiple lines one
            for _ in range(height):
                self.clear(1)
                self.down()
             
            # reset the pen
            self.up(height)

    def write(self, text, color=None):
        max_height, max_width = self.screen.getmaxyx()
        
        # if the pen has gone off the screen, reset it to the top
        if self.y >= max_height-1:
            self.reset()

        if color == None:
            self.screen.addstr(self.y, self.x, text)
        else:
            self.screen.addstr(self.y, self.x, text, color)

    def prompt(self, text="", is_valid=lambda _: True, exit_key="\n", entered=""):
        key = ""

        while not (key == exit_key):
            # update screen
            self.clear(1)
            self.write(text + entered)
            key = self.screen.getkey()

            if key == "\x7f": # suppert delete
                # remove from memory
                entered = entered[:-1]
                # remove from screen
                self.screen.addstr(self.y, len(entered), ' ')
            elif key == exit_key:
                if not is_valid(entered):
                    self.down()
                    self.clear(1)
                    self.write(f"'{entered}' is invalid, try again")
                    self.up()
            else:
                entered += key
        return entered

    def chooseOne(self, text, options, selected=[]):
        selection = self.choose(text, options, selected, 1)
        return selection[0] if selection else self.chooseOne(text, options, selected)

    def choose(self, text, options, selected=[], maxCount=0):
        self.clear(1)
        self.write(text)

        # remove outdated selections
        selected = list(filter(lambda item: item in options, selected))
        
        # select the option
        highlighted_idx = 0
        key = ''
        self.right()
        while key != '\n':
            # draw the options
            for option_idx, option in enumerate(options):
                self.down()

                # create indentation
                self.clear(1)
                self.write(">", curses.A_DIM)
                self.right(2)

                # draw the option
                if option in selected and option_idx == highlighted_idx:
                    self.write(f"{option}", curses.A_DIM | curses.A_REVERSE)
                elif option in selected:
                    self.write(f"{option}", curses.A_REVERSE)
                elif option_idx == highlighted_idx:
                    self.write(f"{option}", curses.A_DIM)
                else:
                    self.write(f"{option}")
                    
                self.left(2)
            
            # prompt for input
            key = self.screen.getkey()
            option = options[highlighted_idx]
            if key == ' ':
                if option in selected:
                    selected.remove(option)
                else:
                    selected.append(option)
                    if maxCount == len(selected):
                        break

            elif key == 'j':
                highlighted_idx += 1
                highlighted_idx = highlighted_idx % len(options)
            elif key == 'k':
                highlighted_idx -= 1
                highlighted_idx = highlighted_idx % len(options)

            # reset the line
            self.up(len(options))
        self.left()
        self.down(len(options) + 1)
        return selected

if __name__ == '__main__':
    pen = Pen()
    pen.write("hello world")
    sleep(1)

import curses
from time import sleep

class Pen:
    def __init__(self):
        self.screen = curses.initscr()
        self.x = 0
        self.y = 0

    def up(self, c=1):
        self.y -= c

    def down(self, c=1):
        self.y += c

    def left(self, c=1):
        self.x -= c

    def right(self, c=1):
        self.x += c

    def top(self):
        self.y = 0

    def reset(self):
        self.top()
        self.clear()

    def clear(self, height=None):
        max_height, max_width = self.screen.getmaxyx()

        # use max heigth by default
        if (height == None or height > (max_height - 1)):
            height = (max_height - 1) - self.y
       
        # draw the empty line
        if height == 1:
            self.screen.addstr(self.y, 0, ' ' * (max_width-1))
        else:
            # clear multiple lines one
            for _ in range(height):
                self.clear(1)
                self.down()
             
            # reset the pen
            self.up(height)

    def write(self, text, color=None):
        max_height, max_width = self.screen.getmaxyx()
        
        # if the pen has gone off the screen, reset it to the top
        if self.y >= max_height-1:
            self.reset()

        if color == None:
            self.screen.addstr(self.y, self.x, text)
        else:
            self.screen.addstr(self.y, self.x, text, color)

    def prompt(self, text="", is_valid=lambda _: True, exit_key="\n", entered=""):
        key = ""

        while not (key == exit_key):
            # update screen
            self.clear(1)
            self.write(text + entered)
            key = self.screen.getkey()

            if key == "\x7f": # suppert delete
                # remove from memory
                entered = entered[:-1]
                # remove from screen
                self.screen.addstr(self.y, len(entered), ' ')
            elif key == exit_key:
                if not is_valid(entered):
                    self.down()
                    self.clear(1)
                    self.write(f"'{entered}' is invalid, try again")
                    self.up()
            else:
                entered += key
        return entered

    def chooseOne(self, text, options, selected=[]):
        selection = self.choose(text, options, selected, 1)
        return selection[0] if selection else self.chooseOne(text, options, selected)

    def choose(self, text, options, selected=[], maxCount=0):
        self.clear(1)
        self.write(text)

        # remove outdated selections
        selected = list(filter(lambda item: item in options, selected))
        
        # select the option
        highlighted_idx = 0
        key = ''
        self.right()
        while key != '\n':
            # draw the options
            for option_idx, option in enumerate(options):
                self.down()

                # create indentation
                self.clear(1)
                self.write(">", curses.A_DIM)
                self.right(2)

                # draw the option
                if option in selected and option_idx == highlighted_idx:
                    self.write(f"{option}", curses.A_DIM | curses.A_REVERSE)
                elif option in selected:
                    self.write(f"{option}", curses.A_REVERSE)
                elif option_idx == highlighted_idx:
                    self.write(f"{option}", curses.A_DIM)
                else:
                    self.write(f"{option}")
                    
                self.left(2)
            
            # prompt for input
            key = self.screen.getkey()
            option = options[highlighted_idx]
            if key == ' ':
                if option in selected:
                    selected.remove(option)
                else:
                    selected.append(option)
                    if maxCount == len(selected):
                        break

            elif key == 'j':
                highlighted_idx += 1
                highlighted_idx = highlighted_idx % len(options)
            elif key == 'k':
                highlighted_idx -= 1
                highlighted_idx = highlighted_idx % len(options)

            # reset the line
            self.up(len(options))
        self.left()
        self.down(len(options) + 1)
        return selected

if __name__ == '__main__':
    pen = Pen()
    pen.write("hello world")
    sleep(1)

# use max heigth by default # draw the empty line # clear multiple lines one # reset the pen # if the pen has gone off the screen, reset it to the top # update screen # suppert delete # remove from memory # remove from screen # remove outdated selections # select the option # draw the options # create indentation # draw the option # prompt for input # reset the line