import os
import subprocess
import sys
import tkinter
from tkinter import ttk
import matplotlib
matplotlib.use('TkAgg')
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


from tklib.tkutils import find_executable_path, get_os, split_file_path
from tklib.tkparams import tkParams
from tklib.tkapplication import tkApplication
import tklib.tkgui.tktkinter as tktkinter


#=========================
# Application base class
#=========================


class tkApplication_GUI(tkApplication):
    def __init__(self, usage_str = "", _globals = None, locals = None, use_user_inifile = False, **args):
        super().__init__(usage_str = usage_str, _globals = _globals, locals = locals, 
                         use_user_inifile = use_user_inifile, **args)
#        self.params      = tkParams()
#        self.argv        = sys.argv
#        self.script_path = sys.argv[0]
# tkinter widget variables
        self.tkvars = tkParams()
#        self.update(**args)

    def __del__(self):
        super().__del__()
#        pass

    def __str__(self):
        return self.ClassPath()

    def get_tkvars_dict(self):
        return self.tkvars.get_param_dict()

    def get_tkvar(self, key, defval = None):
        if self.tkvars.get(key, None) is None:
            setattr(self.tkvars, key, defval)
#            self.tkvars[key] = defval
        return self.tkvars.get(key)

    def mainloop(self):
        self.root_window.mainloop()
        
    def get_screen_size(self):
        return self.root_window.winfo_screenwidth(), self.root_window.winfo_screenhight()

    def get_geometry(self, window = None):
        if window is None:
            window = self.root_window

        geo = window.geometry()
        if 'x' in geo:
            w, h = geo.split('x')
            if '+' in h:
               h, x0, y0 = h.split('+')
               return geo, w, h, x0, y0

        return geo, w, h, None, None

    def set_geometory(self, window = None, geo = None, w = None, h = None, x0 = None, y0 = None):
        if window is None:
            window = self.root_window

        if geo is not None:
            window.geometry(geo)
        elif h is None:
            window.geometry(w)
        elif x0 is None:
            window.geometry("{}x{}".format(w, h))
        else:
            window.geometry("{}x{}+{}+{}".format(w, h, x0, y0))
        print("set_geometory:", self.get_geomery())

    def dialog_setup(self, title = 'Setup', parent = None, 
                    entry_width = 30, button_width = 4, edit_button_width = 2, shell_button_width = 0,
                    widgets = 'editor_path|confirm_on_exit|debug_mode',
                    font_size = 10,
                    is_print = False):
        if parent is None:
            parent = self.root_window
        tktkinter.tkSetupDialog(parent, app = self, title = title, 
                entry_width = entry_width, button_width = button_width, edit_button_width = edit_button_width, 
                shell_button_width = shell_button_width, 
                widgets = widgets,
                font_size = font_size,
                is_print = is_print)

    def on_closing(self, do_confirm = None):
        self.root_window_geometry = self.root_window.geometry()
        if do_confirm is None:
            do_confirm = self.configparams.get('confirm_on_exit', True)
        print("")
        print("on_closing:")
        print("  geometry    :", self.root_window_geometry)
#        print("  confirm_on_exit:", self.configparams.confirm_on_exit)

        try:
            panes = self.main_pane.panes()
#            self.left_pane_width  = self.main_pane.paneconfig(panes[0], cnf = 'width')
#            self.left_pane_height = self.main_pane.paneconfig(panes[0], cnf = 'height')
        except:
            pass
        
        if not do_confirm:
#            print("  confirm?(1):", do_confirm, type(do_confirm))
            self.root_window.destroy()
        else:
#            print("  confirm?(2):", do_confirm, type(do_confirm))
            if tktkinter.dialog_okcancel(self, "Quit", "Do you want to quit?"):
                self.root_window.destroy()

    def PanedWindow1(self, main_pane_args = {}):
        main_pane  = tkinter.PanedWindow(self.root_window, 
                        sashwidth = main_pane_args.get('sashwidth', 2)
                        )

#        left_notebook  = tkinter.ttk.Notebook(main_pane)
#        main_pane.add(left_notebook)#,  width = left_pane_width)

        main_pane.pack(expand = True, fill = tkinter.BOTH, side = tkinter.TOP)

        self.main_pane  = main_pane

#        return main_pane, left_notebook
        return main_pane

    def PanedWindow2(self, main_pane_args = {}, left_pane_args = {}, right_pane_args = {}):
        main_pane  = tkinter.PanedWindow(self.root_window, 
                        sashwidth = main_pane_args.get('sashwidth', 2)
                        )
        width = left_pane_args.get('width', None)
        if width is None:
            width = 300
#        left_notebook  = tkinter.Frame(main_pane, width = width)
        left_notebook  = tkinter.ttk.Notebook(main_pane, 
                        width = width
                        )
#    left_notebook.pack(expand = True, fill = tkinter.BOTH)

        width = right_pane_args.get('width', None)
        if width is None:
            width = 300
#        right_notebook  = tkinter.Frame(main_pane, width = width)
        right_notebook = tkinter.ttk.Notebook(main_pane, 
                        width = width
                        )
#    right_notebook.pack()

        main_pane.add(left_notebook)#,  width = left_pane_width)
        main_pane.add(right_notebook)#, width = right_pane_width)
        main_pane.pack(expand = True, fill = tkinter.BOTH, side = tkinter.TOP)

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

        return [main_pane, left_notebook, right_notebook]

    def add_root_panes(self, root = None, type = '', sashwidth = 2, left_pane_width = 50):
        if root is None:
            root = self.root_window

        if type == 'pane1':
            main_pane  = tkinter.ttk.Notebook(root)
#            main_pane  = tkinter.PanedWindow(root, sashwidth = sashwidth)
            main_pane.pack(expand = True, fill = tkinter.BOTH, side = tkinter.TOP)
            return [main_pane]
        elif type == 'pane2':
            main_pane, left_pane, right_pane \
                = self.PanedWindow2(main_pane_args = {'sashwidth': sashwidth}, 
                               left_pane_args = {'width': left_pane_width})
            return [main_pane, left_pane, right_pane]
        elif type == '':
            return [self.root_window]

        self.terminate(f"Error in tkapplication_gui.add_root_panes(): Invalid type [{type}]")

    def move_top(f = True):
        self.root_window.attributes("-topmost", f)

    def create_subwindow(self):
        w = tkinter.Toplevel(master = self.root_window)
        tkinter.Frame(w).pack()

    def create_window(self, title = None, type = '', geometry = None, minsize = None, 
                    iconfile = None, sashwidth = 2, left_pane_width = 50, icon_path = None, 
                    relief = 'ridge', borderwidth = 2, bg = 'white', default_font = None):
        root = tkinter.Tk()
        if iconfile:
            if not os.path.isfile(iconfile):
                print(f"\nWarning: Icon file [{iconfile}] does not exist.\n")
            else:
                root.iconbitmap(default = iconfile)
# ('FixedSys', 14)
        if default_font is not None:
            root.option_add('*font', default_font)
        if geometry:
            root.geometry(geometry)
#       root.resizable(False, False)
        if minsize:
            root.minsize(*minsize)
        if title is not None:
            root.title(title)
        if icon_path:
            root.iconbitmap(default = icon_path)
        root.configure(borderwidth = borderwidth, relief = relief, bg = bg)
        root.grid()

        root.protocol("WM_DELETE_WINDOW", lambda: self.on_closing())

        self.root_window = root

        return root

    def create_menu(self, **kwargs):
# Menu
        self.menu_bar  = tkinter.Menu(self.root_window, **kwargs)
        self.root_window.config(menu = self.menu_bar)

        return self.menu_bar

    def create_toolbar(self, **kwargs):
# Tool bar
        self.tool_bar = tkinter.Frame(self.root_window, **kwargs)
        self.tool_bar.pack(side = tkinter.TOP, fill = tkinter.NONE, anchor = tkinter.NW, expand = False)

        return self.tool_bar

    def get_editor(self):
        if self.configparams.get('editor_path', None):
            return self.configparams.editor_path

        for key in ['EDITOR_PATH', 'tkEDITOR_PATH', 'EDITOR']:
            if key in os.environ:
                return os.environ[key]
                editor_paths.append(os.environ[key])
        return ''

    def print_tkvars_info(app, cparams, tkvars):
        app.print_warning("")
        app.print_warning("tkvars")
        for key in tkvars.keys():
#            print(f"tkvars[{key}]=", tkvars[key], type(tkvars[key]))
            if type(tkvars[key]) is list:
                app.print_warning(f"  {key}:", tkvars[key])
            elif tkvars[key] is None:
                pass
            elif type(tkvars[key]) is int or type(tkvars[key]) is float or type(tkvars[key]) is str:
                app.print_warning(f"  {key}:", tkvars[key])
            else:
                try:
                    app.print_warning(f"  {key}:", tkvars[key].get())
                except:
                    app.print_warning(f"  {key}:", type(tkvars[key]))

    def print_cparams_info(app, cparams, tkvars, copy_tkvars = None):
        app.print_warning("")
        app.print_warning("cparams")
        if copy_tkvars is not None:
            copy_tkvars(app, cparams, tkvars)
        cparams.print_parameters_warning()

    def print_config_info(app, config, tkvars, copy_tkvars = None):
        app.print_warning("")
        app.print_warning("config")
        if copy_tkvars is not None:
            copy_tkvars(app, config, tkvars)
        config.print_parameters_warning()

    def create_editor_frame(app, frame, 
                head_label_args  = {'text': 'Path:'}, 
                entry_width      = 60, 
                eval_button_args = [],
                show_path_select = True,
                ):
        cparams = app.get_params()
        config  = app.config
        ctkvars = app.tkvars
        tkvars  = ctkvars.get_param_dict()

        def path_button_click():
            tktkinter.path_button_click(app, tkvars['target_file'],
                                    file_type = [('all', '*.*')], ini_dir = '.')

            s = ''
            with open(tkvars['target_file'].get(), 'r') as fp:
                s = fp.read()
            tkvars['edit_text'].delete('1.0', 'end')
            tkvars['edit_text'].insert('end', s)

        if eval_button_args.get("command", None) is None:
            use_custom_command = True
        else:
            use_custom_command = False

        if show_path_select:
            entry_args      = {'textvariable': ['target_file', tkinter.StringVar()], 'width': entry_width}
            button_args     = {'text': 'path', 'width': 5, 'command': lambda: path_button_click()}
        else:
            head_label_args = {}
            entry_args      = {}
            button_args     = {}

        Editor_frame = tktkinter.Editor(app, frame,
                  frame_args       = {'bg': 'white'},
                  head_label_args  = head_label_args,
                  entry_args       = entry_args,
#                 entry_args       = {'textvariable': obj['editor'], 'width': None},
#                  button_args     = {'text': 'eval', 'width': 5},
                  button_args      = button_args,
                  eval_button_args = eval_button_args,
                  borderwidth = 2, relief = 'groove',
                  )
        Editor_frame.pack(fill = tkinter.BOTH, expand = True)

        def help_eval():
            app.print_warning("Global variables")
            dict = app.globals
            for key in dict.keys():
                app.print_warning(f"  {key}: {dict[key]}")

        def evaluate(Text):
            text = frame.Text.get(1.0, "end")
            app.print_warning("")
            app.print_warning("Run:")
            app.print_warning(f"> {text}\n>> ")
            
            redirects_original = app.redirects
            if 'stdout' not in app.redirects:
                app.redirects.append("stdout")
            ret = exec(text, app.globals, {"app": app, "cparams": cparams, "config": config, "tkvars": tkvars, "help": help_eval} )
            app.redirects = redirects_original
            
            if ret is None:
                app.print_warning("")
            else:
                app.print_warning(f"ret={ret}")
            return

            """
#            print(text)
#            print(exec(text))
            lines = text.split('\n')
            for l in lines:
                if l is None:
                    break
                l.strip()
                if l == '':
                    continue

                print(f"> {l}\n>> ", end = '')
                ret = exec(l, app.globals, {"app": app, "cparams": cparams, "config": config, "tkvars": tkvars} )
                if ret is None:
                    print("")
                else:   
                    print(f"ret={ret}")
            """
        
        tkvars['edit_text'] = Editor_frame.Text
        frame.PathFrame     = Editor_frame.PathFrame
        frame.entry         = Editor_frame.entry
        frame.eval_button   = Editor_frame.eval_button
        frame.Text          = Editor_frame.Text

        if use_custom_command and frame.eval_button:
            frame.eval_button.configure(command = lambda: evaluate(frame.Text))
        
        return frame

    def create_editor_tab(app, config, cparams, pane, title,
                entry_width = 60,
                eval_button_args = {}
                ):
        title = app.p(title)
        frame = tktkinter.Frame(app, pane, bg='dim gray')
        pane.add(frame, text = title + ' ')

        Editor_frame = app.create_editor_frame(frame, entry_width = entry_width, eval_button_args = eval_button_args)

        return Editor_frame

    def create_development_tab(app, config, cparams, parent_notebook, copy_tkvars = None):
        config  = app.config
        ctkvars = app.tkvars
        tkvars  = ctkvars.get_param_dict()

        if app.get('hidden_params', None) is None:
            app.hidden_params = tkParams()

            PYTHONPATH = os.environ.get('PYTHONPATH', '')
            if get_os() == 'Windows':
                libpaths = PYTHONPATH.split(';')
            elif ':' in PYTHONPATH:
                libpaths = PYTHONPATH.split(':')
            else:
                libpaths = PYTHONPATH.split(';')
        
            app.hidden_params.libpaths = []
            for path in libpaths:
                if 'tklib' in path or 'tkProg' in path or 'tkprog' in path:
                    app.hidden_params.libpaths.append(path)

        page1_frame = tktkinter.Frame(app, parent_notebook, bg = 'dim gray')
        page1_frame.pack()
        parent_notebook.add(page1_frame, text = app.p('Development') + ' ')
        notebook_frame = page1_frame

# Script path frame
        path_grid_frame = tkinter.Frame(page1_frame)
        tktkinter.make_path_frame(app, cparams, page1_frame, path_grid_frame, 
                    tkvars, varname = 'program_path', 
                    file_type = [("python", "*.py"), ("Text", "*.txt;*.ini"), ("All", "*.*")],
                    working_dir = app.script_dir, 
                    is_print = True, igridrow = 0)
        
        for i in range(len(app.hidden_params.libpaths)):
            path = app.hidden_params.libpaths[i]

#            varname = f'lib{i+1}'
            tktkinter.make_path_frame(app, cparams, page1_frame, path_grid_frame,
                        tkvars, varname = f'lib{i+1}', 
                        file_type = [("python", "*.py"), ("Text", "*.txt;*.ini"), ("All", "*.*")],
                        working_dir = app.hidden_params.libpaths[i], 
                        is_print = True, igridrow = i + 1)

# Maintenance buttons
        button_frame2 = tkinter.Frame(page1_frame)
        button1 = tktkinter.Button(app, button_frame2, text = 'app', width = 8, font = ("", 16),
                            command = lambda: app.print_attributes_warning(print_header = True))
        button1.pack(side = tkinter.LEFT)
        button2 = tktkinter.Button(app, button_frame2, text = 'cparams', width = 8, font = ("", 16),
                            command = lambda: app.print_cparams_info(cparams, tkvars, copy_tkvars = copy_tkvars))
        button2.pack(side = tkinter.LEFT)
        button3 = tktkinter.Button(app, button_frame2, text = 'tkvars', width = 8, font = ("", 16),
                            command = lambda: app.print_tkvars_info(cparams, tkvars))
        button3.pack(side = tkinter.LEFT)
        button4 = tktkinter.Button(app, button_frame2, text = 'config', width = 8, font = ("", 16),
                            command = lambda: app.print_config_info(config, tkvars, copy_tkvars = copy_tkvars))
        button4.pack(side = tkinter.LEFT)
        button_frame2.pack(side = tkinter.TOP, anchor = 'w', padx = 2, pady = 2)

# Maintenance buttons 2
        button_frame3 = tkinter.Frame(page1_frame)
        button1 = tktkinter.Button(app, button_frame3, text = 'env', width = 8, font = ("", 16),
                            command = lambda: app.print_dict_warning(os.environ, header = 'Environment variables'))
        button1.pack(side = tkinter.LEFT)
        svars = app.get('svars', None)
        if svars:
            button2 = tktkinter.Button(app, button_frame3, text = 'app vars', width = 8, font = ("", 12),
                            command = lambda: app.print_dict_warning(svars.get_param_dict(), header = 'Script variables'))
            button2.pack(side = tkinter.LEFT)
        s_engine = app.get('s_engine',  None)
        if s_engine:
            button3 = tktkinter.Button(app, button_frame3, text = 'scr vars', width = 8, font = ("", 12),
                            command = lambda: app.print_dict_warning(s_engine.vars, header = 'Script variables'))
            button3.pack(side = tkinter.LEFT)
        button_frame3.pack(side = tkinter.TOP, anchor = 'w', padx = 2, pady = 2)

        editor_frame = app.create_editor_frame(page1_frame, show_path_select = False,
                    eval_button_args = { 'text': 'eval', 'width': 4, "command": None },
                    )

        return notebook_frame

    def get_editors(self, additional_list = [], filename_only = False):
        os_name = get_os()
        if os_name == 'Windows':
            elist = ['code', 'subl', 'sublime_text', 'atom', 'pspad', 'emeditor', 'uedit64', 
                     'notepad++', 'notepad', 'emacs', 'xemacs', 'gvim', 'vim', 'vi'] + additional_list
        elif os_name == 'Linux':
            elist = ['code', 'gedit', 'gvim', 'kate', 'subl', 'notepadqq', 'nano', 'atom', 'emacs', 'xemacs', 'vim', 'vi'] + additional_list
        else:
            elist = ['code', 'Xcode', 'gvim', 'subl', 'atom', 'bbedit_tool', 'mate', 'TextEdit', 'emacs', 'xemacs', 'vim', 'vi'] + additional_list

        editor_paths = []
        for key in elist:
            path = find_executable_path(key, filename_only = filename_only)
            if path and path not in editor_paths:
                editor_paths.append(path)

        for key in ['EDITOR_PATH', 'tkEDITOR_PATH', 'EDITOR']:
            if key in os.environ and os.environ[key] not in editor_paths:
                editor_paths.append(os.environ[key])

        return editor_paths

    def get_start_apps(self, additional_list = [], filename_only = False):
        os_name = get_os()
        start_paths = []
        if os_name == 'Windows':
            slist = [] + additional_list
            start_paths = ['start']
        elif os_name == 'Linux':
            slist = ['x-open.sh', 'xdg-open'] + additional_list
        else:
            slist = ['m-open.sh', 'open'] + additional_list

        for key in slist:
            path = find_executable_path(key, filename_only = filename_only)
            if path and path not in start_paths:
                start_paths.append(path)

        return start_paths

    def get_shells(self, additional_list = [], filename_only = False):
        os_name = get_os()
        shell_paths = []
        if os_name == 'Windows':
            slist = ['cmd', 'wt', 'powershell'] + additional_list
        elif os_name == 'Linux':
            slist = ['xterm', 'gnome-terminal', 'konsole'] + additional_list
        else:
            slist = ['xterm', 'gnome-terminal', 'konsole'] + additional_list

        for key in slist:
            path = find_executable_path(key, filename_only = filename_only)
            if path and path not in shell_paths:
                shell_paths.append(path)

        return shell_paths

    def get_filers(self, additional_list = [], filename_only = False):
        os_name = get_os()
        if os_name == 'Windows':
            flist = ['explorer'] + additional_list
        elif os_name == 'Linux':
            flist = ['nautilus', 'thunar', 'pcmanfm', 'dlphin', 'nemo', 'ranger'] + additional_list
        else:
            flist = ['Finder', 'Path Finder', 'Commander One', 'muCommander', 'nautilus', 'thunar', 'pcmanfm', 'dlphin', 'nemo', 'ranger'] + additional_list

        filer_paths = []
        for key in flist:
            path = find_executable_path(key, filename_only = filename_only)
            if path and path not in filer_paths:
                filer_paths.append(path)

        return filer_paths

    def get_external_apps(self, paths, use_default = True, filename_only = False):
        if not use_default:
            app_paths = []
            for key in paths:
                path = find_executable_path(key, filename_only = filename_only)
                if (path is None or path == "") and key not in app_paths:
                    app_paths.append(key)
                elif path not in app_paths:
                    app_paths.append(path)

            return app_paths
        
        if get_os() == 'Windows':
            app_paths = ['auto', 'start', 'start cmd.exe /K', 'explorer.exe [/n,/e,%(dir)]']
            for key in ['code', 'notepad', 'emacs', 'vim', 'powershell', 'python3', 'python2', 'python']:
                path = find_executable_path(key)
                if path and path not in app_paths:
                    app_paths.append(path)
        else:
            app_paths = ['auto']
            for key in ['code', 'emacs', 'vim', 'bash', 'tsch', 'python3', 'python2', 'python', 'py']:
                path = find_executable_path(key)
                if path and path not in app_paths:
                    app_paths.append(path)

        for key in ['EDITOR_PATH', 'tkEDITOR_PATH', 'EDITOR', 'VESTA_PATH']:
            if key in os.environ and os.environ[key] not in app_paths:
                app_paths.append(os.environ[key])

        for key in paths:
            path = find_executable_path(key)
            if (path is None or path == "") and key not in app_paths:
                app_paths.append(key)
            elif path not in app_paths:
                app_paths.append(path)

        return app_paths


def main():
    print("")
    print("This is library, not runnable")
    print("")


if __name__ == "__main__":
    main()
