using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using Models.Events;
using Models.Rules;

namespace Models.Game
{
    /// <summary>
    /// The Game class represents a game session in the application.
    /// It contains all the necessary properties and methods to manage a game, including the game loop, dice rolling, and use of the game rules.
    /// </summary>
    public class Game
    {
        private bool _isRunning;
        public Player CurrentPlayer { get; private set; }

        public Map UsedMap { get; private set; }

        private Dice Dice1 { get; }
        private Dice Dice2 { get; }

        private int Turn { get; set; }

        public Rules.Rules GameRules { get; }

        // == Events ==
        public event EventHandler<GameStartedEventArgs> GameStarted;
        public event EventHandler<GameEndedEventArgs> GameEnded;
     
        
        /// <summary>
        /// Initializes a new instance of the <see cref="Game"/> class.
        /// </summary>
        /// <param name="player">The player who play the game.</param>
        /// <param name="map">The map to be used in the game.</param>
        public Game(Player player, Map map)
        {
            _isRunning = false;
            UsedMap = map;
            CurrentPlayer = player;
            Dice1 = new Dice();
            Dice2 = new Dice(1);
            Turn = 1;
            GameRules = new Rules.Rules();
        }

        /// <summary>
        /// Rolls all the dice.
        /// </summary>
        public void RollAllDice()
        {
            Dice1.Roll();
            Dice2.Roll();
        }

        /// <summary>
        /// Performs an operation on the values of two dice based on the provided operation.
        /// </summary>
        /// <param name="o">The operation to perform. This can be LOWER, HIGHER, SUBTRACTION, ADDITION, or MULTIPLICATION.</param>
        /// <returns>
        /// The result of the operation. If the operation is LOWER or HIGHER, it returns the lower or higher value of the two dice respectively.
        /// If the operation is SUBTRACTION, it returns the difference between the higher and lower value of the two dice.
        /// If the operation is ADDITION, it returns the sum of the values of the two dice.
        /// If the operation is MULTIPLICATION, it returns the product of the values of the two dice.
        /// If the operation is not one of the operations, it throws an ArgumentOutOfRangeException.
        /// </returns>
        public int ResultOperation(Operation o)
        {
            switch (o)
            {
                case Operation.LOWER:
                    return Dice1.IsLower(Dice2) ? Dice1.Value : Dice2.Value;

                case Operation.HIGHER:
                    return Dice1.IsLower(Dice2) ? Dice2.Value : Dice1.Value;

                case Operation.SUBTRACTION:
                    return Dice1.IsLower(Dice2) ? Dice2.Value - Dice1.Value : Dice1.Value - Dice2.Value;

                case Operation.ADDITION:
                    return Dice2.Value + Dice1.Value;

                case Operation.MULTIPLICATION:
                    return Dice2.Value * Dice1.Value;
                
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        /// <summary>
        /// Places the result of a dice operation into a chosen cell on the game board.
        /// The result can be placed in the chosen cell if it's the first turn or if the chosen cell is valid according to the game rules.
        /// </summary>
        /// <param name="playerChoice">The cell chosen by the player to place the result.</param>
        /// <param name="result">The result of the dice operation to be placed in the cell.</param>
        public void PlaceResult(Cell playerChoice, int result)
        {
            if (Turn == 1 || GameRules.NearCellIsValid(playerChoice, UsedMap.Boards))
            {
                playerChoice.Value = result;
            }
        }

        public void AddToRopePath(Cell playerChoice,List<Cell> adjacentes)
        {
            int index =0;

            foreach (var cells in adjacentes)
            {
                // La cellule choisi peut creer/s'ajouter a un chemin de corde
                if (cells.Value - playerChoice.Value == 1 || cells.Value - playerChoice.Value == -1)
                {
                    // Le cas si il n'existe aucun chemin de corde
                    if (UsedMap.RopePaths.Count == 0)
                    {
                        // Creer un nouveau chemin de corde avec la cellule choisi par le joueur et celle adjacente
                        UsedMap.RopePaths.Add(new List<Cell> {playerChoice, cells});
                    }


                    // A modifier dans le cas ou il est possible de fusionner deux chemins de corde
                    if (GameRules.IsInRopePaths(playerChoice, UsedMap.RopePaths, index)) break;

                    // Le cas si il existe des chemins de corde

                    // Est-ce que la cellule adjacentes fait parti d'un chemin de corde
                    if (!GameRules.IsInRopePaths(cells,UsedMap.RopePaths,index))
                    {
                        UsedMap.RopePaths.Add(new List<Cell> { playerChoice, cells });
                        continue;
                    }

                    if (!GameRules.AsValue(playerChoice,UsedMap.RopePaths,index))
                    {
                        UsedMap.RopePaths[index].Add(playerChoice);
                    }

                    // Si oui, est-ce que le chemin possede deja la valeur correspondante a la valeur de la cellule du joueur choisi
                    // {playerChoice.Value} n'est pas dans le chemin de corde
                    // Ajouter au chemin
                    // {playerChoice.Value} existe dans le chemin de corde pas possible

                }
            }
            
        }
        
        /// <summary>
        /// Initializes the game.
        /// </summary>
        public void InitializeGame()
        {
            _isRunning = true;
            GameStarted?.Invoke(this, new GameStartedEventArgs(CurrentPlayer));
            GameLoop();
        }
        
        /// <summary>
        /// The main game loop that runs while the game is active.
        /// </summary>
        private async void GameLoop()
        {
            while (_isRunning)
            {
                /*
                if (CheckGameEnd()) //TODO Règle pour check si fin de jeu
                {
                    _isRunning = false;
                    //TODO Code pour comptabiliser les points
                    //GameEnded?.Invoke(this, new GameEndedEventArgs(player));
                }
                */

                await Task.Delay(1000); // 1 seconde
            }
        }

    }
}
