﻿using AsmodeeDigital.Common.Plugin.Manager.Event;
using AsmodeeDigital.Common.Plugin.Manager.Scene;
using AsmodeeDigital.Common.Plugin.Utils.Extensions;
using AsmodeeDigital.PlayReal.Plugin.Domain.GameConfiguration;
using AsmodeeDigital.PlayReal.Plugin.Domain.GameState;
using AsmodeeDigital.PlayReal.Plugin.Domain.Players;
using AsmodeeDigital.PlayReal.Plugin.Logic;
using AsmodeeDigital.PlayReal.Plugin.Manager.Persistence;
using AsmodeeDigital.PlayReal.Plugin.Manager.Save;
using AsmodeeDigital.PlayReal.Plugin.Network;
using com.daysofwonder;
using com.daysofwonder.async;
using System;
using System.Collections.Generic;

namespace AsmodeeDigital.PlayReal.Samples.Logic
{
    /// <summary>
    /// Logic of the lobby view
    /// </summary>
    [Serializable]
    public class LobbyGameLogic : LogicBase, IDisposable
    {
        #region Events
        /// <summary>
        /// Sent by the server from time to time to send the currently open game list
        /// </summary>
        public event Action<GameList> LobbyOpenGameListEvent;

        /// <summary>
        /// Sent by the server from time to time to send the games in which the player is involved
        /// </summary>
        public event Action<List<StatusReport>> LobbyResumeGameListEvent;

        /// <summary>
        /// Event fired when the player is invited in a game
        /// </summary>
        public event Action<Player, List<Player>> InvitationArrivedEvent;

        /// <summary>
        /// Event fired when an invitation is accepted
        /// </summary>
        public event Action<Player> InvitationAcceptedEvent;

        /// <summary>
        /// Event fired when an invitation is rejected
        /// </summary>
        public event Action<Player> InvitationRejectedEvent;

        /// <summary>
        /// Sent to all the player presents in the game (and the joining player) when a player joins a given game and it succeed
        /// </summary>
        public event Action<LobbyNewPlayerRequest> LobbyNewPlayerEvent;

        /// <summary>
        /// Sent by the server in response to the LobbyLeaveGameRequest to all the players in the game and the leaving player
        /// </summary>
        public event Action<Player> LobbyPlayerLeftGameEvent;

        /// <summary>
        /// Sent to the joining player if the server denies the join
        /// </summary>
        public event Action<LobbyJoinDeniedRequest> LobbyJoinDeniedEvent;
        #endregion

        /// <summary>
        /// State of the lobby
        /// </summary>
        private LobbyState _lobbyState = LobbyState.None;

        public LobbyGameLogic(ServerConnection serverConnection, Persistence persistence) : base(serverConnection, persistence)
        {
        }

        /// <summary>
        /// Initialize the view - Create .Net events on the server
        /// </summary>
        public void Init()
        {
            ServerConnection.LobbyGameListEvent += ServerConnection_LobbyGameListEvent;
            ServerConnection.GameStatusReportAllEvent += ServerConnection_GameStatusReportAllEvent;

            ServerConnection.GameCreatedEvent += ServerConnection_GameCreatedEvent;

            ServerConnection.LobbyGameCreatedEvent += ServerConnection_LobbyGameCreatedEvent;
            ServerConnection.LobbyNewPlayerEvent += ServerConnection_LobbyNewPlayerEvent;
            ServerConnection.LobbyPlayerLeftGameEvent += ServerConnection_LobbyPlayerLeftGameEvent;
            ServerConnection.LobbyJoinDeniedEvent += ServerConnection_LobbyJoinDeniedEvent;

            ServerConnection.InvitationAnsweredEvent += ServerConnection_InvitationAnsweredEvent;

            ServerConnection.DisconnectedFromServerEvent += ServerConnection_DisconnectedFromServerEvent;
            ServerConnection.ReconnectedToServerEvent += ServerConnection_ReconnectedToServerEvent;
            ServerConnection.CantReconnectToServerEvent += ServerConnection_CantReconnectToServerEvent;

            Persistence.GameType = GameType.None;

            ServerConnection.WhatsNewPussycat();
        }

        #region Public methods accessed by view
        /// <summary>
        /// Call server to create a distant game
        /// </summary>
        ///<param name="gameConf">Game configuration of the onlne game</param>
        ///<param name="DoWIds">List of DoWId (www_id) of invited friends</param>
        public void CreateInvitationGame(GameConfiguration gameConf, List<int> DoWIds)
        {
            ServerConnection.CreateInvitationGame(gameConf, DoWIds);

            Persistence.InvitationAcceptedByPlayers = new List<int>();
            Persistence.InvitationAcceptedByPlayers.Add(Persistence.ConnectedPlayer.w_w_w_id);

            _lobbyState = LobbyState.WaitingGame;
        }

        /// <summary>
        /// Call server to create a distant game
        /// </summary>
        ///<param name="gameConf">Game configuration of the onlne game</param>
        ///<param name="password">Password of a private game, can be empty if the game is open</param>
        public void CreateOnlineGame(GameConfiguration gameConf, string password)
        {
            ServerConnection.CreateOnlineGame(gameConf, password);
        }

        /// <summary>
        /// Create a local game and load game view
        /// </summary>
        public void CreateLocalGame(List<IPlayerSeat> playerSeats)
        {
            Persistence.GameType = GameType.Local;

            for (int i = 0; i < playerSeats.Count; i++)
            {
                playerSeats[i].LocalId = i + 1;
            }

            Persistence.PlayerSeats = new List<IPlayerSeat>();
            Persistence.PlayerSeats.AddRange(playerSeats);

            SceneManager.LoadScene("3_Game");
        }

        /// <summary>
        /// Join a game
        /// </summary>
        /// <param name="gameDetails"></param>
        /// <param name="password"></param>
        public void JoinGame(IGameDetails gameDetails, string password = "")
        {
            Persistence.CurrentGameDetails = gameDetails;
            Persistence.GameType = GameType.Distant;

            ServerConnection.LobbyJoinGameRequest(gameDetails.game_id, password);
        }

        /// <summary>
        /// Resume a game
        /// </summary>
        /// <param name="gameDetails"></param>
        public void ResumeSyncGame(IGameDetails gameDetails)
        {
            Persistence.CurrentGameDetails = gameDetails;
            Persistence.GameType = GameType.Distant;

            ServerConnection.ResumeSyncGameRequest(gameDetails.game_id);

            ShowGameView();
        }

        public void ShowReplay(GameStateBase gameState)
        {
            Persistence.GameState = gameState;
            Persistence.GameType = GameType.Replay;

            ShowGameView();
        }

        /// <summary>
        /// Ask the Scalable Server for a fresh game list
        /// </summary>
        public void WhatsNewPussycat()
        {
            ServerConnection.WhatsNewPussycat();
        }

        /// <summary>
        /// Accept an invitation
        /// </summary>
        public void AcceptInvitation()
        {
            ServerConnection.AcceptInvitation(Persistence.CurrentGameDetails.game_id);

            Persistence.InvitationAcceptedByPlayers.Add(Persistence.ConnectedPlayer.w_w_w_id);

            _lobbyState = LobbyState.WaitingGame;
        }

        /// <summary>
        /// Decline an invitation
        /// </summary>
        public void DeclineInvitation()
        {
            ServerConnection.DeclineInvitation(Persistence.CurrentGameDetails.game_id);

            Persistence.CurrentGameDetails = null;
            Persistence.InvitationAcceptedByPlayers = new List<int>();
            Persistence.GameType = GameType.None;
        }

        /// <summary>
        /// Leave the waiting game
        /// </summary>
        public void LeaveCurrentWaitingGame()
        {
            ServerConnection.LobbyLeaveGameRequest(Persistence.CurrentGameDetails.game_id);

            Persistence.CurrentGameDetails = null;
            Persistence.InvitationAcceptedByPlayers = new List<int>();
            Persistence.GameType = GameType.None;

            _lobbyState = LobbyState.None;
        }

        /// <summary>
        /// Get replay files stored locally
        /// </summary>
        public List<GameStateBase> GetReplayGameState()
        {
            return SaveManager.GetLocalGameState<GameStateBase>(String.Format(@"AsmodeeDigital\PlayReal\{0}\*_v1.replay",Persistence.ConnectedPlayer.name));
        }

        /// <summary>
        /// Show main menu view
        /// </summary>
        public void ShowMainMenu()
        {
            SceneManager.LoadScene("1_MainMenu");
        }
        #endregion

        #region private methods
        /// <summary>
        /// Show game view
        /// </summary>
        private void ShowGameView()
        {
            if (!SceneManager.IsCurrentScene("3_Game"))
                SceneManager.LoadScene("3_Game");
        }
        #endregion

        #region Network events
        /// <summary>
        /// Event called when a game is created. Load waiting view
        /// </summary>
        /// <param name="gameCreatedRequest"></param>
        private void ServerConnection_GameCreatedEvent(GameCreatedRequest gameCreatedRequest)
        {
            //--- Set the game parameters in the persistance
            Persistence.CurrentGameDetails = gameCreatedRequest.details;
            Persistence.InvitationAcceptedByPlayers = new List<int>();
            Persistence.GameType = GameType.Distant;
            //---

            //---> Get the inviter (in a invitation game, otherwise null)
            Player invitedBy = gameCreatedRequest.details.players.Find(p => p.id == gameCreatedRequest.invited_by);

            //---> If the game is an invitation, show an Inivtation popup on the view
            if (invitedBy != null)
            {
                Persistence.InvitationAcceptedByPlayers.Add(invitedBy.w_w_w_id);

                if (invitedBy.w_w_w_id != Persistence.ConnectedPlayer.w_w_w_id)
                {
                    if (InvitationArrivedEvent != null)
                    {
                        EventManager.Instance.QueueEvent(InvitationArrivedEvent, invitedBy, gameCreatedRequest.details.players);
                    }
                }
            }
            //---> Otherwise, start the game
            else
            {
                ShowGameView();
            }
        }

        /// <summary>
        /// Event raised by the server when a waiting game is created
        /// </summary>
        /// <param name="lobbyGameCreatedRequest"></param>
        private void ServerConnection_LobbyGameCreatedEvent(LobbyGameCreatedRequest lobbyGameCreatedRequest)
        {
            Persistence.CurrentGameDetails = lobbyGameCreatedRequest.game;
            Persistence.InvitationAcceptedByPlayers = new List<int>();
            Persistence.GameType = GameType.Distant;
        }

        /// <summary>
        /// Event fired by the server after a WhatsNewPussycat call
        /// </summary>
        /// <param name="statusReports"></param>
        private void ServerConnection_GameStatusReportAllEvent(List<StatusReport> statusReports)
        {
            if (LobbyResumeGameListEvent != null)
                EventManager.Instance.QueueEvent(LobbyResumeGameListEvent, statusReports);

            //--- If games are waiting confirmation
            List<StatusReport> waitingInvitations = statusReports.FindAll(sr =>
                sr.status == GameStatus.WAITING_INVITATION &&
                !sr.invitation.confirmation.Exists(d => d.player_id == sr.players.Find(p => p.w_w_w_id == Persistence.ConnectedPlayer.w_w_w_id).id));

            if (waitingInvitations.Count > 0 && InvitationArrivedEvent != null)
            {
                StatusReport lastWaitingInvitation = waitingInvitations.Last();
                Player invitedBy = lastWaitingInvitation.players.Find(p => p.id == lastWaitingInvitation.invitation.invited_by);

                Persistence.CurrentGameDetails = (IGameDetails)lastWaitingInvitation;
                Persistence.InvitationAcceptedByPlayers = lastWaitingInvitation.players.FindAll(p => lastWaitingInvitation.invitation.confirmation.Exists(c => c.player_id == p.id)).ConvertAll<int>(p => p.w_w_w_id);
                Persistence.GameType = GameType.Distant;

                EventManager.Instance.QueueEvent(InvitationArrivedEvent, invitedBy, lastWaitingInvitation.players);
            }
            //---

            //--- If current game is starting, show the game view
            List<StatusReport> gameIsReady = statusReports.FindAll(sr =>
               sr.status == GameStatus.IN_PROGRESS && Persistence.CurrentGameDetails != null && Persistence.CurrentGameDetails.game_id == sr.game_id);

            if (gameIsReady.Count > 0 && _lobbyState == LobbyState.WaitingGame)
            {
                ShowGameView();
            }
            //---
        }

        /// <summary>
        /// Event raised by the server to refresh the open game list
        /// </summary>
        /// <param name="gameList"></param>
        private void ServerConnection_LobbyGameListEvent(GameList gameList)
        {
            if (LobbyOpenGameListEvent != null)
                EventManager.Instance.QueueEvent(LobbyOpenGameListEvent, gameList);
        }

        /// <summary>
        /// Event raised by the server when a joien denied error occured
        /// </summary>
        /// <param name="lobbyJoinDeniedRequest"></param>
        private void ServerConnection_LobbyJoinDeniedEvent(LobbyJoinDeniedRequest lobbyJoinDeniedRequest)
        {
            if (LobbyJoinDeniedEvent != null)
                EventManager.Instance.QueueEvent(LobbyJoinDeniedEvent, lobbyJoinDeniedRequest);
        }

        /// <summary>
        /// Event raised by the server when a player responds to an invitation
        /// </summary>
        /// <param name="invitationAnsweredRequest"></param>
        private void ServerConnection_InvitationAnsweredEvent(InvitationAnsweredRequest invitationAnsweredRequest)
        {
            if (Persistence.CurrentGameDetails != null && invitationAnsweredRequest.game_id == Persistence.CurrentGameDetails.game_id)
            {
                //---> The invitee has accepted the invitation
                if (invitationAnsweredRequest.accept)
                {
                    ServerConnection.WhatsNewPussycat(Persistence.CurrentGameDetails.game_id);

                    //---> Add the invitee in the waiting list
                    Persistence.InvitationAcceptedByPlayers.Add(invitationAnsweredRequest.invitee.w_w_w_id);

                    if (InvitationAcceptedEvent != null)
                        EventManager.Instance.QueueEvent(InvitationAcceptedEvent, invitationAnsweredRequest.invitee);
                }
                //---> The invitee has declined the invitation
                else
                {
                    if (InvitationRejectedEvent != null)
                        EventManager.Instance.QueueEvent(InvitationRejectedEvent, invitationAnsweredRequest.invitee);
                }
            }
        }

        /// <summary>
        /// Event raised by the server when a player join a waiting game
        /// </summary>
        /// <param name="lobbyNewPlayerRequest"></param>
        private void ServerConnection_LobbyNewPlayerEvent(LobbyNewPlayerRequest lobbyNewPlayerRequest)
        {
            if (LobbyNewPlayerEvent != null && lobbyNewPlayerRequest.joining_player != Persistence.ConnectedPlayer.w_w_w_id)
                EventManager.Instance.QueueEvent(LobbyNewPlayerEvent, lobbyNewPlayerRequest);
        }

        /// <summary>
        /// Event raised by the server when a player leave a waiting game
        /// </summary>
        /// <param name="lobbyPlayerLeftGameRequest"></param>
        private void ServerConnection_LobbyPlayerLeftGameEvent(LobbyPlayerLeftGameRequest lobbyPlayerLeftGameRequest)
        {
            if (LobbyPlayerLeftGameEvent != null && Persistence.CurrentGameDetails != null && lobbyPlayerLeftGameRequest.game.game_id == Persistence.CurrentGameDetails.game_id)
                EventManager.Instance.QueueEvent(LobbyPlayerLeftGameEvent, lobbyPlayerLeftGameRequest.leaving_player);
        }

        /// <summary>
        /// Event raised when the user is disconnected from the server
        /// </summary>
        private void ServerConnection_DisconnectedFromServerEvent()
        {
            Log("ServerConnection_DisconnectedFromServerEvent");
        }

        /// <summary>
        /// Event raised when the user is reconnected to the server
        /// </summary>
        private void ServerConnection_ReconnectedToServerEvent()
        {
            Log("ServerConnection_ReconnectedToServerEvent");

            ServerConnection.EnterLobbyRequest();
            ServerConnection.AsyncBuddyListRequest();
            ServerConnection.AsyncIgnoreListRequest();
            ServerConnection.WhatsNewPussycat();
        }

        /// <summary>
        /// Event raised when the user can't be reconnected to the server
        /// </summary>
        private void ServerConnection_CantReconnectToServerEvent()
        {
            Log("ServerConnection_CantReconnectToServerEvent");
        }
        #endregion

        /// <summary>
        /// Dispose events
        /// </summary>
        public void Dispose()
        {
            ServerConnection.GameStatusReportAllEvent -= ServerConnection_GameStatusReportAllEvent;
            ServerConnection.LobbyGameListEvent -= ServerConnection_LobbyGameListEvent;

            ServerConnection.GameCreatedEvent -= ServerConnection_GameCreatedEvent;

            ServerConnection.LobbyGameCreatedEvent -= ServerConnection_LobbyGameCreatedEvent;
            ServerConnection.LobbyJoinDeniedEvent -= ServerConnection_LobbyJoinDeniedEvent;
            ServerConnection.LobbyNewPlayerEvent -= ServerConnection_LobbyNewPlayerEvent;
            ServerConnection.LobbyPlayerLeftGameEvent -= ServerConnection_LobbyPlayerLeftGameEvent;

            ServerConnection.InvitationAnsweredEvent -= ServerConnection_InvitationAnsweredEvent;

            ServerConnection.DisconnectedFromServerEvent -= ServerConnection_DisconnectedFromServerEvent;
            ServerConnection.ReconnectedToServerEvent -= ServerConnection_ReconnectedToServerEvent;
            ServerConnection.CantReconnectToServerEvent -= ServerConnection_CantReconnectToServerEvent;
        }

        private enum LobbyState
        {
            None = 0,
            WaitingGame = 1
        }
    }
}