using CoreLibrary.Core;
using CoreLibrary.Events;
using CoreLibrary.Joueurs;
using CoreLibrary.Persistance;
using CoreLibrary.Regles;
using System.Runtime.Serialization;

namespace CoreLibrary
{
    /// <summary>
    /// Représente une partie de jeu.
    /// </summary>
    [DataContract]
    [KnownType(typeof(ReglesClassiques))]
    public class Partie : IEstPersistant
    {
        [DataMember]
        public IRegles Regles { get; private set; }

        [DataMember]
        private readonly List<Joueur> joueurs = new List<Joueur>();
        [DataMember]
        private readonly List<Plateau> plateaux = new List<Plateau>();
        [DataMember]
        private int? courant;

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

        /// <summary>
        /// Événement déclenché lorsqu'il est nécessaire d'ajouter un joueur.
        /// </summary>
        public event EventHandler<AjouterJoueurEventArgs>? 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'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 du nom d'un joueur.
        /// </summary>
        /// <param name="numero">Le numéro du joueur à ajouter</param>
        /// <param name="joueurBuilder">La classe dans laquelle le nom doit être défini</param>
        private void QuandDemanderNom(int numero, JoueurBuilder joueurBuilder) => DemanderNom?.Invoke(this, new DemanderNomEventArgs(numero, joueurBuilder));

        /// <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 AjouterJoueurEventArgs(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="code">Le code dans lequel il faut ajouter les jetons.</param>
        /// <param name="grille">La grille de jeu.</param>
        /// <param name="indicateurs">Les indicateurs de jeu.</param>
        private void QuandNouveauTour(Joueur joueur, int tour, Code code, IEnumerable<IEnumerable<Jeton?>> grille, IEnumerable<IEnumerable<Indicateur>> indicateurs) => NouveauTour?.Invoke(this, new NouveauTourEventArgs(joueur, tour, code, grille, indicateurs));

        /// <summary>
        /// Méthode pour déclencher l'événement d'ajout d'un nouveau code.
        /// </summary>
        /// <param name="code">Le code ajouté.</param>
        private void QuandAjouterCode(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(Joueur[] gagnants, 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>
        /// <param name="persistance">Persistance de la partie.</param>
        public Partie(IRegles regles)
        {
            Regles = regles;
        }

        /// <summary>
        /// Lance le déroulement de la partie.
        /// </summary>
        public void Jouer()
        {
            JoueurBuilder joueurBuilder = new JoueurBuilder();
            joueurBuilder.ConstruireJoueur += Joueur;

            QuandDemanderNom(joueurs.Count + 1, joueurBuilder);
        }

        /// <summary>
        /// Un joueur a saisi son nom
        /// </summary>
        private void Joueur(Object? sender, ConstruireJoueurEventArgs e)
        {
            Joueur joueur = e.Joueur;
            Plateau plateau = new Plateau(Regles.TailleCodeMaximum, Regles.TourMaximum);

            joueurs.Add(joueur);
            plateaux.Add(plateau);
            QuandAjouterJoueur(joueur);
            joueur.JouerCode += Tour;

            if (joueurs.Count != Regles.NbJoueursMaximum)
            {
                JoueurBuilder joueurBuilder = new JoueurBuilder();
                joueurBuilder.ConstruireJoueur += Joueur;
                QuandDemanderNom(joueurs.Count + 1, joueurBuilder);
            }
            else
            {
                Commencer();
            }
        }

        /// <summary>
        /// La partie démarre
        /// </summary>
        private void Commencer()
        {
            courant = 0;
            Joueur joueurCourant = joueurs[courant.Value];
            Plateau plateauCourant = plateaux[courant.Value];
            QuandDebutPartie();

            QuandNouveauTour(joueurCourant, plateauCourant.Tour, new Code(Regles.TailleCodeMaximum), plateauCourant.Grille(), plateauCourant.Indicateurs());
        }

        /// <summary>
        /// Un joueur a joué son tour
        /// </summary>
        private void Tour(Object? sender, JouerCodeEventArgs e)
        {
            Plateau plateauCourant = plateaux[courant!.Value];

            plateauCourant.AjouterCode(e.Code);
            QuandAjouterCode(e.Code);

            if (++courant == joueurs.Count)
                courant = 0;
            QuandPasserMain();
            
            if (courant == 0 && (plateauCourant.Tour > Regles.TourMaximum || plateaux.Where(plateau => plateau.Victoire).Any()))
            {
                Terminee();
            }
            else
            {
                Joueur joueurCourant = joueurs[courant!.Value];
                plateauCourant = plateaux[courant!.Value];
                QuandNouveauTour(joueurCourant, plateauCourant.Tour, new Code(Regles.TailleCodeMaximum), plateauCourant.Grille(), plateauCourant.Indicateurs());
            }
        }

        /// <summary>
        /// La partie est terminée
        /// </summary>
        private void Terminee()
        {
            foreach(Joueur joueur in joueurs)
            {
                joueur.JouerCode -= Tour;
            }

            List<Joueur> gagnants = new List<Joueur>();
            List<Joueur> perdants = new List<Joueur>();

            for (int i = 0; i < joueurs.Count; i++)
            {
                if (plateaux[i].Victoire)
                    gagnants.Add(joueurs[i]);
                else
                    perdants.Add(joueurs[i]);
            }

            QuandPartieTerminee(gagnants.ToArray(), perdants.ToArray());
        }
    }
}
