// ========================================================================
//
// Copyright (C) 2013-2014 MARC CHEVALDONNE
//                         marc.chevaldonne.free.fr
//
// Module        : Game.cs
// Author        : Marc Chevaldonné
// Creation date : 2014-05-07
//
// ========================================================================

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

/// <summary>
/// contient les classes gérant le fonctionnement du jeu
/// </summary>
namespace giMorpionCore
{
    /// <summary>
    /// contient les méthodes de base pour le jeu du Morpion : démarrage du jeu, test de fin du jeu, insertion d'une pièce...
    /// </summary>
    public partial class Game
    {
        /// <summary>
        /// exception lors de l'insertion de pièces
        /// </summary>
        public class GameException : Exception
        {
            public GameException(string message)
                : base(message)
            {
            }
        }

        /// <summary>
        /// grille du morpion
        /// </summary>
        public int[,] Board
        {
            get
            {
                return (int[,])mBoard.Clone();
            }
        }
        /// <seealso cref="Board"/>
        private int[,] mBoard = new int[3, 3];

        /// <summary>
        /// constructeur : crée deux joueurs et initialise la grille à vide
        /// </summary>
        public Game()
        {
            mPlayers[0] = new Player(this);
            mPlayers[1] = new Player(this);
            mBoard = new int[,] { { -1, -1, -1 }, { -1, -1, -1 }, { -1, -1, -1 } };
        }

        /// <summary>
        /// démarre le jeu du Morpion
        /// </summary>
        public virtual void Start()
        {
            OnGameStarted();

            int winnerId;
            giMorpionCore.Case[] places;
            while (!IsGameOver(out winnerId, out places))
            {
                if (NotifyNextPlayer())
                {
                    ChangeNextPlayer();
                }
            }
        }

        /// <summary>
        /// insère une pièce
        /// </summary>
        /// <param name="place">case où la pièce doit être insérée</param>
        /// <param name="playerId">joueur insérant la pièce</param>
        /// <returns>true si la pièce peut être insérée, false sinon (case en dehors de la grille, ou case déjà occupée)</returns>
        /// <remarks>si la pièce peut être insérée, cette méthode désigne le prochain joueur</remarks>
        protected internal virtual bool InsertPiece(Case place, int playerId)
        {
            if (place.Line < 0 || place.Line >= 3 || place.Column < 0 || place.Column >= 3)
            {
                throw new GameException("Les indices de ligne et colonne doivent être compris entre 0 et 2 inclus");
            }
            if (playerId < 0 || playerId >= 2)
            {
                throw new GameException("L'indice du joueur doit être compris entre 0 et 1 inclus");
            }
            if (mBoard[place.Line, place.Column] != -1)
            {
                return false;
            }

            mBoard[place.Line, place.Column] = playerId;

            System.Threading.Thread.Sleep(mRandom.Next(2000));

            return true;
        }

        /// <summary>
        /// met à jour le prochain joueur
        /// </summary>
        void ChangeNextPlayer()
        {
            mNextPlayer++;
            mNextPlayer %= 2;
        }

        static Random mRandom = new Random();

        /// <summary>
        /// teste si la partie est terminée
        /// </summary>
        /// <param name="winnerId">identifiant du vainqueur (0 ou 1) si la partie est gagnée, -1 en cas d'égalité</param>
        /// <param name="places">cases ayant permis au vainqueur de gagner (non significatives si la partie n'est pas terminée ou s'il y a égalité)</param>
        /// <returns>true si terminée, false sinon</returns>
        protected virtual bool IsGameOver(out int winnerId, out Case[] places)
        {
            places = new Case[3];
            winnerId = -1;
            if(CheckLines(ref winnerId, ref places) 
                || CheckColumns(ref winnerId, ref places) 
                || CheckDiagonals(ref winnerId, ref places))
            {
                OnGameOver(new GameOverEventArgs(winnerId, places));
                return true;
            }

            bool isfull = mBoard.Cast<int>().Count(c => c == -1) == 0;
            if (isfull)
            {
                OnGameOver(new GameOverEventArgs(-1, null));
                return true;
            }
            return false;
        }

        private bool CheckLines(ref int winnerId, ref Case[] places)
        {
            for (int l = 0; l <= 2; l++)
            {
                if (mBoard[l, 0] != -1 && mBoard[l, 0] == mBoard[l, 1] && mBoard[l, 0] == mBoard[l, 2])
                {
                    winnerId = mBoard[l, 0];
                    places[0] = new Case(l, 0);
                    places[1] = new Case(l, 1);
                    places[2] = new Case(l, 2);
                    return true;
                }
            }
            return false;
        }

        private bool CheckColumns(ref int winnerId, ref Case[] places)
        {
            for (int c = 0; c <= 2; c++)
            {
                if (mBoard[0, c] != -1 && mBoard[0, c] == mBoard[1, c] && mBoard[0, c] == mBoard[2, c])
                {
                    winnerId = mBoard[0, c];
                    places[0] = new Case(0, c);
                    places[1] = new Case(1, c);
                    places[2] = new Case(2, c);
                    return true;
                }
            }
            return false;
        }

        private bool CheckDiagonals(ref int winnerId, ref Case[] places)
        {
            if (mBoard[0, 0] != -1 && mBoard[0, 0] == mBoard[1, 1] && mBoard[0, 0] == mBoard[2, 2])
            {
                winnerId = mBoard[0, 0];
                places[0] = new Case(0, 0);
                places[1] = new Case(1, 1);
                places[2] = new Case(2, 2);
                return true;
            }
            if (mBoard[0, 2] != -1 && mBoard[0, 2] == mBoard[1, 1] && mBoard[2, 0] == mBoard[0, 2])
            {
                winnerId = mBoard[0, 2];
                places[0] = new Case(0, 2);
                places[1] = new Case(1, 1);
                places[2] = new Case(2, 0);
                return true;
            }
            return false;
        }

        /// <summary>
        /// identifiant du prochain joueur
        /// </summary>
        protected int NextPlayer
        {
            get
            {
                return mNextPlayer;
            }
        }
        /// <seealso cref="NextPlayer"/>
        private int mNextPlayer = 0;

        /// <summary>
        /// joueurs du jeu
        /// </summary>
        private Player[] mPlayers = new Player[2];

        /// <summary>
        /// renseigne le prochain joueur qu'il doit joueur et le fait jouer
        /// </summary>
        /// <returns>rend true si le joueur a pu jouer</returns>
        protected virtual bool NotifyNextPlayer()
        {
            OnPlayerNotified(new PlayerNotifiedEventArgs(mNextPlayer));

            Case place = mPlayers[mNextPlayer].Play();
            bool result = InsertPiece(place, mPlayers[mNextPlayer].Id);

            OnPieceInserted(new PieceInsertedEventArgs(mPlayers[mNextPlayer].Id, place));
            return result;
        }
    }
}
