import tklib.tkimport as imp
tk  = imp.import_lib("tkinter", stop_by_error = False)
from tkinter import ttk
from tkinter import simpledialog
imp.messages(stop_by_error = True)

from tklib.tkobject import tkObject
from tklib.tkutils import print_data, pint, pfloat, index2val, pconv_by_type
from tklib.tkparams import tkParams
from tklib.tkgui.tktkinter import PathFrame


def get_window_from_plt(plt):
    return plt.get_current_fig_manager().window

def show_varibles(config):
    config.print_parameters(heading = "config:")
    for key in dir(config):
        if key.startswith("_"): continue
        
        val = getattr(config, key, None)
        if val is None or callable(val): continue
        t = type(val)
        if t is list or t is tuple: continue

        print(f"key={key}  val={val}  type(val)={type(val)}")

def update_variables(config, vars):
    '''
    add_widgetsで追加した[String|Int|Double]Variable変数は、configオブジェクトの var_{varname}に入っている。
    この関数では、configのvar_{varname}変数から.get()で値を取得し、varsの {varname} 変数に設定する
    '''
    
    for key in dir(config):
        if not key.startswith("var_"): continue

        widget = getattr(config, key, None)
        if widget is None or callable(widget): continue

        var_key = key[4:]
        var = getattr(vars, var_key, None)
        if var is None: continue

        setattr(vars, key[4:], widget.get())


class tkScrolledListbox(tk.Listbox):
    def __init__(self, master, **key):
        self.frame = tk.Frame(master)
        self.yscroll = tk.Scrollbar(self.frame, orient = tk.VERTICAL)
        self.yscroll.pack(side = tk.RIGHT, fill = tk.Y, expand = 1)
        key['yscrollcommand'] = self.yscroll.set
        tk.Listbox.__init__(self, self.frame, **key)
        self.yscroll.config(command = self.yview)

        self.pack(side = tk.LEFT, fill = tk.BOTH, expand = 1)

    def clear(self):
        self.delete(0, tk.END)

def add_widgets(parent, widgets, vars = None, config = None, **kwargs):
    ''' 
    input: 多次元リストでwidgetsの定義を与える
    output: top_frame: widgetsを全て含めたframe
            config: frameのリスト、widgetオブジェクトのリスト、メンバ変数を含むconfigオブジェクト
            varsが与えられている場合、varsのメンバ変数の値がwidgetに入れられる
    '''
    
    if config is None:
        config = tkParams()
    if vars is None:
        vars = config

    config.frames = []
    top_frame = tk.Frame(parent)
    top_frame.pack(**kwargs)

    for widget_list in widgets:
            frame = tk.Frame(top_frame)
            frame.pack(**kwargs)
            config.frames.append(frame)

            for inf in widget_list:
#                print("type(inf)=", type(inf))
                if type(inf) is not dict:
                    print("Error in tksimple_gui.add_widgets: Contens of widget_list (inf) must be dict:", inf)
                    exit()

                t       = inf["type"]
                wt      = inf.get("widget_type", "widget")

                varname = inf.get("varname", None)
                variable = None
                if varname:
                   variable = getattr(config, f"var_{varname}", None)
                vartype = inf.get("vartype", "str")
                val = None
                if varname:
                    val = getattr(vars, varname, None)
                    if val is None:
                        val = inf.get("value", None)

                label   = inf.get("label", "no text")
                text    = inf.get("text", "no text")

                width   = inf.get("width", None)
                height  = inf.get("height", None)

                command = inf.get("command", None)

                side    = inf.get("side", "left")
                anchor  = inf.get("anchor", None)

                expand  = inf.get("expand", None)
                fill    = inf.get("fill", None)

                w = None

                if t == "widget":
                    name = inf.get("name", "widget")
                    w  = inf.get("widget", None)
                    if w is None: continue

                    setattr(config, f"{wt}_{name}", w)

                elif t == "button":
                    name = inf.get("name", "button")

                    w = tk.Button(frame, text = text, command = command)

                    setattr(config, f"button_{name}", w)

                elif t == "label":
                    name = inf.get("name", "label")
                    if varname is not None and variable is None:
                        variable = tk.StringVar(value = sel)
                    elif variable:
                        variable.set(sel)

                    w = tk.Label(frame, text = label, textvariable = variable)
                    setattr(config, f"label_{name}", w)
                    if variable:
                        setattr(config, f"var_{varname}", variable)

                elif t == "entry":
#                    name = inf.get("name", "entry")

                    if variable is None:
                        if vartype == "str":
                            variable = tk.StringVar(value = val, name = varname)
                        elif vartype == "float":
                            variable = tk.DoubleVar(value = val, name = varname)
                        elif vartype == "int":
                            variable = tk.IntVar(value = val, name = varname)
                        elif vartype == "bool":
                            variable = tk.BooleanVar(value = val, name = varname)
                        else:
                            print(f"Error in tksimple_gui::add_widgets(): Invalid variable type [{vartype}]")
                            exit()
                    else:
                        variable.set(val)

                    w = tk.Entry(frame, textvariable = variable, width = width, state = inf.get("state", None))
#                    if val is not None:
#                        w.insert(0, str(val))

                    setattr(config, f"entry_{varname}", w)
                    setattr(config, f"var_{varname}", variable)

                elif t == "listbox":
                    options = inf.get("options", [])
                    sel = inf.get("sel", None)
                    if variable is None:
                        variable = tk.StringVar(value = options)

#                    w = tk.Listbox(frame, listvariable = variable, width = width, height = height)
                    w = tkScrolledListbox(frame, listvariable = variable, width = width, height = height)
                    w.frame.pack(side = side, anchor = anchor, expand = expand, fill = fill)
                    if sel is not None:
                        w.selection_set(sel)
                        w.see(sel)

                    setattr(config, f"listbox_{varname}", w)
                    setattr(config, f"var_{varname}", variable)

                elif t == "combobox":
                    options = inf.get("options", [])
                    sel = inf.get("sel", None)

                    w = ttk.Combobox(frame, values = options, width = width)
                    if sel is not None:
                        idx = pint(sel, None)
                        if idx is not None:
                            w.set(options[idx])
                        else:
                            w.set(sel)

                    setattr(config, f"combobox_{varname}", w)
                    setattr(config, f"var_{varname}", w)#variable)

                elif t == "checkbox":
                    value = inf.get("value", None)
                    text = inf.get("text", "no text")

                    if variable is None:
                        variable = tk.BooleanVar(value = value)
                    else:
                        variable.set(value)

                    w = tk.Frame(frame)
                    cb = tk.Checkbutton(w, variable = variable, width = width, height = height, onvalue = True, offvalue = False)
                    cb.pack(side = tk.LEFT, anchor = 'w')

                    label = tk.Label(w, text = text)
                    label.pack(side = tk.LEFT, anchor = 'w')

                    setattr(config, f"combobox_{varname}", cb)
                    setattr(config, f"var_{varname}", variable)

                elif t == "radiobutton":
                    options = inf.get("options", [])
                    sel = inf.get("sel", None)

                    if variable is None:
                        variable = tk.IntVar(value = sel)
                    else:
                        variable.set(sel)
                    
                    w = tk.Frame(frame)
                    for idx, s in enumerate(options):
                        rb = tk.Radiobutton(frame, text = s, value = idx, variable = variable)
                        rb.pack(side = "top", anchor = 'w')

                    setattr(config, f"combobox_{varname}", w)
                    setattr(config, f"var_{varname}", variable)

                elif t == "path":
                    save_dialog = inf.get("save_dialog", False)
                    file_types  = inf.get("file_types", [("all", "*")])
                    initial_dir = inf.get("initial_dir", ".")
                    title       = inf.get("title", "Choose file")

                    def path_button_click(entry_variable, save_dialog = False, file_types = [("all", "*")], initial_dir = None, title = "Choose file"):
                        if save_dialog:
                            selpath = tk.filedialog.asksaveasfilename(filetypes = file_type, initialdir = initial_dir, title = title)
                        else:
                            selpath = tk.filedialog.askopenfilename(filetypes = file_types, initialdir = initial_dir, title = title)

                        if selpath is not None and selpath != '':
                            entry_variable.set(selpath)
                            entry_variable.prev_val = selpath

                    if command is None:
                        command = lambda: path_button_click(variable, save_dialog = save_dialog,
                                                file_types = file_types, initial_dir = initial_dir, title = title)

                    w = tk.Frame(frame)
                    lbl = tk.Label(w, text = label)
                    lbl.pack(side = 'left', anchor = 'w')

                    if variable is None:
                        variable = tk.StringVar(value = val, name = varname)
                    else:
                        variable.set(val)

                    et = tk.Entry(w, textvariable = variable, width = width)
                    et.pack(side = 'left', anchor = 'w', expand = True, fill = 'x')

                    btn = tk.Button(w, text = "path", command = command)
                    btn.pack(side = 'left', anchor = 'w')

                    setattr(config, f"entry_{varname}", et)
                    setattr(config, f"var_{varname}", variable)

                else:
                    print(f"Error in tksimple_gui::add_widgets(): Invalid widget type [{t}]")
                    exit()

                if w:
                    w.pack(side = side, anchor = anchor, expand = expand, fill = fill)

    return top_frame, config

class CustomDialog_with_config(simpledialog.Dialog):
        """
        A custom dialog class that extends the simpledialog.Dialog class using config.
        
        Args:
            master: The parent widget for the dialog.
            config: An object representing the configuration.
              config.widgets: A list of widgets to display in the dialog.
                The list is a list of dictionalies of widget configurations,
                which refer to the variable names, types and values in vars.
            vars: An object of variables.
            title: The title of the dialog (optional).
        """
        def __init__(self, master, config, vars, title = None):
            self.config = config
            self.vars = vars
            config.ret = None
            super().__init__(parent=master, title=title)

        def body(self, master):
            frame, config = add_widgets(master, self.config.widgets, self.vars, self.config)
            frame.pack(expand = True, fill = "both")
#            vars(self.config).update(vars(config))
            user_defined_vars = [attr for attr in dir(config) if not attr.startswith('__')]
            for key in user_defined_vars:
                setattr(self.config, key, getattr(config, key, None))

        def apply(self):
            for widget_list in self.config.widgets:
                for inf in widget_list:
                    if inf["type"] == "entry":
                        varname = inf["varname"]
                        var  = getattr(self.vars, varname, None)
                        widget = getattr(self.config, f"entry_{varname}", None)
                        if var is not None and widget:
                            val = pconv_by_type(widget.get(), type = type(var), defval = var, strict = False)
                            print(f"set {val} to {varname}")
                            setattr(self.vars, varname, val)

            self.config.ret = True

class tkWidgets(tkObject):
    def __init__(self, parent = None, plt = None, config = None, vars = None):
        if config is None:
            config = tkParams()
        if vars is None:
            vars = tkParams()

        self.config = config
        self.vars   = vars
        self.parent = parent
        self.plt    = plt

    def set_font(self, family = "Helvetica", size = 10, weight = "normal"):
        self.default_font = tk.font.Font(family = family, size = size, weight = weight)
        self.parent.option_add("*Font", self.default_font)

    def add_toolbar(self, parent = None):
        if parent is None: parent = self.parent
        self.tool_bar = tk.Frame(parent)
        self.tool_bar.pack(side = "top", anchor = "nw", expand = False, fill = "none")
        return self.tool_bar

    def add_paned_window(self, parent = None, width = None, sashwidth = 2):
        if parent is None: parent = self.parent

        pane = tk.PanedWindow(parent, sashwidth = sashwidth)
        pane.pack(side = "top", expand = True, fill = "both")

        left_notebook  = tk.ttk.Notebook(pane, width = width)
        right_notebook = tkinter.ttk.Notebook(pane)
        pane.add(left_notebook)#,  width = left_pane_width)
        pane.add(right_notebook)#, width = right_pane_width)

        self.main_pane      = pane
        self.left_notebook  = left_notebook
        self.right_notebook = right_notebook

        return self.main_pane, self.left_notebook, self.right_notebook

    def add_tab(self, parent = None):
        if parent is None: parent = self.parent

        notebook  = tk.ttk.Notebook(parent)
#            main_pane  = tkinter.PanedWindow(root, sashwidth = sashwidth)
        notebook.pack(side = "top", expand = True, fill = "both")

        self.notebook = notebook

        return self.notebook

    def add_page(self, notebook = None, title = "main", bg = 'dim gray'):
        if notebook is None: notebook = self.notebook

        page_frame = tk.Frame(notebook, bg = bg)
        page_frame.pack()
        notebook.add(page_frame, text = title)
        return page_frame

    def get_window_from_plt(self, plt):
        self.plt = plt
        return plt.get_current_fig_manager().window

    def add_widgets(self, parent, widgets, vars = None, config = None, **kwargs):
        if config is None:
            config = self.config
        if vars is None:
            vars = self.vars

        return add_widgets(parent, widgets, vars = vars, config = config, **kwargs)

    def show_varibles(self, vars, heading = None):
        if heading:
            print(heading)

        return show_varibles(vars)

    def update_variables(self, config = None, vars = None):
        if config is None:
            config = self.config
        if vars is None:
            vars = self.vars

        return update_variables(config, vars)

