"""
Custom ListBox for customtkinter
Author: Akash Bora
"""

import customtkinter

class CTkListbox(customtkinter.CTkScrollableFrame):

    def __init__(self,
                 master: any,
                 height: int = 100,
                 width: int = 200,
                 hightlight_color: str = "default",
                 fg_color: str = "transparent",
                 bg_color: str = None,
                 text_color: str = "default",
                 select_color: str = "default",
                 hover_color: str = "default",
                 border_width: int = 3,
                 font: tuple = "default",
                 multiple_selection: bool = False,
                 listvariable = None,
                 hover: bool = True,
                 command = None,
                 justify = "left",
                 **kwargs):

        super().__init__(master, width=width, height=height, fg_color=fg_color, border_width=border_width, **kwargs)
        self._scrollbar.grid_configure(padx=(0,border_width))
        self._scrollbar.configure(width=12)

        if bg_color:
            super().configure(bg_color=bg_color)

        self.select_color = customtkinter.ThemeManager.theme["CTkButton"]["fg_color"] if select_color=="default" else select_color
        self.text_color = customtkinter.ThemeManager.theme["CTkButton"]["text_color"] if text_color=="default" else text_color
        self.hover_color = customtkinter.ThemeManager.theme["CTkButton"]["hover_color"] if hover_color=="default" else hover_color
        self.font = (customtkinter.ThemeManager.theme["CTkFont"]["family"],13) if font=="default" else font
        if justify=="left":
            self.justify = "w"
        elif justify=="right":
            self.justify = "e"
        else:
            self.justify = "c"
        self.buttons = {}
        self.command = command
        self.multiple = multiple_selection
        self.selected = None
        self.hover = hover
        self.end_num = 0
        self.selections = []

        if listvariable:
            self.listvariable = listvariable
            self.listvariable.trace_add('write', lambda a,b,c: self.update_listvar())
            self.update_listvar()

        super().bind("<Destroy>", lambda e: self.unbind_all("<Configure>"))

    def update_listvar(self):
        values = list(eval(self.listvariable.get()))
        self.delete("all")
        for i in values:
            self.insert("END", option=i)

    def select(self, index):
        """ select the option """
        for options in self.buttons.values():
            options.configure(fg_color="transparent")

        if self.multiple:
            if self.buttons[index] in self.selections:
                self.selections.remove(self.buttons[index])
                self.buttons[index].configure(fg_color="transparent", hover=False)
                self.after(100, lambda: self.buttons[index].configure(hover=self.hover))
            else:
                self.selections.append(self.buttons[index])
            for i in self.selections:
                i.configure(fg_color=self.select_color, hover=False)
                self.after(100, lambda button=i: button.configure(hover=self.hover))
        else:
            self.selected = self.buttons[index]
            self.buttons[index].configure(fg_color=self.select_color, hover=False)
            self.after(100, lambda: self.buttons[index].configure(hover=self.hover))

        if self.command:
            self.command(self.get())

    def activate(self, index):
        if str(index).lower()=="all":
            if self.multiple:
                for i in self.buttons:
                    self.select(i)
            return
        selected = list(self.buttons.keys())[index]
        self.select(selected)

    def curselection(self):
        index = 0
        if self.multiple:
            indexes = []
            for i in self.buttons.values():
                if i in self.selections:
                    indexes.append(index)
                index +=1
            return tuple(indexes)

        else:
            for i in self.buttons.values():
                if i==self.selected:
                    return index
                else:
                    index +=1

    def bind(self, key, func):
        super().bind_all(key, lambda e: func(self.get()), add="+")

    def deselect(self, index):
        if not self.multiple:
            self.selected.configure(fg_color="transparent")
            self.selected = None
            return
        if self.buttons[index] in self.selections:
            self.selections.remove(self.buttons[index])
            self.buttons[index].configure(fg_color="transparent")

    def deactivate(self, index):
        if str(index).lower()=="all":
            for i in self.buttons:
                self.deselect(i)
            return
        selected = list(self.buttons.keys())[index]
        self.deselect(selected)

    def insert(self, index, option, keybind=None ,func=None, **args):
        """ add new option in the listbox """

        if str(index).lower()=="end":
            index = f"END{self.end_num}"
            self.end_num +=1

        if index in self.buttons:
            self.buttons[index].destroy()

        self.buttons[index] = customtkinter.CTkButton(self, text=option, fg_color="transparent", anchor=self.justify,
                                                      text_color=self.text_color, font=self.font,
                                                      hover_color=self.hover_color, **args)
        self.buttons[index].configure(command=lambda num=index: self.select(num))
        self.buttons[index].pack(padx=0, pady=(0,5), fill="x", expand=True)

        if keybind:
            self.buttons[index].bind(keybind, func)

    def delete(self, index, last=None):
        """ delete options from the listbox """

        if str(index).lower()=="all":
            for i in self.buttons:
                self.buttons[i].destroy()
            self.buttons = {}
            self.end_num = 0
            return

        if str(index).lower()=="end":
            index = f"END{self.end_num}"
            self.end_num -=1
        else:
            if int(index)==len(self.buttons):
                index = len(self.buttons)-1
            if int(index)>len(self.buttons):
                return
            if not last:
                index = list(self.buttons.keys())[int(index)]

        if last:
            if str(last).lower()=="end":
                last = len(self.buttons)-1
            elif int(last)>=len(self.buttons):
                last = len(self.buttons)-1

            deleted_list = []
            for i in range(index, int(last)+1):
                list(self.buttons.values())[i].destroy()
                deleted_list.append(list(self.buttons.keys())[i])
            for i in deleted_list:
                del self.buttons[i]
        else:
            self.buttons[index].destroy()
            del self.buttons[index]

    def size(self):
        """ return total number of items in the listbox """
        return len(self.buttons.keys())

    def get(self, index=None):
        """ get the selected value """
        if index:
            if str(index).lower()=="all":
                return list(item.cget("text") for item in self.buttons.values())
            else:
                index = list(self.buttons.keys())[int(index)]
                return self.buttons[index].cget("text")
        else:
            if self.multiple:
                return [x.cget("text") for x in self.selections] if len(self.selections)>0 else None
            else:
                return self.selected.cget("text") if self.selected is not None else None

    def configure(self, **kwargs):
        """ configurable options of the listbox """

        if "hover_color" in kwargs:
            self.hover_color = kwargs.pop("hover_color")
            for i in self.buttons.values():
                i.configure(hover_color=self.hover_color)
        if "highlight_color" in kwargs:
            self.select_color = kwargs.pop("highlight_color")
            if self.selected: self.selected.configure(fg_color=self.select_color)
            if len(self.selections)>0:
                for i in self.selections:
                    i.configure(fg_color=self.select_color)
        if "text_color" in kwargs:
            self.text_color = kwargs.pop("text_color")
            for i in self.buttons.values():
                i.configure(text=self.text_color)
        if "font" in kwargs:
            self.font = kwargs.pop("font")
            for i in self.buttons.values():
                i.configure(font=self.font)
        if "command" in kwargs:
            self.command = kwargs.pop("command")

        super().configure(**kwargs)
