﻿using AsmodeeDigital.Common.Plugin.UI;
using AsmodeeDigital.PlayReal.Plugin.Domain.GameConfiguration;
using AsmodeeDigital.PlayReal.Plugin.Domain.Players;
using AsmodeeDigital.PlayReal.Plugin.Manager;
using AsmodeeDigital.PlayReal.Samples.Domain.Game;
using AsmodeeDigital.PlayReal.Samples.Logic;
using AsmodeeDigital.PlayReal.Samples.Logic.Gameplay;
using AsmodeeDigital.PlayReal.Samples.UI.Game;
using AsmodeeDigital.PlayReal.Samples.UI.LobbyPlayers;
using com.daysofwonder.async;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace AsmodeeDigital.PlayReal.Samples.Views
{
    /// <summary>
    /// View displayed when a game start
    /// </summary>
    public class GameView : MonoBehaviour
    {
        /// <summary>
        /// UI references of GameView
        /// </summary>
        [Serializable]
        public class UI
        {
            public List<UserItem> UserItems;
            public Button RollDiceButton;
            public Button EndTurnButton;
            public List<Die> Dice;
            public Popup PopupQuit;
            public InfoPanel InfoPanel;
            public Popup PopupConnectionError;

            [Header("GameOver")]
            public Text GameOverText;
            public Transform GameOverPanel;

            [Header("Chat")]
            public Animator AnimatorChat;
            public ScrollRect ChatItemsParent;
            public InputField ChatInputField;
            public Button ChatButton;
        }

        public ChatItem ChatItemPrefab;

        /// <summary>
        /// UI instance
        /// </summary>
        public UI ui;

        /// <summary>
        /// List of Euler angles for each face value on a 6-sided dice
        /// </summary>
        public List<Vector3> DiceFaceUpRotation = new List<Vector3>();

        /// <summary>
        /// Logic of the view
        /// </summary>
        private GameplayLogicBase _gameplayLogic;

        /// <summary>
        /// Chat logic
        /// </summary>
        private ChatLogic _chatLogic;

        private void Start()
        {
            Init();
        }

        /// <summary>
        /// Initialize the view - Creating Local or Distant Logic according to the parameter of the game - Creating .Net events
        /// </summary>
        private void Init()
        {
            //--- Instantiate the GamePlayLogic depending on GameType (Distant, Local or Replay)
            switch (PlayRealManager.Instance.Persistence.GameType)
            {
                case GameType.Local:
                    _gameplayLogic = new LocalGameplayLogic(PlayRealManager.Instance.Persistence, ui.Dice);
                    break;
                case GameType.Distant:
                    _gameplayLogic = new DistantGameplayLogic(PlayRealManager.Instance.ServerConnection, PlayRealManager.Instance.Persistence, ui.Dice);
                    break;
                case GameType.Replay:
                    _gameplayLogic = new ReplayGameplayLogic(PlayRealManager.Instance.Persistence, ui.Dice);
                    break;
                default:
                    break;
            }

            _gameplayLogic.LogEnabled = PlayRealManager.Instance.LogLogicEnabled;
            //---

            //--- General local and online events
            _gameplayLogic.LocalPlayerTurnEvent += GameplayLogic_LocalPlayerTurnEvent;
            _gameplayLogic.OtherPlayerTurnEvent += GameplayLogic_OtherPlayerTurnEvent;
            _gameplayLogic.RoundEndedEvent += GameplayLogic_RoundEndedEvent;
            _gameplayLogic.GameOutcomeEvent += GameplayLogic_GameOutcomeEvent;
            //---

            //--- Dice game specific events
            _gameplayLogic.DiePickedIndexEvent += GameplayLogic_DicePickedIndexEvent;
            _gameplayLogic.RollDiceAnimationEvent += GameplayLogic_RollDiceAnimationEvent;
            //---

            //---> Online events
            if (PlayRealManager.Instance.Persistence.GameType == GameType.Distant)
            {
                ((DistantGameplayLogic)_gameplayLogic).PlayerPresenceUpdatedEvent += GameplayLogic_PlayerPresenceUpdatedEvent;
                ((DistantGameplayLogic)_gameplayLogic).RefreshGameStatusEvent += GameplayLogic_RefreshGameStatusEvent;
                ((DistantGameplayLogic)_gameplayLogic).GameAbortedEvent += GameplayLogic_GameAbortedEvent;
                ((DistantGameplayLogic)_gameplayLogic).OtherPlayerQuitGameEvent += GameplayLogic_PlayerQuitGameEvent;
                ((DistantGameplayLogic)_gameplayLogic).LocalPlayerQuitGameEvent += GameplayLogic_LocalPlayerQuitGameEvent;
                ((DistantGameplayLogic)_gameplayLogic).PlayerJoinGameEvent += GameplayLogic_PlayerJoinGameEvent;

                ((DistantGameplayLogic)_gameplayLogic).DisconnectedFromServerEvent += GameplayLogic_DisconnectedFromServerEvent;
                ((DistantGameplayLogic)_gameplayLogic).ReconnectedToServerEvent += GameplayLogic_ReconnectedToServerEvent;
                ((DistantGameplayLogic)_gameplayLogic).CantReconnectToServerEvent += GameplayLogic_CantReconnectToServerEvent;


                _chatLogic = new ChatLogic(PlayRealManager.Instance.ServerConnection, PlayRealManager.Instance.Persistence);
                _chatLogic.ClientChatEvent += ChatLogic_ClientChatEvent;

                _chatLogic.Init();
            }
            else
            {
                //---> Hide the Chat button in local mode
                ui.ChatButton.gameObject.SetActive(false);
            }

            //---> Initialize the logic
            _gameplayLogic.Init();
        }

        #region UI events
        /// <summary>
        /// Call the logic to roll the selected dice (create event in game state) and animate them
        /// </summary>
        public void RollDice()
        {
            HideText();
            _gameplayLogic.RollSelectedDice();
            StartCoroutine(RollDiceAnimationAsync());
        }

        /// <summary>
        /// Call the logic to end the turn of current player
        /// </summary>
        public void EndTurn()
        {
            HideText();
            ui.EndTurnButton.interactable = false;
            _gameplayLogic.EndOfTurn();
        }

        /// <summary>
        /// Call the logic to select a dice. In a distant game, the logic send a multicast message to select the dice on all players devices
        /// </summary>
        /// <param name="eventData"></param>
        public void Dice_PointerClicked(BaseEventData eventData)
        {
            Die die = ((PointerEventData)eventData).rawPointerPress.GetComponent<Die>();
            _gameplayLogic.TryChangeDiceSelection(die.Index, !die.Selected);
        }

        public void ShowPopupQuit()
        {
            Button leaveButton = ui.PopupQuit.ui.Buttons[0];
            Button forfeitButton = ui.PopupQuit.ui.Buttons[1];
            string message = string.Empty;

            if (PlayRealManager.Instance.Persistence.GameType != GameType.Distant)
            {
                leaveButton.gameObject.SetActive(true);
                forfeitButton.gameObject.SetActive(false);

                message = "Are you sure you want to quit?";
            }
            else
            {
                //---> In Async mode, player can leave a game and will be replace by a bot
                if (PlayRealManager.Instance.Persistence.CurrentGameDetails.configuration.game_mode == GameConfiguration.GameMode.ASYNCHRONOUS)
                {
                    leaveButton.gameObject.SetActive(true);
                    forfeitButton.gameObject.SetActive(true);

                    message = "Do you want to leave or to forfeit?";
                }
                //---> In Sync mode, a player can forfeit to leave a game
                else
                {
                    leaveButton.gameObject.SetActive(false);
                    forfeitButton.gameObject.SetActive(true);

                    message = "Are you sure you want to forfeit?";
                }
            }

            ui.PopupQuit.Show("QUIT", message);
        }

        /// <summary>
        /// Call the logic to show the main menu view
        /// </summary>
        public void ShowMainMenuView()
        {
            _gameplayLogic.ShowMainMenuView();
        }

        /// <summary>
        /// Call the logic to show the lobby view
        /// </summary>
        public void ShowLobbyView()
        {
            _gameplayLogic.ShowLobbyView();
        }

        /// <summary>
        /// Call the distant gameplay logic to the current player leaves the game by forfeit
        /// </summary>
        public void Forfeit()
        {
            ((DistantGameplayLogic)_gameplayLogic).Forfeit();
            ui.PopupQuit.Hide();
        }

        /// <summary>
        /// Call the logic to leave a game and show the main menu view
        /// </summary>
        public void LeaveGame()
        {
            _gameplayLogic.LeaveGame();
        }

        public void ToggleChat()
        {
            bool unfold = ui.AnimatorChat.GetBool("Unfold");
            ui.AnimatorChat.SetBool("Unfold", !unfold);
        }

        public void ChatInput_OnEndEdit(string value)
        {
            if (!String.IsNullOrEmpty(ui.ChatInputField.text))
            {
                _chatLogic.SendChatToCurrentGame(ui.ChatInputField.text);

                ui.ChatInputField.text = string.Empty;
                ui.ChatInputField.Select();
                ui.ChatInputField.ActivateInputField();
            }
        }
        #endregion

        #region Private view methods
        /// <summary>
        /// Set the animation selection of a dice
        /// </summary>
        /// <param name="die">Dice to be selected</param>
        private void RefreshDiceSelectionAnimation(Die die)
        {
            die.Animator.SetBool("Selected", die.Selected);

            ui.RollDiceButton.interactable = ui.Dice.Exists(d => d.Selected);
        }

        /// <summary>
        /// Coroutine of dice animation. After 1.75 seconds of rolling, the score is shown
        /// </summary>
        /// <returns></returns>
        private IEnumerator RollDiceAnimationAsync()
        {
            //--- Get dice indice to roll
            List<int> indiceRolledDice = new List<int>();
            for (int i = 0; i < 3; i++)
            {
                if (ui.Dice[i].Selected)
                    indiceRolledDice.Add(ui.Dice[i].Index);
            }
            //---

            //--- Get dice values
            List<int> diceValues = _gameplayLogic.GetLastDiceValues();
            //---

            //--- Rotation of the dice
            foreach (int index in indiceRolledDice)
            {
                Die die = ui.Dice.Find(d => d.Index == index);
                _gameplayLogic.ChangeDiceSelection(die.Index, false);
                die.AutoRotation.StartAutoritation(1f, DiceFaceUpRotation[diceValues[index] - 1]);
            }
            //---

            ui.RollDiceButton.interactable = false;
            ui.EndTurnButton.interactable = false;

            yield return new WaitForSeconds(1.75f);

            RefreshUI();
            ShowDiceSum();

            if (_gameplayLogic.CurrentPlayer is LocalPlayerSeat && !(_gameplayLogic is ReplayGameplayLogic))
            {
                if (_gameplayLogic.CanRollDice())
                    ShowText("Pick dice and reroll them or end your turn", false);
                else
                    ShowText("End your turn", false);
            }
        }

        /// <summary>
        /// Show the dice sum of the current player
        /// </summary>
        private void ShowDiceSum()
        {
            int sum = _gameplayLogic.GetLastDiceSum();

            UserItem userItem = ui.UserItems[_gameplayLogic.CurrentPlayer.LocalId - 1];

            userItem.ui.Sum.text = sum == -1 ? "" : sum.ToString();

            if (_gameplayLogic.CurrentPlayer is LocalPlayerSeat)
                ShowText(String.Format("You have {0} pts", sum));
            else
                ShowText(String.Format("{0} has {1} pts", _gameplayLogic.CurrentPlayer.Name, sum));
        }

        /// <summary>
        /// Show a text in the header of the window
        /// </summary>
        /// <param name="text">Text to show</param>
        /// <param name="showOnly1Second">Show the message for 1 second</param>
        private void ShowText(string text, bool showOnly1Second = true)
        {
            ui.InfoPanel.AddTooltip(text, showOnly1Second);
        }

        /// <summary>
        /// Hide the text
        /// </summary>
        private void HideText()
        {
            ui.InfoPanel.Hide();
        }

        /// <summary>
        /// Show the game over popup
        /// </summary>
        /// <param name="text">Text to show in the game over popup</param>
        private void ShowGameOver(string text)
        {
            ui.GameOverPanel.gameObject.SetActive(true);
            ui.GameOverText.text = text;
        }

        /// <summary>
        /// Refresh all the players frame with their score, sum, name.
        /// Refresh the state of all buttons
        /// </summary>
        private void RefreshUI()
        {
            foreach (IPlayerSeat playerSeat in _gameplayLogic.PlayersSeats)
            {
                bool isCurrentPlayer = (playerSeat.LocalId == _gameplayLogic.CurrentPlayer.LocalId);
                UserItem userItem = ui.UserItems[playerSeat.LocalId - 1];

                bool? isOnline = null;

                if (playerSeat is DistantPlayerSeat)
                    isOnline = ((DistantPlayerSeat)playerSeat).Online;

                if (!userItem.gameObject.activeSelf)
                    userItem.Init(playerSeat, isOnline);

                userItem.Refresh(isCurrentPlayer, isOnline);

                int sum = _gameplayLogic.GameState == null ? 0 : _gameplayLogic.GetLastDiceSum(playerSeat.LocalId);

                userItem.ui.UserName.text = playerSeat.Name;
                userItem.ui.Sum.text = sum == -1 ? "" : sum.ToString();
            }

            //--- Refresh Buttons states
            bool enabledButtons = _gameplayLogic.CurrentPlayer is LocalPlayerSeat && !(_gameplayLogic is ReplayGameplayLogic);

            ui.RollDiceButton.gameObject.SetActive(enabledButtons);
            ui.EndTurnButton.gameObject.SetActive(enabledButtons);

            if (enabledButtons)
            {
                ui.RollDiceButton.interactable = _gameplayLogic.CanRollDice() && ui.Dice.Exists(d => d.Selected);
                ui.EndTurnButton.interactable = !_gameplayLogic.IsFirtDiceRoll();
            }
            //---

            //--- Set dice to last values
            List<int> diceValues = _gameplayLogic.GetLastDiceValues();
            if (diceValues != null)
            {
                for (int i = 0; i < 3; i++)
                {
                    if (diceValues[i] > 0)
                        ui.Dice[i].AutoRotation.StartAutoritation(0f, DiceFaceUpRotation[diceValues[i] - 1]);
                }
            }
            //---
        }

        public void AddChatItem(ChatEntry chatEntry)
        {
            bool isScrollAtBottom = ui.ChatItemsParent.verticalNormalizedPosition < 0.01f;

            ChatItem chatItem = Instantiate<ChatItem>(ChatItemPrefab);

            chatItem.Init(chatEntry, ui.ChatItemsParent.content);

            if (isScrollAtBottom)
            {
                Canvas.ForceUpdateCanvases();
                ui.ChatItemsParent.verticalNormalizedPosition = 0;
            }
        }


        #endregion

        #region Logic gameplay events
        /// <summary>
        /// Event called when a dice need to be selected (AI or distant player)
        /// </summary>
        /// <param name="index"></param>
        private void GameplayLogic_DicePickedIndexEvent(int index)
        {
            Die die = ui.Dice.Find(d => d.Index == index);

            RefreshDiceSelectionAnimation(die);
        }

        /// <summary>
        /// Event called to launch dice animation
        /// </summary>
        private void GameplayLogic_RollDiceAnimationEvent()
        {
            StartCoroutine(RollDiceAnimationAsync());
        }
        #endregion

        #region Logic events callbacks
        /// <summary>
        /// Event called when it's local player turn
        /// </summary>
        /// <param name="playerSeat">Player who must play</param>
        private void GameplayLogic_LocalPlayerTurnEvent(IPlayerSeat playerSeat)
        {
            ShowText("It's your turn. Please roll the dice", false);

            RefreshUI();
        }

        /// <summary>
        /// Event called when it's other player turn (bot or distant player)
        /// </summary>
        /// <param name="playerSeat">Player who must play</param>
        private void GameplayLogic_OtherPlayerTurnEvent(IPlayerSeat playerSeat)
        {
            ShowText(playerSeat.Name + "'s turn");

            RefreshUI();
        }

        /// <summary>
        /// Event called when a round finished
        /// </summary>
        /// <param name="winners">List of all winners of the current round</param>
        private void GameplayLogic_RoundEndedEvent(List<IPlayerSeat> winners)
        {
            string winnerText = string.Empty;
            foreach (IPlayerSeat playerSeat in winners)
            {
                winnerText += playerSeat.Name + "\r\n";

                UserItem userItem = ui.UserItems[playerSeat.LocalId - 1];
                int score = _gameplayLogic.GetScore(playerSeat.LocalId);
                userItem.ui.AnimatorCrowns[score - 1].SetTrigger("Active");
            }

            ShowText(String.Format("{0}win the round", winnerText));
            RefreshUI();
        }

        /// <summary>
        /// Event called when a game has finished
        /// </summary>
        /// <param name="winners">List of all winners of the game</param>
        private void GameplayLogic_GameOutcomeEvent(List<IPlayerSeat> winners)
        {
            RefreshUI();

            string winnerText = string.Empty;
            foreach (IPlayerSeat playerSeat in winners)
            {
                winnerText += "\r\n" + playerSeat.Name;
            }
            ShowGameOver("Winner : " + winnerText);
        }
        #endregion

        #region Network logic events callbacks
        /// <summary>
        /// Event called when a player presence is updated
        /// </summary>
        private void GameplayLogic_PlayerPresenceUpdatedEvent()
        {
            RefreshUI();
        }

        /// <summary>
        /// Event called when a game status is refreshed
        /// </summary>
        /// <param name="statusReport"></param>
        private void GameplayLogic_RefreshGameStatusEvent(StatusReport statusReport)
        {
            RefreshUI();
        }

        /// <summary>
        /// Event called when a game is aborted
        /// </summary>
        /// <param name="gameAbortedRequest"></param>
        private void GameplayLogic_GameAbortedEvent(GameAbortedRequest gameAbortedRequest)
        {
            ShowGameOver("Game aborted");
        }

        /// <summary>
        /// Event called when a distant player leaves the game
        /// </summary>
        /// <param name="playerSeat">Player who left the game</param>
        /// <param name="playerStatus">Player status</param>
        private void GameplayLogic_PlayerQuitGameEvent(IPlayerSeat playerSeat, PlayerTimeoutRequest.PlayerStatus playerStatus)
        {
            ShowText(String.Format("{0} leave the game\r\nand is replaced by a bot", playerSeat.Name));
        }

        /// <summary>
        /// Event called when a local player leaves the game
        /// </summary>
        /// <param name="playerSeat">Player who left the game</param>
        /// <param name="playerStatus">Player status</param>
        private void GameplayLogic_LocalPlayerQuitGameEvent(IPlayerSeat playerSeat, PlayerTimeoutRequest.PlayerStatus playerStatus)
        {
            switch (playerStatus)
            {
                case PlayerTimeoutRequest.PlayerStatus.TIMEOUT:
                    ShowGameOver("You timed out");

                    break;
                case PlayerTimeoutRequest.PlayerStatus.FORFEIT:
                    ShowGameOver("You forfeited");

                    break;
                case PlayerTimeoutRequest.PlayerStatus.LEFT:
                    ShowGameOver("You left");

                    break;
                case PlayerTimeoutRequest.PlayerStatus.ROBOT:
                    ShowGameOver("You are replaced by a robot");

                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Event called when a player joins the game
        /// </summary>
        /// <param name="playerSeat">Player who join the game</param>
        private void GameplayLogic_PlayerJoinGameEvent(IPlayerSeat playerSeat)
        {
            ShowText(String.Format("{0} join the game", playerSeat.Name));
        }

        private void ChatLogic_ClientChatEvent(ChatEntry chatEntry)
        {
            AddChatItem(chatEntry);
        }

        /// <summary>
        /// Event raised when the user is disconnected from the server
        /// </summary>
        private void GameplayLogic_DisconnectedFromServerEvent()
        {
            ui.PopupConnectionError.Show("CONNECTION ERROR", "The connection to the server is lost. Attempting to reconnect automatically.\r\n\r\nPress the Leave button to return to the main menu or wait until the connection is established");
        }

        /// <summary>
        /// Event raised when the user is reconnected to the server
        /// </summary>
        private void GameplayLogic_ReconnectedToServerEvent()
        {
            ui.PopupConnectionError.Hide();
        }

        /// <summary>
        /// Event raised when the user can't be reconnected to the server
        /// </summary>
        private void GameplayLogic_CantReconnectToServerEvent()
        {
            ui.PopupConnectionError.Show("CONNECTION ERROR", "Can't reconnect to the server.\r\n\r\nPlease press the Leave button to return to the main menu");
        }
        #endregion

        /// <summary>
        /// Dispose events and logic
        /// </summary>
        private void OnDestroy()
        {
            _gameplayLogic.LocalPlayerTurnEvent -= GameplayLogic_LocalPlayerTurnEvent;
            _gameplayLogic.OtherPlayerTurnEvent -= GameplayLogic_OtherPlayerTurnEvent;
            _gameplayLogic.DiePickedIndexEvent -= GameplayLogic_DicePickedIndexEvent;
            _gameplayLogic.RoundEndedEvent -= GameplayLogic_RoundEndedEvent;
            _gameplayLogic.GameOutcomeEvent -= GameplayLogic_GameOutcomeEvent;
            //_gameplayLogic.RefreshPlayersInfoEvent -= GameplayLogic_RefreshPlayersInfoEvent;
            _gameplayLogic.RollDiceAnimationEvent -= GameplayLogic_RollDiceAnimationEvent;

            if (_gameplayLogic is DistantGameplayLogic)
            {
                ((DistantGameplayLogic)_gameplayLogic).PlayerPresenceUpdatedEvent -= GameplayLogic_PlayerPresenceUpdatedEvent;
                ((DistantGameplayLogic)_gameplayLogic).RefreshGameStatusEvent -= GameplayLogic_RefreshGameStatusEvent;
                ((DistantGameplayLogic)_gameplayLogic).GameAbortedEvent -= GameplayLogic_GameAbortedEvent;
                ((DistantGameplayLogic)_gameplayLogic).OtherPlayerQuitGameEvent -= GameplayLogic_PlayerQuitGameEvent;
                ((DistantGameplayLogic)_gameplayLogic).LocalPlayerQuitGameEvent -= GameplayLogic_LocalPlayerQuitGameEvent;
                ((DistantGameplayLogic)_gameplayLogic).PlayerJoinGameEvent -= GameplayLogic_PlayerJoinGameEvent;

                ((DistantGameplayLogic)_gameplayLogic).DisconnectedFromServerEvent -= GameplayLogic_DisconnectedFromServerEvent;
                ((DistantGameplayLogic)_gameplayLogic).ReconnectedToServerEvent -= GameplayLogic_ReconnectedToServerEvent;
                ((DistantGameplayLogic)_gameplayLogic).CantReconnectToServerEvent -= GameplayLogic_CantReconnectToServerEvent;

                _chatLogic.ClientChatEvent -= ChatLogic_ClientChatEvent;
            }

            _gameplayLogic.Dispose();
        }
    }
}