import carb
import carb.settings
import omni.kit.app
import omni.ui as ui
from ..preferences_window import PreferenceBuilder, SettingType, PERSISTENT_SETTINGS_PREFIX
from .developer_page import THREAD_SYNC_PRESETS # kit-extensions/kit-scene extension access this
from typing import Any
from typing import Dict
from typing import Optional


class RenderingPreferences(PreferenceBuilder):
    def post_notification(message: str, info: bool = False, duration: int = 3):
        import omni.kit.notification_manager as nm

        if info:
            type = nm.NotificationStatus.INFO
        else:
            type = nm.NotificationStatus.WARNING

        nm.post_notification(message, status=type, duration=duration)

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

        self._persistentDistillMaterialPath = PERSISTENT_SETTINGS_PREFIX + "/rtx/mdltranslator/distillMaterial"
        self._persistentMultiGPUPath = PERSISTENT_SETTINGS_PREFIX + "/renderer/multiGpu/enabled"
        self._persistentOpacityMicromapPath = PERSISTENT_SETTINGS_PREFIX + "/renderer/raytracingOmm/enabled"

        settings = carb.settings.get_settings()
        if settings.get(self._persistentDistillMaterialPath) is None:
            settings.set_default_bool(self._persistentDistillMaterialPath, False)

        self._sub_material_distilling_changed = omni.kit.app.SettingChangeSubscription(
            self._persistentDistillMaterialPath,
            self._on_material_distilling_changed
        )

        self._sub_opacity_micromap_changed = omni.kit.app.SettingChangeSubscription(
            self._persistentOpacityMicromapPath,
            self._on_opacity_micromap_changed
        )

        self._persistentPlaceholderTextureColorPath = PERSISTENT_SETTINGS_PREFIX + "/rtx/resourcemanager/placeholderTextureColor"
        if settings.get(self._persistentPlaceholderTextureColorPath) is None:
            settings.set_float_array(self._persistentPlaceholderTextureColorPath, [0,1,1])
        # Note: No SettingChangeSubscription because it shows the same popup like 6 times when the color is changed

        self._enableFabricSceneDelegatePath = "/app/useFabricSceneDelegate"
        self._sub_fabric_delegate_changed = omni.kit.app.SettingChangeSubscription(
            self._enableFabricSceneDelegatePath,
            self._on_fabric_delegate_changed
        )

        self._fabricMemBudgetPath = "/app/usdrt/scene_delegate/gpuMemoryBudgetPercent"
        if settings.get(self._fabricMemBudgetPath) is None:
            settings.set_default_float(self._fabricMemBudgetPath, 90)

        self._fabricEnableGeometryStreaming = "/app/usdrt/scene_delegate/geometryStreaming/enabled"
        if settings.get(self._fabricEnableGeometryStreaming) is None:
            settings.set_default_bool(self._fabricEnableGeometryStreaming, True)

        self._fabricEnableGeometryStreamingMinSize = "/app/usdrt/scene_delegate/geometryStreaming/solidAngleLimit"
        if settings.get(self._fabricEnableGeometryStreamingMinSize) is None:
            settings.set_default_float(self._fabricEnableGeometryStreamingMinSize, 0.)

        self._fabricEnableProxyCubes = "/app/usdrt/scene_delegate/enableProxyCubes"
        if settings.get(self._fabricEnableProxyCubes) is None:
            settings.set_default_bool(self._fabricEnableProxyCubes, False)

        self._fabricMergeSubcomponents = "/app/usdrt/population/utils/mergeSubcomponents"
        if settings.get(self._fabricMergeSubcomponents) is None:
            settings.set_default_bool(self._fabricMergeSubcomponents, False)

        self._fabricMergeInstances = "/app/usdrt/population/utils/mergeInstances"
        if settings.get(self._fabricMergeInstances) is None:
            settings.set_default_bool(self._fabricMergeInstances, False)

        self._fabricMergeMaterials = "/app/usdrt/population/utils/mergeMaterials"
        if settings.get(self._fabricMergeMaterials) is None:
            settings.set_default_bool(self._fabricMergeMaterials, True)

        self._fabricReadMaterials = "/app/usdrt/population/utils/readMaterials"
        if settings.get(self._fabricReadMaterials) is None:
            settings.set_default_bool(self._fabricReadMaterials, True)

        self._fabricReadLights = "/app/usdrt/population/utils/readLights"
        if settings.get(self._fabricReadLights) is None:
            settings.set_default_bool(self._fabricReadLights, True)

        self._fabricReadPrimvars = "/app/usdrt/population/utils/readPrimvars"
        if settings.get(self._fabricReadPrimvars) is None:
            settings.set_default_bool(self._fabricReadPrimvars, True)

        self._fabricInferDisplayColorFromMaterial = "/app/usdrt/population/utils/inferDisplayColorFromMaterial"
        if settings.get(self._fabricInferDisplayColorFromMaterial) is None:
            settings.set_default_bool(self._fabricInferDisplayColorFromMaterial, False)

        self._fabricHandleSceneGraphInstances = "/app/usdrt/population/utils/handleSceneGraphInstances"
        if settings.get(self._fabricHandleSceneGraphInstances) is None:
            settings.set_default_bool(self._fabricHandleSceneGraphInstances, True)

        self._fabricUseHydraBlendShape = "/app/usdrt/scene_delegate/useHydraBlendShape"
        if settings.get(self._fabricUseHydraBlendShape) is None:
            settings.set_default_bool(self._fabricUseHydraBlendShape, False)

    def build(self):
        with ui.VStack(height=0):

            """ Hydra Scene Delegate """
            with self.add_frame("Fabric Scene Delegate"):
                with ui.VStack():
                    self.create_setting_widget("Enable Fabric delegate (preview feature, requires scene reload)",
                        self._enableFabricSceneDelegatePath,
                        SettingType.BOOL,
                        tooltip="Enable Fabric Hydra scene delegate for faster load times on large scenes.\n"
                                "This is a preview release of this new feature and some scene interactions will be limited.\n"
                                "You should expect faster load times with geometry streaming, faster playback of USD animation, "
                                "and GPU memory staying under a predefined budget.")
                    self.create_setting_widget("Fabric delegate GPU memory budget %",
                        self._fabricMemBudgetPath,
                        SettingType.FLOAT,
                        range_from=0,
                        range_to=100,
                        tooltip="Fabric Scene Delegate will stop loading geometry when this threshold of available GPU memory is reached.")
                    with ui.CollapsableFrame(title="Advanced Fabric Scene Delegate Settings", collapsed=True):
                        with ui.VStack():
                            self.create_setting_widget("Enable Geometry Streaming in Fabric Scene Delegate",
                                self._fabricEnableGeometryStreaming,
                                SettingType.BOOL,
                                tooltip="This enables the progressive and sorted loading of geometry, as well as the device memory limit checks.")
                            self.create_setting_widget("Geo Streaming Minimum Size",
                                self._fabricEnableGeometryStreamingMinSize,
                                SettingType.FLOAT,
                                range_from=0,
                                range_to=1,
                                range_step=0.0001,
                                tooltip="This sets a minimum relative size on screen for objects to be loaded, 0 loads everything.")
                            self.create_setting_widget("Enable proxy cubes for unloaded prims",
                                self._fabricEnableProxyCubes,
                                SettingType.BOOL,
                                tooltip="This helps visualizing scene content when having low device memory, but can have performance impact for very large scenes.")
                            self.create_setting_widget("Merge subcomponents",
                                self._fabricMergeSubcomponents,
                                SettingType.BOOL,
                                tooltip="Fabric Scene Delegate will merge all meshes within USD subcomponents.\n"
                                        "This does NOT affect the USD stage, only the Fabric representation.")
                            self.create_setting_widget("Merge instances",
                                self._fabricMergeInstances,
                                SettingType.BOOL,
                                tooltip="Fabric Scene Delegate will merge all meshes within USD scene graph instances.\n"
                                        "This does NOT affect the USD stage, only the Fabric representation.")
                            self.create_setting_widget("Merge materials",
                                self._fabricMergeMaterials,
                                SettingType.BOOL,
                                tooltip="Fabric Scene Delegate will identify unique materials and drop all duplicates.\n"
                                        "This does NOT affect the USD stage, only the Fabric representation.")
                            self.create_setting_widget("Read materials",
                                self._fabricReadMaterials,
                                SettingType.BOOL,
                                tooltip="When off, Fabric Scene Delegate will not read any material from USD, which can speed up load time.\n"
                                        "This does NOT affect the USD stage, only the Fabric representation.")
                            self.create_setting_widget("Infer displayColor from material",
                                self._fabricInferDisplayColorFromMaterial,
                                SettingType.BOOL,
                                tooltip="When on, Fabric Scene Delegate will read material info to infer mesh displayColor.\n"
                                        "This does NOT affect the USD stage, only the Fabric representation.")
                            self.create_setting_widget("Read lights",
                                self._fabricReadLights,
                                SettingType.BOOL,
                                tooltip="When off, Fabric Scene Delegate will not read any lights from USD.\n"
                                        "This does NOT affect the USD stage, only the Fabric representation.")
                            self.create_setting_widget("Read primvars",
                                self._fabricReadPrimvars,
                                SettingType.BOOL,
                                tooltip="When off, Fabric Scene Delegate will not read any mesh primvar from USD.\n"
                                        "This does NOT affect the USD stage, only the Fabric representation.")
                            self.create_setting_widget("Use Fabric Scene Graph Instancing",
                                self._fabricHandleSceneGraphInstances,
                                SettingType.BOOL,
                                tooltip="When off, Fabric Scene Delegate will ignore USD Scene Graph Instances.\n"
                                        "Each instanced geometry will be duplicated in Fabric.")
                            self.create_setting_widget("Use Hydra BlendShape",
                                self._fabricUseHydraBlendShape,
                                SettingType.BOOL,
                                tooltip="Fabric Scene Delegate will compute hydra BlendShape.\n"
                                        "This will be effective after next stage loading.")


#define USDRT_POPULATION_UTILS_INFERDISPLAYCOLORFROMMATERIAL "/app/usdrt/population/utils/"

            self.spacer()

            """ White Mode """
            with self.add_frame("White Mode"):
                with ui.VStack():
                    widget = self.create_setting_widget("Material", "/rtx/debugMaterialWhite", SettingType.STRING)
                    widget.enabled = False
                    self.create_setting_widget(
                        "Exceptions (Requires Scene Reload)",
                        PERSISTENT_SETTINGS_PREFIX + "/app/rendering/whiteModeExceptions",
                        SettingType.STRING,
                    )

            self.spacer()

            """ MDL """
            with self.add_frame("MDL"):
                with ui.VStack():
                    self.create_setting_widget(
                        "Material Distilling (Experimental, requires app restart)",
                        self._persistentDistillMaterialPath,
                        SettingType.BOOL,
                        tooltip="Enables transforming MDL materials of arbitrary complexity to predefined target material models, which can improve material rendering fidelity in RTX Real-Time mode."
                                "\nImproves rendering fidelity of complex materials such as Clear Coat in RTX Real-Time mode."
                                "\nRequires app restart to take effect."
                    )

            self.spacer()

            """ Texture Streaming """
            with self.add_frame("Texture Streaming"):
                with ui.VStack():
                    self.create_setting_widget(
                        "Placeholder Texture Color (requires app restart)",
                        self._persistentPlaceholderTextureColorPath,
                        SettingType.COLOR3,
                        tooltip="Sets the color of the placeholder texture which is used while actual textures are loaded."
                                "\nRequires app restart to take effect."
                    )

            self.spacer()

            """ Multi-GPU """
            with self.add_frame("Multi-GPU"):
                with ui.VStack():
                    with ui.HStack(height=24):
                        self.label("Multi-GPU")

                        settings = carb.settings.get_settings()
                        mgpu = str(settings.get(self._persistentMultiGPUPath))
                        index = 0
                        if mgpu == "True":
                            index = 1
                        elif mgpu == "False":
                            index = 2
                        widget = ui.ComboBox(index, "Auto", "True", "False")
                        widget.model.add_item_changed_fn(self._on_multigpu_changed)

            self.spacer()

            """ Opacity MicroMap """
            with self.add_frame("Opacity MicroMap"):
                with ui.VStack():
                    self.create_setting_widget(
                        "Enable Opacity MicroMap",
                        self._persistentOpacityMicromapPath,
                        SettingType.BOOL,
                        tooltip="Opacity MicroMaps improve efficiency of rendering translucent objects."
                                "\nThis feature requires an Ada Lovelace architecture GPU."
                                "\nRequires app restart to take effect."
                    )

    def _on_opacity_micromap_changed(self, value: bool, event_type: carb.settings.ChangeEventType):
        if event_type == carb.settings.ChangeEventType.CHANGED:
            msg = "Opacity MicroMap settings has been changed. You will need to restart Omniverse for this to take effect."
            try:
                import asyncio
                import omni.kit.notification_manager
                import omni.kit.app

                async def show_msg():
                    await omni.kit.app.get_app().next_update_async()
                    omni.kit.notification_manager.post_notification(msg, hide_after_timeout=False)

                asyncio.ensure_future(show_msg())
            except:
                carb.log_warn(msg)

    def _on_material_distilling_changed(self, value: bool, event_type: carb.settings.ChangeEventType):
        if event_type == carb.settings.ChangeEventType.CHANGED:
            msg = "Material distilling settings has been changed. You will need to restart Omniverse for this to take effect."
            try:
                import asyncio
                import omni.kit.notification_manager
                import omni.kit.app

                async def show_msg():
                    await omni.kit.app.get_app().next_update_async()
                    omni.kit.notification_manager.post_notification(msg, hide_after_timeout=False)

                asyncio.ensure_future(show_msg())
            except:
                carb.log_warn(msg)

    def _on_fabric_delegate_changed(self, value: str, event_type: carb.settings.ChangeEventType):
        import omni.usd

        if event_type == carb.settings.ChangeEventType.CHANGED:
            stage = omni.usd.get_context().get_stage()
            if not stage or stage.GetRootLayer().anonymous:
                return

            msg = "Hydra scene delegate changed. You will need to reload your stage for this to take effect."
            try:
                import asyncio
                import omni.kit.notification_manager
                import omni.kit.app

                async def show_msg():
                    await omni.kit.app.get_app().next_update_async()
                    omni.kit.notification_manager.post_notification(msg, hide_after_timeout=True, duration=2)

                asyncio.ensure_future(show_msg())
            except:
                carb.log_warn(msg)

    def _on_multigpu_changed(self, model, item):
        current_index = model.get_item_value_model().as_int
        settings = carb.settings.get_settings()
        if current_index == 0:
            # this is a string not a bool and will have no effect
            settings.set_string(self._persistentMultiGPUPath, "auto")
        elif current_index == 1:
            settings.set_bool(self._persistentMultiGPUPath, True)
        elif current_index == 2:
            settings.set_bool(self._persistentMultiGPUPath, False)

        # print restart message
        RenderingPreferences.post_notification(f"You need to restart {omni.kit.app.get_app().get_app_name()} for changes to take effect", info=True)
