using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using PhotonHashtable = ExitGames.Client.Photon.Hashtable;

public class GameLauncher : MonoBehaviourPunCallbacks 
{
	[Header("Launch Settings")]
	[SerializeField] CloudRegion m_cloudRegion;
	string _lobbyID;

	[SerializeField] GameObject m_playerPrefab;
	public bool testPlay = false;

	public int maxPlayersPerRoom = 8;
	public int maxSpectatorsPerRoom = 1;
	public bool joinAsSpectator = false;
    public SpectatorController spectatorController;

	public int totalPlayers = 0;
	public int totalSpectators = 0;

	public TMP_InputField classIDField;
	public TMP_InputField spectateIDField;
	public TMP_Text joinErrorMessage;

	public CanvasManagerScript m_canvasManager;
	public TrackManagerScript manager;
	public RacerLobbyUI racerLobbyUI;
    [SerializeField] CountdownDisplay m_countdownDisplay;

	[SerializeField] float matchStartTime = -1f;
    public float MatchStartTime { get { return matchStartTime; } }

    // DELEGATES //
    #region
    public delegate void OnAppStartAction();
    public OnAppStartAction OnAppStart;

    public delegate void OnJoinAsPlayerAction();
    public OnJoinAsPlayerAction OnJoinAsPlayer;

    public delegate void OnJoinAsSpectatorAction();
    public OnJoinAsSpectatorAction OnJoinAsSpectator;

    public delegate void OnLeaveLobbyAction();
    public OnLeaveLobbyAction OnLeaveLobby;

    public delegate void OnJoinFailedAction();
    public OnJoinFailedAction OnJoinFailed;
    #endregion

    // DEBUG //
    #region
    [Header("Debug")]
    [SerializeField] DebugChannelSO m_debugChannel;
    #endregion

    public enum CloudRegion {
		USEast, USWest, Europe, CanadaEast
	}
    
	public Dictionary<CloudRegion, string> cloudRegionTokens = new Dictionary<CloudRegion, string>()
    {
		{ CloudRegion.USEast, "us" },
		{ CloudRegion.USWest, "usw" },
		{ CloudRegion.Europe, "eu" },
		{ CloudRegion.CanadaEast, "cae" },
	};

    new void OnEnable()
    {
        base.OnEnable();

        OnJoinAsPlayer += SpawnLocalPlayer;
        OnJoinAsSpectator += StartSpectating;
    }

    new void OnDisable()
    {
        base.OnDisable();

        OnJoinAsPlayer -= SpawnLocalPlayer;
        OnJoinAsSpectator -= StartSpectating;
    }

    void Awake()
    {
		if (!m_canvasManager)
			m_canvasManager = FindObjectOfType<CanvasManagerScript>();

        if (!m_countdownDisplay)
            m_countdownDisplay = FindObjectOfType<CountdownDisplay>();

		PhotonNetwork.AutomaticallySyncScene = true;

		string token = "us";

		if(cloudRegionTokens.TryGetValue(m_cloudRegion, out token))
			((ServerSettings)Resources.Load("PhotonServerSettings", typeof(ServerSettings))).AppSettings.FixedRegion = token;
	}

    private void Start()
    {
        if (OnAppStart != null)
            OnAppStart.Invoke();
	}

    public void Connect() 
	{
		if (!PhotonNetwork.IsConnected)
        {
			if (m_debugChannel)
				m_debugChannel.Raise(this, "Connection failed because Photon is not currently connected. Try again!");

			((ServerSettings)Resources.Load("PhotonServerSettings", typeof(ServerSettings))).AppSettings.AppVersion = GameVersion.ClientVersion;
			//PhotonNetwork.GameVersion = _gameVersion;
			PhotonNetwork.ConnectUsingSettings();
			return;
		}

		if (m_debugChannel)
			m_debugChannel.Raise(this, "Attempting to join as user " + PersistentData.localPlayerData.username + " as a " + (joinAsSpectator ? "spectator" : "player"));

		PhotonHashtable playerProperties = new PhotonHashtable() { { "IsSpectator", joinAsSpectator }, { "DisplayName", PersistentData.localPlayerData.username } };
		PhotonNetwork.LocalPlayer.SetCustomProperties(playerProperties);
		PhotonHashtable expectedCustomRoomProperties = new PhotonHashtable() { { joinAsSpectator ? "spectator_slots_availabile" : "player_slots_availabile", true } };
		PhotonNetwork.JoinRandomRoom(expectedCustomRoomProperties, (byte)(maxPlayersPerRoom + maxSpectatorsPerRoom));

		//PhotonNetwork.JoinRandomRoom();
	}

	public override void OnConnectedToMaster() 
	{
		if (m_debugChannel)
			m_debugChannel.Raise(this, "OnConnectedToMaster() was called by PUN");

		if (m_debugChannel)
			m_debugChannel.Raise(this, "Attempting to join as user " + PersistentData.localPlayerData.username + " as a " + (joinAsSpectator ? "spectator" : "player"));

		PhotonHashtable playerProperties = new PhotonHashtable() { { "IsSpectator", joinAsSpectator }, { "DisplayName", PersistentData.localPlayerData.username } };
		PhotonNetwork.LocalPlayer.SetCustomProperties(playerProperties);
		PhotonHashtable expectedCustomRoomProperties = new PhotonHashtable() { { joinAsSpectator ? "spectator_slots_availabile" : "player_slots_availabile", true } };
		PhotonNetwork.JoinRandomRoom(expectedCustomRoomProperties, (byte)(maxPlayersPerRoom + maxSpectatorsPerRoom));
	}


	public override void OnDisconnected(DisconnectCause cause) 
	{
		if (m_debugChannel)
			m_debugChannel.Raise(this, "OnDisconnected() was called by PUN with reason" + cause, DebugChannelSO.Severity.Warning);

		//Debug.LogWarningFormat("[" + this.GetType().ToString() + "] OnDisconnected() was called by PUN with reason {0}", cause);

		GameErrorLogger.WriteToLog("[" + this.GetType().ToString() + "] OnDisconnected() was called by PUN with reason " + cause);
	}

	public override void OnJoinRandomFailed(short returnCode, string message) 
	{
		if (m_debugChannel)
			m_debugChannel.Raise(this, "OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom");

		RoomOptions roomOptions = new RoomOptions();
		roomOptions.CustomRoomPropertiesForLobby = new string[] { "player_slots_availabile", "spectator_slots_availabile" };
		roomOptions.CustomRoomProperties = new PhotonHashtable() { { "player_slots_availabile", true }, { "spectator_slots_availabile", true } };
		roomOptions.MaxPlayers = (byte)(maxPlayersPerRoom + maxSpectatorsPerRoom);
		PhotonNetwork.CreateRoom(null, roomOptions);

		// #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
		//PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = (byte)(maxPlayersPerRoom + maxSpectatorsPerRoom) });
		/*
		Debug.Log("OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom");
		GameErrorLogger.WriteToLog("OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom");
		// #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
		PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = (byte)maxPlayersPerRoom });
		*/
	}



	public override void OnJoinedRoom()
    {
		if (m_debugChannel)
			m_debugChannel.Raise(this, "Region: " + PhotonNetwork.CloudRegion);

		if (m_debugChannel)
			m_debugChannel.Raise(this, "Client has joined a lobby as a " + (joinAsSpectator ? "spectator" : "player"));
        
		if (testPlay && m_playerPrefab != null)
		{
			GameObject _newPlayer = Instantiate (m_playerPrefab, new Vector3 (0, 200, 0), Quaternion.identity);

			if (!_newPlayer)
				if (m_debugChannel)
					m_debugChannel.Raise(this, "Player failed to instantiate correctly.");

			return;
		}

        switch (joinAsSpectator)
        {
            case (true):
                if (OnJoinAsSpectator != null)
                    OnJoinAsSpectator.Invoke();
                break;
            case (false):
                if (OnJoinAsPlayer != null)
                    OnJoinAsPlayer.Invoke();
                break;
        }

        CountPeopleInRoom();

        manager.VotingGUIActive = true;
    }

    void SpawnLocalPlayer()
    {
        if (m_debugChannel)
        {
            m_debugChannel.Raise(this, "Joined as a Racer!");

            if (PhotonNetwork.IsMasterClient)
                m_debugChannel.Raise(this, "Player is Master Client");
        }

        PhotonNetwork.Instantiate(m_playerPrefab.name, manager.racerSpots.GetChild(PhotonNetwork.LocalPlayer.ActorNumber - 1).position, manager.trackStart.rotation);
    }

    void StartSpectating()
    {
        if (!spectatorController)
            return;
        
        spectatorController.enabled = true;

        if (!spectatorController.VerifyDefaults())
            return;

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Joined as a Spectator!");

        StartCoroutine(SetSpectatorTarget());
    }

    public override void OnPlayerEnteredRoom(Player other)
	{
		CountPeopleInRoom();
	}

	public override void OnPlayerLeftRoom(Player other)
	{
		CountPeopleInRoom();
	}

	void CountPeopleInRoom()
	{
		object value;
		totalPlayers = 0;
		totalSpectators = 0;

		Dictionary<int, Player> players = PhotonNetwork.CurrentRoom.Players;

		foreach (KeyValuePair<int, Player> playerItem in players)
			if (playerItem.Value.CustomProperties.TryGetValue("IsSpectator", out value))
			{
				if ((bool)value)
					totalSpectators++;
				else
					totalPlayers++;
			}

		if (PhotonNetwork.IsMasterClient)
			UpdateRoomSlotProperties();
	}

	void UpdateRoomSlotProperties()
	{
		PhotonHashtable update = new PhotonHashtable { { "player_slots_availabile", totalPlayers < maxPlayersPerRoom }, { "spectator_slots_availabile", totalSpectators < maxSpectatorsPerRoom } };
		PhotonNetwork.CurrentRoom.SetCustomProperties(update);

        if (m_debugChannel)
        {
            m_debugChannel.Raise(this, "Players count: " + totalPlayers);
            m_debugChannel.Raise(this, "Spectators count: " + totalSpectators);
            m_debugChannel.Raise(this, "Total in room: " + (totalSpectators + totalPlayers));
        }
	}

	public void Submit(bool joinAsSpectator)
	{
		ToggleJoinAsSpectator(joinAsSpectator);

		if (joinAsSpectator)
		{
			_lobbyID = spectateIDField.text.ToUpper() + "_Esports";

            if (!spectatorController)
                spectatorController = FindObjectOfType<SpectatorController>();

            if (spectatorController)
                if (m_debugChannel)
                    m_debugChannel.Raise(this, "Spectating!");
        }
		else
		{
			_lobbyID = classIDField.text.ToUpper() + "_Esports";
		}

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Attempting to join lobby with ID " + _lobbyID);

		if(_lobbyID.Length < 7)
        {
            if (OnJoinFailed != null)
                OnJoinFailed.Invoke();

			return;
		}

		((ServerSettings)Resources.Load("PhotonServerSettings", typeof(ServerSettings))).AppSettings.AppVersion = _lobbyID;

		//PhotonNetwork.GameVersion = _gameVersion;
		Connect();

		if (joinAsSpectator)
		{
			// True because spectator exists
			m_canvasManager.SetInLobbyCanvasActive(true);
		}
		else
		{
			m_canvasManager.SetInLobbyCanvasActive(false);
		}
	}

	public void SubmitSpectate(TMP_InputField textField)
	{
		ToggleJoinAsSpectator(true);

		if (joinAsSpectator)
		{
			_lobbyID = textField.text.ToUpper() + "_Esports";

			if (!spectatorController)
				spectatorController = FindObjectOfType<SpectatorController>();

			if (spectatorController)
				if (m_debugChannel)
					m_debugChannel.Raise(this, "Spectating!");
		}
		else
		{
			_lobbyID = textField.text.ToUpper() + "_Esports";
		}

		if (m_debugChannel)
			m_debugChannel.Raise(this, "Attempting to join lobby with ID " + _lobbyID);

		if (_lobbyID.Length < 7)
		{
			if (OnJoinFailed != null)
				OnJoinFailed.Invoke();

			return;
		}

		((ServerSettings)Resources.Load("PhotonServerSettings", typeof(ServerSettings))).AppSettings.AppVersion = _lobbyID;

		//PhotonNetwork.GameVersion = _gameVersion;
		Connect();

		if (joinAsSpectator)
		{
			// True because spectator exists
			m_canvasManager.SetInLobbyCanvasActive(true);
		}
		else
		{
			m_canvasManager.SetInLobbyCanvasActive(false);
		}
	}

	public void QuickplaySubmit()
	{
		_lobbyID = "RKR Random Match";
		joinErrorMessage.gameObject.SetActive(false);
		((ServerSettings)Resources.Load("PhotonServerSettings", typeof(ServerSettings))).AppSettings.AppVersion = _lobbyID;
		Connect();
		m_canvasManager.SetInLobbyCanvasActive(false);
	}

    // MATCH START METHODS //

	public void StartMatch() {
		if (PhotonNetwork.IsMasterClient) {
			SceneManager.LoadScene (manager.selectedLevel);
		}
	}

	// Should only happen if the host is by themselves
	public void ResetMatchStartTime ()
    {
		matchStartTime = -1f;

		PhotonGameDebugger.IncrementNumberOfMessages("Setting match start time");
		photonView.RPC("SetMatchStartTime", RpcTarget.AllBuffered, -1f, false);
	}

	IEnumerator SetSpectatorTarget()
	{
		RacerCore[] karts = FindObjectsOfType<RacerCore>();

		while (karts.Length <= 0)
		{
			yield return null;

            karts = FindObjectsOfType<RacerCore>();
        }

		if (spectatorController)
        {
            spectatorController.SpectateAnyPlayer();
        }
	}

    public void ToggleJoinAsSpectator(bool _joinAsSpectator)
	{
		joinAsSpectator = _joinAsSpectator;
	}

    // PHOTON / RPC //
    #region
    [PunRPC]
	public void SetMatchStartTime (float sendTime, float startTime, bool addTime) {

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Photon Time: " + PhotonNetwork.Time.ToString() + ", Start Time: " + startTime.ToString());

		matchStartTime = startTime;

        if (startTime == -1f)
        {
            racerLobbyUI.Cancel();

            if (m_debugChannel)
                m_debugChannel.Raise(this, "Canceled lobby countdown");
        }
        else if (addTime)
        {
            if (m_debugChannel)
                m_debugChannel.Raise(this, "Adding time to lobby countdown");
        }
        else if (sendTime < startTime)
        {
            StartCoroutine(racerLobbyUI.CountdownToStart());

            if (m_debugChannel)
                m_debugChannel.Raise(this, "Start lobby countdown");
        }
        else if (sendTime >= startTime)
        {
            if (m_debugChannel)
                m_debugChannel.Raise(this, "Failed to set new start time", DebugChannelSO.Severity.Error);
        }
	}
	[PunRPC]	
	public void BeginMatch (float beginTime)
    {
		matchStartTime = beginTime;

		if (PhotonNetwork.Time < beginTime)
        {
            if (m_debugChannel)
                m_debugChannel.Raise(this, "Starting match start countdown");

            PhotonNetwork.CurrentRoom.IsOpen = false;
            PhotonNetwork.CurrentRoom.IsVisible = false;

			StartCoroutine (racerLobbyUI.BeginMatchCountdown());
        }
	}
    #endregion
}

