using System;
using System.Collections.Generic;

using UnityEngine;
using UnityEngine.UI;

using Sfs2X;
using Sfs2X.Core;
using Sfs2X.Entities;
using Sfs2X.Entities.Data;
using Sfs2X.Entities.Invitation;
using Sfs2X.Entities.Match;
using Sfs2X.Entities.Variables;
using Sfs2X.Requests;
using Sfs2X.Requests.Buddylist;
using Sfs2X.Requests.Cluster;
using Sfs2X.Requests.Game;
using Sfs2X.Util;

/**
 * Script attached to the Controller object in the Lobby scene.
 */
public class LobbySceneController : BaseSceneController
{
	public static string GAME_ROOMS_GROUP_NAME = "games";

	public static string BUDDYVAR_YEAR = SFSBuddyVariable.OFFLINE_PREFIX + "year";
	public static string BUDDYVAR_MOOD = "mood";

	public static string USERVAR_EXPERIENCE = "exp";
	public static string USERVAR_RANKING = "rank";

	public static string ROOMVAR_USER_EXPERIENCE = "user-exp";
	public static string ROOMVAR_USER_MIN_RANKING = "user-min-rank";

	//----------------------------------------------------------
	// UI elements
	//----------------------------------------------------------

	public Text loggedInAsLabel;
	public Text userStatusLabel;
	public UserProfilePanel userProfilePanel;
	public InvitationPanel invitationPanel;
	public WarningPanel warningPanel;
    public WaitingPanel waitingPanel;

    public GameObject buddyListPanel;
	public Transform buddyListContent;
	public BuddyListItem buddyListItemPrefab;
	public InputField buddyNameInput;
	public BuddyChatPanel chatPanel;

	public Queue<InvitationWrapper> invitationQueue;

	//----------------------------------------------------------
	// Private properties
	//----------------------------------------------------------

	private SmartFox sfsLobby;
	private Dictionary<string, BuddyListItem> buddyListItems;
	private string invitedBuddy;

	// Comment EXTENSION_ID and EXTENSION_CLASS constants below and
	// uncomment the following to use the Java version of the Tic Tac Toe Extension
	private const string EXTENSION_ID = "TicTacToe";
	private const string EXTENSION_CLASS = "sfs2x.extensions.games.tictactoe.TicTacToeExtension";

	// Comment above EXTENSION_ID and EXTENSION_CLASS constants and
	// uncomment the following to use the JavaScript version of the Tic Tac Toe Extension
	//private const string EXTENSION_ID = "TicTacToe-JS";
	//private const string EXTENSION_CLASS = "TicTacToeExtension.js";

	//----------------------------------------------------------
	// Unity callback methods
	//----------------------------------------------------------
	#region
	private void Start()
	{
		Debug.Log("LOBBY SCENE - Scene loaded");

		// Get reference to static SmartFox client instance for the lobby
		sfsLobby = gm.GetLobbyClient();

		// Add lobby event listeners
		AddSmartFoxLobbyListeners();

		// Init or display player profile details
		InitPlayerProfile();

		// Initialize Buddy List system
		// We have to check if it was already initialized, in case this scene was reloaded after leaving the Game scene
		if (!sfsLobby.BuddyManager.Inited)
			sfsLobby.Send(new InitBuddyListRequest());
		else
			InitializeBuddyClient();

		// Display username in footer and user profile panel
		loggedInAsLabel.text = "Logged in as <b>" + sfsLobby.MySelf.Name + "</b>";
		userProfilePanel.InitUserProfile(sfsLobby.MySelf.Name);

		// Check if a connection lost message should be displayed
		CheckConnectionLost();
	}
	#endregion

	//----------------------------------------------------------
	// Abstract methods from BaseSceneController implementation
	//----------------------------------------------------------
	#region
	override protected void RemoveSmartFoxListeners()
	{
		// Remove lobby-related SmartFox event listeners
		RemoveSmartFoxLobbyListeners();
	}

	override public void ShowError(string errorMsg)
	{
		// Display connection or login error
		warningPanel.Show(errorMsg);
	}

	override protected void HideModals()
	{
		userProfilePanel.Hide();
		invitationPanel.Hide();
		warningPanel.Hide();
    }

    //----------------------------------------------------------

    override public void OnGameJoinFailed()
    {
        // Schedule another request to join or create a game
        Invoke("JoinOrCreateGame", 2);
    }
    #endregion

    //----------------------------------------------------------
    // UI event listeners
    //----------------------------------------------------------
    #region
    /**
	 * On Logout button click, disconnect from SmartFoxServer.
	 * This causes the SmartFox listeners added by this scene to be removed (see BaseSceneController.OnDestroy method)
	 * and the Login scene to be loaded (see GlobalManager.OnLobbyNodeConnectionLost method).
	 */
    public void OnLogoutButtonClick()
	{
		// Disconnect from lobby node
		sfsLobby.Disconnect();
	}

	/**
	 * On Play button click, send request to lobby to join a random game Room (or create one if none is available).
	 */
	public void OnPlayButtonClick()
	{
        invitedBuddy = null;

        // Show waiting message
        waitingPanel.Show();

        // Request to join or create game Room
        JoinOrCreateGame();
    }

    /**
	 * On Cancel button click, stop requesting to join or create a game Room to lobby.
	 */
    public void OnCancelButtonClick()
    {
        CancelInvoke("JoinOrCreateGame");

        // Hide waiting message
        waitingPanel.Hide();
    }

    /**
	 * On User icon click, show User Profile Panel prefab instance.
	 */
    public void OnUserIconClick()
	{
		userProfilePanel.Show();
	}

	/**
	 * On buddy name input edit end, if the Enter key was pressed, send request to add buddy to user's Buddy List.
	 */
	public void OnBuddyNameInputEndEdit()
	{
		if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))
			OnAddBuddyButtonClick();
	}

	/**
	 * On Add buddy button click, send request to add buddy to user's Buddy List.
	 */
	public void OnAddBuddyButtonClick()
	{
		if (buddyNameInput.text != "")
		{
			// Request buddy adding to buddy list
			sfsLobby.Send(new AddBuddyRequest(buddyNameInput.text));
			buddyNameInput.text = "";
		}
	}

	/**
	 * On Add buddy button click in Buddy List Item prefab instance, send request to add temporary buddy to user's Buddy List.
	 */
	public void OnAddBuddyButtonClick(string buddyName)
	{
		sfsLobby.Send(new AddBuddyRequest(buddyName));
	}

	/**
	 * On Block buddy button click in Buddy List Item prefab instance, send request to block/unblock buddy in user's Buddy List.
	 */
	public void OnBlockBuddyButtonClick(string buddyName)
	{
		bool isBlocked = sfsLobby.BuddyManager.GetBuddyByName(buddyName).IsBlocked;

		// Request buddy block/unblock
		sfsLobby.Send(new BlockBuddyRequest(buddyName, !isBlocked));
	}

	/**
	 * On Remove buddy button click in Buddy List Item prefab instance, send request to remove buddy from user's Buddy List.
	 */
	public void OnRemoveBuddyButtonClick(string buddyName)
	{
		// Request buddy removal from buddy list
		sfsLobby.Send(new RemoveBuddyRequest(buddyName));
	}

	/**
	 * On Chat button click in Buddy List Item prefab instance, initialize and show chat panel.
	 */
	public void OnChatBuddyButtonClick(string buddyName)
	{
		// Skip if chat is already active with the same buddy
		if (chatPanel.BuddyName != buddyName)
		{
			// Get chat history (if any) and reset new messages counter
			List<BuddyChatMessage> buddyChatMessages = BuddyChatHistoryManager.GetBuddyChatMessages(buddyName);
			BuddyChatHistoryManager.ResetUnreadMessagesCount(buddyName);

			// Initialize and show panel
			chatPanel.Init(sfsLobby.BuddyManager.GetBuddyByName(buddyName), buddyChatMessages);

			// Move buddy list item to top of the list and reset new messages counter
			buddyListItems.TryGetValue(buddyName, out BuddyListItem buddyListItem);

			if (buddyListItem != null)
			{
				buddyListItem.gameObject.transform.SetAsFirstSibling();
				buddyListItem.SetChatMsgCounter(0);
			}
		}
	}

	/**
	 * On Invite button click in Buddy List Item prefab instance, start new game and invite buddy.
	 */
	public void OnInviteBuddyButtonClick(string buddyName)
	{
		invitedBuddy = buddyName;

        // Show waiting message
        waitingPanel.Show();

        // Request to join or create game Room
        JoinOrCreateGame();
    }

	/**
	 * On custom event fired by Buddy Chat Panel prefab instance, send message to buddy.
	 */
	public void OnBuddyMessageSubmit(string buddyName, string message)
	{
		// Add a custom parameter containing the recipient name
		ISFSObject _params = new SFSObject();
		_params.PutUtfString("recipient", buddyName);

		// Retrieve buddy
		Buddy buddy = sfsLobby.BuddyManager.GetBuddyByName(buddyName);

		// Send message to buddy
		sfsLobby.Send(new BuddyMessageRequest(message, buddy, _params));
	}

	/**
	 * On custom event fired by User Profile Panel prefab instance, toggle user online state in Buddy List system.
	 */
	public void OnOnlineToggleChange(bool isChecked)
	{
		// Send request to toggle online/offline state
		sfsLobby.Send(new GoOnlineRequest(isChecked));
	}

	/**
	 * On custom event fired by User Profile Panel prefab instance, set user's Buddy Variables.
	 */
	public void OnBuddyDetailChange(string varName, object value)
	{
		List<BuddyVariable> buddyVars = new List<BuddyVariable>();
		buddyVars.Add(new SFSBuddyVariable(varName, value));
		
		// Set Buddy Variables
		sfsLobby.Send(new SetBuddyVariablesRequest(buddyVars));
	}

	/**
	 * On custom event fired by User Profile Panel prefab instance, set user's User Variables.
	 */
	public void OnPlayerDetailChange(string varName, object value)
	{
		List<UserVariable> userVars = new List<UserVariable>();
		userVars.Add(new SFSUserVariable(varName, value));

		// Set User Variables
		sfsLobby.Send(new SetUserVariablesRequest(userVars));
	}

	/**
	 * On custom event fired by Invitation Panel prefab instance, send reply to invitation.
	 */
	public void OnInvitationReplyClick(Invitation invitation, bool accept)
	{
		// Accept/refuse invitation
		sfsLobby.Send(new InvitationReplyRequest(invitation, (accept ? InvitationReply.ACCEPT : InvitationReply.REFUSE)));

		// If invitation was accepted, refuse all remaining invitations in the queue
		if (accept)
		{
			// Refuse other invitations
			foreach (InvitationWrapper iw in invitationQueue)
				sfsLobby.Send(new InvitationReplyRequest(iw.invitation, InvitationReply.REFUSE));

			// Reset queue
			invitationQueue.Clear();
		}

		// If invitation was refused, process next invitation in the queue (if any)
		else
			ProcessInvitations();
	}
	#endregion

	//----------------------------------------------------------
	// Helper methods
	//----------------------------------------------------------
	#region
	/**
	 * Add all SmartFoxServer lobby-related event listeners required by the scene.
	 */
	private void AddSmartFoxLobbyListeners()
	{
		Debug.Log("LOBBY SCENE - Adding lobby node event listeners");

		sfsLobby.AddEventListener(SFSEvent.USER_VARIABLES_UPDATE, OnUserVariablesUpdate);
		sfsLobby.AddEventListener(SFSEvent.INVITATION, OnInvitation);

		sfsLobby.AddEventListener(SFSBuddyEvent.BUDDY_LIST_INIT, OnBuddyListInit);
		sfsLobby.AddEventListener(SFSBuddyEvent.BUDDY_ERROR, OnBuddyError);
		sfsLobby.AddEventListener(SFSBuddyEvent.BUDDY_ONLINE_STATE_UPDATE, OnBuddyOnlineStateUpdate);
		sfsLobby.AddEventListener(SFSBuddyEvent.BUDDY_VARIABLES_UPDATE, OnBuddyVariablesUpdate);
		sfsLobby.AddEventListener(SFSBuddyEvent.BUDDY_BLOCK, OnBuddyBlock);
		sfsLobby.AddEventListener(SFSBuddyEvent.BUDDY_REMOVE, OnBuddyRemove);
		sfsLobby.AddEventListener(SFSBuddyEvent.BUDDY_ADD, OnBuddyAdd);
		sfsLobby.AddEventListener(SFSBuddyEvent.BUDDY_MESSAGE, OnBuddyMessage);
	}

	/**
	 * Remove all SmartFoxServer lobby-related event listeners added by the scene.
	 */
	private void RemoveSmartFoxLobbyListeners()
	{
		Debug.Log("LOBBY SCENE - Removing lobby node event listeners");

		sfsLobby.RemoveEventListener(SFSEvent.USER_VARIABLES_UPDATE, OnUserVariablesUpdate);
		sfsLobby.RemoveEventListener(SFSEvent.INVITATION, OnInvitation);

		sfsLobby.RemoveEventListener(SFSBuddyEvent.BUDDY_LIST_INIT, OnBuddyListInit);
		sfsLobby.RemoveEventListener(SFSBuddyEvent.BUDDY_ERROR, OnBuddyError);
		sfsLobby.RemoveEventListener(SFSBuddyEvent.BUDDY_ONLINE_STATE_UPDATE, OnBuddyOnlineStateUpdate);
		sfsLobby.RemoveEventListener(SFSBuddyEvent.BUDDY_VARIABLES_UPDATE, OnBuddyVariablesUpdate);
		sfsLobby.RemoveEventListener(SFSBuddyEvent.BUDDY_BLOCK, OnBuddyBlock);
		sfsLobby.RemoveEventListener(SFSBuddyEvent.BUDDY_REMOVE, OnBuddyRemove);
		sfsLobby.RemoveEventListener(SFSBuddyEvent.BUDDY_ADD, OnBuddyAdd);
		sfsLobby.RemoveEventListener(SFSBuddyEvent.BUDDY_MESSAGE, OnBuddyMessage);
	}

	/**
	 * Initialize player profile.
	 * 
	 * IMPORTANT NOTE
	 * The Experience and Ranking details are custom parameters used in this example to show how to filter players through SmartFoxServer's matchmaking system
	 * when sending invitations to play a game. Of course they (or any other parameter with a similar purpose) shouldn't be set manually, but they should be
	 * determined by the game logic, saved in a database and set in User Variables by a server-side Extension upon user login.
	 * In this example, for sake of simplicity, such details are set to default values when the Lobby scene is loaded and they are lost upon user logout.
	 */
	private void InitPlayerProfile()
	{
		// Check if player details are set in User Variables
		if (sfsLobby.MySelf.GetVariable(USERVAR_EXPERIENCE) == null || sfsLobby.MySelf.GetVariable(USERVAR_RANKING) == null)
		{
			// If not, set player profile default values; this in turn causes a UserVariablesUpdate event to be fired,
			// upon which we will display the player details in the user profile panel
			SFSUserVariable expVar = new SFSUserVariable(USERVAR_EXPERIENCE, "Novice");
			SFSUserVariable rankVar = new SFSUserVariable(USERVAR_RANKING, 3);

			sfsLobby.Send(new SetUserVariablesRequest(new List<UserVariable>() { expVar, rankVar }));
		}
		else
		{
			// If yes, display player details in user profile panel
			userProfilePanel.InitPlayerProfile(sfsLobby.MySelf);
		}
	}

	/**
	 * Show current user state in Buddy List system.
	 */
	private void DisplayUserStateAsBuddy()
	{
		if (sfsLobby.BuddyManager.MyOnlineState)
			userStatusLabel.text = sfsLobby.BuddyManager.MyState;
		else
			userStatusLabel.text = "Offline";
	}

	/**
	 * Initialize buddy-related entities.
	 */
	private void InitializeBuddyClient()
	{
		// Init buddy-related data structures
		buddyListItems = new Dictionary<string, BuddyListItem>();

		// For the buddy list we use a scrollable area containing a separate prefab for each buddy
		// The prefab contains clickable buttons to invite a buddy to play, chat with them, block or remove them
		List<Buddy> buddies = sfsLobby.BuddyManager.BuddyList;

		// Display buddy list items
		// All blocked buddies are displayed at the bottom of the list
		foreach (Buddy buddy in buddies)
			AddBuddyListItem(buddy, !buddy.IsBlocked);

		// Set current user details in buddy system
		userProfilePanel.InitBuddyProfile(sfsLobby.BuddyManager);

		// Display user state in buddy list system
		DisplayUserStateAsBuddy();

		// Show/hide buddy list
		buddyListPanel.SetActive(sfsLobby.BuddyManager.MyOnlineState);
	}

	/**
	 * Create Buddy List Item prefab instance and add to buddy list.
	 */
	private void AddBuddyListItem(Buddy buddy, bool toTop = false)
	{
		string buddyName = buddy.Name;

		// Check if buddy list item already exist
		// This could happen if a temporary buddy is added permanently
		if (buddyListItems.ContainsKey(buddyName))
		{
			BuddyListItem buddyListItem = buddyListItems[buddyName];
			buddyListItem.SetState(buddy);
		}
		else
		{
			// Create buddy list item
			BuddyListItem buddyListItem = Instantiate(buddyListItemPrefab);
			buddyListItems.Add(buddyName, buddyListItem);

			// Init buddy list item
			buddyListItem.Init(buddy);

			// Set unread messages counter
			// (buddy could have sent messages while user was in the game scene)
			buddyListItem.SetChatMsgCounter(BuddyChatHistoryManager.GetUnreadMessagesCount(buddy.Name));

			// Add listeners to buttons
			buddyListItem.removeButton.onClick.AddListener(() => OnRemoveBuddyButtonClick(buddyName));
			buddyListItem.addButton.onClick.AddListener(() => OnAddBuddyButtonClick(buddyName));
			buddyListItem.blockButton.onClick.AddListener(() => OnBlockBuddyButtonClick(buddyName));
			buddyListItem.chatButton.onClick.AddListener(() => OnChatBuddyButtonClick(buddyName));
			buddyListItem.inviteButton.onClick.AddListener(() => OnInviteBuddyButtonClick(buddyName));

			// Add buddy list item to container
			buddyListItem.gameObject.transform.SetParent(buddyListContent, false);

			if (toTop)
				buddyListItem.gameObject.transform.SetAsFirstSibling();
		}
	}

	/**
	 * Update Buddy List Item prefab instance when buddy state changes.
	 */
	private void UpdateBuddyListItem(Buddy buddy)
	{
		// Get reference to buddy list item corresponding to Buddy
		buddyListItems.TryGetValue(buddy.Name, out BuddyListItem buddyListItem);

		if (buddyListItem != null)
		{
			// Check if the update was cause by a block/unblock request
			if (buddy.IsBlocked != buddyListItem.isBlocked)
			{
				// Move buddy to the bottom of the list
				if (buddy.IsBlocked)
					buddyListItem.gameObject.transform.SetAsLastSibling();
				else
					buddyListItem.gameObject.transform.SetAsFirstSibling();
			}

			// Update buddy list item
			buddyListItem.SetState(buddy);

			// Update buddy chat panel
			if (chatPanel.BuddyName == buddy.Name)
				chatPanel.SetState(buddy);
		}
	}

    /**
	 * On Play button click (invitedBuddy is set to null), quick join an existing public game Room, or create one.
	 * On Invite buddy button click (on a Buddy List Item prefab instance - invitedBuddy is set to buddu name), create a private game Room and invite the buddy.
	 */
    private void JoinOrCreateGame()
    {
		// Set the common Room settings to create a new game Room if needed
		ClusterRoomSettings settings = new ClusterRoomSettings("TicTacToe_" + new System.Random().Next());
		settings.GroupId = GAME_ROOMS_GROUP_NAME;
		settings.IsGame = true;
		settings.MaxUsers = 2;
		settings.MaxSpectators = 0;
		settings.MinPlayersToStartGame = 2;
		settings.NotifyGameStarted = false;
		settings.Extension = new RoomExtension(EXTENSION_ID, EXTENSION_CLASS);

		// Set Room as public or private
		// A public Game Room needs criteria to be "findable" by other users; these are expressed by means of Room Variables and a match expression
		// A private Game Room can't be found by users: they must be invited to join
		if (invitedBuddy == null)
        {
			// Set Room as public
			settings.IsPublic = true;

			// Set requirements to allow users find the Room (see match expression below) in Room Variables
			List<RoomVariable> roomVars = new List<RoomVariable>();
			roomVars.Add(new SFSRoomVariable(ROOMVAR_USER_EXPERIENCE, sfsLobby.MySelf.GetVariable(USERVAR_EXPERIENCE).GetStringValue()));
			roomVars.Add(new SFSRoomVariable(ROOMVAR_USER_MIN_RANKING, sfsLobby.MySelf.GetVariable(USERVAR_RANKING).GetIntValue()));
			settings.Variables = roomVars;

			// Set the match expression to search for an existing game Room:
			// - it must belong to the generic group "games" (see GAME_ROOMS_GROUP_NAME constant)
			// - it is reserved to users with an experience equal to the one set when the Room was created (see Room Variable set above)
			// - it must have a minimum required ranking equal to or lower than the ranking of the user searching the Room to join, as set when the Room was created (see Room Variable set above)
			MatchExpression exp = new MatchExpression(RoomProperties.GROUP_ID, StringMatch.EQUALS, GAME_ROOMS_GROUP_NAME)
												 .And(ROOMVAR_USER_EXPERIENCE, StringMatch.EQUALS, sfsLobby.MySelf.GetVariable(USERVAR_EXPERIENCE).GetStringValue())
												 .And(ROOMVAR_USER_MIN_RANKING, NumberMatch.LESS_OR_EQUAL_THAN, sfsLobby.MySelf.GetVariable(USERVAR_RANKING).GetIntValue());

			// Send request
			sfsLobby.Send(new ClusterJoinOrCreateRequest(exp, new List<string>() { GAME_ROOMS_GROUP_NAME }, settings));
		}
		else
        {
			// Set Room as private
			settings.IsPublic = false;

            // Invite a buddy
            settings.InvitedPlayers = new List<object>();
            settings.InvitedPlayers.Add(sfsLobby.BuddyManager.GetBuddyByName(invitedBuddy));
			settings.InvitationExpiryTime = 15;

			// Send request
			sfsLobby.Send(new ClusterJoinOrCreateRequest(settings));
		}
    }

    /**
	 * Process the invitation in queue, displaying the invitation accept/refuse panel.
	 */
    private void ProcessInvitations()
	{
		// If the invitation panel is visible, then the user is already dealing with an invitation
		// Otherwise we can go on with the processing
		if (!invitationPanel.IsVisible)
		{
			while (invitationQueue.Count > 0)
			{
				// Get next invitation in queue
				InvitationWrapper iw = invitationQueue.Dequeue();

				// Evaluate remaining time for replying
				DateTime now = DateTime.Now;
				TimeSpan ts = now - iw.date;

				// Update expiration time
				iw.expiresInSeconds -= (int)Math.Floor(ts.TotalSeconds);

				// Display invitation only if expiration will occur in 3 seconds or more, otherwise discard it
				if (iw.expiresInSeconds >= 3)
				{
					invitationPanel.Show(iw);
					break;
				}
			}
		}
	}
	#endregion

	//----------------------------------------------------------
	// SmartFoxServer event listeners
	//----------------------------------------------------------
	#region
	private void OnBuddyListInit(BaseEvent evt)
	{
		// Initialize buddy-related entities
		InitializeBuddyClient();
	}

	private void OnBuddyError(BaseEvent evt)
	{
		// Show Warning Panel prefab instance
		ShowError("Buddy list system error: " + (string)evt.Params["errorMessage"]);
	}

	public void OnBuddyOnlineStateUpdate(BaseEvent evt)
	{
		Buddy buddy = (Buddy)evt.Params["buddy"];
		bool isItMe = (bool)evt.Params["isItMe"];

		// As this event is fired in case of online state update for both the current user and their buddies,
		// we have to check who the event refers to

		if (!isItMe)
			UpdateBuddyListItem(buddy);
		else
		{
			DisplayUserStateAsBuddy();

			// Show/hide buddy list
			buddyListPanel.SetActive(sfsLobby.BuddyManager.MyOnlineState);

			if (!sfsLobby.BuddyManager.MyOnlineState)
			{
				// Hide chat if current user went offline
				chatPanel.Hide();
			}
			else
			{
				// Update all buddy items if current user came online
				foreach (Buddy b in sfsLobby.BuddyManager.BuddyList)
					UpdateBuddyListItem(b);
			}
		}
	}

	public void OnBuddyVariablesUpdate(BaseEvent evt)
	{
		Buddy buddy = (Buddy)evt.Params["buddy"];
		bool isItMe = (bool)evt.Params["isItMe"];
		
		// As this event is fired in case of Buddy Variables update for both the current user and their buddies,
		// we have to check who the event refers to

		if (!isItMe)
			UpdateBuddyListItem(buddy);
		else
			DisplayUserStateAsBuddy();
	}

	public void OnBuddyBlock(BaseEvent evt)
	{
		Buddy buddy = (Buddy)evt.Params["buddy"];

		UpdateBuddyListItem(buddy);
	}

	public void OnBuddyRemove(BaseEvent evt)
	{
		Buddy buddy = (Buddy)evt.Params["buddy"];

		// Get reference to buddy list item corresponding to Buddy
		buddyListItems.TryGetValue(buddy.Name, out BuddyListItem buddyListItem);

		// Remove buddy list item
		if (buddyListItem != null)
		{
			// Remove listeners
			buddyListItem.chatButton.onClick.RemoveAllListeners();
			buddyListItem.inviteButton.onClick.RemoveAllListeners();
			buddyListItem.blockButton.onClick.RemoveAllListeners();
			buddyListItem.removeButton.onClick.RemoveAllListeners();

			// Remove buddy list item from dictionary
			buddyListItems.Remove(buddy.Name);

			// Destroy game object
			GameObject.Destroy(buddyListItem.gameObject);

			// Block chat interaction
			if (chatPanel.BuddyName == buddy.Name)
				chatPanel.Hide();
		}
	}

	public void OnBuddyAdd(BaseEvent evt)
	{
		Buddy buddy = (Buddy)evt.Params["buddy"];

		// Add buddy list item at the top of the list
		AddBuddyListItem(buddy, true);
	}

	public void OnBuddyMessage(BaseEvent evt)
	{
		// Add message to queue
		BuddyChatMessage chatMsg = BuddyChatHistoryManager.AddMessage(evt.Params);

		// Show message or increase message counter
		if (chatPanel.BuddyName == chatMsg.buddyName)
		{
			// Display message in chat panel
			chatPanel.PrintChatMessage(chatMsg);
		}
		else
		{
			// Increase unread messages count
			// NOTE: there's no need to make sure the sender is not the current user,
			// as in this example the current user can't send buddy messages in other ways than
			// the chat panel (which would make the above if statement true)
			int unreadMsgCnt = BuddyChatHistoryManager.IncreaseUnreadMessagesCount(chatMsg.buddyName);

			// Update buddy list item
			BuddyListItem buddyListItem = buddyListItems[chatMsg.buddyName];
			buddyListItem.gameObject.transform.SetAsFirstSibling();
			buddyListItem.SetChatMsgCounter(unreadMsgCnt);
		}
	}

	public void OnUserVariablesUpdate(BaseEvent evt)
	{
		User user = (User)evt.Params["user"];

		// Display player details in user profile panel
		if (user.IsItMe)
			userProfilePanel.InitPlayerProfile(user);
	}

	public void OnInvitation(BaseEvent evt)
	{
		Invitation invitation = (Invitation)evt.Params["invitation"];

		// Add invitation wrapper to queue
		// We use a quue because a user could receive multiple invitations within the time it takes to accept or refuse one
		if (invitationQueue == null)
			invitationQueue = new Queue<InvitationWrapper>();

		invitationQueue.Enqueue(new InvitationWrapper(invitation));

		// Trigger invitation processing
		ProcessInvitations();
	}
	#endregion
}
