import os
import carb
import carb.settings
from typing import List, Union, Dict
from enum import Enum
from .builder_utils import get_action_path, create_prebuild_entry
from .builder_utils import MenuItemDescription, PrebuiltItemOrder, LayoutSourceSearch


class MenuLayout:
    class MenuLayoutItem:
        def __init__(self, name=None, source=None, source_search=LayoutSourceSearch.EVERYWHERE):
            self.name = name
            self.source = source
            self.source_search = source_search

        def __repr__(self):
            if self.source:
                return f"<{self.__class__} name:{self.name} source:{self.source}>"
            return f"<{self.__class__} name:{self.name}>"

        def json_enc(self):
            sub_items = {}
            values = {f"MenuLayout.{self.__class__.__name__}": sub_items}
            for index in dir(self):
                if not index.startswith("_"):
                    item = getattr(self, index)
                    if item is not None and not callable(item):
                        sub_items[index] = item
            return values

    class Menu(MenuLayoutItem):
        def __init__(self, name, items=[], source=None, source_search=LayoutSourceSearch.EVERYWHERE, remove=False):
            super().__init__(name, source, source_search)
            self.items = items
            self.remove = remove

    class SubMenu(MenuLayoutItem):
        def __init__(self, name, items=[], source=None, source_search=LayoutSourceSearch.EVERYWHERE, remove=False):
            super().__init__(name, source, source_search)
            self.items = items
            self.remove = remove

    class Item(MenuLayoutItem):
        def __init__(self, name, source=None, source_search=LayoutSourceSearch.EVERYWHERE, remove=False):
            super().__init__(name, source, source_search)
            self.remove = remove

    class Seperator(MenuLayoutItem):
        def __init__(self, name=None, source=None, source_search=LayoutSourceSearch.EVERYWHERE):
            super().__init__(name, source, source_search)

    class Group(MenuLayoutItem):
        def __init__(self, name, items=[], source=None, source_search=LayoutSourceSearch.EVERYWHERE):
            super().__init__(name, source, source_search)
            self.items = items

    class Sort(MenuLayoutItem):
        def __init__(self, name=None, source=None, source_search=LayoutSourceSearch.EVERYWHERE, exclude_items=[], sort_submenus=False):
            super().__init__(name, source)
            self.items = exclude_items
            self.sort_submenus = sort_submenus

        def __repr__(self):
            return f"<{self.__class__} items:{self.items}>"


    def __init__(self, debuglog = False):
        self._layout_template = []
        self._menus_created = False
        self._debuglog = debuglog

    def __del__(self):
        self.destroy()

    def destroy(self):
        self._layout_template = None

    def menus_created(self):
        self._menus_created = True

    def add_layout(self, layout: List[MenuLayoutItem]):
        self._layout_template.append(layout.copy())

    def remove_layout(self, layout: List[MenuLayoutItem]):
        self._layout_template.remove(layout)

    def get_layout(self):
        return self._layout_template

    def apply_layout(self, prebuilt_menus: dict):
        if not self._menus_created:
            return

        menu_layout_lists = self.get_layout()

        for menu_layout in menu_layout_lists:
            MenuLayout.process_layout(prebuilt_menus, menu_layout, menu_name=None, submenu_name=None, parent_layout_offset=0, parent_layout_index=PrebuiltItemOrder.UNORDERED, debuglog=self._debuglog)

    # static functions to prevent self leaks
    @staticmethod
    def find_menu_item(menu_items: List, menu_items_root: List, layout_item: MenuLayoutItem):
        if layout_item.source:
            if "/" in layout_item.source.replace("\\", "/"):
                sub_name = get_action_path(os.path.dirname(layout_item.source), "")
                sub_prefix = os.path.basename(layout_item.source)
                if sub_name in menu_items_root:
                    menu_subitems = menu_items_root[sub_name]["items"]
                    for item in menu_subitems:
                        if item.name == sub_prefix:
                            return item, menu_subitems, sub_name
                return None, None, None

            for item in menu_items:
                if item.name == layout_item.source:
                    return item, None, None
        else:
            for item in menu_items:
                if item.name == layout_item.name:
                    return item, None, None
            if layout_item.source_search == LayoutSourceSearch.EVERYWHERE:
                # not in current menu, search them all....
                for sub_name in menu_items_root.keys():
                    menu_subitems = menu_items_root[sub_name]["items"]
                    for item in menu_subitems:
                        if item.name == layout_item.name:
                            layout_item.source = f"{sub_name}/{layout_item.name}"
                            return item, menu_subitems, sub_name
        return None, None, None

    @staticmethod
    def process_layout(prebuilt_menus: dict, menu_layout:List, menu_name: str, submenu_name: str, parent_layout_offset: int, parent_layout_index: int, debuglog: bool):
        def item_in_menu(menus, menu_name):
            for sub_item in menus:
                if sub_item.name == menu_name:
                    return True
            return False
        def create_menu_entry(menu_name, submenu_name):
            action_prefix = get_action_path(menu_name, submenu_name)
            if not action_prefix in prebuilt_menus:
                if menu_name in prebuilt_menus:
                    menu_subitems = prebuilt_menus[menu_name]["items"]
                    # if item not already in submenu, add it
                    if not item_in_menu(menu_subitems, submenu_name):
                        item = MenuItemDescription(submenu_name, sub_menu=action_prefix)
                        item.user["prebuilt_order"] = PrebuiltItemOrder.LAYOUT_ORDERED + parent_layout_offset + parent_layout_index
                        menu_subitems.append(item)

                create_prebuild_entry(prebuilt_menus, submenu_name, action_prefix)
                prebuilt_menus[action_prefix]["sub_menu"] = True

        for layout_index, layout_item in enumerate(menu_layout):
            # MenuLayout.Menu
            if isinstance(layout_item, MenuLayout.Menu):
                action_prefix = get_action_path(layout_item.name, None)
                if layout_item.remove:
                    if action_prefix in prebuilt_menus:
                        del prebuilt_menus[action_prefix]
                    elif debuglog:
                        carb.log_warn(f"Warning: Layout item {layout_item} not found in prebuilt_menus")
                else:
                    # menu doesn't exist, create one
                    if not action_prefix in prebuilt_menus:
                        carb.log_warn(f"Menu {layout_item.name} not found. Create with; (\"menu_index\" controls menu order)")
                        carb.log_warn(f"  self._menu_placeholder = omni.kit.menu.utils.add_menu_items([MenuItemDescription(name=\"placeholder\", show_fn=lambda: False)], name=\"{layout_item.name}\", menu_index=90)")
                        continue

                    MenuLayout.process_layout(prebuilt_menus, layout_item.items, layout_item.name, submenu_name, 0, layout_index, debuglog)

            # MenuLayout.SubMenu
            elif isinstance(layout_item, MenuLayout.SubMenu):
                if not menu_name:
                    carb.log_warn(f"Warning: Bad Layout item {layout_item}. Cannot have SubMenu without Menu as parent")

                if layout_item.remove:
                    action_prefix = get_action_path(menu_name, layout_item.name)
                    if action_prefix in prebuilt_menus:
                        del prebuilt_menus[action_prefix]
                        menu_subitems = prebuilt_menus[menu_name]["items"]
                        for item in menu_subitems:
                            if item.sub_menu == layout_item.name:
                                menu_subitems.remove(item)
                                break
                    elif debuglog:
                        carb.log_warn(f"Warning: Layout item {layout_item} not found in prebuilt_menus")
                else:
                    action_prefix = get_action_path(menu_name, None)
                    if action_prefix in prebuilt_menus:
                        menu_subitems = prebuilt_menus[action_prefix]["items"]
                        menu_subitem, source_menu, _ = MenuLayout.find_menu_item(menu_subitems, prebuilt_menus, layout_item)
                        if menu_subitem:
                            menu_subitem.user["prebuilt_order"] = PrebuiltItemOrder.LAYOUT_ORDERED + parent_layout_offset + layout_index
                        else:
                            # add submenu item
                            menu_subitem = MenuItemDescription(name=layout_item.name, sub_menu=get_action_path(menu_name, layout_item.name))
                            menu_subitems.append(menu_subitem)
                            menu_subitem.user["prebuilt_order"] = PrebuiltItemOrder.LAYOUT_ORDERED + parent_layout_offset + layout_index

                    if submenu_name:
                        action_prefix = get_action_path(menu_name, submenu_name)
                        MenuLayout.process_layout(prebuilt_menus, layout_item.items, action_prefix, layout_item.name, parent_layout_offset, layout_index + 1, debuglog)
                    else:
                        MenuLayout.process_layout(prebuilt_menus, layout_item.items, menu_name, layout_item.name, parent_layout_offset, layout_index, debuglog)
                    parent_layout_offset += len(layout_item.items) + 1

            # MenuLayout.Item
            elif isinstance(layout_item, MenuLayout.Item):
                action_prefix = get_action_path(menu_name, submenu_name)
                create_menu_entry(menu_name, submenu_name)
                menu_subitems = prebuilt_menus[action_prefix]["items"]
                menu_subitem, source_menu, orig_root_menu = MenuLayout.find_menu_item(menu_subitems, prebuilt_menus, layout_item)
                if not submenu_name and orig_root_menu:
                    prebuilt_menus[orig_root_menu]["remapped"].append(action_prefix)
                if menu_subitem:
                    menu_subitem.user["prebuilt_order"] = PrebuiltItemOrder.LAYOUT_ORDERED + parent_layout_offset + layout_index
                    if source_menu and source_menu != menu_subitems:
                        menu_subitems.append(menu_subitem)
                        source_menu.remove(menu_subitem)
                        if layout_item.source:
                            menu_subitem.name = layout_item.name
                    else:
                        if layout_item.remove:
                            menu_subitems.remove(menu_subitem)
                        elif layout_item.source:
                            menu_subitem.name = layout_item.name
                elif debuglog:
                    carb.log_warn(f"Warning: Layout not found {layout_item}")

            # MenuLayout.Seperator
            elif isinstance(layout_item, MenuLayout.Seperator):
                action_prefix = get_action_path(menu_name, submenu_name)
                if action_prefix in prebuilt_menus:
                    menu_subitems = prebuilt_menus[action_prefix]["items"]
                    item = MenuItemDescription()
                    if layout_item.name:
                        item.header = layout_item.name
                    item.user["prebuilt_order"] = PrebuiltItemOrder.LAYOUT_ORDERED + parent_layout_offset + layout_index
                    menu_subitems.append(item)

            # MenuLayout.Group
            elif isinstance(layout_item, MenuLayout.Group):
                action_prefix = get_action_path(menu_name, submenu_name)
                create_menu_entry(menu_name, submenu_name)

                menu_subitems = prebuilt_menus[action_prefix]["items"]
                item = MenuItemDescription(header=layout_item.name)
                item.user["prebuilt_order"] = PrebuiltItemOrder.LAYOUT_ORDERED + parent_layout_offset + layout_index
                menu_subitems.append(item)
                MenuLayout.process_layout(prebuilt_menus, layout_item.items, menu_name, submenu_name, parent_layout_offset + layout_index + 1, parent_layout_index, debuglog)
                parent_layout_offset += len(layout_item.items) + 1

                if layout_item.source:
                    menu_subitem, source_menu, _ = MenuLayout.find_menu_item(menu_subitems, prebuilt_menus, layout_item)
                    if source_menu and source_menu != menu_subitems:
                        if menu_subitem.sub_menu:
                            for item in prebuilt_menus[menu_subitem.sub_menu]["items"]:
                                item.user["prebuilt_order"] = PrebuiltItemOrder.LAYOUT_ORDERED + parent_layout_offset + layout_index
                                menu_subitems.append(item)
                                parent_layout_offset += 1
                        else:
                            menu_subitems.append(menu_subitem)
                        source_menu.remove(menu_subitem)

            # MenuLayout.Sort
            elif isinstance(layout_item, MenuLayout.Sort):
                action_prefix = get_action_path(menu_name, submenu_name)
                create_menu_entry(menu_name, submenu_name)
                menu_subitems = prebuilt_menus[action_prefix]["items"]
                sort_submenus = []
                items = [i for i in menu_subitems if i.name != "" and not i.name in layout_item.items]
                for index, item in enumerate(sorted(items, key=lambda a: a.name)):
                    item_offset = PrebuiltItemOrder.LAYOUT_SUBMENU_SORTED if item.sub_menu else PrebuiltItemOrder.LAYOUT_ITEM_SORTED
                    item.user["prebuilt_order"] = item_offset + parent_layout_offset + index
                    if layout_item.sort_submenus and item.sub_menu and not item.sub_menu in layout_item.items:
                        sort_submenus.append(item.sub_menu)

                for sub_menu in sort_submenus:
                    item_list = prebuilt_menus[sub_menu]["items"]
                    # seperators must not move, just sort items inbetween
                    groups = []
                    last_seperator = 0
                    for index, item in enumerate(item_list):
                        if item.name == "":
                            if last_seperator != index:
                                groups.append((last_seperator, index))
                            last_seperator = index
                    if last_seperator:
                        groups.append((last_seperator, len(item_list)))
                        for gstart, gend in groups:
                            items = [i for i in item_list[gstart : gend] if not i.name in layout_item.items]
                            item_offset = items[0].user["prebuilt_order"]
                            items = items[1:]
                            for index, item in enumerate(sorted(items, key=lambda a: a.name)):
                                item.user["prebuilt_order"] = item_offset + index
                                if item.sub_menu and not item.sub_menu in layout_item.items:
                                    sort_submenus.append(item.sub_menu)
                    else:
                        items = [i for i in item_list if not i.name in layout_item.items]
                        for index, item in enumerate(sorted(items, key=lambda a: a.name)):
                            item_offset = PrebuiltItemOrder.LAYOUT_SUBMENU_SORTED if item.sub_menu else PrebuiltItemOrder.LAYOUT_ITEM_SORTED
                            item.user["prebuilt_order"] = item_offset + parent_layout_offset + index
                            if item.sub_menu and not item.sub_menu in layout_item.items:
                                sort_submenus.append(item.sub_menu)
            elif debuglog:
                carb.log_warn(f"Warning: Unknown layout type {layout_item}")
