#!/usr/bin/python3
import os
import sys
import yaml
import logging
import conf2Utils
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
import tkinter.filedialog as filedialog
import tkinter.simpledialog as simpledialog
import dbus
from gi.repository import GLib

target_view = {}

def on_tree_right_click(event):
    global right_select_item
    item = tree.identify_row(event.y)
    if item and tree.parent(item) == "":
        right_select_item = item
        download_menu.post(event.x_root, event.y_root)

def close_menu(event):
    download_menu.unpost()

def on_search():
    text = search_button.cget('text')
    if text == 'search':
        search_button.config(text='cancel')
        view_frame.pack_forget()
        search_frame.pack(side='bottom', fill='both', expand=True)
        search_tree.delete(*search_tree.get_children())
        search_entry.config(state='normal')
        # search_entry.event_generate("<KeyRelease>")
    elif text == 'cancel':
        search_button.config(text='search')
        search_frame.pack_forget()
        view_frame.pack(side='bottom', fill='both', expand=True)
        items = tree.selection()
        if items:
            text = tree.item(items[0], 'tags')[0]
            search_entry.delete(0, 'end')
            search_entry.insert(0, text)
        else:
            search_entry.delete(0, 'end')
        search_entry.config(state='readonly')

def on_search_entry_change(event):
    new_text = event.widget.get()
    search_tree.delete(*search_tree.get_children())
    if new_text == '':
        return
    for key in search_list.keys():
        name_list = key.split('/')
        if new_text in name_list[-1] or key.startswith(new_text):
            search_tree.insert('', 'end', text= key)

def on_download():
    class MultiSelectDialog(tk.simpledialog.Dialog):
        def body(self, master):
            self.results = []
            self.options = list(target_view.keys())
            self.vars = []
            for i, option in enumerate(self.options):
                var = tk.IntVar()
                checkbutton = tk.Checkbutton(master, text=option, variable=var)
                checkbutton.grid(row=i, sticky='w')
                self.vars.append(var)
            
            select_all_button = tk.Button(master, text="全选", command=self.all_select)
            select_all_button.grid(row=len(self.options), column=0, sticky='w')

            select_none_button = tk.Button(master, text="全不选", command=self.none_select)
            select_none_button.grid(row=len(self.options), column=1, sticky='w')

        def apply(self):
            self.results = [option for var, option in zip(self.vars, self.options) if var.get() == 1]

        def all_select(self):
            for var in self.vars:
                var.set(1)

        def none_select(self):
            for var in self.vars:
                var.set(0)

    global target_view
    dialog = MultiSelectDialog(root, title="Multi-Select Dialog")
    if dialog.results != []:
        path = filedialog.askdirectory(mustexist=True, title=' Choose a folder')
        if path:
            try:
                converter._value_override_default(target_view)
                for app in dialog.results:
                    yaml_file_path = f'{path}/{app}.yaml'
                    if os.path.exists(yaml_file_path):
                        msgbox.askyesno(message=f'{yaml_file_path} is existed. Do you want to replace it')
                    else:
                        with open(yaml_file_path, 'w') as yaml_file:
                            yaml_file.write(yaml.safe_dump({app:target_view[app]}, allow_unicode = True))
                msgbox.showinfo(message='Save successed')
            except Exception as e:
                msgbox.showinfo(message='Save failed')

def on_download_one():
    global target_view
    app = tree.item(right_select_item, 'text')
    apps = []
    apps.append(app)
    path = filedialog.askdirectory(mustexist=True, title=' Choose a folder')
    if path:
        success = converter.editor_save(apps, target_view, path)
        if success:
            msgbox.showinfo(message='Save successed')
        else:
            msgbox.showinfo(message='Save failed')

def on_import_file():
    pass

def on_set():
    try:
        if combobox.get() != 'custom':
            msgbox.showinfo(message='Not custom veiw')

        user = destination_file.split('/')[2]

        key = id = value = permission = type = range = ''
        for widget in inner_frame.winfo_children():
            if isinstance(widget, tk.Text):
                if 'key' == widget.winfo_name():
                    key = widget.get(1.0, 'end')
                    key = key.strip()
                if 'id' == widget.winfo_name():
                    id = widget.get(1.0, 'end')
                    id = id.strip()
                if 'type' == widget.winfo_name():
                    type = widget.get(1.0, 'end')
                    type = type.strip()
                if 'permission' == widget.winfo_name():
                    permission = widget.get(1.0, 'end')
                    permission = permission.strip()
                if 'range' == widget.winfo_name():
                    range = widget.get(1.0, 'end')
                    range = range.strip()
                if 'custom' == widget.winfo_name():
                    value = widget.get(1.0, 'end')
                    value = value.strip()

        path  = search_entry.get()
        version = path.split('/')[1]

        # 类型检测
        try:
            if type != 'enum':
                variant_type = GLib.VariantType(type)
                variant = GLib.Variant.parse(variant_type, value)
        except Exception as e:
            msgbox.showinfo('unknown keyword')
            return
        
        # 取值范围检测
        if type == 'enum' and value not in range:
            msgbox.showinfo(message='Out of range')
            return
        elif type != 'enum' and range != '':
            try:
                min = GLib.Variant.parse(variant_type, range.split(',')[0].strip())
                max = GLib.Variant.parse(variant_type, range.split(',')[1].strip())
                if variant.compare(min) < 0 or variant.compare(max) > 0:
                    msgbox.showinfo(message='Out of range')
                    return
            except Exception as e:
                msgbox.showinfo(message=e)
                return

        bus = dbus.SystemBus()
        obj = bus.get_object('com.kylin.kysdk.conf2', '/com/kylin/kysdk/conf2')
        obj_interface = dbus.Interface(obj, dbus_interface='com.kylin.kysdk.conf2')
        success = obj_interface.set(user, id, version, key, value)
        if success:
            global target_view
            target_view = sqlhelper.db2dict(f'{destination_file}/.config/kylin-config/user.db')
            msgbox.showinfo(message='Set successed')
        else:
            msgbox.showinfo(message='Set failed')
    except Exception as e:
        print(e)

def on_search_tree_select(event):
    items = search_tree.selection()
    text = search_tree.item(items, 'text')
    if text == '':
        return
    tree.selection_set(search_list[text])
    tree.see(search_list[text])
    search_frame.pack_forget()
    view_frame.pack(side='bottom', fill='both', expand=True)
    search_button_text = search_button.cget('text')
    if search_button_text == 'cancel':
        search_button.config(text='search')

def on_tree_select(event):
    selected_item = tree.selection()
    global target_view
    if not selected_item:
        return
    item = selected_item[0]
    tag = tree.item(item, 'tags')[0]

    search_entry.config(state='normal')
    search_entry.delete(0, 'end')
    search_entry.insert(0, tag)
    search_entry.config(state='readonly')

    if tree.parent(item) == '':
        return
    
    group_list = tag.split('/')
    tmp = target_view
    for group in group_list:
        tmp = tmp[group]
    for widget in inner_frame.winfo_children():
        widget.destroy()

    text = tree.item(item, 'text')
    
    name_label = tk.Label(inner_frame, text=f'名称', anchor='w')
    name_label.pack(side='top', fill='x')

    name_text = tk.Text(inner_frame, height=1, width=100, wrap='word', name='key')
    name_text.insert('1.0', text)
    name_text.config(state='disabled')
    name_text.pack(side='top', fill='x', expand=True)
    
    type = tmp['_type'] if '_type' in tmp else 'group'

    if len(group_list) > 1:
        group_list.pop(1)
    if type != 'group':
        group_list.pop(-1)

    id_label = tk.Label(inner_frame, text=f'路径', anchor='w')
    id_label.pack(side='top', fill='x')

    id_text = tk.Text(inner_frame, height=1, width=100, wrap='word', name='id')
    id_text.insert('1.0', '.'.join(group_list))
    id_text.config(state='disabled')
    id_text.pack(side='top', fill='x', expand=True)

    type_label = tk.Label(inner_frame, text=f'类型', anchor='w')
    type_label.pack(side='top', fill='x')

    type_text = tk.Text(inner_frame, height=1, width=100, wrap='word', name='type')
    type_text.insert('1.0', type)
    type_text.config(state='disabled')
    type_text.pack(side='top', fill='x', expand=True)

    if tmp.get('_summary') is not None:
        summary_label = tk.Label(inner_frame, text=f'摘要', anchor='w')
        summary_label.pack(side='top', fill='x')

        summary_text = tk.Text(inner_frame, height=3, width=100, wrap='word', name='summary')
        summary_text.insert('1.0', tmp.get('_summary'))
        summary_text.config(state='disabled')
        summary_text.pack(side='top', fill='x', expand=True)

    if tmp.get('_description') is not None:
        decription_label = tk.Label(inner_frame, text=f'描述', anchor='w')
        decription_label.pack(side='top', fill='x')

        decription_text = tk.Text(inner_frame, height=3, width=100, wrap='word' ,name='description')
        decription_text.insert('1.0', tmp.get('_description'))
        decription_text.config(state='disabled')
        decription_text.pack(side='top', fill='x', expand=True)

    permission = tmp.get('_permission', 'public')
    permission_label = tk.Label(inner_frame, text=f'权限', anchor='w')
    permission_label.pack(side='top', fill='x')

    permission_text = tk.Text(inner_frame, height=1, width=100, wrap='word', name='permission')
    permission_text.insert('1.0', permission)
    permission_text.config(state='disabled')
    permission_text.pack(side='top', fill='x', expand=True)

    if tmp.get('_range') is not None:
        decription_label = tk.Label(inner_frame, text=f'取值范围', anchor='w')
        decription_label.pack(side='top', fill='x')
        range = tmp.get('_range')
        if tmp.get('_type') == 'enum':
            if range.startswith('@'):
                path = search_entry.get()
                version_data = target_view[path.split('/')[0]][path.split('/')[1]]
                # _开头的应该是内部函数仅在类内部使用，但我实在懒得搬代码了
                element_list = sqlhelper._recursive_search(version_data, range[1:])
                value_list = []
                for element in element_list:
                    value_list.append(element['_nick'])
                range = str(value_list)
            else:
                data = yaml.safe_load(range)
                range = str(list(data.keys()))
        decription_text = tk.Text(inner_frame, height=3, width=100, wrap='word' ,name='range')
        decription_text.insert('1.0', range)
        decription_text.config(state='disabled')
        decription_text.pack(side='top', fill='x', expand=True)

    if '_default' in tmp:
        default_value = tmp['_default']
        if isinstance(default_value, bool):
            default_value = 'true' if tmp['_default'] else 'false'

        default_label = tk.Label(inner_frame, text=f'默认值', anchor='w')
        default_label.pack(side='top', fill='x')

        default_text = tk.Text(inner_frame, height=5, width=100, wrap='word', name='default')
        default_text.insert('1.0', tmp['_default'])
        default_text.config(state='disabled')
        default_text.pack(side='top', fill='x', expand=True)

        if combobox.get() == 'custom':
            custom_value = tmp.get('_value')
            if custom_value is None:
                custom_value = '默认值'
            custom_label = tk.Label(inner_frame, text=f'当前值', anchor='w')
            custom_label.pack(side='top', fill='x')

            custom_text = tk.Text(inner_frame, height=5, width=100, wrap='word', name='custom')
            custom_text.insert('1.0', custom_value)
            custom_text.config(state='disabled')
            custom_text.pack(side='top', fill='x', expand=True)
    
    if type != 'group' and permission == 'public' and combobox.get() == 'custom':
        custom_text.config(state='normal')
        set_button = tk.Button(inner_frame, text='Set', command=on_set)
        set_button.pack(side='top')

def on_combobox_select(event):
    selected_item = combobox.get()
    global target_view
    if selected_item == 'custom':
        target_view = sqlhelper.db2dict(f'{destination_file}/.config/kylin-config/user.db')
    else:
        dirs = []
        for dir in list(reversed(options)):
            if dir == 'user':
                dirs.append(f'{destination_file}/.config/kylin-config/configs')
            else:
                dirs.append(f'/etc/kylin-config/{dir}')
            if dir == selected_item:
                break
        tmp = dirs.copy()
        target_view = yamlhelper.read_yaml_dirs(tmp)

    tree.delete(*tree.get_children())
    for widget in inner_frame.winfo_children():
        widget.destroy()
    search_list.clear()
    populate_tree(tree, '', target_view)
    view_frame.update()

def populate_tree(tree, node, dictionary):
    global search_list
    tag = tree.item(node, 'tags')
    for key, value in dictionary.items():
        if isinstance(value, dict):
            child_node = tree.insert(node, 'end', text=key)
            if isinstance(tag, str):
                tree.item(child_node, tags=(key))
            else:
                tree.item(child_node, tags=(f'{tag[0]}/{key}'))
                search_list[f'{tag[0]}/{key}'] = child_node
            populate_tree(tree, child_node, value)

root = tk.Tk()
root.title("kconf2-editor")
root.geometry('1024x768')
root.bind("<Button-1>", close_menu)

download_menu = tk.Menu(root, tearoff=0)
download_menu.add_command(label="下载", command=on_download_one)
# download_menu.add_command(label="导入到其他目录", command=on_import_file)

search_list = {}

destination_file = os.getenv('HOME')
logger = logging.getLogger(f'{destination_file}/.log/widget.log')
converter = conf2Utils.converter(logger)
sqlhelper = conf2Utils.sqlHelper(logger)
yamlhelper = conf2Utils.yamlHelper(logger)

# 读取统一视图
target_view = sqlhelper.db2dict(f'{destination_file}/.config/kylin-config/user.db')

# 创建一个Frame来包含按钮，使其一直位于界面上方
button_frame = tk.Frame(root, height=10)
# 创建一个Frame来包含Treeview，占满界面其他空余部分
view_frame = tk.Frame(root)
# 创建一个Frame来显示搜索结果，它与view_frame只会显示一个
search_frame = tk.Frame(root)

# 排列root的三个子组件
button_frame.pack(side="top", fill="x")
view_frame.pack(side='top', fill='both', expand=True)

# 搜索栏
search_entry = tk.Entry(button_frame, state='readonly')
search_entry.bind("<KeyRelease>", on_search_entry_change)

# 搜索按钮
# search_icon = tk.PhotoImage('/etc/kylin-config/search.png')
search_button = tk.Button(button_frame, text='search', command=on_search, width=20)

# 下载按钮
# download_icon = tk.PhotoImage(file="/etc/kylin-config/download.png")
download_button = tk.Button(button_frame, text='export', command=on_download, width=20)

# 视图选择列表
with open('/etc/kylin-config/conf2.yaml', 'r') as file:
    configures = yaml.safe_load(file)
options = configures['dirs']
if os.path.exists(f'{destination_file}/.config/kylin-config/configs'):
    options.append('mutable')
options.append('custom')
options.reverse()

combobox = ttk.Combobox(button_frame, values=options, width=20, state='readonly')
combobox.set(options[0])
combobox.bind("<<ComboboxSelected>>", on_combobox_select)

# 排列button_frame子组件
search_entry.pack(side="left", fill="both", expand= True)
combobox.pack(side='right')
download_button.pack(side="right", fill="x")
search_button.pack(side="right", fill="x")

# 创建Frame在view_frame的左侧，显示配置树
left_frame = tk.Frame(view_frame)
left_frame.pack(side='left', fill='both')

# 创建Treeview，显示配置树
tree = ttk.Treeview(left_frame)
tree.column('#0', width=200, stretch=True)
tree.heading('#0', text='SubFolders')
tree.bind("<<TreeviewSelect>>", on_tree_select)
tree.bind("<Button-3>", on_tree_right_click)
search_list.clear()
populate_tree(tree, '', target_view)
tree.pack(side='left', fill='both')

# 创建滚动条
tree_scrollbar = ttk.Scrollbar(left_frame, orient="vertical", command=tree.yview)
tree_scrollbar.pack(side="right", fill="y")
tree.configure(yscrollcommand=tree_scrollbar.set)

# 创建Frame在view_frame的右侧，显示配置信息
right_frame = tk.Frame(view_frame)
right_frame.pack(side='right', fill='both', expand=True)

# 创建一个Canvas
canvas = tk.Canvas(right_frame)
canvas.pack(side="left", fill="both", expand=True)
canvas.config(scrollregion=canvas.bbox("all"))

right_scrollbar = ttk.Scrollbar(right_frame, orient="vertical", command=canvas.yview)
right_scrollbar.pack(side="right", fill="y")
canvas.configure(yscrollcommand=right_scrollbar.set)

# 将Canvas的窗口设置为Frame
inner_frame = tk.Frame(canvas)

canvas_window = canvas.create_window((0, 0), window=inner_frame, anchor="nw")
inner_frame.bind("<Configure>", lambda event: canvas.config(scrollregion=canvas.bbox("all")))
# 控制滚动条滚动 Button-4是向上滚动， Button-5是向下滚动 event.num的数值代表按钮id
def on_mouse_scroll(event):
    inner_height = inner_frame.winfo_height()
    canvas_height = canvas.winfo_height()
    # 内容长度是否需要滚动
    if inner_height > canvas_height:
        # 仅响应在canvas空间区域的滚轮事件
        if canvas.winfo_containing(event.x_root, event.y_root) == canvas:
            if event.num == 4:
                canvas.yview_scroll(-2, "units")
            elif event.num == 5:
                canvas.yview_scroll(2, "units")
        
canvas.bind_all("<Button-4>", on_mouse_scroll)
canvas.bind_all("<Button-5>", on_mouse_scroll)

# 创建Treeview显示搜索匹配的路径
search_tree = ttk.Treeview(search_frame, show='tree')
search_tree.bind("<<TreeviewSelect>>", on_search_tree_select)
search_tree.pack(side='left', fill='both', expand=True)

search_scrollbar = ttk.Scrollbar(search_frame, orient="vertical", command=search_tree.yview)
search_scrollbar.pack(side="right", fill="y")
search_tree.configure(yscrollcommand=search_scrollbar.set)

root.mainloop()
