using System;
using System.Collections.Generic;
using System.ComponentModel;
using OrderStacks.model;
using OrderStacks.model.card;
using OrderStacks.model.card.cardType;
using OrderStacks.model.deck;
using OrderStacks.model.@event;
using OrderStacks.model.gameActions.abstractRules;
using OrderStacks.model.gameActions.classic;
using OrderStacks.model.gameActions.decimals;
using OrderStacks.model.gameActions.fraction;
using OrderStacks.model.manager;
using OrderStacks.model.piles;

namespace OrderStacks.viewmodel
{
    public class Main : INotifyPropertyChanged
    {
        public event EventHandler<EventArgs> EndGame;
        public event EventHandler<EventArgs> AlertChanged;
        public event PropertyChangedEventHandler PropertyChanged;
        private List<PlayerVM> players = new List<PlayerVM>();
        private string alert = "";
        public string Alert
        {
            get { return alert; }
            set
            {
                alert = value;
                OnAlertChanged();
            }
        }
        private string pseudo = "Pseudo";
        public string Pseudo
        {
            get { return pseudo; }
            set
            {
                pseudo = value;
                OnPropertyChanged("Pseudo");
            }
        }

        /**
         * <param name="info">Nom de la propriété</param>
         * Fonction permettant de notifier qu'une propriété a changé
         */
        protected virtual void OnPropertyChanged(string info)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
        }

        /**
         * Fonction permettant de notifier qu'un nouveau message utilisateur doit être affiché
         */
        protected virtual void OnAlertChanged()
        {
            AlertChanged?.Invoke(this, new EventArgs());
        }

        private GameManager gameManager;
        public List<CardVM> CurrentHand { get; set; } = new List<CardVM>();
        public List<Stack<CardVM>> listOrderedStacks = new List<Stack<CardVM>>();


        /**
         * <param name="indexMode">Index du mode de jeu</param>
         * <param name="nbCard">Nombre de carte maximal pour les piles</param>
         * <param name="nbPile">Nombre de piles</param>
         * <param name="playersNames">Pseudo(s) du/des joueur(s)</param>
         * Constructeur
         */
        public Main(List<string> playersNames, int nbPile, int nbCard, int indexMode)
        {
            playersNames.ForEach(name => players.Add(new PlayerVM(new Player(name))));
            GameMode gameMode;
            switch (indexMode)
            {
                case 0:
                    gameMode = new GameModeClassic(new ClassicPiles(nbPile), new ClassicDeck(nbCard, 1, 99));
                    break;
                case 1:
                    gameMode = new GameModeClassic(new PilesMoins51To51(nbPile), new RelativeDeck(nbCard, -49, 49));
                    break;
                case 2:
                    gameMode = new GameModeDecimal(new Piles0To10(nbPile), new DizaineDeck(nbCard, 0.1m, 9.9m));
                    break;
                case 3:
                    gameMode = new GameModeDecimal(new PilesMoins5To5(nbPile), new CentaineDeck(nbCard, -4.99m, 4.99m));
                    break;
                case 4:
                    gameMode = new GameModeDecimal(new PilesMoins5To5(nbPile), new MilliemeDeck(nbCard, -4.999m, 4.999m));
                    break;
                case 5:
                    gameMode = new GameModeFraction(new FractionPiles(nbPile), new FractionDeck(nbCard, 0m, 51m));
                    break;
                case 6:
                    gameMode = new GameModeDecimal(new Piles0To10(nbPile), new Decimal1Deck(nbCard, 0, 100));
                    break;
                case 7:
                    gameMode = new GameModeDecimal(new Piles0To10(nbPile), new Decimal2Deck(nbCard, 0, 1000));
                    break;
                default:
                    gameMode = new GameModeClassic(new ClassicPiles(nbPile), new ClassicDeck(nbCard, 1, 99));
                    break;
            }
            Parametreur parametreur = new Parametreur(gameMode);
            players.ForEach(player => parametreur.AddPlayer(player.View));
            gameManager = new SoloGameManager(parametreur);

            gameManager.EndGame += OnEndGame;

            gameManager.PlayerChanged += OnPlayerChanged;

            gameManager.CurrentHand.ForEach( card =>
            {
                if (card.GetType() == typeof(FractionCard))
                {
                    CurrentHand.Add(new FractionCardVM((FractionCard)card));
                }
                else
                {
                    CurrentHand.Add(new CardVM(card));
                }
            });

            Pseudo = players[0].Pseudo;

            for (int i = 0; i < gameManager.getPiles().Size; i++)
            {
                Stack<Card> pileView = gameManager.getPiles().getStack(i);
                Stack<CardVM> pile = new Stack<CardVM>();
                Card[] cards = pileView.ToArray();
                for (int j = 0; j < cards.Length; j++)
                {
                    if (cards[j].GetType() == typeof(FractionCard))
                    {
                        pile.Push(new FractionCardVM((FractionCard)cards[j]));
                    }
                    else
                    {
                        pile.Push(new CardVM(cards[j]));
                    }
                }
                listOrderedStacks.Add(pile);
            }
        }

        /**
         * Fonction permettant de retourner une vue des piles ordonnées
         * <returns>Vue des piles ordonnées</returns>
         */
        public IReadOnlyList<Stack<CardVM>> getListOrderedStacks()
        {
            return listOrderedStacks.AsReadOnly();
        }

        /**
         * <param name="source">Source de l'événement</param>
         * <param name="args">Argument(s) de l'événement</param>
         * Evénement permettant de déclencher le changement de joueur.
         */
        protected internal void OnPlayerChanged(object source, PlayerChangedEventArgs args)
        {
            CurrentHand.Clear();

            args.NewCurrentHand.ForEach( card =>
            {
                if (card.GetType() == typeof(FractionCard))
                {
                    CurrentHand.Add(new FractionCardVM((FractionCard)card));
                }
                else
                {
                    CurrentHand.Add(new CardVM(card));
                }
            });
            Pseudo = args.Pseudo;
        }

        /**
         * <param name="args">Argument(s) de l'événement</param>
         * <param name="source">Source de l'événement</param>
         * Evénement permettant de déclencher la fin du jeu.
         */
        public void OnEndGame(object source, EventArgs args)
        {
            Alert = gameManager.EndMessage;
            EndGame?.Invoke(source, args);
        }

        /**
         * <param name="numStack">Numéro de la pile séléctionnée pour jouer</param>
         * <param name="valueCard">Valeur de la carte joué</param>
         * Fonction permettant de demander à jouer une carte puis de retourner si elle a été joué
         * <returns>Booléen de carte joué</returns>
         */
        public bool played(int numStack, decimal valueCard)
        {
            if (!gameManager.joue(numStack, valueCard))
            {
                Alert = gameManager.EndMessage;
                return false;
            }
            else
            {
                foreach(CardVM card in CurrentHand)
                {
                    if (card.Value.CompareTo(valueCard) == 0)
                    {
                        CurrentHand.Remove(card);
                        break;
                    }
                }
            }
            if (gameManager.getPiles().getStack(numStack).Peek().GetType() == typeof(FractionCard))
            {
                listOrderedStacks[numStack].Push(new FractionCardVM((FractionCard)gameManager.getPiles().getStack(numStack).Peek()));
            }
            else
            {
                listOrderedStacks[numStack].Push(new CardVM(gameManager.getPiles().getStack(numStack).Peek()));
            }
            return true;
        }

        /**
         * Fonction permettant de changer de joueur.
         * <returns>Booléen de fin de jeu</returns>
         */
        public bool endTurn()
        {
            bool isEnd = gameManager.endTurn();
            if (string.IsNullOrEmpty(gameManager.EndMessage) && !isEnd)
            {
                return false;
            }
            Alert = gameManager.EndMessage;
            return true;
        }

    }
}
