# listbox.py Extension to ugui providing the Listbox class

# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from gui.core.ugui import Widget, display
from gui.core.colors import *

dolittle = lambda *_ : None

# Behaviour has issues compared to touch displays because movement between
# entries is sequential. This can affect the choice in when the callback runs.
# It always runs when select is pressed. See 'also' ctor arg.

class Listbox(Widget):
    ON_MOVE = 1  # Also run whenever the currency moves.
    ON_LEAVE = 2  # Also run on exit from the control.

    @staticmethod
    def dimensions(writer, elements):
        entry_height = writer.height + 2 # Allow a pixel above and below text
        le = len(elements)
        height = entry_height * le + 2
        textwidth = max(writer.stringlen(s) for s in elements) + 4
        return entry_height, height, textwidth

    def __init__(self, writer, row, col, *, elements, width=None, value=0,
                 fgcolor=None, bgcolor=None, bdcolor=False, fontcolor=None, select_color=DARKBLUE,
                 callback=dolittle, args=[], also=0):

        self.entry_height, height, textwidth = self.dimensions(writer, elements)
        self.also = also
        if width is None:
            width = textwidth
        if not isinstance(value, int) or value >= len(elements):
            value = 0
        super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, value, True)
        self.cb = callback
        self.cb_args = args
        self.select_color = select_color
        self.fontcolor = fontcolor
        fail = False
        try:
            self.elements = [s for s in elements if type(s) is str]
        except:
            fail = True
        else:
            fail = len(self.elements) == 0
        if fail:
            raise ValueError('elements must be a list or tuple of one or more strings')
        self._value = value # No callback until user selects
        self.ev = value

    def show(self):
        if not super().show(False):  # Clear to self.bgcolor
            return

        length = len(self.elements)
        x = self.col
        y = self.row
        for n in range(length):
            if n == self._value:
                display.fill_rect(x, y + 1, self.width, self.entry_height - 1, self.select_color)
                display.print_left(self.writer, x + 2, y + 1, self.elements[n], self.fontcolor, self.select_color)
            else:
                display.print_left(self.writer, x + 2, y + 1, self.elements[n], self.fontcolor, self.bgcolor)
            y += self.entry_height

    def textvalue(self, text=None): # if no arg return current text
        if text is None:
            return self.elements[self._value]
        else: # set value by text
            try:
                v = self.elements.index(text)
            except ValueError:
                v = None
            else:
                if v != self._value:
                    self.value(v)
            return v

    def do_up(self, _):
        if v := self._value:
            self.value(v - 1)
        if (self.also & Listbox.ON_MOVE):  # Treat as if select pressed
            self.do_sel()

    def do_down(self, _):
        if (v := self._value) < len(self.elements) - 1:
            self.value(v + 1)
        if (self.also & Listbox.ON_MOVE):
            self.do_sel()

    # Callback runs if select is pressed. Also (if ON_LEAVE) if user changes
    # list currency and then moves off the control. Otherwise if we have a
    # callback that refreshes another control, that second control does not
    # track currency.
    def do_sel(self): # Select was pushed
        self.ev = self._value
        self.cb(self, *self.cb_args)

    def enter(self):
        self.ev = self._value  # Value change detection

    def leave(self):
        if (self.also & Listbox.ON_LEAVE) and self._value != self.ev:
            self.do_sel()
