import re
import ctypes
import sys

import dearpygui.dearpygui as dpg

from styles import GuiStyle, INDENT, SPACE, FONT_SIZE, ICON_PATH
from config import load_config, dump_config

ctypes.windll.shcore.SetProcessDpiAwareness(1)

def get_absolute_y(item):
    y = dpg.get_item_pos(item)[1]
    parent = dpg.get_item_parent(item)
    while parent:
        if dpg.get_item_type(parent) == 'mvAppItemType::mvChildWindow':
            y += dpg.get_item_pos(parent)[1]
        parent = dpg.get_item_parent(parent)
    return y

def get_item_level(item):
    level = 0
    parent = dpg.get_item_parent(item)
    while parent:
        if dpg.get_item_type(parent) == 'mvAppItemType::mvTabBar':
            level += 1
        parent = dpg.get_item_parent(parent)
    return level

def config_iter(config, prefix=""):
    for path in config:
        namespaces = path.split('.')
        for i in range(len(namespaces)):
            yield prefix + '.' + '.'.join(namespaces[:i+1])

class ConfigEditor:
    def __init__(self):
        dpg.create_context()
        self.styles = GuiStyle()
        self.config = dict()
        self.config_file_path = str()

    def delete_config_window_and_clear_aliases(self, file_path):
        if dpg.does_item_exist(file_path):

            if dpg.get_item_type(file_path) == "mvAppItemType::mvChildWindow":
                uuid = dpg.get_item_parent(dpg.get_item_parent(file_path))
                dpg.delete_item(uuid)
            else:
                dpg.delete_item(file_path)

        if dpg.does_alias_exist(file_path):
            dpg.remove_alias(file_path)
        for path in config_iter(self.config, prefix=self.config_file_path):
            if dpg.does_alias_exist(path):
                dpg.remove_alias(path)

    def update_config(self):
        for path in self.config:
            if dpg.does_item_exist(f"{self.config_file_path}.{path}"):
                self.config[path].value = dpg.get_value(f"{self.config_file_path}.{path}")

    def check_tabview(self, root):
        for uuid in dpg.get_item_children(root, 1):
            if dpg.get_item_type(uuid) == "mvAppItemType::mvTabBar":
                return True, uuid
        return False, None

    def manage_file(self, sender, sender_data, user_data):
        file_path = sender_data['file_path_name']

        if not file_path:
            return

        if user_data == "open":
            self.delete_config_window_and_clear_aliases(self.config_file_path)
            self.config_file_path = file_path
            self.config = load_config(file_path)
            self.construct_config_ui()
        elif user_data == "save_as" or user_data == "save":
            self.update_config()
            dump_config(self.config, file_path)
            dpg.show_item("file_save_popup")

    def set_file_path(self, sender, sender_data, user_data):
        file_path = sender_data['file_path_name']
        dpg.set_value(user_data, file_path)

    def open_file_dialog(self, sender, sender_data, user_data):
        dpg.show_item("file_dialog")
        dpg.set_item_user_data("file_dialog", user_data)

    def open_folder_dialog(self, sender, sender_data, user_data):
        dpg.show_item("folder_dialog")
        dpg.set_item_user_data("folder_dialog", user_data)

    def construct_config_ui(self):
        self.update_config()
        self.delete_config_window_and_clear_aliases(self.config_file_path)
        if dpg.get_value("use_tabs_ui"):
            self.construct_config_ui_tabs()
        else:
            self.construct_config_ui_collapsing_headers()

    def construct_main_window(self):
        dpg.bind_font(self.styles.montserrat_font)
        dpg.bind_theme(self.styles.main_theme)

        with dpg.window(label="config file editor", pos=(10, 10), width=dpg.get_viewport_client_width(), height=dpg.get_viewport_client_height(), tag="main", menubar=True):
            with dpg.menu_bar(tag="main_menu_bar"):
                with dpg.menu(label="file"):
                    dpg.add_menu_item(label="open", callback=self.open_file_dialog, tag="open_btn", user_data="open")
                    dpg.add_menu_item(label="save", callback=lambda: self.manage_file("", {'file_path_name': self.config_file_path}, "save"))
                    dpg.add_menu_item(label="save as", callback=self.open_file_dialog, tag="save_as_btn", user_data="save_as")

                with dpg.menu(label="view"):
                    dpg.add_menu_item(label="use default theme", check=True, callback=lambda _, v: dpg.bind_theme(None if v else self.styles.main_theme), default_value=False)
                    dpg.add_menu_item(label="use default font", check=True, callback=lambda _, v: dpg.bind_font(None if v else self.styles.montserrat_font), default_value=False)
                    dpg.add_menu_item(label="use tabs", check=True, callback=lambda: self.construct_config_ui(), default_value=False, tag="use_tabs_ui")
                    dpg.add_menu_item(label="show theme editor", callback=dpg.show_style_editor)

                with dpg.menu(label="settings"):
                    dpg.add_menu_item(label="show mouse coords", check=True, callback=lambda _, v: dpg.show_item("mouse") if v else dpg.hide_item("mouse"), default_value=False)

                dpg.add_text("", tag="mouse", show=False)

        with dpg.file_dialog(directory_selector=False, show=False, callback=self.manage_file, tag="file_dialog", width=600, height=400):
            dpg.add_file_extension(".txt", color=(0, 255, 0, 255))
            dpg.add_file_extension(".*", color=(255, 255, 255, 255))

        with dpg.file_dialog(directory_selector=True, show=False, callback=self.set_file_path, tag="folder_dialog", width=500, height=400):
            dpg.add_file_extension(".*", color=(255, 255, 255, 255))

        with dpg.window(label="", autosize=True, no_resize=True, modal=True, tag="file_save_popup", show=False, pos=(40, 40)):
            dpg.add_text("file saved successfully:)")

        with dpg.item_handler_registry(tag="main_handler") as handler:
            dpg.add_item_resize_handler(callback=lambda: self.change_height(self.config_file_path))

        dpg.bind_item_handler_registry("main", "main_handler")
        dpg.set_viewport_resize_callback(lambda: self.change_height(self.config_file_path))

        uuid = dpg.add_handler_registry()
        def clb(s, a, u):
            dpg.set_value("mouse", str(a))
        dpg.add_mouse_move_handler(parent=uuid, callback=clb)

    def construct_config_ui_collapsing_headers(self):
        dpg.add_collapsing_header(label=self.config_file_path, parent="main", tag=self.config_file_path, indent=0, default_open=True)

        for full_path in self.config:
            namespaces = full_path.split('.')
            parent = self.config_file_path
            for level in range(len(namespaces) - 1):
                path = self.config_file_path + '.' + '.'.join(namespaces[:level+1])

                if not dpg.does_item_exist(path):
                    dpg.add_collapsing_header(label=namespaces[level], parent=parent, tag=path, indent=INDENT)

                parent = path

            self.create_widget(full_path)

    def construct_config_ui_tabs(self):
        tab_bar_exists, tab_bar_uuid = self.check_tabview("main")
        if not tab_bar_exists:
            tab_bar_uuid = dpg.add_tab_bar(parent="main")
        tab_uuid = dpg.add_tab(label=self.config_file_path, parent=tab_bar_uuid)
        dpg.add_child_window(parent=tab_uuid, tag=self.config_file_path, border=True, autosize_y=False, autosize_x=True)
        dpg.set_item_user_data(self.config_file_path, 2 * SPACE)

        for full_path in self.config:
            namespaces = full_path.split('.')
            parent = self.config_file_path
            for level in range(len(namespaces) - 1):
                path = self.config_file_path + '.' + '.'.join(namespaces[:level+1])

                tab_bar_exists, tab_bar_uuid = self.check_tabview(parent)

                if not tab_bar_exists:
                    tab_bar_uuid = dpg.add_tab_bar(parent=parent, callback=lambda s, a: self.change_height(a))
                    dpg.set_item_user_data(parent, dpg.get_item_user_data(parent) + 4 * SPACE + FONT_SIZE)

                if not dpg.does_item_exist(path):
                    tab_uuid = dpg.add_tab(label=namespaces[level], parent=tab_bar_uuid)
                    dpg.add_child_window(parent=tab_uuid, tag=path, border=True, autosize_y=False, autosize_x=True)
                    dpg.set_item_user_data(path, 2 * SPACE)

                parent = path

            self.create_widget(full_path)

    def change_height(self, item):
        if not dpg.does_item_exist(item):
            return
        if dpg.get_item_type(item) == "mvAppItemType::mvChildWindow":
            uuid = dpg.get_item_parent(item)
            new_height = dpg.get_viewport_client_height() - get_absolute_y(uuid) - SPACE * get_item_level(item)

            dpg.set_item_height(item, max(new_height, dpg.get_item_user_data(item)))

        for child in dpg.get_item_children(item, 1):
            self.change_height(child)

    def create_widget(self, full_path):
        tag = self.config_file_path + '.' + full_path
        parent =  '.'.join([self.config_file_path] + full_path.split('.')[:-1])

        value = self.config[full_path].value
        value_type = self.config[full_path].value_type
        key = self.config[full_path].key
        comment = self.config[full_path].comment

        if dpg.get_value("use_tabs_ui") and parent != self.config_file_path and full_path != key:
            dpg.set_item_user_data(parent, dpg.get_item_user_data(parent) + 2 * SPACE + FONT_SIZE + SPACE)

        indent = INDENT
        if dpg.get_value("use_tabs_ui"):
            indent = 0

        with dpg.group(horizontal=True, parent=parent, indent=indent):
            text_uuid = dpg.add_text(key)
            if value_type == 'BOOLEAN':
                dpg.add_checkbox(default_value=value, tag=tag)
            elif value_type == 'DOUBLE':
                dpg.add_input_double(default_value=value, tag=tag, format="%.10f")
            elif value_type == 'INT':
                dpg.add_input_int(default_value=value, tag=tag)
            elif "dir" in key.lower():
                dpg.add_input_text(default_value=value, tag=tag)
                dpg.add_button(label="...", callback=self.open_folder_dialog, user_data=tag)
            else:
                dpg.add_input_text(default_value=value, tag=tag)
            if comment:
                dpg.add_text(comment, parent=dpg.add_tooltip(parent=text_uuid))

    def run(self):
        dpg.create_viewport(
            title='config file editor',
            small_icon=ICON_PATH,
            large_icon=ICON_PATH
        )
        dpg.setup_dearpygui()
        dpg.show_viewport()
        self.construct_main_window()
        if self.config_file_path:
            self.construct_config_ui()
        dpg.set_primary_window("main", True)
        dpg.start_dearpygui()
        dpg.destroy_context()

if __name__ == "__main__":
    app = ConfigEditor()

    if len(sys.argv) > 1:
        app.config_file_path = sys.argv[1]
        app.config = load_config(sys.argv[1])

    app.run()