using Godot;

using Sfs3.Client.Core;
using Sfs3.Client.Requests;
using Sfs3.Client.Entities;
using System.Collections.Generic;

using System.Linq;
using System;
using Sfs3.Client.Requests.Buddylist;
using Sfs3.Client.Entities.Variables;
using Sfs3.Client.Entities.Data;

/**
 * Script attached to the main node in the scene.
 */
public partial class LobbyController : BaseController
{
    public static readonly string GAME_ROOMS_GROUP_NAME = "games";
    public static readonly string BUDDYVAR_YEAR = SFSBuddyVariable.OFFLINE_PREFIX + "year";
    public static readonly string BUDDYVAR_MOOD = "mood";

    //----------------------------------------------------------
    // UI elements
    //----------------------------------------------------------
    [ExportCategory("UI Settings")]

    [Export]
    public ProfileConfigurator profileConfigurator;
	[Export]
	public PopupDialog warningPanel;
    [Export]
    public Label warningLabel;
    [Export]
    public GameList gameList;
    [Export]
    public Control buddyListContainer;
    [Export]
    public BuddyList buddyList;
    [Export]
    public LineEdit buddyNameInput;
    [Export]
    public ChatPanel buddyChatPanel;

    //----------------------------------------------------------
    // Callback Methods
    //----------------------------------------------------------
    #region
    public override void _Ready()
    {
        base._Ready();
        
        sfs = Global.GetSfsClient();

        // Add SFS event listeners
        AddSmartFoxListeners();

        // Add listener to join button click event fired by GameList class
        gameList.JoinGameAction = OnJoinGamePressed;

        // Add listeners to events fired by BuddyList class
        buddyList.StartChatAction = OnStartBuddyChatPressed;
        buddyList.BlockAction = OnBlockBuddyPressed;
        buddyList.RemoveAction = OnRemoveBuddyPressed;
        buddyList.AddAction = OnAddBuddyPressed;

        // Add listeners to events fired by ProfileConfigurator class
        profileConfigurator.OnlineChangeAction = OnOnlineToggleChanged;
        profileConfigurator.BuddySettingChangeAction = OnBuddySettingChanged;

        // Add listeners to events fired by ChatPanel class
        buddyChatPanel.SendMessageAction = OnBuddyChatMessageSubmit;

        // Populate list of available games
        var rooms = sfs.RoomManager.GetRoomList();
        gameList.PopulateList(rooms);

        // Display user name in profile modal
        profileConfigurator.DisplayUserName(sfs.MySelf.Name);

        // Initialize Buddy List system
        // We have to check if it was already initialized if this scene is relaoded after leaving the Game scene
        if (!sfs.BuddyManager.IsInited)
            sfs.Send(new InitBuddyListRequest());
        else
            InitializeBuddyClient();
    }
    #endregion

    //----------------------------------------------------------
    // UI event listeners
    //----------------------------------------------------------
    #region
    
    public void OnLogoutButtonClick()
    {
        BuddyChatHistoryManager.ResetAllHistory();

        sfs.Disconnect();
    }

    /**
     * On New Game button click, create and join a new game Room.
     */
    public void OnNewGameButtonClick()
    {
        // Configure Room
        RoomSettings settings = new(sfs.MySelf.Name + "'s game")
        {
            GroupId = GAME_ROOMS_GROUP_NAME,
            IsGame = true,
            MaxUsers = 2,
            MaxSpectators = 10
        };

        // Request Room creation to server
        sfs.Send(new CreateRoomRequest(settings, true));
    }

    /**
     * On Play or Watch button click in Game List, join an existing game Room as a player or Spectator respectively.
     */
    public void OnJoinGamePressed(int roomId, bool asSpectator)
    {
        sfs.Send(new JoinRoomRequest(roomId, null, null, asSpectator));
    }
    
    /**
     * On Add buddy button click, send request to add buddy to user's Buddy List.
     */
    public void OnAddBuddyButtonClick()
    {
        OnBuddyNameInputSubmit(buddyNameInput.Text);

        buddyNameInput.GrabFocus();
    }

    /**
	 * On buddy name input submit, send request to add buddy to user's Buddy List.
	 */
    public void OnBuddyNameInputSubmit(string userName)
    {
        if (userName != "")
        {
            // Request buddy adding to buddy list
            sfs.Send(new AddBuddyRequest(userName));

            // Reset buddy name input
            buddyNameInput.Text = "";
            buddyNameInput.GrabFocus();
        }
    }

    /**
     * On Chat button press in Buddy List item, initialize and show chat panel.
     */
    public void OnStartBuddyChatPressed(string buddyName)
    {
        // Skip if chat is already active with the same buddy
        if (buddyChatPanel.UserName != buddyName)
        {
            // Get chat history (if any) and reset new messages counter
            var buddyChatMessages = BuddyChatHistoryManager.GetBuddyChatMessages(buddyName);
            BuddyChatHistoryManager.ResetUnreadMessagesCount(buddyName);

            // Initialize and show panel
            InitBuddyChatPanel(sfs.BuddyManager.GetBuddyByName(buddyName), buddyChatMessages);

            // Move buddy list item to top of the list and reset new messages counter
            buddyList.MoveListItemToTop(buddyName);
            buddyList.SetItemChatMsgCounter(buddyName, 0);
        }
    }

    /**
     * On Block/Unblock button press in Buddy List item, send request to block/unblock buddy in user's Buddy List.
     */
    public void OnBlockBuddyPressed(string buddyName)
    {
        bool isBlocked = sfs.BuddyManager.GetBuddyByName(buddyName).IsBlocked;

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

    /**
     * On Remove button press in Buddy List item, send request to remove buddy from user's Buddy List.
     */
    public void OnRemoveBuddyPressed(string buddyName)
    {
        // Request buddy removal from buddy list
		sfs.Send(new RemoveBuddyRequest(buddyName));
    }

    /**
     * On Add button press in Buddy List item, send request to add a temporary buddy to user's Buddy List.
     */
    public void OnAddBuddyPressed(string buddyName)
    {
        // Request buddy removal from buddy list
		sfs.Send(new AddBuddyRequest(buddyName));
    }

    /**
     * On custom event fired by Profile Configurator control, toggle user online state in Buddy List system.
     */
    public void OnOnlineToggleChanged(bool goOnline)
    {
        // Request online/offline state toggle
        sfs.Send(new GoOnlineRequest(goOnline));
    }

    /**
     * On custom event fired by Profile Configurator control/s, set user's Buddy Variables.
     */
    public void OnBuddySettingChanged(string varName, object value)
    {
        var type = value == null ? VariableType.NULL : (value is int ? VariableType.INT : VariableType.STRING);

        List<BuddyVariable> buddyVars = [];
        buddyVars.Add(new SFSBuddyVariable(varName, value, type));

        // Set Buddy Variables
        sfs.Send(new SetBuddyVariablesRequest(buddyVars));
    }

    public void OnBuddyChatMessageSubmit(string msg)
    {
        string buddyName = buddyChatPanel.UserName;

        // Add a custom parameter containing the recipient name
        ISFSObject paramsObj = new SFSObject();
        paramsObj.PutString("recipient", buddyName);

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

        // Send message to buddy
        sfs.Send(new BuddyMessageRequest(msg, buddy, paramsObj));
    }
    #endregion

    //----------------------------------------------------------
    // Helper methods
    //----------------------------------------------------------
    #region
    private void AddSmartFoxListeners()
    {
        sfs.AddEventListener(SFSEvent.ROOM_CREATION_ERROR, OnRoomCreationError);
        sfs.AddEventListener(SFSEvent.ROOM_ADD, OnRoomAdded);
        sfs.AddEventListener(SFSEvent.ROOM_REMOVE, OnRoomRemoved);
        sfs.AddEventListener(SFSEvent.USER_COUNT_CHANGE, OnUserCountChanged);
        sfs.AddEventListener(SFSEvent.ROOM_JOIN, OnRoomJoin);
        sfs.AddEventListener(SFSEvent.ROOM_JOIN_ERROR, OnRoomJoinError);

		sfs.AddEventListener(SFSBuddyEvent.BUDDY_LIST_INIT, OnBuddyListInit);
		sfs.AddEventListener(SFSBuddyEvent.BUDDY_ERROR, OnBuddyError);
		sfs.AddEventListener(SFSBuddyEvent.BUDDY_ONLINE_STATE_CHANGE, OnBuddyOnlineStateChange);
		sfs.AddEventListener(SFSBuddyEvent.BUDDY_VARIABLES_UPDATE, OnBuddyVariablesUpdate);
		sfs.AddEventListener(SFSBuddyEvent.BUDDY_BLOCK, OnBuddyBlock);
		sfs.AddEventListener(SFSBuddyEvent.BUDDY_REMOVE, OnBuddyRemove);
		sfs.AddEventListener(SFSBuddyEvent.BUDDY_ADD, OnBuddyAdd);
		sfs.AddEventListener(SFSBuddyEvent.BUDDY_MESSAGE, OnBuddyMessage);
    }

    public override void RemoveSmartFoxListeners()
    {
        if (sfs != null)
        {
            sfs.RemoveEventListener(SFSEvent.ROOM_CREATION_ERROR, OnRoomCreationError);
            sfs.RemoveEventListener(SFSEvent.ROOM_ADD, OnRoomAdded);
            sfs.RemoveEventListener(SFSEvent.ROOM_REMOVE, OnRoomRemoved);
            sfs.RemoveEventListener(SFSEvent.USER_COUNT_CHANGE, OnUserCountChanged);
            sfs.RemoveEventListener(SFSEvent.ROOM_JOIN, OnRoomJoin);
            sfs.RemoveEventListener(SFSEvent.ROOM_JOIN_ERROR, OnRoomJoinError);

            sfs.RemoveEventListener(SFSBuddyEvent.BUDDY_LIST_INIT, OnBuddyListInit);
            sfs.RemoveEventListener(SFSBuddyEvent.BUDDY_ERROR, OnBuddyError);
            sfs.RemoveEventListener(SFSBuddyEvent.BUDDY_ONLINE_STATE_CHANGE, OnBuddyOnlineStateChange);
            sfs.RemoveEventListener(SFSBuddyEvent.BUDDY_VARIABLES_UPDATE, OnBuddyVariablesUpdate);
            sfs.RemoveEventListener(SFSBuddyEvent.BUDDY_BLOCK, OnBuddyBlock);
            sfs.RemoveEventListener(SFSBuddyEvent.BUDDY_REMOVE, OnBuddyRemove);
            sfs.RemoveEventListener(SFSBuddyEvent.BUDDY_ADD, OnBuddyAdd);
            sfs.RemoveEventListener(SFSBuddyEvent.BUDDY_MESSAGE, OnBuddyMessage);
        }
    }

    private void ShowBuddyList(bool show)
    {
        buddyListContainer.Visible = show;
    }

    private void InitializeBuddyClient()
    {
        // Display buddy list items
        // All blocked buddies are displayed at the bottom of the list
        foreach (Buddy buddy in sfs.BuddyManager.BuddyList)
            buddyList.AddListItem(buddy, !buddy.IsBlocked);

        // Set current user details in Buddy List system
        profileConfigurator.InitBuddyProfile(sfs.BuddyManager);

        // Display user state in Buddy List system
        DisplayUserStateAsBuddy();

        // Show/hide buddy list
        ShowBuddyList(sfs.BuddyManager.MyOnlineState);
    }

    /**
     * Show current user state in Buddy List system in footer.
     */
    private void DisplayUserStateAsBuddy()
    {
        string state = sfs.BuddyManager.MyOnlineState ? sfs.BuddyManager.MyState : "Offline";
        profileConfigurator.DisplayUserState(state);
    }

    private void UpdateBuddyListEntry(Buddy buddy)
    {
        buddyList.UpdateListItem(buddy);

        // Also update buddy chat panel
        if (buddyChatPanel.UserName == buddy.Name)
        {
            // Update nickname if changed
            string newBuddyDisplayName = buddy.DisplayName;

            // Get current name displayed in chat
            string currBuddyDisplayName = buddyChatPanel.DisplayName;

            if (newBuddyDisplayName != currBuddyDisplayName)
            {
                buddyChatPanel.PrintSystemMessage(currBuddyDisplayName + " is now known as " + newBuddyDisplayName);
                buddyChatPanel.SetUser(buddy.Name, buddy.Nickname);
            }

            // Enable/disable message input
            buddyChatPanel.Disabled = !buddy.IsOnline || buddy.IsBlocked;
        }
    }

    private void InitBuddyChatPanel(Buddy buddy, List<BuddyChatMessage> msgHistory)
    {
        // Reset panel (it is reused for different buddies)
        buddyChatPanel.Reset();

        // Set buddy name
        buddyChatPanel.SetUser(buddy.Name, buddy.Nickname);

        // Print chat history
        if (msgHistory != null)
            foreach (var messageObj in msgHistory)
                buddyChatPanel.PrintChatMessage(messageObj.Message, messageObj.Sender, messageObj.SentByMe, messageObj.Date);

        // Show chat panel
        buddyChatPanel.SetVisible();

        // Focus on message input
        buddyChatPanel.SetFocus();
    }
    #endregion

    //----------------------------------------------------------
    // SmartFoxServer event listeners
    //----------------------------------------------------------
    #region
    private void OnRoomCreationError(ApiEvent evt)
    {
        // Show warning
        warningLabel.Text = String.Format("Game Room creation failed.\n{0}.", evt.Params[EventParam.ErrorMessage]);
        warningPanel.Show();
    }

    private void OnRoomAdded(ApiEvent evt)
    {
        Room room = (Room)evt.Params[EventParam.Room];

        // Display game list item
        gameList.AddListItem(room);
    }

    public void OnRoomRemoved(ApiEvent evt)
    {
        Room room = (Room)evt.Params[EventParam.Room];

        // Remove game list item
        gameList.RemoveListItem(room.Id);
    }

    public void OnUserCountChanged(ApiEvent evt)
    {
        Room room = (Room)evt.Params[EventParam.Room];
        
        // Update game list item
        gameList.SetItemState(room.Id);
    }

    private void OnRoomJoin(ApiEvent evt)
    {
        // Set state to "Away" in Buddy List system
        if (sfs.BuddyManager.MyOnlineState)
        {
            List<BuddyVariable> buddyVars = [];
            buddyVars.Add(new SFSBuddyVariable(ReservedBuddyVariables.BV_STATE, "Away", VariableType.STRING));
            sfs.Send(new SetBuddyVariablesRequest(buddyVars));
        }

        // Load game scene
        GetTree().ChangeSceneToFile("scenes/Game.tscn");
    }

    private void OnRoomJoinError(ApiEvent evt)
    {
        // Show warning
        warningLabel.Text = String.Format("Room join failed.\n{0}.", evt.Params[EventParam.ErrorMessage]);
        warningPanel.Show();
    }

    private void OnBuddyListInit(ApiEvent evt)
    {
        // Initialize buddy-related entities
        InitializeBuddyClient();
    }

    private void OnBuddyError(ApiEvent evt)
    {
        warningLabel.Text = String.Format("Buddy list system error.\n{0}.", evt.Params[EventParam.ErrorMessage]);
        warningPanel.Show();
    }

    private void OnBuddyOnlineStateChange(ApiEvent evt)
    {
        Buddy buddy = (Buddy)evt.Params[EventParam.Buddy];
        bool isItMe = (bool)evt.Params[EventParam.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)
            UpdateBuddyListEntry(buddy);
        else
        {
            DisplayUserStateAsBuddy();

            bool isOnline = sfs.BuddyManager.MyOnlineState;

            // Show/hide buddy list
            ShowBuddyList(isOnline);

            if (!isOnline)
            {
                // Hide chat if current user went offline
                buddyChatPanel.ResetAndHide();
            }
            else
            {
                // Update all buddy items if current user came online
                foreach (Buddy b in sfs.BuddyManager.BuddyList)
                    UpdateBuddyListEntry(b);
            }
        }
    }

    private void OnBuddyVariablesUpdate(ApiEvent evt)
    {
        Buddy buddy = (Buddy)evt.Params[EventParam.Buddy];
        bool isItMe = (bool)evt.Params[EventParam.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)
            UpdateBuddyListEntry(buddy);
        else
            DisplayUserStateAsBuddy();
    }

    private void OnBuddyBlock(ApiEvent evt)
    {
        Buddy buddy = (Buddy)evt.Params[EventParam.Buddy];

        UpdateBuddyListEntry(buddy);

        // Reset unread messages counter
        BuddyChatHistoryManager.ResetHistory(buddy.Name);
    }

    private void OnBuddyRemove(ApiEvent evt)
    {
        Buddy buddy = (Buddy)evt.Params[EventParam.Buddy];

        buddyList.RemoveListItem(buddy.Name);

        if (buddyChatPanel.UserName == buddy.Name)
            buddyChatPanel.ResetAndHide();

        // Reset unread messages counter and update counter in buddy list tab
        BuddyChatHistoryManager.ResetHistory(buddy.Name);
    }

    private void OnBuddyAdd(ApiEvent evt)
    {
        Buddy buddy = (Buddy)evt.Params[EventParam.Buddy];

        // Add buddy list item at the top of the list
        buddyList.AddListItem(buddy, true);
    }

    private void OnBuddyMessage(ApiEvent evt)
    {
        Buddy sender = (Buddy)evt.Params[EventParam.Buddy];
        bool isItMe = (bool)evt.Params[EventParam.IsItMe];
        string message = (string)evt.Params[EventParam.Message];
        string recipient = ((SFSObject)evt.Params[EventParam.Data]).GetString("recipient");

        string targetBuddyName = isItMe ? recipient : sender.Name;
        string senderDisplayName = isItMe ? sfs.BuddyManager.MyDisplayName : sender.DisplayName;

        // Add message to queue
        var chatMsg = BuddyChatHistoryManager.AddMessage(targetBuddyName, senderDisplayName, isItMe, message);

        // Show message or increase message counter
        if (buddyChatPanel.UserName == chatMsg.Target)
        {
            // Display message in chat panel
            buddyChatPanel.PrintChatMessage(chatMsg.Message, chatMsg.Sender, chatMsg.SentByMe, chatMsg.Date);
        }
        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.Target);

            // Update buddy list item
            buddyList.SetItemChatMsgCounter(chatMsg.Target, unreadMsgCnt);
        }
    }
    #endregion
}