using BNG;
using Fusion.Sockets;
using Fusion.XR.Host.Rig;
using Photon.Realtime;
using System;
using System.Collections.Generic;
using System.Security.Policy;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using static Unity.Collections.Unicode;

namespace Fusion.Addons.ConnectionManagerAddon
{
    /**
     * 
     * Handles:
     * - connection launch (either with room name or matchmaking session properties)
     * - user representation spawn on connection
     **/
    public class ConnectionManager : MonoBehaviour, INetworkRunnerCallbacks
    {
        public GameObject[] chairs;
        public bool presentation;
        public GameObject Loading;
        [System.Flags]
        public enum ConnectionCriterias
        {
            RoomName = 1,
            SessionProperties = 2
        }
        public Transform itemPos;
        [System.Serializable]
        public struct StringSessionProperty
        {
            public string propertyName;
            public string value;
        }
        public void Update()
        {
            if (Application.loadedLevelName.Equals("Lobby"))
                Destroy(gameObject);
            if (Input.GetKeyDown(KeyCode.I))
            {
                FusionRig.instance.transform.position = usersPlace[0].transform.position;
            }
            if (runner.IsConnectedToServer&&presentation&&!PlayerTeleport.instance.chair&&runner.LocalPlayer.PlayerId>0)
            {
                chairs[runner.LocalPlayer.PlayerId].GetComponent<Chair>().SetSit();

            }
            Loading.gameObject.SetActive(!runner||! runner.IsConnectedToServer);
        }
        [Header("Room configuration")]
        public GameMode gameMode = GameMode.Shared;
        public string roomName = "SampleFusion";
        public bool connectOnStart = true;
        [Tooltip("Set it to 0 to use the DefaultPlayers value, from the Global NetworkProjectConfig (simulation section)")]
        public int playerCount = 0;
        public static ConnectionManager instance;
        [Header("Room selection criteria")]
        public ConnectionCriterias connectionCriterias = ConnectionCriterias.RoomName;
        [Tooltip("If connectionCriterias include SessionProperties, additionalSessionProperties (editable in the inspector) will be added to sessionProperties")]
        public List<StringSessionProperty> additionalSessionProperties = new List<StringSessionProperty>();
        public Dictionary<string, SessionProperty> sessionProperties;

        [Header("Fusion settings")]
        [Tooltip("Fusion runner. Automatically created if not set")]
        public NetworkRunner runner;
        public INetworkSceneManager sceneManager;

        //[Header("Local user spawner")]
       // public NetworkObject userPrefab;
        public NetworkObject whiteboardPrefab;

        [Header("Event")]
        public UnityEvent onWillConnect = new UnityEvent();

        [Header("Info")]
        public List<StringSessionProperty> actualSessionProperties = new List<StringSessionProperty>();

        // Dictionary of spawned user prefabs, to store them on the server for host topology, and destroy them on disconnection (for shared topology, use Network Objects's "Destroy When State Authority Leaves" option)
        private Dictionary<PlayerRef, NetworkObject> _spawnedUsers = new Dictionary<PlayerRef, NetworkObject>();

        bool ShouldConnectWithRoomName => (connectionCriterias & ConnectionManager.ConnectionCriterias.RoomName) != 0;
        bool ShouldConnectWithSessionProperties => (connectionCriterias & ConnectionManager.ConnectionCriterias.SessionProperties) != 0;

        private void Awake()
        {
            if (!EN.instance)
            {

                //Application.LoadLevel(0);
                Destroy(gameObject);
                return;
            }
            // Check if a runner exist on the same game object
            if (runner == null) runner = GetComponent<NetworkRunner>();
            instance = this;
            // Create the Fusion runner and let it know that we will be providing user input
            if (runner == null) runner = gameObject.AddComponent<NetworkRunner>();
            runner.ProvideInput = true;

        }
        public void createChairs()
        {
            for (int i = 0; i < chairs.Length; i++)
            {
                chairs[i].GetComponent<Chair>().enabled = true;
                chairs[i].GetComponentInChildren<VRCanvas>().enabled = true;
            }
        }

        private async void Start()
        {
            chairs = GameObject.FindGameObjectsWithTag("chair");
            //Invoke("createChairs", 5);
            PlaySound("intro");
            // Launch the connection at start
            if (connectOnStart) await Connect();
          
        }

        Dictionary<string, SessionProperty> AllConnectionSessionProperties
        {
            get
            {
                var propDict = new Dictionary<string, SessionProperty>();
                actualSessionProperties = new List<StringSessionProperty>();
                if (sessionProperties != null)
                {
                    foreach (var prop in sessionProperties)
                    {
                        propDict.Add(prop.Key, prop.Value);
                        actualSessionProperties.Add(new StringSessionProperty { propertyName = prop.Key, value = prop.Value });
                    }
                }
                if (additionalSessionProperties != null)
                {
                    foreach (var additionalProperty in additionalSessionProperties)
                    {
                        propDict[additionalProperty.propertyName] = additionalProperty.value;
                        actualSessionProperties.Add(additionalProperty);
                    }

                }
                return propDict;
            }
        }
        public Transform pos;
        public virtual NetworkSceneInfo CurrentSceneInfo()
        {
            var activeScene = SceneManager.GetActiveScene();
            SceneRef sceneRef = default;

            if (activeScene.buildIndex < 0 || activeScene.buildIndex >= SceneManager.sceneCountInBuildSettings)
            {
                Debug.LogError("Current scene is not part of the build settings");
            }
            else
            {
                sceneRef = SceneRef.FromIndex(activeScene.buildIndex);
            }

            var sceneInfo = new NetworkSceneInfo();
            if (sceneRef.IsValid)
            {
                sceneInfo.AddSceneRef(sceneRef, LoadSceneMode.Single);
            }
            return sceneInfo;
        }

        public async Task Connect()
        {
            // Create the scene manager if it does not exist
            if (sceneManager == null) sceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>();
            if (onWillConnect != null) onWillConnect.Invoke();

            // Start or join (depends on gamemode) a session with a specific name
            var args = new StartGameArgs()
            {
                GameMode = gameMode,
                Scene = CurrentSceneInfo(),
                SceneManager = sceneManager
            };
            // Connection criteria
            if (ShouldConnectWithRoomName)
            {
                args.SessionName = roomName;
            }
            if (ShouldConnectWithSessionProperties)
            {
                args.SessionProperties = AllConnectionSessionProperties;
            }
            // Room details
            if (playerCount > 0)
            {
                args.PlayerCount = playerCount;
            }

            await runner.StartGame(args);

            string prop = "";
            if (runner.SessionInfo.Properties != null && runner.SessionInfo.Properties.Count > 0)
            {
                prop = "SessionProperties: ";
                foreach (var p in runner.SessionInfo.Properties) prop += $" ({p.Key}={p.Value.PropertyValue}) ";
            }
            Debug.Log($"Session info: Room name {runner.SessionInfo.Name}. Region: {runner.SessionInfo.Region}. {prop}");
            if ((connectionCriterias & ConnectionManager.ConnectionCriterias.RoomName) == 0)
            {
                roomName = runner.SessionInfo.Name;
            }
        }

        #region Player spawn
        public void OnPlayerJoinedSharedMode(NetworkRunner runner, PlayerRef player)
        {
            if (player == runner.LocalPlayer)
            {
                // Spawn the user prefab for the local user
        //       pos.transform.position = usersPlace[runner.SessionInfo.PlayerCount - 1].transform.position;
          //     NetworkObject networkPlayerObject = runner.Spawn(EN.instance.SelectedUser.character, position: pos.transform.position, rotation: transform.rotation, player, (runner, obj) => {
             //  });

            }
        }
        NetworkObject laser,marker, tablet;
        public NetworkObject Marker;
        public NetworkObject Tablet;
        public NetworkObject Laser;
        public NetworkObject spark;

        internal void CreateTablet(Transform trans)
        {
            if (tablet)
                runner.Despawn(tablet);
          //  if (marker)
            //    runner.Despawn(marker);
            //if (marker)
            //    runner.Despawn(marker);

            //if (laser)
            //    runner.Despawn(laser);
            tablet = runner.Spawn(Tablet, position: itemPos.position, rotation: itemPos.rotation, inputAuthority: runner.LocalPlayer, (runner, obj) => {
               // obj.GetComponent<MarkerNetwork>().DrawColor = color;


            });
        }  
        internal void CreateLaser(Transform trans)
        {
            //if (ereaser)
            //    runner.Despawn(ereaser);
            //if (marker)
            //    runner.Despawn(marker);
            if (laser)
                runner.Despawn(laser);
            laser = runner.Spawn(Laser, position: (trans.transform.position + new Vector3(0, 0, .0f)), rotation: trans.rotation, inputAuthority: runner.LocalPlayer, (runner, obj) => {
               // obj.GetComponent<MarkerNetwork>().DrawColor = color;


            });
        }   internal void CreateSpark(NetworkObject item,Transform trans)
        {
            //if (ereaser)
            //    runner.Despawn(ereaser);
            //if (marker)
            //    runner.Despawn(marker);
            if (spark)
                runner.Despawn(spark);
            spark = runner.Spawn(item, position: (trans.transform.position + new Vector3(0, 0, .0f)), rotation: trans.rotation, inputAuthority: runner.LocalPlayer, (runner, obj) => {
               // obj.GetComponent<MarkerNetwork>().DrawColor = color;


            });
        }
        internal void CreateImage(Transform trans, NetworkObject obj,string url)
        {
          
        runner.Spawn(obj, position: (trans.transform.position + new Vector3(0, 0, .0f)), rotation: trans.rotation, inputAuthority: runner.LocalPlayer, (runner, obj) => {
        
            obj.GetComponent<ImagePlayer>().Show(url);
            });
        }   
        internal void CreateVideo(Transform trans, NetworkObject obj,string url)
        {
          
        runner.Spawn(obj, position: (trans.transform.position + new Vector3(0, 0, .0f)), rotation: trans.rotation, inputAuthority: runner.LocalPlayer, (runner, obj) => {
        
            obj.GetComponent<VideoPlayerManager>().Show(url);
            });
        }
        
        internal void CreateMarker(Transform trans, Color color)
        {
            if (marker)
                runner.Despawn(marker);
          
            marker = runner.Spawn(Marker, position: (trans.transform.position + new Vector3(0, 0, .0f)), rotation: trans.rotation, inputAuthority: runner.LocalPlayer, (runner, obj) => {
           obj.GetComponent<MarkerNetwork>().DrawColor = color;


            });
        }
        public NetworkObject networkPlayerObject;
        public void OnPlayerJoinedHostMode(NetworkRunner runner, PlayerRef player)
        {
            if (player != runner.LocalPlayer)
                return;
                // The user's prefab has to be spawned by the host
                pos.transform.position = usersPlace[runner.SessionInfo.PlayerCount - 1].transform.position;
            // We make sure to give the input authority to the connecting player for their user's object

            if (  EN.instance.SelectedUser.character != null)
            {
                Debug.Log($"OnPlayerJoined. PlayerId: {player.PlayerId}" + usersPlace[runner.SessionInfo.PlayerCount - 1].transform.position.ToString());
         
                networkPlayerObject = runner.Spawn(EN.instance.Me.character, position: pos.position, rotation: transform.rotation, inputAuthority: player, (runner, obj) => {

                    obj.GetComponent<playerManager>().player = player;
                 //   obj.GetComponent<playerManager>().RPC_RequestCharacterSpawn(player);
                });
            

                // Keep track of the player avatars so we can remove it when they disconnect
                _spawnedUsers.Add(player, networkPlayerObject);
            }
            if (player.PlayerId == runner.LocalPlayer.PlayerId)
                FusionRig.instance.transform.position = pos.position;
        }

        // Despawn the user object upon disconnection
        public void OnPlayerLeftHostMode(NetworkRunner runner, PlayerRef player)
        {
            // Find and remove the players avatar (only the host would have stored the spawned game object)
            if (_spawnedUsers.TryGetValue(player, out NetworkObject networkObject))
            {
                runner.Despawn(networkObject);
                _spawnedUsers.Remove(player);
            }
        }
       // public GameObject[] users;
        #endregion

        #region INetworkRunnerCallbacks
        public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
        {
            print(player.PlayerId + "==" + runner.LocalPlayer.PlayerId + "," + runner.SessionInfo.PlayerCount);
         //   if ((runner.SessionInfo.PlayerCount == 1) && player.PlayerId == runner.LocalPlayer.PlayerId)
               // whiteboard = runner.Spawn(whiteboardPrefab, position: wbPostion.position,
             //       rotation: wbPostion.rotation);
         //   if (runner.Topology == Topologies.ClientServer)
            {
                OnPlayerJoinedHostMode(runner, player);
            }
           // else
            {

             //   OnPlayerJoinedSharedMode(runner, player);
            }
        }
        public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
        {
            if (runner.Topology == Topologies.ClientServer)
            {
                OnPlayerLeftHostMode(runner, player);
            }
        }
        NetworkObject whiteboard;
        public Transform wbPostion;
        public Transform[] usersPlace;
        #endregion

        #region INetworkRunnerCallbacks (debug log only)
        public void OnConnectedToServer(NetworkRunner runner)
        {
            Debug.Log("OnConnectedToServer:" + runner.IsServer);


        }
        public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason)
        {
            Debug.Log("Shutdown: " + shutdownReason);
            //Application.LoadLevel(0);
        }
        public void OnDisconnectedFromServer(NetworkRunner runner, NetDisconnectReason reason)
        {
            Debug.Log("OnDisconnectedFromServer: " + reason);
            Application.LoadLevel(0);
            
        }
        public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason)
        {
            Debug.Log("OnConnectFailed: " + reason);
            Application.LoadLevel(0);
        }
        #endregion

        #region Unused INetworkRunnerCallbacks 

        public void OnInput(NetworkRunner runner, NetworkInput input) { }
        public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { }
        public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { }
        public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { }
        public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList) { }
        public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data) { }
        public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { }
        public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ArraySegment<byte> data) { }
        public void OnSceneLoadDone(NetworkRunner runner) { }
        public void OnSceneLoadStart(NetworkRunner runner) { }
        public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { }
        public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { }
        public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment<byte> data) { }
        public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress) { }


        #endregion

        public void PlaySound(string soundName,float volume=1f)
        {
            // بارگذاری صدا از پوشه Resources
            AudioClip clip = Resources.Load<AudioClip>("sounds/" + soundName);

            if (clip != null)
            {
                // ایجاد یک GameObject جدید برای پخش صدا
                GameObject audioObject = new GameObject("AudioSource_" + soundName);
                
                AudioSource audioSource = audioObject.AddComponent<AudioSource>();
                audioSource.volume = volume;
                audioSource.spatialBlend = 1;

                // تنظیم clip و پخش صدا
                audioSource.clip = clip;
                audioSource.Play();

                // حذف GameObject بعد از پایان صدا
                Destroy(audioObject, clip.length);
            }
            else
            {
                Debug.LogError("Sound not found: " + soundName);
            }
        }

        internal void CreateImage(Transform trans, NetworkObject obj, RawImage rawImage)
        {
            runner.Spawn(obj, position: (trans.transform.position + new Vector3(0, 0, .0f)), rotation: trans.rotation, inputAuthority: runner.LocalPlayer, (runner, obj) => {

                obj.GetComponent<ImagePlayer>().Show(rawImage);
            });
        }
    }


    }
