﻿/* Copyright (C) Itseez3D, Inc. - All Rights Reserved
* You may not use this file except in compliance with an authorized license
* Unauthorized copying of this file, via any medium is strictly prohibited
* Proprietary and confidential
* UNLESS REQUIRED BY APPLICABLE LAW OR AGREED BY ITSEEZ3D, INC. IN WRITING, SOFTWARE DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED
* See the License for the specific language governing permissions and limitations under the License.
* Written by Itseez3D, Inc. <support@avatarsdk.com>, December 2020
*/

using ItSeez3D.AvatarSdk.Core;
using ItSeez3D.AvatarSdkSamples.Core;
using Photon.Pun;
using Photon.Realtime;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Photon.Pun.UtilityScripts;
using UnityEngine;


namespace ItSeez3D.AvatarSdkSamples.Cloud
{
    /// <summary>
    /// This class is responsible for handling PUN's callback. 
    /// </summary>
    public class PhotonManager : MonoBehaviourPunCallbacks
    {

        public GameObject playerPrefab;
        public GameObject playerInstance;
        public List<GameObject> spawnPositions;
        public FullbodyAnimatorsHolder fullbodyAnimatorsHolder;
        public delegate void AvatarPlayerConnectedDelegate(string avatarCode, GameObject avatarObject, string haircutName, string outfitName);
        public event AvatarPlayerConnectedDelegate AvatarPlayerConnectedEvent;
        public delegate void ConnectedToPhotonServerDelegate();
        public event ConnectedToPhotonServerDelegate ConnectedToPhotonServerEvent;
        public delegate void ConnectionErrorDelegate(string message);
        public event ConnectionErrorDelegate ConnectionErrorEvent;


        /// <summary>
        /// https://doc.photonengine.com/en-us/pun/current/demos-and-tutorials/webgl-tabsinbackground
        /// </summary>
        public void HeartBeat()
        {
            Debug.Log("Heart Beat");
            PhotonNetwork.NetworkingClient.LoadBalancingPeer.DispatchIncomingCommands();
            PhotonNetwork.NetworkingClient.LoadBalancingPeer.SendOutgoingCommands();
        }
        public override void OnConnectedToMaster()
        {
            Debug.Log("OnConnectedToMaster");
            JoinOrCreateRoom();
        }
        public bool IsConnected { get { return PhotonNetwork.IsConnected; } }


        private const string AVATAR_CODE_PROP_KEY = "avatarCode";
        private const string SPAWN_POS_IDX_PROP_KEY = "spawnPositionIdx";
        private const string HAIRCUT_KEY = "haircutId";
        private const string OUTFIT_KEY = "outfitId";

        string RoomName = "Avatar SDK Sample Room";

        private void JoinOrCreateRoom()
        {
            Debug.Log("JoinOrCreateRoom()");

            RoomOptions roomOptions = new RoomOptions();
            roomOptions.MaxPlayers = (byte)spawnPositions.Count;
            roomOptions.PlayerTtl = 60000; //60 sec to rejoin
            roomOptions.EmptyRoomTtl = 60000;

            TypedLobby typedLobby = null;
            Debug.Log("Trying to join / create room");
            PhotonNetwork.JoinOrCreateRoom(RoomName, roomOptions, typedLobby);
        }

        public override void OnJoinRoomFailed(short returnCode, string message)
        {
            String errString = String.Format("Failed to join room: {0} Error code: {1}", message, returnCode);
            ConnectionErrorEvent.Invoke(errString);
            Debug.Log(errString);
            base.OnJoinRoomFailed(returnCode, message);
        }
        public override void OnJoinedRoom()
        {
            ConnectedToPhotonServerEvent.Invoke();
            LogRoomData();
            base.OnJoinedRoom();
        }
        public override void OnLeftRoom()
        {
            Debug.Log("Left Room");
            base.OnLeftRoom();
        }
        public void LogRoomData()
        {
            if(PhotonNetwork.CurrentRoom == null) {
                Debug.LogFormat("CurrentRoom is null");
                return; 
            }
            Debug.LogFormat("PlayerCount: {0}", PhotonNetwork.CurrentRoom.PlayerCount);
            Debug.LogFormat("Name: {0}", PhotonNetwork.CurrentRoom.Name);
            Debug.LogFormat("MaxPlayers: {0}", PhotonNetwork.CurrentRoom.MaxPlayers);
            Debug.LogFormat("IsOpen: {0}", PhotonNetwork.CurrentRoom.IsOpen);
            Debug.LogFormat("MasterClientId: {0}", PhotonNetwork.CurrentRoom.MasterClientId);

        }
        private int DetectSpawn()
        {
            if(PhotonNetwork.CurrentRoom == null || PhotonNetwork.CurrentRoom.Players == null)
            {
                Debug.Log("DetectSpawn(): Reconnect and rejoin...");

                if (!PhotonNetwork.ReconnectAndRejoin())
                {
                    Debug.Log("DetectSpawn(): Reconnect and rejoin: false");
                }
            }
            var players = PhotonNetwork.CurrentRoom.Players;
            var spawnPositionsIdxs = Enumerable.Range(0, spawnPositions.Count);
            Debug.Log("Calculating spawn pos...");
            LogRoomData();

            var occupied = players.
                 Where(p => p.Value.CustomProperties.ContainsKey(SPAWN_POS_IDX_PROP_KEY))
                .Select(p => (int)p.Value.CustomProperties[SPAWN_POS_IDX_PROP_KEY]);
            var free = spawnPositionsIdxs.Except(occupied);
            return free.FirstOrDefault();
        }
        public override void OnDisconnected(DisconnectCause cause)
        {
            Debug.Log("Disconnection. Cause: " + cause.ToString());
            Debug.Log("Will try to reconnect/rejoin");
            if (PhotonNetwork.ReconnectAndRejoin() && PhotonNetwork.CurrentRoom != null)
            {
                Debug.Log("ReconnectAndRejoin: Success");
            }
            else
            {
                Debug.Log("ReconnectAndRejoin: Fail");
            }
            LogRoomData();
            //if(PhotonNetwork.CurrentRoom == null)
            //{
            //    PhotonNetwork.JoinRoom(RoomName);
            //}
            base.OnDisconnected(cause);
        }
        public void InstatiateLocalFullbodyAvatar(string avatarCode, string haircutName, string outfitName, PipelineType pipelineType)
        {
            Vector3 spawnPos;
            Quaternion spawnRot;
            int spawnIdx = DetectSpawn();
            spawnPos = spawnPositions[spawnIdx].transform.position;
            spawnRot = spawnPositions[spawnIdx].transform.rotation;
            object[] data = new object[] { avatarCode, spawnIdx, haircutName, outfitName };
            playerInstance = PhotonNetwork.Instantiate(this.playerPrefab.name, spawnPos, spawnRot, 0, data );
            
            ExitGames.Client.Photon.Hashtable avatarProperties = new ExitGames.Client.Photon.Hashtable();
            avatarProperties.Add(AVATAR_CODE_PROP_KEY, avatarCode);
            avatarProperties.Add(SPAWN_POS_IDX_PROP_KEY, spawnIdx);
            avatarProperties.Add(HAIRCUT_KEY, haircutName);
            avatarProperties.Add(OUTFIT_KEY, outfitName);
            PhotonNetwork.SetPlayerCustomProperties(avatarProperties);
            var photonView = playerInstance.GetComponent<PhotonView>();
            if (photonView.IsMine && playerInstance.GetComponent<MoveByMouse>() == null)
            {
                playerInstance.AddComponent<MoveByMouse>();
            }

            StartCoroutine(InstantiateFullbodyAvatar(avatarCode, haircutName, outfitName, playerInstance, pipelineType));
        }

        GameObject GetPlayersGameObject(Player player)
        {
            var photonViews = UnityEngine.Object.FindObjectsOfType<PhotonView>().ToList();
            return photonViews.FirstOrDefault(pv => pv.Owner == player)?.gameObject;
        }

        public IEnumerator InstantiateFullbodyAvatar(string avatarCode, string haircutName, string outfitName, GameObject avatarObject, PipelineType pipelineType)
        {
            FullbodyAvatarLoader avatarLoader = new FullbodyAvatarLoader(AvatarSdkMgr.GetFullbodyAvatarProvider());
            avatarLoader.AvatarGameObject = avatarObject;
            yield return avatarLoader.LoadAvatarAsync(avatarCode);

            var showHaircutRequest = avatarLoader.ShowHaircutAsync(haircutName);
            yield return showHaircutRequest;

            var showOutfitRequest = avatarLoader.ShowOutfitAsync(outfitName);
            yield return showOutfitRequest;

            var animationManager = avatarObject.AddComponent<FullbodyAnimationManager>();
            
            string[] bodyAnimationsNames = new string[] { "A-Pose", "Waving", "Jumping", "Rumba Dancing", "Spin", "Arguing", "Dancing" };

            var controller = fullbodyAnimatorsHolder.GetAnimatorController(pipelineType);
            animationManager.AssignAnimatorController(controller, bodyAnimationsNames);
        }

        // Start is called before the first frame update
        void Start()
        {
            PhotonNetwork.UseAlternativeUdpPorts = true;
            SupportLogger supportLogger = this.gameObject.GetComponent<SupportLogger>();
            supportLogger.Client = PhotonNetwork.NetworkingClient;
            if (PhotonNetwork.CurrentRoom != null)
            {
                PhotonNetwork.LeaveRoom();
                Debug.Log("Start(): Leaving Room");

            } else
            {
                Debug.Log("Start(): Don't need to leave Room");
            }
            Connect();
            PhotonAvatarNotifier.AvatarPrefabInstantiatedEvent += OnAvatarPrefabInstantiated;
        }

        private void OnAvatarPrefabInstantiated(Player player, GameObject gameObject, string avatarCode, string outfitName, string haircutName, int spawnIdx)
        {
            if (player.IsLocal)
            {
                return;
            }

            AvatarPlayerConnectedEvent?.Invoke(avatarCode, gameObject, haircutName, outfitName);
        }

        public void Connect()
        {
            if (PhotonNetwork.IsConnected)
            {
                Debug.Log("Connect(): IsConnected");
                JoinOrCreateRoom();
            }
            else
            {
                Debug.Log("Connect(): ConnectUsingSettings");
                PhotonNetwork.ConnectUsingSettings();
                PhotonNetwork.GameVersion = "0.0.1";
            }
        }

        public void Awake()
        {
            PhotonNetwork.AutomaticallySyncScene = true;
        }

        // Update is called once per frame
        void Update()
        {

        }
    }
}