import asyncio
import carb
import omni.usd
import omni.kit.app
import omni.kit.usd.layers as layers
import omni.kit.notification_manager as nm
import omni.kit.collaboration.presence_layer as pl

from pxr import Sdf, Usd, Tf, UsdGeom

from .manipulator import PeerUserCamera
from .local_user_shared_data import LocalUserSharedData
from omni.kit.viewport.utility import get_active_viewport_window


class PeerUserSharedData:
    """
    It works to manage peer user's live camera inside users.live and camera label widget.
    """

    def __init__(
        self, usd_context: omni.usd.UsdContext,
        user_info: layers.LiveSessionUser,
        local_user_shared_data: LocalUserSharedData
    ):
        self.user_info = user_info
        self.usd_context = usd_context
        self.presence_layer = pl.get_presence_layer_interface(self.usd_context)
        
        # It's used to control the status of followed user.
        self.__local_user_shared_data = local_user_shared_data
        self.__camera_viewport_widget: PeerUserCamera = None
        self.__current_bound_camera_path = Sdf.Path.emptyPath
        self.__task_or_future = None
        self.__followed = False
    
    @property
    def user_id(self):
        return self.user_info.user_id
    
    @property
    def user_name(self):
        return self.user_info.user_name
    
    def destroy(self):
        self.followed = False
        self.usd_context = None
        self.destroy_camera_widget()
        self.__local_user_shared_data = None
        if self.__task_or_future:
            self.__task_or_future.cancel()
            self.__task_or_future = None
    
    def check_and_switch_widget_mode(self, viewport_camera_position=None):
        """Update widget to check if it's out of viewport and show offscreen pointers instead."""
        if self.__camera_viewport_widget:
            self.__camera_viewport_widget.on_view_translation_changed(viewport_camera_position)
    
    @property
    def widget_position(self):
        if self.__camera_viewport_widget:
            return self.__camera_viewport_widget.position
        
        return None
    
    @property
    def is_bound_to_builtin_camera(self):
        """If this user is bound to builtin camera, or prim in the local stage."""
        return self.presence_layer.is_bound_to_builtin_camera(self.user_id)
    
    @property
    def bound_camera_prim(self) -> Usd.Prim:
        """
        It will return the prim path in the local stage if property `is_bound_to_builtin_camera` is False.
        Or the replicated camera path in the session layer of local stage if it's bound to builtin camera.
        """

        return self.presence_layer.get_bound_camera_prim(self.user_id)
    
    def destroy_camera_widget(self):
        if self.__camera_viewport_widget:
            self.__camera_viewport_widget.destroy()
            self.__camera_viewport_widget = None

    def update_camera_widget_label(self, position=None):
        if self.__camera_viewport_widget:
            self.__camera_viewport_widget.on_world_translation_changed(position)
    
    def refresh_camera_widget(self, update_widget_label=True):
        if self.presence_layer.is_in_following_mode(self.user_id):
            self.destroy_camera_widget()
            return

        is_bound_to_builtin_camera = self.is_bound_to_builtin_camera
        bound_camera_prim = self.bound_camera_prim
        if not bound_camera_prim:
            bound_camera_path = Sdf.Path.emptyPath
        else:
            bound_camera_path = bound_camera_prim.GetPath()
        
        bound_camera_changed = self.__current_bound_camera_path != bound_camera_path
        if bound_camera_changed:
            # Unhide old tracked camera
            if self.__followed:
                self.__set_display_name_and_lock(self.__current_bound_camera_path, True)

            self.__current_bound_camera_path = bound_camera_path
            if not is_bound_to_builtin_camera and bound_camera_prim and self.__followed:
                self.__set_display_name_and_lock(bound_camera_path, False)
            
            if not bound_camera_prim:
                self.followed = False
            elif self.__followed:
                self.__switch_viewport_bound_camera(bound_camera_path, True)

        if not bound_camera_prim:
            self.destroy_camera_widget()
            return
        
        # Don't show label widget for followed user
        if not self.__followed:
            if not self.__camera_viewport_widget:
                self.__camera_viewport_widget = PeerUserCamera(self.user_info, bound_camera_path, self.usd_context)
            elif bound_camera_changed:
                self.__camera_viewport_widget.set_tracked_camera(bound_camera_path)
            elif update_widget_label:
                self.__camera_viewport_widget.on_world_translation_changed()
    
    @property
    def following_user_id(self):
        return self.presence_layer.get_following_user_id(self.user_id)

    @property
    def followed(self):
        return self.__followed
    
    @followed.setter
    def followed(self, value):
        if value == self.__followed:
            return
        
        bound_camera_prim = self.bound_camera_prim
        if value and not bound_camera_prim:
            return 
        elif bound_camera_prim:
            camera_prim_path = bound_camera_prim.GetPath()
        else:
            camera_prim_path = Sdf.Path.emptyPath

        if value and self.following_user_id:
            carb.log_warn(f"Cannot follow user {self.user_name} as the user is following other user already.")
            return

        if not self.is_bound_to_builtin_camera and camera_prim_path:
            self.__set_display_name_and_lock(camera_prim_path, not value)
        
        self.__switch_viewport_bound_camera(camera_prim_path, value)
    
    def __switch_viewport_bound_camera(self, camera_path, followed):
        viewport_window = get_active_viewport_window()
        viewport_api = viewport_window.viewport_api
        if followed:
            if self.presence_layer.enter_follow_mode(self.user_id):
                self.destroy_camera_widget()
                self.__followed = True
                self.__local_user_shared_data.following_user_id = self.user_id
                viewport_api.camera_path = str(camera_path)
                self.__local_user_shared_data.update_viewport_bound_camera(False)
        else:
            self.__local_user_shared_data.following_user_id = None
            self.__followed = False
            self.presence_layer.quit_follow_mode()
            # Changes it to perspective camera if user is unfollowed.
            viewport_api.camera_path = "/OmniverseKit_Persp"
            self.__local_user_shared_data.update_viewport_bound_camera(True)
            self.refresh_camera_widget(False)
    
    def __set_display_name_and_lock(self, camera_path, clear):
        if not camera_path:
            return

        stage = self.usd_context.get_stage()
        prim = stage.GetPrimAtPath(camera_path)
        if not prim:
            return

        with Sdf.ChangeBlock():
            with Usd.EditContext(stage, stage.GetSessionLayer()):
                if clear:
                    omni.usd.editor.set_display_name(prim, None)
                    prim.RemoveProperty("omni:kit:cameraLock")
                else:
                    omni.usd.editor.set_display_name(prim, self.user_name)
                    prim.CreateAttribute("omni:kit:cameraLock", Sdf.ValueTypeNames.Bool).Set(True)
        
