using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Shared;
namespace Server
{
    /// <summary>
    /// Represente un joueur connecte au server
    /// </summary>
    class Gamer
    {
        private static int maxPlayers = 6;
        private static List<Gamer> gamers = new List<Gamer>();
        private static BinaryFormatter bf = new BinaryFormatter();
        private static bool lobby = true, won = false;
        private static int counter = 0, currentPlayer = -1, nbPlayers = 0;
        private void updateId() { bf.Serialize(ns, id); }
        private NetworkStream ns;
        private string name="Player";
        private bool ready = false, alive = false, running = true, playing = false, hasReported = false;
        private int id { get { return gamers.IndexOf(this); } }
        private int fixedId;
        private void toConsole(string s) { Console.WriteLine(id + ":" + name + ": " + s); }
        private void sendToEveryOne(Object o) { sendToEveryOneButGa(o, null); }
        /// <summary>
        /// Indique si la partie est gagnee
        /// </summary>
        private static bool gameOver
        {
            get
            {
                int i = 0;
                lock (gamers) foreach (Gamer g in gamers.Where(delegate(Gamer g) { return g.alive; })) i++;
                return i <= 1;
            }
        }
        /// <summary>
        /// Transmet un message a tout le monde sauf moi
        /// </summary>
        /// <param name="o">Le message a transmettre</param>
        private void sendToEveryOneButGa(Object o, Gamer ga)
        {
            lock (gamers) foreach (Gamer g in gamers)
                    try { if (g != ga) bf.Serialize(g.ns, o); }
                    catch (Exception) { g.Close(); }
        }
        public Gamer(TcpClient tc) { ns = tc.GetStream(); }
        /// <summary>
        /// Retourne la liste des joueurs pour la transmettre sur le reseau
        /// </summary>
        private Users userStatus
        {
            get
            {
                Users users = new Users();
                lock (gamers) foreach (Gamer g in gamers) users.list.Add(new User((lobby) ? g.id : g.fixedId, g.name, g.ready));
                return users;
            }
        }
        /// <summary>
        /// Traite les donnee en provenance des clients
        /// </summary>
        public void listen()
        {
            try
            {
                Console.WriteLine("New client connected");
                // S'inscrit dans la liste des joueurs
                lock (gamers) if (gamers.Count < maxPlayers && lobby) gamers.Add(this); else { Close(); toConsole("User kicked, server is full or in a game"); }
                // Indique au client qui il est
                bf.Serialize(ns, id);
                sendToEveryOne(userStatus);
                while (running)
                {
                    Object o = bf.Deserialize(ns);
                    if (lobby)
                    {
                        if (o is string) { toConsole("Changed name to " + o); name = o.ToString(); sendToEveryOne(userStatus); }
                        if (o is Message) { toConsole("Message: " + o); sendToEveryOne(o); }
                        else if (o is bool) { toConsole("Set ready status to " + o); ready = (bool)o; sendToEveryOne(userStatus); }
                        else if (o is Close) Close();
                        else if (o is Start)
                        {
                            lobby = false;
                            lock (gamers) foreach (Gamer g in gamers) g.fixedId = g.id;
                            sendToEveryOne(new Game(userStatus));
                            toConsole("Started the game");
                            nbPlayers = gamers.Count;
                        }
                    }
                    else
                    {
                        if (o is Close) Close();
                        if (o is Message) { toConsole("Message: " + o); sendToEveryOne(o); }
                        if (o is bool)
                        {
                            toConsole("Reported its status to the server: " + o);
                            alive = (bool)o;
                            lock (gamers)
                            {
                                counter++;
                                hasReported = true;
                                // Si tout le monde s'est annoncer on relance un nouveau round
                                if (counter == nbPlayers) nextRound();
                            }
                        }
                        if (o is Angle) sendToEveryOneButGa(o, this);
                        if (o is Shoot) { toConsole("shooted"); playing = false; sendToEveryOneButGa(o, this); }
                    }
                }
            }
            catch (Exception ex)
            {
                toConsole(ex.Message);
                if (running) Close();
            }
        }
        /// <summary>
        /// Creer le nouveau round ou termine la partie si necessaire
        /// </summary>
        private void nextRound()
        {
            lock (gamers)
            {
                if (gameOver)
                {
                    toConsole("Game over");
                    // Evite que le gagnant soit transmit plusieurs fois
                    if (!won)
                    {
                        // Retourne le gagnant
                        try
                        {
                            Gamer winner = gamers.First(delegate(Gamer g) { return g.alive; });
                            toConsole("Winner : " + winner.name);
                            sendToEveryOne(new User(winner.fixedId, winner.name, true));
                        }
                        catch (Exception)
                        {
                            sendToEveryOne(new User(0, null, true));
                            toConsole("Winner : None");
                        }
                        won = true;
                    }
                }
                else
                {
                    counter = 0;
                    nbPlayers = gamers.Count;
                    do { currentPlayer = ++currentPlayer % gamers.Count; } while (!gamers[currentPlayer].alive);
                    toConsole("New round - Current player :" + gamers[currentPlayer].name + " ID:" + gamers[currentPlayer].fixedId);
                    gamers[currentPlayer].playing = true;
                    foreach (Gamer g in gamers) g.hasReported = false;
                    sendToEveryOne(new Round(gamers[currentPlayer].fixedId));
                }
            }
        }
        /// <summary>
        /// Ferme la connection avec un client
        /// </summary>
        private void Close()
        {
            toConsole("Closing connection");
            lock (gamers)
            {
                gamers.Remove(this);
                foreach (Gamer g in gamers) g.updateId();
                sendToEveryOneButGa(userStatus, this);
                if (!lobby)
                {
                    //Si le joueur qui a la main deconnecte il tire a vide
                    if (playing)
                    {
                        sendToEveryOneButGa(new Shoot(0, 0, -1, 0, 0), this);
                        toConsole("Empty shot sent to other clients");
                    }
                    //Si un joueur deconnecte avant de dire s'il est vivant ou mort on en tient compte
                    if (hasReported) counter--;
                    nbPlayers--;

                    //Lorsque le dernier joueur deconnecte en repasse en mode lobby
                    if (nbPlayers == 0)
                    {
                        toConsole("Server in lobby mode");
                        won = !(lobby = true);
                    }
                    else if (nbPlayers - counter <= 1) nextRound();
                }
                running = false;
                ns.Close();
            }
        }
    }
    /// <summary>
    /// Classe representant le server
    /// </summary>
    class Server
    {
        static void Main(string[] args)
        {
            TcpListener listener;
            int port;
            try { port = int.Parse(args[0]); }
            catch (Exception) { port = 1337; }

            try
            {
                listener = new TcpListener(IPAddress.Any, port);
                listener.Start();
            }
            catch (Exception e) { Console.WriteLine(e.Message); return; }
            Console.WriteLine("Waiting for client...");
            while (true)
            {
                TcpClient newClient = listener.AcceptTcpClient();
                Gamer g = new Gamer(newClient);
                new Thread(new ThreadStart(g.listen)).Start();
            }
        }
    }
}
