﻿using AsmodeeDigital.Common.Plugin.Domain.Data;
using AsmodeeDigital.Common.Plugin.Domain.Players;
using AsmodeeDigital.Common.Plugin.Manager.Coroutine;
using AsmodeeDigital.Common.Plugin.Manager.Event;
using AsmodeeDigital.Common.Plugin.Manager.Random;
using AsmodeeDigital.Common.Plugin.Manager.Scene;
using AsmodeeDigital.Common.Plugin.Tests.Mocks;
using AsmodeeDigital.PlayReal.Plugin.Domain.Players;
using AsmodeeDigital.PlayReal.Plugin.Manager.Persistence;
using AsmodeeDigital.PlayReal.Plugin.Network;
using AsmodeeDigital.PlayReal.Samples.Domain.Game;
using AsmodeeDigital.PlayReal.Samples.Logic;
using AsmodeeDigital.PlayReal.Samples.Logic.Gameplay;
using com.daysofwonder;
using com.daysofwonder.async;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using UnityEngine;

public class PlayReal_Samples_Tests : ScriptableObject
{
    public DoWNetworkParameters NetworkParameters;

    public User Alice;
    public User Bob;
    public User Carol;

    /// <summary>
    /// Start log entry with time
    /// </summary>
    public bool LogWithTime = true;

    /// <summary>
    /// Duration after a timeout in millisecond
    /// </summary>
    private int TimeOutDuration = 5000;

    /// <summary>
    /// Thread synchronization
    /// </summary>
    private ManualResetEvent _manualEvent = new ManualResetEvent(false);

    public PlayReal_Samples_Tests()
    {
    }

    /// <summary>
    /// Clear Unity Console
    /// </summary>
    public static void ClearLog()
    {
        var assembly = Assembly.GetAssembly(typeof(UnityEditor.ActiveEditorTracker));
        var type = assembly.GetType("UnityEditorInternal.LogEntries");
        var method = type.GetMethod("Clear");
        method.Invoke(new object(), null);
    }

    [SetUp]
    public void InitializeTest()
    {
        ClearLog();

        EventManager.Instance = new EventManagerTest();
        SceneManager.Init(new SceneManagerTest());
        RandomManager.Init(new RandomManagerTest());
        CoroutineManager.Init(new CoroutineManagerTest());

        Alice.Waiting = Bob.Waiting = Carol.Waiting = false;
    }

    /// <summary>
    /// A user stop waiting. If all users are not waiting, the thread can continue
    /// </summary>
    /// <param name="user"></param>
    private void CheckWaitings(User user)
    {
        //--- Synchronization of all clients
        //Log(String.Format("{0} is not waiting", user.Login));
        user.Waiting = false;

        if (!Alice.Waiting && !Bob.Waiting && !Carol.Waiting)
            _manualEvent.Set();
    }

    /// <summary>
    /// Start an action and block the current thread until all users are not waiting
    /// </summary>
    /// <param name="step"></param>
    /// <param name="action"></param>
    private void WaitManualEvent(string step, Action action = null)
    {
        //--- Synchronization of all tasks
        Log(String.Format("Waiting : {0}", step));

        if (action != null)
            action();

        bool succes = _manualEvent.WaitOne(TimeOutDuration);

        if (!succes)
        {
            Log(String.Format("Time out : {0}", step), LogType.Error);

            throw new TimeoutException();
        }
        else
        {
            Log(String.Format("Success : {0}", step));

            _manualEvent.Reset();
        }
    }

    /// <summary>
    /// Log a message
    /// </summary>
    /// <param name="message">Message to log</param>
    /// <param name="logType">Log type</param>
    private void Log(string message, LogType logType = LogType.Log)
    {
        string logMessage = string.Format("{0}{1}", LogWithTime ? DateTime.Now.ToString("HH:mm:ss ") : "", message);

        switch (logType)
        {
            case LogType.Error:
            case LogType.Exception:
                Debug.LogError(logMessage);
                break;
            case LogType.Assert:
            case LogType.Warning:
            case LogType.Log:
                Debug.Log(logMessage);

                break;
            default:
                break;
        }
    }

    /// <summary>
    /// Alice start an invitation game with Bob and Carol
    /// After 7 rounds Carol win with 3 points, Bob and Alice have 2 points
    /// </summary>
    [Test]
    public void DistantGameWith3Friends()
    {
        MainMenuLogic mainMenuLogicAlice = null;
        MainMenuLogic mainMenuLogicBob = null;
        MainMenuLogic mainMenuLogicCarol = null;

        LobbyGameLogic lobbyGameLogicAlice = null;
        LobbyGameLogic lobbyGameLogicBob = null;
        LobbyGameLogic lobbyGameLogicCarol = null;

        DistantGameplayLogic distantGameplayLogicAlice = null;
        DistantGameplayLogic distantGameplayLogicBob = null;
        DistantGameplayLogic distantGameplayLogicCarol = null;

        try
        {
            //--- Context
            //TODO : convert Dice to ScriptableObject
            List<Die> dice = new List<Die>();

            Die die1 = new Die();
            die1.Index = 0;
            die1.Selected = false;

            Die die2 = new Die();
            die2.Index = 1;
            die2.Selected = false;

            Die die3 = new Die();
            die3.Index = 2;
            die3.Selected = false;

            dice.Add(die1);
            dice.Add(die2);
            dice.Add(die3);
            //---

            //--- Set random values for a predictable test
            RandomManager.SetSeed(1234);
            ((RandomManagerTest)RandomManager.InternalRandomManager).SetRangeValues(new List<int>()
            {   1, 1, 1,  2, 2, 2,  3, 3, 3,   // Round 1 : Carol Win   (3  - 6  - 9)
                4, 4, 4,  6, 6, 6,  5, 5, 5,   // Round 2 : Bob Win     (12 - 18 - 10)
                3, 3, 3,  2, 2, 2,  1, 1, 1,   // Round 3 : Alice Win   (9  - 6  - 3)
                1, 1, 1,  3, 3, 3,  2, 2, 2,   // Round 4 : Bob Win     (3  - 9  - 6)
                1, 1, 1,  2, 2, 2,  3, 3, 3,   // Round 5 : Carol Win   (3  - 6  - 9)
                3, 3, 3,  2, 2, 2,  1, 1, 1,   // Round 6 : Alice Win   (9  - 6  - 3)
                1, 1, 1,  2, 2, 2,  3, 3, 3    // Round 7 : Carol Win   (3  - 6  - 9)
            });
            //---

            bool logServerConnectionEnabled = true;
            bool logLogicEnabled = true;

            //--- Alice
            ServerConnection srvCnxAlice = new ServerConnection(NetworkParameters);
            srvCnxAlice.LogName = "Alice";
            srvCnxAlice.LogEnabled = logServerConnectionEnabled;

            Persistence persistenceAlice = new Persistence();
            mainMenuLogicAlice = new MainMenuLogic(srvCnxAlice, persistenceAlice);
            lobbyGameLogicAlice = new LobbyGameLogic(srvCnxAlice, persistenceAlice);
            distantGameplayLogicAlice = new DistantGameplayLogic(srvCnxAlice, persistenceAlice, dice);

            distantGameplayLogicAlice.LogName = "Alice";
            distantGameplayLogicAlice.LogEnabled = logLogicEnabled;
            //---

            //--- Bob
            ServerConnection srvCnxBob = new ServerConnection(NetworkParameters);
            srvCnxBob.LogName = "Bob";
            srvCnxBob.LogEnabled = logServerConnectionEnabled;

            Persistence persistenceBob = new Persistence();
            mainMenuLogicBob = new MainMenuLogic(srvCnxBob, persistenceBob);
            lobbyGameLogicBob = new LobbyGameLogic(srvCnxBob, persistenceBob);
            distantGameplayLogicBob = new DistantGameplayLogic(srvCnxBob, persistenceBob, dice);

            distantGameplayLogicBob.LogName = "Bob";
            distantGameplayLogicBob.LogEnabled = logLogicEnabled;
            //---

            //--- Carol
            ServerConnection srvCnxCarol = new ServerConnection(NetworkParameters);
            srvCnxCarol.LogName = "Carol";
            srvCnxCarol.LogEnabled = logServerConnectionEnabled;

            Persistence persistenceCarol = new Persistence();
            mainMenuLogicCarol = new MainMenuLogic(srvCnxCarol, persistenceCarol);
            lobbyGameLogicCarol = new LobbyGameLogic(srvCnxCarol, persistenceCarol);
            distantGameplayLogicCarol = new DistantGameplayLogic(srvCnxCarol, persistenceCarol, dice);

            distantGameplayLogicCarol.LogName = "Carol";
            distantGameplayLogicCarol.LogEnabled = logLogicEnabled;
            //---

            //--- Connection
            mainMenuLogicAlice.Init();
            mainMenuLogicBob.Init();
            mainMenuLogicCarol.Init();

            mainMenuLogicAlice.ConnectedToServerEvent += () => { CheckWaitings(Alice); };
            mainMenuLogicBob.ConnectedToServerEvent += () => { CheckWaitings(Bob); };
            mainMenuLogicCarol.ConnectedToServerEvent += () => { CheckWaitings(Carol); };

            Alice.Waiting = Bob.Waiting = Carol.Waiting = true;

            WaitManualEvent("Connection", () =>
            {
                mainMenuLogicAlice.Connect();
                mainMenuLogicBob.Connect();
                mainMenuLogicCarol.Connect();
            });
            //---

            //--- Authentication
            mainMenuLogicAlice.AuthenticatedEvent += (AsyncConnectedRequest asyncConnectedRequest) => { CheckWaitings(Alice); };
            mainMenuLogicBob.AuthenticatedEvent += (AsyncConnectedRequest asyncConnectedRequest) => { CheckWaitings(Bob); };
            mainMenuLogicCarol.AuthenticatedEvent += (AsyncConnectedRequest asyncConnectedRequest) => { CheckWaitings(Carol); };

            Alice.Waiting = Bob.Waiting = Carol.Waiting = true;

            WaitManualEvent("Authentication", () =>
            {
                mainMenuLogicAlice.AuthenticateUser(Alice.Login, Alice.Password);
                mainMenuLogicBob.AuthenticateUser(Bob.Login, Bob.Password);
                mainMenuLogicCarol.AuthenticateUser(Carol.Login, Carol.Password);
            });
            //---

            //--- Bob, Carol, accepts invitation of Alice
            lobbyGameLogicBob.InvitationArrivedEvent += (Player invitedBy, List<Player> players) =>
            {
                lobbyGameLogicBob.AcceptInvitation();
                CheckWaitings(Bob);
            };

            lobbyGameLogicCarol.InvitationArrivedEvent += (Player invitedBy, List<Player> players) =>
            {
                lobbyGameLogicCarol.AcceptInvitation();
                CheckWaitings(Carol);
            };
            //---

            //--- Alice - Create Room
            lobbyGameLogicAlice.Init();
            lobbyGameLogicBob.Init();
            lobbyGameLogicCarol.Init();

            Alice.Waiting = false;
            Bob.Waiting = true;
            Carol.Waiting = true;

            WaitManualEvent("Accept invitation", () =>
            {
                GameConfiguration gameConf = new GameConfiguration();
                gameConf.name = "Alice's invitation game";
                gameConf.first_player = 1;
                gameConf.rated = false;
                gameConf.timeout = 60;
                gameConf.game_mode = GameConfiguration.GameMode.SYNCHRONOUS;

                lobbyGameLogicAlice.CreateInvitationGame(gameConf, new List<int>() { Bob.DoWID, Carol.DoWID });
            });
            //---

            //--- Alice turn
            distantGameplayLogicAlice.LocalPlayerTurnEvent += ((LocalPlayerSeat playerSeat) =>
            {
                distantGameplayLogicAlice.RollSelectedDice();
                distantGameplayLogicAlice.EndOfTurn();
            });
            //---

            //--- Bob turn
            distantGameplayLogicBob.LocalPlayerTurnEvent += ((LocalPlayerSeat playerSeat) =>
            {
                distantGameplayLogicBob.RollSelectedDice();
                distantGameplayLogicBob.EndOfTurn();
            });
            //---

            //--- Carol turn
            distantGameplayLogicCarol.LocalPlayerTurnEvent += ((LocalPlayerSeat playerSeat) =>
            {
                distantGameplayLogicCarol.RollSelectedDice();
                distantGameplayLogicCarol.EndOfTurn();
            });
            //---

            //--- End round : show score
            distantGameplayLogicAlice.RoundEndedEvent += ((List<IPlayerSeat> listPlayers) =>
             {
                 Log(String.Format("Alice sum : {0}", distantGameplayLogicAlice.GetLastDiceSum(1)));
                 Log(String.Format("Bob sum : {0}", distantGameplayLogicAlice.GetLastDiceSum(2)));
                 Log(String.Format("Carol sum : {0}", distantGameplayLogicAlice.GetLastDiceSum(3)));

                 Log(String.Format("Winners : {0}", String.Join(",", distantGameplayLogicAlice.GetWinnerLastRound().ConvertAll<string>(w => w.Name).ToArray())));
             });
            //---

            //--- Initialize gameplay logic and start game
            //    Alice, Bob and Carol are playing in loop until there is a winner
            //    LocalPlayerTurnEvent are fired for each player, then RoundEndedEvent of all players is fired after the end of a round
            //    When the game is finished (one of the player has a score of 3), GameOutcomeEvent is fired for each player
            distantGameplayLogicAlice.Init();
            distantGameplayLogicBob.Init();
            distantGameplayLogicCarol.Init();
            //---

            //--- GameOver
            distantGameplayLogicAlice.GameOutcomeEvent += (List<IPlayerSeat> listPlayers) =>
            {
                CheckWaitings(Alice);
            };

            distantGameplayLogicBob.GameOutcomeEvent += (List<IPlayerSeat> listPlayers) =>
            {
                CheckWaitings(Bob);
            };

            distantGameplayLogicCarol.GameOutcomeEvent += (List<IPlayerSeat> listPlayers) =>
            {
                CheckWaitings(Carol);
            };

            Alice.Waiting = Bob.Waiting = Carol.Waiting = true;

            WaitManualEvent("Game Over");
            //---

            //Debug.Log("-----");
            //Debug.LogFormat("Alice score : {0}", distantGameplayLogicAlice.GetScore());
            //Debug.LogFormat("Bob score : {0}", distantGameplayLogicBob.GetScore());
            //Debug.LogFormat("Carol score : {0}", distantGameplayLogicCarol.GetScore());
            //Debug.Log("-----");
            //Debug.LogFormat("Alice score : {0}", distantGameplayLogicAlice.GetScore(1));
            //Debug.LogFormat("Bob score : {0}", distantGameplayLogicAlice.GetScore(2));
            //Debug.LogFormat("Carol score : {0}", distantGameplayLogicAlice.GetScore(3));
            //Debug.Log("-----");
            //Debug.LogFormat("Alice score : {0}", distantGameplayLogicBob.GetScore(1));
            //Debug.LogFormat("Bob score : {0}", distantGameplayLogicBob.GetScore(2));
            //Debug.LogFormat("Carol score : {0}", distantGameplayLogicBob.GetScore(3));
            //Debug.Log("-----");
            //Debug.LogFormat("Alice score : {0}", distantGameplayLogicCarol.GetScore(1));
            //Debug.LogFormat("Bob score : {0}", distantGameplayLogicCarol.GetScore(2));
            //Debug.LogFormat("Carol score : {0}", distantGameplayLogicCarol.GetScore(3));
            //Debug.Log("-----");

            int aliceScore = distantGameplayLogicAlice.GetScore(1);
            int bobScore = distantGameplayLogicAlice.GetScore(2);
            int carolScore = distantGameplayLogicAlice.GetScore(3);

            Assert.AreEqual(2, aliceScore, "Alice score is not 2 but {0}", aliceScore);
            Assert.AreEqual(2, bobScore, "Bob score is not 2 but {0}", bobScore);
            Assert.AreEqual(3, carolScore, "Carol score is not 3 but {0}", carolScore);
        }
        finally
        {
            mainMenuLogicAlice.Disconnect();
            mainMenuLogicBob.Disconnect();
            mainMenuLogicCarol.Disconnect();

            mainMenuLogicAlice.Dispose();
            mainMenuLogicBob.Dispose();
            mainMenuLogicCarol.Dispose();

            lobbyGameLogicAlice.Dispose();
            lobbyGameLogicBob.Dispose();
            lobbyGameLogicCarol.Dispose();

            distantGameplayLogicAlice.Dispose();
            distantGameplayLogicBob.Dispose();
            distantGameplayLogicCarol.Dispose();
        }
    }

    /// <summary>
    /// Alice start an invitation game with Bob and Carol
    /// Bob forfeit at is first turn, then the AI played by Alice or Carol replace Bob
    /// After 7 rounds Carol win with 3 points, Bob and Alice have 2 points
    /// </summary>
    [Test]
    public void OneForfeit()
    {
        MainMenuLogic mainMenuLogicAlice = null;
        MainMenuLogic mainMenuLogicBob = null;
        MainMenuLogic mainMenuLogicCarol = null;

        LobbyGameLogic lobbyGameLogicAlice = null;
        LobbyGameLogic lobbyGameLogicBob = null;
        LobbyGameLogic lobbyGameLogicCarol = null;

        DistantGameplayLogic distantGameplayLogicAlice = null;
        DistantGameplayLogic distantGameplayLogicBob = null;
        DistantGameplayLogic distantGameplayLogicCarol = null;

        try
        {
            //--- Context
            //TODO : convert Dice to ScriptableObject
            List<Die> dice = new List<Die>();

            Die dice1 = new Die();
            dice1.Index = 0;
            dice1.Selected = false;

            Die dice2 = new Die();
            dice2.Index = 1;
            dice2.Selected = false;

            Die dice3 = new Die();
            dice3.Index = 2;
            dice3.Selected = false;

            dice.Add(dice1);
            dice.Add(dice2);
            dice.Add(dice3);
            //---

            //--- Set random values for a predictable test
            RandomManager.SetSeed(1234);
            ((RandomManagerTest)RandomManager.InternalRandomManager).SetRangeValues(new List<int>()
            { 1, 1, 1,  2, 2, 2,  2, 2, 2,  4, 4, 4,   // Round 1 : Carol Win (Bob roll 2 times) - 3  - 6  - 12
              4, 4, 4,  6, 6, 6,  5, 5, 5,             // Round 2 : Bob Win                      - 12 - 18 - 15
              3, 3, 3,  2, 2, 2,  1, 1, 1,  1, 1, 1,   // Round 3 : Alice Win (Bob roll 2 times) - 9  - 3  - 3
              1, 1, 1,  4, 4, 4,  2, 2, 2,             // Round 4 : Bob Win                      - 9  - 3  - 3
              1, 1, 1,  2, 2, 2,  2, 2, 2,  3, 3, 3,   // Round 5 : Carol Win (Bob roll 2 times) - 3  - 12 - 6
              3, 3, 3,  2, 2, 2,  2, 2, 2,  1, 1, 1,   // Round 6 : Alice Win (Bob roll 2 times) - 3  - 6  - 9
              1, 1, 1,  2, 2, 2,  2, 2, 2,  3, 3, 3    // Round 7 : Carol Win (Bob roll 2 times) - 9  - 6  - 3
            });
            //---

            bool logServerConnectionEnabled = true;
            bool logLogicEnabled = true;

            //--- Alice
            ServerConnection srvCnxAlice = new ServerConnection(NetworkParameters);
            srvCnxAlice.LogName = "Alice";
            srvCnxAlice.LogEnabled = logServerConnectionEnabled;

            Persistence persistenceAlice = new Persistence();
            mainMenuLogicAlice = new MainMenuLogic(srvCnxAlice, persistenceAlice);
            lobbyGameLogicAlice = new LobbyGameLogic(srvCnxAlice, persistenceAlice);
            distantGameplayLogicAlice = new DistantGameplayLogic(srvCnxAlice, persistenceAlice, dice);

            distantGameplayLogicAlice.LogName = "Alice";
            distantGameplayLogicAlice.LogEnabled = logLogicEnabled;
            //---

            //--- Bob
            ServerConnection srvCnxBob = new ServerConnection(NetworkParameters);
            srvCnxBob.LogName = "Bob";
            srvCnxBob.LogEnabled = logServerConnectionEnabled;

            Persistence persistenceBob = new Persistence();
            mainMenuLogicBob = new MainMenuLogic(srvCnxBob, persistenceBob);
            lobbyGameLogicBob = new LobbyGameLogic(srvCnxBob, persistenceBob);
            distantGameplayLogicBob = new DistantGameplayLogic(srvCnxBob, persistenceBob, dice);

            distantGameplayLogicBob.LogName = "Bob";
            distantGameplayLogicBob.LogEnabled = logLogicEnabled;
            //---

            //--- Carol
            ServerConnection srvCnxCarol = new ServerConnection(NetworkParameters);
            srvCnxCarol.LogName = "Carol";
            srvCnxCarol.LogEnabled = logServerConnectionEnabled;

            Persistence persistenceCarol = new Persistence();
            mainMenuLogicCarol = new MainMenuLogic(srvCnxCarol, persistenceCarol);
            lobbyGameLogicCarol = new LobbyGameLogic(srvCnxCarol, persistenceCarol);
            distantGameplayLogicCarol = new DistantGameplayLogic(srvCnxCarol, persistenceCarol, dice);

            distantGameplayLogicCarol.LogName = "Carol";
            distantGameplayLogicCarol.LogEnabled = logLogicEnabled;
            //---

            //--- Connection
            mainMenuLogicAlice.Init();
            mainMenuLogicBob.Init();
            mainMenuLogicCarol.Init();

            mainMenuLogicAlice.ConnectedToServerEvent += () => { CheckWaitings(Alice); };
            mainMenuLogicBob.ConnectedToServerEvent += () => { CheckWaitings(Bob); };
            mainMenuLogicCarol.ConnectedToServerEvent += () => { CheckWaitings(Carol); };

            Alice.Waiting = Bob.Waiting = Carol.Waiting = true;

            WaitManualEvent("Connection", () =>
            {
                mainMenuLogicAlice.Connect();
                mainMenuLogicBob.Connect();
                mainMenuLogicCarol.Connect();
            });
            //---

            //--- Authentication
            mainMenuLogicAlice.AuthenticatedEvent += (AsyncConnectedRequest asyncConnectedRequest) => { CheckWaitings(Alice); };
            mainMenuLogicBob.AuthenticatedEvent += (AsyncConnectedRequest asyncConnectedRequest) => { CheckWaitings(Bob); };
            mainMenuLogicCarol.AuthenticatedEvent += (AsyncConnectedRequest asyncConnectedRequest) => { CheckWaitings(Carol); };

            Alice.Waiting = Bob.Waiting = Carol.Waiting = true;

            WaitManualEvent("Authentication", () =>
            {
                mainMenuLogicAlice.AuthenticateUser(Alice.Login, Alice.Password);
                mainMenuLogicBob.AuthenticateUser(Bob.Login, Bob.Password);
                mainMenuLogicCarol.AuthenticateUser(Carol.Login, Carol.Password);
            });
            //---

            //--- Bob, Carol, accept invitation of Alice
            lobbyGameLogicBob.InvitationArrivedEvent += (Player invitedBy, List<Player> players) =>
            {
                lobbyGameLogicBob.AcceptInvitation();
                CheckWaitings(Bob);
            };

            lobbyGameLogicCarol.InvitationArrivedEvent += (Player invitedBy, List<Player> players) =>
            {
                lobbyGameLogicCarol.AcceptInvitation();
                CheckWaitings(Carol);
            };
            //---

            //--- Alice - Create Room
            lobbyGameLogicAlice.Init();
            lobbyGameLogicBob.Init();
            lobbyGameLogicCarol.Init();

            Alice.Waiting = false;
            Bob.Waiting = true;
            Carol.Waiting = true;

            WaitManualEvent("Accept invitation", () =>
            {
                GameConfiguration gameConf = new GameConfiguration();
                gameConf.name = "Alice's invitation game";
                gameConf.first_player = 1;
                gameConf.rated = false;
                gameConf.timeout = 60;
                gameConf.game_mode = GameConfiguration.GameMode.SYNCHRONOUS;

                lobbyGameLogicAlice.CreateInvitationGame(gameConf, new List<int>() { Bob.DoWID, Carol.DoWID });
            });
            //---

            //--- Alice turn
            distantGameplayLogicAlice.LocalPlayerTurnEvent += ((LocalPlayerSeat playerSeat) =>
            {
                Log("Alice play");
                distantGameplayLogicAlice.RollSelectedDice();
                distantGameplayLogicAlice.EndOfTurn();
            });
            //---

            //--- Bob turn - Forfeit
            bool forfeited = false;

            distantGameplayLogicBob.LocalPlayerTurnEvent += ((LocalPlayerSeat playerSeat) =>
            {
                if (!forfeited)
                {
                    forfeited = true;
                    distantGameplayLogicBob.Forfeit();
                }
            });
            //---

            //--- Carol turn
            distantGameplayLogicCarol.LocalPlayerTurnEvent += ((LocalPlayerSeat playerSeat) =>
            {
                Log("Carol play");

                distantGameplayLogicCarol.RollSelectedDice();
                distantGameplayLogicCarol.EndOfTurn();
            });
            //---

            //--- End round : show score
            distantGameplayLogicAlice.RoundEndedEvent += ((List<IPlayerSeat> listPlayers) =>
            {
                Log(String.Format("Alice sum : {0}", distantGameplayLogicAlice.GetLastDiceSum(1)));
                Log(String.Format("Bob sum : {0}", distantGameplayLogicAlice.GetLastDiceSum(2)));
                Log(String.Format("Carol sum : {0}", distantGameplayLogicAlice.GetLastDiceSum(3)));

                Log(String.Format("Winners : {0}", String.Join(",", distantGameplayLogicAlice.GetWinnerLastRound().ConvertAll<string>(w => w.Name).ToArray())));
            });
            //---

            //--- Initialize gameplay logic and start game
            //    Alice, Bob and Carol are playing in loop until there is a winner
            //    LocalPlayerTurnEvent are fired for each player, then RoundEndedEvent of all players is fired after the end of a round
            //    Bob forfeit at it's first turn, Alice or Carol play AI for Bob
            //    When the game is finished (one of the player has a score of 3), GameOutcomeEvent is fired for each player
            distantGameplayLogicCarol.Init();
            distantGameplayLogicBob.Init();
            distantGameplayLogicAlice.Init();
            //---

            //--- GameOver
            distantGameplayLogicAlice.GameOutcomeEvent += (List<IPlayerSeat> listPlayers) =>
            {
                CheckWaitings(Alice);
            };

            distantGameplayLogicCarol.GameOutcomeEvent += (List<IPlayerSeat> listPlayers) =>
            {
                CheckWaitings(Carol);
            };

            Bob.Waiting = false;
            Alice.Waiting = Carol.Waiting = true;

            WaitManualEvent("Game Over");
            //---

            int aliceScore = distantGameplayLogicAlice.GetScore(1);
            int bobScore = distantGameplayLogicAlice.GetScore(2);
            int carolScore = distantGameplayLogicAlice.GetScore(3);

            Assert.AreEqual(2, aliceScore, "Alice score is not 2 but {0}", aliceScore);
            Assert.AreEqual(2, bobScore, "Bob score is not 2 but {0}", bobScore);
            Assert.AreEqual(3, carolScore, "Carol score is not 3 but {0}", carolScore);
        }
        finally
        {
            mainMenuLogicAlice.Disconnect();
            mainMenuLogicBob.Disconnect();
            mainMenuLogicCarol.Disconnect();

            mainMenuLogicAlice.Dispose();
            mainMenuLogicBob.Dispose();
            mainMenuLogicCarol.Dispose();

            lobbyGameLogicAlice.Dispose();
            lobbyGameLogicBob.Dispose();
            lobbyGameLogicCarol.Dispose();

            distantGameplayLogicAlice.Dispose();
            distantGameplayLogicBob.Dispose();
            distantGameplayLogicCarol.Dispose();
        }
    }
}
