__all__ = ["AbstractCameraButtonDelegate", "AbstractCameraMenuItemDelegate"]

import abc
import weakref
import omni.ui as ui

from enum import IntEnum
from pxr import Sdf
from typing import Optional


class AbstractWidgetDelegate(abc.ABC):
    """
    The base class for camera widget delegate. This delegate works for providing the
    additional camera widget for items of camera list.
    """

    @abc.abstractmethod
    def build_widget(self, parent_id, viewport_api, camera_path: Sdf.Path) -> Optional[ui.Widget]:
        """
        Builds widget. This must be overrided to provide the layout.

        parent_id: It's unique id of the camera item, with which you
            can use to manage the camera widget.
        viewport_api: Viewport interface.
        camera_path (Sdf.Path): The camera path to build widget for.
        """

        pass

    @abc.abstractmethod
    def on_parent_destroyed(self, parent_id) -> None:
        """
        Called before the destroy of the parent widget. This function
        can be overrided to release the bound resources specific to
        the parent.
        """

        pass

    @abc.abstractmethod
    def on_camera_path_changed(self, parent_id, camera_path):
        """The camera tracked has changed."""

        pass

    def destroy(self):
        """Place to release resources."""
        pass

    @property
    def order(self) -> int:
        """
        The order of the widget relative to the right of camera label.
        The lower the order number, the closer the widget to the camera label.
        """

        return 0


class AbstractCameraButtonDelegate(AbstractWidgetDelegate):
    # Use a list to track all instances
    __g_registered = []

    @classmethod
    def get_instances(cls):
        remove = []
        # Return a copy to avoid remove in the middle of iterating.
        all_instances = AbstractCameraButtonDelegate.__g_registered.copy()
        for wref in all_instances:
            obj = wref()
            if obj:
                yield obj
            else:
                remove.append(wref)

        for wref in remove:
            AbstractCameraButtonDelegate.__g_registered.remove(wref)

    def __init__(self):
        super().__init__()

        self.__g_registered.append(
            weakref.ref(self, lambda r: AbstractCameraButtonDelegate.__g_registered.remove(r))
        )

    def __del__(self):
        self.destroy()

    def destroy(self):
        """Place for releasing resources."""

        for wref in AbstractCameraButtonDelegate.__g_registered:
            if wref() == self:
                AbstractCameraButtonDelegate.__g_registered.remove(wref)
                break

        super().destroy()


class AbstractCameraMenuItemDelegate(AbstractWidgetDelegate):
    # Use a list to track all instances
    __g_registered = []

    @classmethod
    def get_instances(cls):
        remove = []
        # Return a copy to avoid remove in the middle of iterating.
        all_instances = AbstractCameraMenuItemDelegate.__g_registered.copy()
        for wref in all_instances:
            obj = wref()
            if obj:
                yield obj
            else:
                remove.append(wref)

        for wref in remove:
            AbstractCameraMenuItemDelegate.__g_registered.remove(wref)

    def __init__(self):
        super().__init__()

        self.__g_registered.append(
            weakref.ref(self, lambda r: AbstractCameraMenuItemDelegate.__g_registered.remove(r))
        )

    def __del__(self):
        self.destroy()

    def destroy(self):
        """Place for releasing resources."""

        for wref in AbstractCameraMenuItemDelegate.__g_registered:
            if wref() == self:
                AbstractCameraMenuItemDelegate.__g_registered.remove(wref)
                break

        super().destroy()


class CameraWidgetDelegateManager:

    def destroy(self):
        for delegate in AbstractCameraButtonDelegate.get_instances():
            delegate.destroy()

        for delegate in AbstractCameraMenuItemDelegate.get_instances():
            delegate.destroy()

    def __get_all_delegates(self, camera_button_or_menu_item: bool):
        all_widget_delegates = []

        if camera_button_or_menu_item:
            for delegate in AbstractCameraButtonDelegate.get_instances():
                all_widget_delegates.append(delegate)
        else:
            for delegate in AbstractCameraMenuItemDelegate.get_instances():
                all_widget_delegates.append(delegate)

        # Sort list in ascending order.
        all_widget_delegates.sort(key=lambda item: item.order)

    def build_widgets(
        self, parent_id, viewport_api, camera_path: Sdf.Path, camera_button_or_menu_item: bool
    ):
        """Build all camera widgets for specific camera path with sort based on their orders."""

        all_widget_delegates = self.__get_all_delegates(camera_button_or_menu_item)
        for delegate in all_widget_delegates:
            delegate.build_widget(parent_id, viewport_api, camera_path)

    def on_parent_destroyed(self, parent_id, camera_button_or_menu_item: bool) -> None:
        all_widget_delegates = self.__get_all_delegates(camera_button_or_menu_item)
        for delegate in all_widget_delegates:
            delegate.on_parent_destroyed(parent_id)

    def on_camera_path_changed(self, parent_id, camera_path, camera_button_or_menu_item: bool):
        all_widget_delegates = self.__get_all_delegates(camera_button_or_menu_item)
        for delegate in all_widget_delegates:
            delegate.on_camera_path_changed(parent_id, camera_path)