import carb

from pxr import Sdf
from urllib.parse import unquote
from .layer_utils import LayerUtils
from .._omni_kit_usd_layers import (
    acquire_layers_state_interface,
    release_layers_state_interface,
    ILayersInstance,
)


class LayersState:
    def __init__(self, layers_instance: ILayersInstance, usd_context) -> None:
        self._layers_instance = layers_instance
        self._layers_state_interface = acquire_layers_state_interface()
        self._dictionary = carb.dictionary.get_dictionary()
        self._usd_context = usd_context

    @property
    def usd_context(self):
        return self._usd_context

    def destroy(self):
        self._layers_instance = None
        release_layers_state_interface(self._layers_state_interface)

    def set_muteness_scope(self, global_scope: bool):
        self._layers_state_interface.set_muteness_scope(self._layers_instance, global_scope)

    def is_muteness_global(self) -> bool:
        """
        Global muteness is an extended concept for Omniverse so muteness can be authored into
        USD for persistence. When you set muteness scope as global with set_muteness_scope,
        all muteness of sublayers will be stored to root layer's custom data and it will be
        loaded for next stage open.
        """
        
        return self._layers_state_interface.is_muteness_global(self._layers_instance)

    def is_layer_locally_muted(self, layer_identifier: str) -> bool:
        return self._layers_state_interface.is_layer_locally_muted(self._layers_instance, layer_identifier)

    def is_layer_globally_muted(self, layer_identifier: str) -> bool:
        """
        Checkes if layer is globally muted or not in this usd context.
        Global muteness is a customize concept in Kit that's not from USD. It's used for complement
        the USD muteness, that works for two purposes:
        1. It's stage bound. So a layer is globally muted in this stage will not influence others.
        2. It's persistent. Right now, it's saved inside the custom data of root layer.
        
        After stage load, it will be read to initialize the muteness of layers. Also, global muteness
        only takes effective when it's in global state mode. See omni.usd.UsdContext.set_layer_muteness_scope
        about how to switch muteness scope. When it's not in global state mode, authoring global
        muteness will not incluence layer's muteness in stage.
        """

        return self._layers_state_interface.is_layer_globally_muted(self._layers_instance, layer_identifier)

    def is_layer_writable(self, layer_identifier: str) -> bool:
        """
        Checkes if layer is writable. A layer is writable means it can be set as edit target, which should satisfy:
        1. It's not read-only on disk.
        2. It's not locked by set_layer_lock_state.
        3. It's not muted.
        It still can be set as edit target with scripts, while this can be used for guardrails.
        """

        return self._layers_state_interface.is_layer_writable(self._layers_instance, layer_identifier)
    
    def is_layer_readonly_on_disk(self, layer_identifier: str) -> bool:
        """
        Checkes if this layer is physically read-only on disk.
        """

        return self._layers_state_interface.is_layer_readonly_on_disk(self._layers_instance, layer_identifier)

    def is_layer_savable(self, layer_identifier: str) -> bool:
        """
        Checkes if this layer is savable. If it's savable means it's true by checking is_layer_writable and not anonymous.
        """

        return self._layers_state_interface.is_layer_savable(self._layers_instance, layer_identifier)

    def set_layer_lock_state(self, layer_identifier: str, locked: bool):
        """
        Layer lock is an extended concept in Omniverse that works for lock this layer temporarily without real change
        the file permission of this layer. It's just authored as a meta inside layer's custom data section, and read by
        UI.
        """

        self._layers_state_interface.set_layer_lock_state(self._layers_instance, layer_identifier, locked)

    def is_layer_locked(self, layer_identifier: str) -> bool:
        return self._layers_state_interface.is_layer_locked(self._layers_instance, layer_identifier)

    def set_layer_name(self, layer_identifier: str, name: str):
        self._layers_state_interface.set_layer_name(self._layers_instance, layer_identifier, name)

    def get_layer_name(self, layer_identifier: str) -> str:
        name = self._layers_state_interface.get_layer_name(self._layers_instance, layer_identifier)
        if not name:
            if Sdf.Layer.IsAnonymousLayerIdentifier(layer_identifier):
                layer = Sdf.Find(layer_identifier)
                if layer:
                    name = LayerUtils.get_custom_layer_name(layer)
                else:
                    name = layer_identifier
            else:
                name = Sdf.Layer.GetDisplayNameFromIdentifier(layer_identifier)

        return unquote(name)

    def is_layer_outdated(self, layer_identifier: str) -> bool:
        """
        If layer is out of sync. This only works for layer inside server.
        """

        return self._layers_state_interface.is_layer_outdated(self._layers_instance, layer_identifier)
    
    def _populate_all_identifiers(self, item: carb.dictionary.Item):
        all_layer_identifiers = []
        count = self._dictionary.get_item_child_count(item)
        for i in range(count):
            layer_item = self._dictionary.get_item_child_by_index(item, i)
            layer_identifier = self._dictionary.get_as_string(layer_item)
            all_layer_identifiers.append(layer_identifier)

        return all_layer_identifiers 

    def get_local_layer_identifiers(self, include_session_layers=False, include_anonymous_layers=True, include_invalid_layers=False):
        item = self._layers_state_interface.get_local_layer_identifiers(self._layers_instance, include_session_layers, include_anonymous_layers, include_invalid_layers)
        if not item:
            return []

        all_layer_identifiers = self._populate_all_identifiers(item)
        self._dictionary.destroy_item(item)
    
        return all_layer_identifiers
    
    def get_dirty_layer_identifiers(self):
        """Gets all layer identifiers that have pending edits that are not saved."""

        item = self._layers_state_interface.get_dirty_layer_identifiers(self._layers_instance)
        if not item:
            return []

        all_layer_identifiers = self._populate_all_identifiers(item)
        self._dictionary.destroy_item(item)
        
        return all_layer_identifiers
    
    def has_local_layer(self, layer_identifier):
        """Layer is in the local layer stack of current stage."""

        stage = self._usd_context.get_stage()
        if not stage:
            return False
        
        layer = Sdf.Find(layer_identifier)
        if not layer:
            return False
        
        return stage.HasLocalLayer(layer)
    
