
__all__ = ["WindowItem", "WindowListModel", "InspectorTreeView"]

from typing import List, cast
import asyncio
import omni.ui as ui
import omni.kit.app
import carb

import carb.events
from .inspector_tree_model import InspectorTreeModel, WidgetItem
from .inspector_tree_delegate import InspectorTreeDelegate
from omni.ui_query import OmniUIQuery

from pathlib import Path

ICON_PATH = Path(__file__).parent.parent.parent.parent.parent.parent.joinpath("icons")

DARK_BACKGROUND = 0xFF333333


class WindowItem(ui.AbstractItem):
    def __init__(self, window_name: str, display_name: str = ""):
        super().__init__()
        self.model = ui.SimpleStringModel(display_name if display_name else window_name)
        self.window_name = window_name


class WindowListModel(ui.AbstractItemModel):
    def __init__(self):
        super().__init__()

        self._current_index = ui.SimpleIntModel()
        self._current_index.add_value_changed_fn(lambda a: self._item_changed(None))

        self._items = []
        self._forbidden_windows = ["Inspector"]

        async def refresh_later():
            await omni.kit.app.get_app().next_update_async()
            self.refresh()

        asyncio.ensure_future(refresh_later())

    def refresh(self):
        self._items = []
        window_list = []
        for w in ui.Workspace.get_windows():
            if isinstance(w, ui.Window) and not w.title in self._forbidden_windows and w.visible:
                window_list.append(w)

        window_list = sorted(window_list, key=lambda a: a.title)

        for index, w in enumerate(window_list):
            self._items.append(WindowItem(w.title))
            if w.title == "DemoWindow":
                self._current_index.set_value(index)

        self._item_changed(None)

    def get_current_window(self) -> str:
        index = self.get_item_value_model(None, 0).as_int
        if not self._items:
            print ("no other windows available!")
            return None

        item = self._items[index]
        return item.window_name

    def get_item_children(self, item):
        return self._items

    def get_item_value_model(self, item, column_id):
        if item is None:
            return self._current_index
        return item.model


class InspectorTreeView:
    def destroy(self):
        self._window = None
        self._frame = None
        self._model = None
        self._delegate.set_window(None)

    def __init__(self, window: ui.Window, update_window_cb: callable, **kwargs):
        self._delegate = InspectorTreeDelegate()
        self._preview_window = None
        self._tree_view = None
        self._model = None

        self._sender_id = hash("InspectorTreeView") & 0xFFFFFFFF
        self._window = window
        self._frame = window.frame if window else None
        self._update_window_cb = update_window_cb

        self._main_stack = ui.VStack()
        self._update_window(window)
        self._build_ui()

        app = omni.kit.app.get_app()
        self._update_sub_sel = app.get_message_bus_event_stream().create_subscription_to_pop(
            self.on_selection_changed, name="Inspector Selection Changed"
        )

        self._update_sub_exp = app.get_message_bus_event_stream().create_subscription_to_pop(
            self.on_expansion_changed, name="Inspector Expand"
        )

    def on_expansion_changed(self, event: carb.events.IEvent):
        if not self._model:
            # carb.log_error("NO Model setup")
            return

        if event.sender == self._sender_id:
            # we don't respond to our own changes
            return

        from ..inspector_widget import INSPECTOR_ITEM_EXPANDED_ID

        def set_expansion_state(item, exp_state):
            for c in item.children:
                c.expanded = exp_state
                set_expansion_state(c, exp_state)

        if event.type == INSPECTOR_ITEM_EXPANDED_ID:
            expansion_path = OmniUIQuery.find_widget(event.payload["item_path"])
            expansion_item = cast(ui.AbstractItem, self._model.get_item_for_widget(expansion_path))
            expand = event.payload["expand"]
            self.set_expanded(expansion_item, expand, True)
            expansion_item.expanded = expand
            set_expansion_state(expansion_item, expand)

    def on_selection_changed(self, event: carb.events.IEvent):
        if not self._model:
            # carb.log_error("NO Model setup")
            return

        if event.sender == self._sender_id:
            # we don't respond to our own changes
            return

        from ..inspector_widget import INSPECTOR_ITEM_SELECTED_ID

        if event.type == INSPECTOR_ITEM_SELECTED_ID:
            selection = OmniUIQuery.find_widget(event.payload["item_path"])
            if selection:
                selection_item, items = cast(ui.AbstractItem, self._model.get_item_with_path_for_widget(selection))
                if selection_item:
                    if len(self._tree_view.selection) == 1 and self._tree_view.selection[0] != selection_item:
                        for item in reversed(items):
                            self._tree_view.set_expanded(item, True, False)
                    self._selection = [selection_item]
                    self._tree_view.selection = self._selection

    def _update_window(self, window: ui.Window):
        self._window = window
        self._frame = window.frame if window else None
        if self._update_window_cb:
            self._update_window_cb(window)

        self._model = InspectorTreeModel(window)
        self._delegate.set_window(window)
        self._selection: List[WidgetItem] = []

        if self._tree_view:
            self._tree_view.model = self._model

    def toggle_visibility(self):
        self._main_stack.visible = not self._main_stack.visible

    def set_expanded(self, item: WidgetItem, expanded: bool, recursive: bool = False):
        """
        Sets the expansion state of the given item.

        Args:
            item (:obj:`WidgetItem`): The item to effect.
            expanded (bool): True to expand, False to collapse.
            recursive (bool): Apply state recursively to descendent nodes. Default False.

        """
        if self._tree_view and item:
            self._tree_view.set_expanded(item, expanded, recursive)

    def _build_ui(self):
        with self._main_stack:
            with ui.ZStack(height=0):
                ui.Rectangle(style={"background_color": DARK_BACKGROUND})
                with ui.VStack():
                    ui.Spacer(height=5)
                    with ui.HStack(height=0):
                        ui.Spacer(width=5)
                        ui.Label("Windows", height=20, width=0)
                        ui.Spacer(width=10)

                        def combo_changed_fn(model: WindowListModel, item: WindowItem):
                            window_name = model.get_current_window()
                            if window_name:
                                window: ui.Window = ui.Workspace.get_window(window_name)
                                if window:
                                    self._update_window(window)

                        self._window_list_model = WindowListModel()
                        self._window_list_model.add_item_changed_fn(combo_changed_fn)

                        ui.ComboBox(self._window_list_model, identifier="WindowListComboBox")
                        ui.Spacer(width=5)

                        def refresh_windows():
                            self._window_list_model.refresh()

                        ui.Button(
                            image_url=f"{ICON_PATH}/sync.svg",
                            style={"margin": 0},
                            width=19,
                            height=19,
                            clicked_fn=refresh_windows,
                        )
                        ui.Spacer(width=5)
                    ui.Spacer(height=10)

            with ui.ScrollingFrame(
                horizontal_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_OFF,
                style_type_name_override="TreeView.ScrollingFrame",
                style={"TreeView.ScrollingFrame": {"background_color": 0xFF23211F}},
            ):
                with ui.VStack():
                    ui.Spacer(height=5)
                    self._tree_view = ui.TreeView(
                        self._model,
                        delegate=self._delegate,
                        column_widths=[ui.Fraction(1), 40],
                        header_visible=False,
                        root_visible=False,
                        selection_changed_fn=self._widget_selection_changed,
                        identifier="WidgetInspectorTree"
                    )

    def _widget_selection_changed(self, selection: list):
        if len(selection) == 0:
            self._selection = selection
            return

        if not selection[0]:
            return

        if len(self._selection) > 0 and self._selection[0] == selection[0]:
            return

        self._selection = selection
        first_widget_path = self._selection[0].path

        # Send the Selection Changed Message
        from ..inspector_widget import INSPECTOR_ITEM_SELECTED_ID

        stream = omni.kit.app.get_app().get_message_bus_event_stream()
        stream.push(INSPECTOR_ITEM_SELECTED_ID, sender=self._sender_id, payload={"item_path": first_widget_path})
        return
        for a_selection in self._selection:
            widget: ui.Widget = a_selection.widget

            print("style", widget.get_style())

            print(widget.checked)
            print(widget.computed_content_width)
            print(widget.computed_content_height)

            print(widget.computed_height)
            print(widget.computed_width)

            print(widget.width)
            print(widget.height)
            print(widget.screen_position_x)
            print(widget.screen_position_y)

            properties = ["font_size", "border_width", "border_color", "alignment"]

            color = widget.get_resolved_style_value("color")
            if color:
                print("color", hex(color))

            bg_color = widget.get_resolved_style_value("background_color")
            if bg_color:
                print("background_color", hex(bg_color))

            for a_style in properties:
                print(a_style, widget.get_resolved_style_value(a_style))

            # widget.set_style({"debug_color": 0x1100DD00})
