using CoreLibrary.Events;
using CoreLibrary.Exceptions;

namespace CoreLibrary
{
    /// <summary>
    /// Représente une partie de jeu.
    /// </summary>
    public class Partie
    {
        private readonly IRegles regles;


        /// <summary>
        /// Définit le délégué pour gérer les événements avec des arguments de type string.
        /// </summary>
        public delegate string? StringEventHandler<in TEventArgs>(Object? sender, TEventArgs e);

        /// <summary>
        /// Définit le délégué pour gérer les événements avec des arguments de type Jeton.
        /// </summary>
        public delegate Jeton? JetonEventHandler<in TEventArgs>(Object? sender, TEventArgs e);

        /// <summary>
        /// Événement déclenché lorsqu'il est nécessaire de d'ajouter un joueur.
        /// </summary>
        public event StringEventHandler<DemanderJoueurEventArgs>? DemanderJoueur;

        /// <summary>
        /// Événement déclenché lorsqu'il est nécessaire d'ajouter un jeton.
        /// </summary>
        public event JetonEventHandler<DemanderJetonEventArgs>? DemanderJeton;

        /// <summary>
        /// Événement déclenché lorsqu'il est nécessaire d'ajouter un joueur.
        /// </summary>
        public event EventHandler<AjouterJoueursEventArgs>? AjouterJoueur;

        /// <summary>
        /// Événement déclenché lorsqu'une partie commence.
        /// </summary>
        public event EventHandler<DebutPartieEventArgs>? DebutPartie;

        /// <summary>
        /// Événement déclenché lorsqu'un nouveau tour commence.
        /// </summary>
        public event EventHandler<NouveauTourEventArgs>? NouveauTour;

        /// <summary>
        /// Événement déclenché lorsqu'il est nécessaire d'ajouter un jeton.
        /// </summary>
        public event EventHandler<AjouterJetonEventArgs>? AjouterJeton;


        /// <summary>
        /// Événement déclenché lorsqu'un jeton est supprimé.
        /// </summary>
        public event EventHandler<SupprimerDernierJetonEventArgs>? SupprimerDernierJeton;

        /// <summary>
        /// Événement déclenché lorsqu'un code est ajouté.
        /// </summary>
        public event EventHandler<AjouterCodeEventArgs>? AjouterCode;

        /// <summary>
        /// Événement déclenché lorsque la main est passée au joueur suivant.
        /// </summary>
        public event EventHandler<PasserMainEventArgs>? PasserMain;

        /// <summary>
        /// Événement déclenché lorsque la partie est terminée.
        /// </summary>
        public event EventHandler<PartieTermineeEventArgs>? PartieTerminee;

        /// <summary>
        /// Méthode pour déclencher l'événement de demande d'ajout d'un joueur.
        /// </summary>
        /// <param name="numero">Le numéro du joueur à ajouter</param>
        /// <returns>Le nom du joueur demandé</returns></returns>
        private string? QuandDemanderJoueur(int numero) => DemanderJoueur?.Invoke(this, new DemanderJoueurEventArgs(numero));

        /// <summary>
        /// Méthode pour déclencher l'événement de demande d'ajout d'un jeton.
        /// </summary>
        /// <returns>Le jeton demandé</returns>
        private Jeton? QuandDemanderJeton() => DemanderJeton?.Invoke(this, new DemanderJetonEventArgs());

        /// <summary>
        /// Méthode pour déclencher l'événement d'ajout d'un joueur.
        /// </summary>
        /// <param name="joueur">Le joueur à ajouter.</param>
        private void QuandAjouterJoueur(Joueur joueur) => AjouterJoueur?.Invoke(this, new AjouterJoueursEventArgs(joueur));

        /// <summary>
        /// Méthode pour déclencher l'événement du début d'un partie.
        /// </summary>
        private void QuandDebutPartie() => DebutPartie?.Invoke(this, new DebutPartieEventArgs());


        /// <summary>
        /// Méthode pour déclencher l'événement d'un nouveau tour.
        /// </summary>
        /// <param name="joueur">Le joueur dont c'est le tour.</param>
        /// <param name="tour">Le numéro du tour.</param>
        /// <param name="grille">La grille de jeu.</param>
        /// <param name="indicateurs">Les indicateurs de jeu.</param>
        private void QuandNouveauTour(Joueur joueur, int tour, IEnumerable<IEnumerable<Jeton?>> grille, IEnumerable<IEnumerable<Indicateur>> indicateurs) => NouveauTour?.Invoke(this, new NouveauTourEventArgs(joueur, tour, grille, indicateurs));

        /// <summary>
        /// Méthode pour déclencher l'événement d'ajout d'un nouveau jeton.
        /// </summary>
        /// <param name="jeton">Le jeton à ajouter.</param>
        private void QuandNouveauJeton(Jeton jeton) => AjouterJeton?.Invoke(this, new AjouterJetonEventArgs(jeton));

        /// <summary>
        /// Méthode pour déclencher l'événement de suppression du dernier jeton du code.
        /// </summary>
        private void QuandSupprimerDernierJeton() => SupprimerDernierJeton?.Invoke(this, new SupprimerDernierJetonEventArgs());

        /// <summary>
        /// Méthode pour déclencher l'événement d'ajout d'un nouveau code.
        /// </summary>
        /// <param name="code">Le code à ajouter.</param>
        private void QuandNouveauCode(Code code) => AjouterCode?.Invoke(this, new AjouterCodeEventArgs(code));

        /// <summary>
        /// Méthode pour déclencher l'événement de passage de la main au joueur suivant.
        /// </summary>
        private void QuandPasserMain() => PasserMain?.Invoke(this, new PasserMainEventArgs());

        /// <summary>
        /// Méthode pour déclencher l'événement de fin de partie.
        /// </summary>
        /// <param name="gagnants">La liste des joueurs gagnants.</param>
        /// <param name="perdants">La liste des joueurs perdants.</param>
        private void QuandPartieTerminee(IEnumerable<Joueur> gagnants, IEnumerable<Joueur> perdants) => PartieTerminee?.Invoke(this, new PartieTermineeEventArgs(gagnants, perdants));

        /// <summary>
        /// Crée une nouvelle instance de la classe Partie.
        /// </summary>
        /// <param name="regles">Les règles de la partie.</param>
        public Partie(IRegles regles)
        {
            this.regles = regles;
        }

        /// <summary>
        /// Lance le déroulement de la partie.
        /// </summary>
        public void Jouer()
        {
            /// Ajout des joueurs jusqu'à atteindre le nombre maximum de joueurs défini par les règles
            AjouterJoueurs();

            regles.CommencerLaPartie();
            QuandDebutPartie();

            // Boucle principale du jeu qui dure jusqu'à qu'une condition de fin soit déclenchée
            while (!regles.EstTerminee())
            {
                Joueur joueurCourant = regles.JoueurCourant();
                Plateau plateauCourant = joueurCourant.Plateau; 

                QuandNouveauTour(joueurCourant, plateauCourant.Tour, plateauCourant.Grille(), plateauCourant.Indicateurs());

                Code code = regles.GenererCode();

                CreerCode(code);

                // Phase de saisie du code par le joueur jusqu'à que le code soit complet.

                plateauCourant.AjouterCode(code);
                QuandNouveauCode(code);

                regles.PasserLaMain();
                QuandPasserMain();
            }


            regles.Gagnants();
            regles.Perdants();

            QuandPartieTerminee(regles.Gagnants(), regles.Perdants());
        }

        private void AjouterJoueurs()
        {
            while (regles.NbJoueurs != regles.NbJoueursMaximum)
            {
                string nom = QuandDemanderJoueur(regles.NbJoueurs + 1) ?? $"Joueur {regles.NbJoueurs + 1}";
                Joueur joueur = regles.AjouterJoueur(nom);
                QuandAjouterJoueur(joueur);
            }
        }

        private void CreerCode(Code code)
        {
            while (!code.EstComplet())
            {
                Jeton? jeton = QuandDemanderJeton();

                if (DemanderJeton == null)
                {
                    jeton = new Jeton();
                }
                
                if(!jeton.HasValue)
                {
                    code.SupprimerDernierJeton();
                    QuandSupprimerDernierJeton();
                }
                else
                {
                    code.AjouterJeton(jeton.Value);
                    QuandNouveauJeton(jeton.Value);
                }       
            }
        }
    }
}
