using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TELTest.Matchmaking;

namespace TELTest {
    public abstract class QuizGamemode {
        public static List<QuizGamemode> Registry { get; } = new List<QuizGamemode>() { 
            new Gamemodes.Classic(),
            new Gamemodes.ClassicMP(),
            new Gamemodes.Kbmio(),
            new Gamemodes.Pisania(),
            new Gamemodes.Comments(),
            //new Gamemodes.Turns()
        };
        protected QuizGamemode() { }
        protected QuizGamemode(QuizContainer container, BaseQuizContext context) {
            this.Quiz = container;
            this.Context = context;
        }
        public QuizContainer Quiz { get; protected set; }
        public BaseQuizContext Context { get; protected set; }
        public abstract QuizGamemode CreateInstance(QuizContainer quiz, BaseQuizContext context);
        public QuizGamemode CreateCheckInstance(QuizContainer quiz, BaseQuizContext context) { 
            var inst = CreateInstance(quiz, context);
            if (inst.GetType() != this.GetType()) throw new InvalidOperationException("Sanity check: QuizGamemode.CreateInstance should return the current type, it returned " + inst.GetType() + " instead of " + this.GetType() + ".");
            return inst;
        }
        public abstract string Name { get; }
        public abstract string SystemName { get; }
        public abstract string Description { get; }
        protected const string SYSTEMNAME_WHITELIST = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
        public abstract Task Begin();
        public void JoinExistingLobby(Lobby lobby) {
            m_JoinMe = lobby;
        }
        private Lobby m_JoinMe = null;
        protected bool ValidateSystemName() {
            if (string.IsNullOrWhiteSpace(Name)) return false;
            for (int i = 0; i < SystemName.Length; i++) {
                if (!SYSTEMNAME_WHITELIST.Contains(SystemName[i])) return false;
            }
            return true;
        }
        public virtual async Task<Lobby> CreateJoinLobbyStandard() {
            await GUIUtilities.AskUsernameIfNeeded(Context.Handler, Context.Stream);
            Lobby lobby = null;
            if (m_JoinMe == null) { 
                lobby = MatchmakingStuff.RequestNew(Quiz, this);
                lobby.Players.Add(this.Context.Handler);
                await GUIUtilities.LobbySettings(this.Context.Handler, this.Context.Stream, lobby);
            } else {
                lobby = m_JoinMe;
                lobby.Players.Add(this.Context.Handler);
                m_JoinMe = null;
            }
            bool aw = await GUIUtilities.LobbyAwait(this.Context.Handler, this.Context.Stream, lobby);
            if (!aw) {
                lobby.Players.Remove(Context.Handler);
                return null;
            }
            //MakePlayersUnready(lobby);
            return lobby;
        }
        const string SPINNER = "|/-\\";
        public virtual async Task WaitForPlayersReady(Lobby lobby, bool clearScreen = false) {
            //lobby.Players.Where(x => x == Context.Handler).First().PlayerReady = true;
            Context.Handler.PlayerReady = true;
            int spinstage = 0;
            if (clearScreen) {
                await Clear();
                await WriteLine();
            }
            while (true) {
                bool allready = true;
                for (int i = 0; i < lobby.Players.Count; i++) {
                    allready &= lobby.Players[i].PlayerReady;
                }

                await Write("\r " + SPINNER[spinstage] + " Czekanie na reszte graczy... " + SPINNER[spinstage] + "    ");
                await Task.Delay(150);
                spinstage++;
                if (spinstage >= SPINNER.Length) spinstage = 0;
                if (allready) break;
            }
            await WriteLine();
            await Task.Delay(300);
        }
        public void MakePlayersUnready(Lobby lobby) {
            for (int i = 0; i < lobby.Players.Count; i++) {
                lobby.Players[i].PlayerReady = false;
            }
        }
        public virtual async Task DoScoreSubmission(int score, bool _reserved = false) {
            if (!ValidateSystemName()) throw new InvalidOperationException("The system name of the gamemode is null, empty, exclusively whitespace, or contains invalid characters. Valid characters are \"" + SYSTEMNAME_WHITELIST + "\" (without quotes).");
            Context.Handler.Flush(Context.Stream);
            var scoreboard = Scoreboard.Of(Quiz.Guid + "_" + SystemName);
            await Clear();
            string nic = "";
            if (!string.IsNullOrWhiteSpace(Context.Handler.PlayerName)) {
                if (await Context.Handler.Choice(Context.Stream, new[] { 'Y', 'N' }, "Zapisac wynik?", def: 'Y') == 'Y') {
                    nic = Context.Handler.PlayerName;
                    scoreboard.Add(new Scoreboard.ScoreboardEntry(nic, score));
                }

            } else {
                await Write(Localization.UI["SAVESCORE"] + " ");
                nic = await Context.Handler.Read(Context.Stream, new ReadConfiguration() {
                    Echo = true,
                    Terminator = '\n',
                    FlushAfterReading = true,
                    MaxLength = 30,
                    Whitelist = SYSTEMNAME_WHITELIST
                });
                nic = nic.TrimEnd('\r', '\n').Trim();
                if (!string.IsNullOrWhiteSpace(nic)) {
                    scoreboard.Add(new Scoreboard.ScoreboardEntry(nic, score));
                    Context.Handler.PlayerName = nic;
                }
            }
            await Clear();
            StringBuilder sb = new StringBuilder();
            var high = scoreboard.Highscore();
            bool hadScore = false;
            for (Int32 i = 0; i < high.Length; i++) {
                Scoreboard.ScoreboardEntry item = high[i];
                bool current = item.Name == nic;
                hadScore |= current;
                sb.AppendLineRN(((i + 1).ToString().PadLeft(2) + ". " + (current? "-> " : "   ") + item.Name).PadRight(38) + "  " + item.Value);
            }
            
            if (!string.IsNullOrWhiteSpace(nic) && !hadScore) {
                for (Int32 i = 0; i < scoreboard.Entries.Count; i++) {
                    var item = scoreboard.Entries[i];
                    if (item.Name == nic) {
                        sb.AppendLineRN();
                        bool current = true;
                        sb.AppendLineRN((i.ToString().PadLeft(2) + ". " + (current ? "->" : "") + item.Name).PadRight(38) + "  " + item.Value);
                    }
                }
            }
            sb.AppendLineRN();
            await WriteLine(sb.ToString());
            await Pause();
        }
        public Task EndMP(Lobby lobby) {
            lobby.Players.Remove(Context.Handler);
            if (lobby.Players.Count == 0 && !lobby.AnnouncePublic) {
                lock (MatchmakingStuff.AllLobbies)
                    MatchmakingStuff.AllLobbies.Remove(lobby);
            }
            Context.Handler.PlayerReady = false;
            return Task.FromResult(true);
        }
        public Task Flush() {
            Context.Handler.Flush(Context.Stream);
            return Task<bool>.FromResult(true);
        }
        public async Task Clear() {
            await Context.Handler.TryClear(Context.Stream);
        }
        public async Task Pause() {
            await Write(Localization.UI["ANYKEYCONTINUE"]);
            await ReadKey(false);
            await Flush();
            await WriteLine();
        }
        public async Task WriteLine() => await WriteLine("");
        public async Task WriteLine(object format, params object[] args) {
            await Write(format + "\r\n", args);
        }
        public async Task Write(object format, params object[] args) {
            await Context.Handler.WriteASCII(Context.Stream, string.Format(format.ToString(), args));
        }
        public async Task<string> ReadLine() => await ReadLine(true);
        public async Task<string> ReadLine(bool echo) {
            string rl = await Context.Handler.Read(Context.Stream, '\n', echo);
            return rl.TrimEnd('\r', '\n');
        }
        public async Task<char> ReadUppercaseKey() {
            char c = char.ToUpper(await ReadKey(false));
            await Write(c);
            return c;
        }
        public async Task<char> ReadKey() => await ReadKey(true);
        public async Task<char> ReadKey(bool echo) {
            return await Context.Handler.ReadChar(Context.Stream, echo);
        }

    }
}
