using Godot;
using Sfs3.Client;
using Sfs3.Client.Core;
using Sfs3.Client.Entities;
using Sfs3.Client.Entities.Data;
using Sfs3.Client.Requests;
using Sfs3.Client.Requests.Buddylist;
using System;
using System.Collections.Generic;
using System.Linq;

public partial class TicTacToeGameManager : Control
{
    [Export]
    private Label stateLabel;
    [Export]
    private TextureButton joinButton;
    [Export]
    private TextureButton restartButton;
    [Export]
    private Control p1Tag;
    [Export]
    private Control p2Tag;
    [Export]
    private GameBoard gameBoard;

    private enum State
    {
        WAITING_FOR_PLAYERS = 1,
        RUNNING,
        GAME_WON,
        GAME_LOST,
        GAME_TIE,
        INTERRUPTED,
    }

    private SmartFox sfs;
    private State state;
    private int whoseTurn;
    private int lastWinner = -1;

    private readonly Dictionary<int, Control> playerTags = [];
    private readonly Dictionary<int, int> playerWins = [];

    //----------------------------------------------------------
    // Initialization methods
    //----------------------------------------------------------

    public void Init(SmartFox sfs)
    {
        this.sfs = sfs;

        // Init player info
        InitPlayerInfo();

        // Init board
        gameBoard.Init();
        gameBoard.SlotClickAction = OnBoardSlotClick;

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

        // Add SmartFoxServer-related event listeners required by this game
        sfs.AddEventListener(SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse);
        sfs.AddEventListener(SFSEvent.USER_ENTER_ROOM, OnUserEnterRoom);
        sfs.AddEventListener(SFSEvent.USER_EXIT_ROOM, OnUserExitRoom);
        sfs.AddEventListener(SFSEvent.SPECTATOR_TO_PLAYER, OnSpectatorToPlayer);

        // Display player tags
        UpdatePlayerTags();

        // Set initial state
        state = State.WAITING_FOR_PLAYERS;

        // Display game state
        UpdateGameState();

        // Tell Room Extension that user is ready
        sfs.Send(new ExtensionRequest("ready", null, sfs.LastJoinedRoom));
    }

    public void Destroy()
    {
        sfs.RemoveEventListener(SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse);
        sfs.RemoveEventListener(SFSEvent.USER_ENTER_ROOM, OnUserEnterRoom);
        sfs.RemoveEventListener(SFSEvent.USER_EXIT_ROOM, OnUserExitRoom);
        sfs.RemoveEventListener(SFSEvent.SPECTATOR_TO_PLAYER, OnSpectatorToPlayer);
    }

    private void InitPlayerInfo()
    {
        // Player 1
        playerTags[1] = p1Tag;
        ((TextureButton)p1Tag.FindChild("AddBuddyButton")).Pressed += () => { OnAddBuddyButtonClick(p1Tag); };
        p1Tag.Visible = false;
        playerWins[1] = 0;

        // Player 2
        playerTags[2] = p2Tag;
        ((TextureButton)p2Tag.FindChild("AddBuddyButton")).Pressed += () => { OnAddBuddyButtonClick(p2Tag); };
        p2Tag.Visible = false;
        playerWins[2] = 0;
    }

    //----------------------------------------------------------
    // Game methods
    //----------------------------------------------------------

    /**
     * Display current game state.
     */
    private void UpdateGameState()
    {
        // Set label default color
        stateLabel.AddThemeColorOverride("font_color", Colors.White);

        if (state == State.WAITING_FOR_PLAYERS)
        {
            if (sfs.MySelf.IsPlayer)
                UpdateHeader("Waiting for your opponent");
            else
            {
                if (sfs.LastJoinedRoom.PlayerList.Count < 2)
                    UpdateHeader("Waiting for players;\ndo you want to join?", false, true);
            }
        }

        else if (state == State.INTERRUPTED)
        {
            if (sfs.MySelf.IsPlayer)
            {
                string text = "Your opponent left the game";

                // If the current Room is private, no other player will ever join it
                // If spectators could be invited in a private game, we should also check that no one is in the Room
                if (sfs.LastJoinedRoom.IsPasswordProtected)
                    text += ";\nyou should leave too";
                else
                    text += ";\nwaiting for a new player";

                UpdateHeader(text);
            }
            else
                UpdateHeader("Game interrupted due to missing\nplayer; do you want to join?", false, true);
        }

        else if (state == State.RUNNING)
        {
            if (sfs.MySelf.IsPlayer)
                UpdateHeader(sfs.MySelf.PlayerId == whoseTurn ? "It's your turn" : "It's your opponent's turn");
            else
                UpdateHeader("It's player " + whoseTurn + " turn");
        }

        else
        {
            string text = "GAME OVER";

            if (sfs.MySelf.IsPlayer)
            {
                Color color = Colors.White;

                switch (state)
                {
                    case State.GAME_WON:
                        text += "\nYou are the winner!";
                        color = Colors.Green;
                        break;

                    case State.GAME_LOST:
                        text += "\nYou've lost!";
                        color = Colors.Red;
                        break;

                    case State.GAME_TIE:
                        text += "\nIt's a tie!";
                        color = Colors.Yellow;
                        break;
                }

                UpdateHeader(text, true, false);
                stateLabel.AddThemeColorOverride("font_color", color);
            }
            else
            {
                switch (state)
                {
                    case State.GAME_TIE:
                        text += "\nIt's a tie!";
                        break;

                    default:
                        text += "\nPlayer " + lastWinner + " is the winner!";
                        break;
                }

                UpdateHeader(text);
            }
        }
    }

    private void UpdateHeader(string message, bool showRestart = false, bool showJoin = false)
    {
        stateLabel.Text = message;
        restartButton.Visible = showRestart;
        joinButton.Visible = showJoin;
    }

    public void UpdatePlayerTags()
    {
        foreach (User user in sfs.LastJoinedRoom.PlayerList)
        {
            var i = user.PlayerId;
            var tag = playerTags[i];

            if (tag != null)
            {
                tag.Visible = true;

                Label nameLabel = (Label)tag.FindChild("NameLabel");
                Label winsLabel = (Label)tag.FindChild("WinsLabel");
                TextureButton addBuddyButton = (TextureButton)tag.FindChild("AddBuddyButton");
                int wins = playerWins[i];

                nameLabel.Text = user.Name;
                winsLabel.Text = "Wins: " + wins;
                addBuddyButton.Visible = !user.IsItMe && (sfs.BuddyManager.GetBuddyByName(user.Name) == null || sfs.BuddyManager.GetBuddyByName(user.Name).IsTemp);
            }
        }
    }

    private void ShowPlayerWins(int p1Wins, int p2Wins)
    {
        playerWins[1] = p1Wins;
        playerWins[2] = p2Wins;
        UpdatePlayerTags();
    }
    
    /**
    * Start the game.
    */
    private void StartGame(int? whoseTurn, int? p1Wins, int? p2Wins)
    {
        this.whoseTurn = whoseTurn.Value;

        // Reset game board
        gameBoard.Reset();

        // Set game state
        state = State.RUNNING;

        // Display game state
        UpdateGameState();

        // Display player wins
        ShowPlayerWins(p1Wins.Value, p2Wins.Value);

        // Enable game board
        if (sfs.MySelf.IsPlayer)
            gameBoard.IsEnabled = true;
    }

    /**
     * Stop the game.
     */
    private void StopGame()
    {
        // Set game state
        state = State.INTERRUPTED;

        // Display game state
        UpdateGameState();

        // Disable game board
        gameBoard.IsEnabled = false;
    }

    /**
     * Spectator receives board update. If match isn't started yet,
     * a message is displayed and they can click the join button
     */
    private void SetSpectatorBoard(int? whoseTurn, bool? gameRunning, ISFSArray boardData, int? p1Wins, int? p2Wins, int? lastWinner)
    {
        this.whoseTurn = whoseTurn.Value;

        // Set game state
        if (gameRunning.Value)
            state = State.RUNNING;

        // Display game state
        UpdateGameState();

        // Display player wins
        ShowPlayerWins(p1Wins.Value, p2Wins.Value);

        // Update board
        // NOTE: the board data is a SFSArray (the board rows) containing three other SFSArrays (the columns)
        // As SFSArrays are 0-based, we have to convert to board coordinates
        for (int i = 0; i < 3; i++)
        {
            int[] values = boardData.GetIntArray(i).ToArray<int>();

            for (int j = 0; j < 3; j++)
            {
                int row = i + 1;
                int col = j + 1;

                gameBoard.SetMark(row, col, values[j]);
            }
        }

        if (this.lastWinner > -1)
            DeclareWinner(lastWinner.Value);
    }

    /**
     * On move received, add mark to game board.
     */
    private void ExecuteMove(int? playerId, int? row, int? col)
    {
        // Set new turn
        whoseTurn = (playerId == 1) ? 2 : 1;

        // Display game state
        UpdateGameState();

        // Show mark on game board
        gameBoard.SetMark(row.Value, col.Value, playerId.Value);
    }

    /**
     * Declare game winner.
     */
    private void DeclareWinner(int? winnerId)
    {
        lastWinner = winnerId.Value;

        // Set game state
        if (winnerId > 0)
        {
            // Current user is the winner
            if (sfs.MySelf.PlayerId == winnerId)
                state = State.GAME_WON;

            // Opponent is the winner
            else
                state = State.GAME_LOST;

            // Update player wins
            playerWins[winnerId.Value]++;
            UpdatePlayerTags();
        }
        else
            state = State.GAME_TIE;

        // Display game state
        UpdateGameState();

        // Disable game board
        gameBoard.IsEnabled = false;
    }
    
    //----------------------------------------------------------
    // UI event listeners
    //----------------------------------------------------------

    /**
     * On Add buddy button click, add oppontent to the user's Buddy List.
     */
    private void OnAddBuddyButtonClick(Control playerTag)
    {
        Label nameLabel = (Label)playerTag.FindChild("NameLabel");
        string userName = nameLabel.Text;

        if (userName != "")
            sfs.Send(new AddBuddyRequest(userName));
    }

    /**
     * On Restart button click, start new game.
     */
    private void OnRestartClick()
    {
        // Send request to Extension
		sfs.Send(new ExtensionRequest("restart", null, sfs.LastJoinedRoom));
    }

    /**
     * On Join button click, turn current user from spectator to player.
     */
    private void OnJoinClick()
    {
        // Send request to server
		sfs.Send(new SpectatorToPlayerRequest());
    }

    /**
     * Listen to custom click event dispatched by GameBoard instance.
     */
    private void OnBoardSlotClick(int row, int col)
    {
        // Only player in turn is allowed to interact with the board when the game is running
        if (state == State.RUNNING && sfs.MySelf.PlayerId == whoseTurn)
        {
            var move = new SFSObject();
            move.PutInt("r", row);
            move.PutInt("c", col);

            // Send move to Extension
            sfs.Send(new ExtensionRequest("move", move, sfs.LastJoinedRoom));
        }
    }

    //----------------------------------------------------------
    // SmartFoxServer event listeners
    //----------------------------------------------------------

    private void OnUserEnterRoom(ApiEvent evt)
    {
        UpdatePlayerTags();
    }

    private void OnUserExitRoom(ApiEvent evt)
    {
        User user = (User)evt.Params[EventParam.User];

        if (!user.IsItMe && user.IsPlayer)
            playerTags[user.PlayerId].Visible = false;
    }

    private void OnSpectatorToPlayer(ApiEvent evt)
    {
        UpdateGameState();
        UpdatePlayerTags();
    }

    private void OnExtensionResponse(ApiEvent evt)
    {
        string cmd = (string)evt.Params[EventParam.Cmd];
        ISFSObject resParams = (SFSObject)evt.Params[EventParam.ExtParams];
        
        switch (cmd)
        {
            case "start":
                StartGame(resParams.GetInt("t"), resParams.GetInt("p1w"), resParams.GetInt("p2w"));
                break;

            case "state":
                SetSpectatorBoard(resParams.GetInt("t"), resParams.GetBool("run"), resParams.GetSFSArray("board"), resParams.GetInt("p1w"), resParams.GetInt("p2w"), resParams.ContainsKey("w") ? resParams.GetInt("w") : -1);
                break;

            case "stop":
                StopGame();
                break;

            case "move":
                ExecuteMove(resParams.GetInt("t"), resParams.GetInt("r"), resParams.GetInt("c"));
                break;

            case "over":
                DeclareWinner(resParams.GetInt("w"));
                break;
        }
    }
}
