using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TELTest.Matchmaking;

namespace TELTest {
    class NormalConnectionHandler : ConnectionHandler {
        public override async Task HandleMe(NetworkStream stream) {
            //await HandleTerminalSetup(stream);
            //await DoQuizNew(stream);
            await QuizExplorer(stream);
            //await DoQuizNewest(stream);
        }
        protected async Task QuizExplorer(NetworkStream stream) {
            int selx = 1;
            int sely = 0;
        TooLazyToAddWhile:
            string[] publicq = new string[QuizListing.CachePublic.Count];
            QuizEntry[] qe = new QuizEntry[QuizListing.CachePublic.Count];
            lock (QuizListing.Entries) {
                for (Int32 i = 0; i < QuizListing.CachePublic.Count; i++) {
                    QuizEntry item = QuizListing.CachePublic[i];
                    publicq[i] = item.Name;
                    qe[i] = item;
                }
            }
            PlayerReady = false;
            var cd = Path.Combine(".", "Quizzes", "Quizhouse_poor");
            var cdn = Path.Combine(".", "Quizzes", "New");
            string[] dirqhp = Directory.GetFileSystemEntries(cd, "*.jp2");
            for (int i = 0; i < dirqhp.Length; i++) {
                dirqhp[i] = "FILE:" + dirqhp[i];
            }

            string[] dirneu = Directory.GetFileSystemEntries(cdn, "*.xml");
            for (int i = 0; i < dirneu.Length; i++) {
                dirneu[i] = "FILE:" + dirneu[i];
            }

            string[] labels = new[] { "Klasycznym", "Menu", "Nowymi" };
            string[][] menu = new string[3][];
            //List<string> ls = new List<string>();
            
            //for (int i = 0; i < 30; i++) {
            //    ls.Add(Utilities.RandomString());
            //}
            menu[0] = dirqhp;
            menu[1] = new[] {
                /* 0 */ "Dolacz do lobby",
                /* 1 */ "Publiczne lobby",
                //* 2 */ "Matchmaking",
                /* 2 */ "Stworz quiz",
                /* 3 */ "Zmien nick (" + (PlayerName == null? "Brak" : PlayerName)+ ")",
                /* 4 */ "Otworz prywatny quiz...",
                /* 5 */ "Wyjdz",
            };
            menu[2] = publicq;

            var res = await GUIUtilities.Create2DMenu(this, stream, menu, labels, null, defaultx: selx, defaulty: sely);
            int xsel = res.Item1;
            int ysel = res.Item2;
            selx = xsel;
            sely = ysel;
            string sp = menu[xsel][ysel];
            if (xsel == 1) {
                switch (ysel) {
                    case 0:
                        await JoinLobby(this, stream);
                        break;
                    case 1:
                        await LobbyBrowser(stream);
                        break;
                    case 2:
                        QuizCreator creator = new QuizCreator();
                        await creator.Handle(this, stream);
                        break;
                    case 3:
                        await GUIUtilities.AskUsernameIfNeeded(this, stream, true);
                        break;
                    case 4:
                        await OpenPrivate(stream);
                        break;
                    case 5:
                        return;
                    default:
                        break;
                }
            }
            if (xsel == 2) {
                var itm = qe[ysel];
                QuizContainer qc = QuizDraftManager.GetDraft(itm.ID);
                await ProcOpenQuiz(stream, qc);
            }
            if (sp.StartsWith("FILE:")) {
                sp = sp.Substring(5).Trim();
                QuizContainer qc = null;
                switch (Path.GetExtension(sp).ToUpper()) {
                    case ".JP2":
                        string content = System.IO.File.ReadAllText(sp);
                        qc = QuizContainer.FromQuizhouse_poor(content);
                        string meta = sp + ".meta";

                        if (!System.IO.File.Exists(meta)) {
                            System.IO.File.AppendAllText(meta, Guid.NewGuid() + "\r\n");
                        }
                        Guid g = Guid.Parse(System.IO.File.ReadAllLines(meta)[0].Trim());
                        qc.Guid = g;
                        break;
                    case ".XML":
                        qc = QuizContainer.FromXml(System.IO.File.ReadAllText(sp));
                        break;
                    default:
                        throw new InvalidOperationException("Not a filename - " + sp);
                }
                await ProcOpenQuiz(stream, qc);
            }
            goto TooLazyToAddWhile;
        }
        public async Task OpenPrivate(NetworkStream stream) {
            await WriteASCII(stream, "Wpisz ID quizu aby go otworzyc.");
            bool invalid = false;
            while (true) {
                await TryClear(stream);
                await WriteASCII(stream, "Wpisz identyfikator quizu aby go otworzyc.\r\n\r\n");
                if (invalid) await WriteASCII(stream, "Wpisano nieprawidlowy identyfikator.\r\n");
                await WriteASCII(stream, "Identyfikator: ");
                string nam = await Read(stream, new ReadConfiguration() {
                    Terminator = '\r',
                    Echo = true,
                    MaxLength = 20,
                    Whitelist = Utilities.ALPHANUMERIC,
                    FlushAfterReading = true,
                });
                string li = nam.Trim();
                bool lockObtained = false;
                try {
                    Monitor.Enter(QuizListing.Entries);
                    lockObtained = true;
                    for (int i = 0; i < QuizListing.Entries.Count; i++) {
                        var itm = QuizListing.Entries[i];
                        if (itm.ID == li) {
                            await TryClear(stream);
                            await WriteASCII(stream, "Czekaj....");
                            if (!QuizDraftManager.Contains(itm.ID)) {
                                Monitor.Exit(QuizListing.Entries);
                                lockObtained = false;
                                await TryClear(stream);
                                await WriteASCII(stream, "Wystapil nieoczekiwany blad - znaleziono wpis quizu ale nie znaleziono danych.");
                                await ReadChar(stream);
                                Flush(stream);
                                return;
                            }
                            Monitor.Exit(QuizListing.Entries);
                            lockObtained = false;
                            await ProcOpenQuiz(stream, QuizDraftManager.GetDraft(itm.ID));
                            return;
                        }
                    }
                } finally {
                    if (lockObtained) Monitor.Exit(QuizListing.Entries);
                }
                
                invalid = true;
                if (string.IsNullOrWhiteSpace(nam)) break;
            }
        }
        public async Task ProcOpenQuiz(NetworkStream stream, QuizContainer container) {
            BaseQuizContext bq = new BaseQuizContext(this, stream, container);
            await bq.Handle();
        }
        public async Task LobbyBrowser(NetworkStream stream) {
            Lobby[] lobbies = MatchmakingStuff.GetPublic().ToArray();
            int lastsel = 1;
            while (true) {
                string[] names = lobbies.Select(x => 
                x.Name + ", " + x.Subject.Author + " - " + x.Subject.Title 
                + " [" + x.Identifier + (x.PlayerLimit == -1 ? "" : (", " + x.Players.Count + "/" + x.PlayerLimit)) + "]").ToArray();
                string[][] menu = new string[1][];
                menu[0] = new string[names.Length + 2];
                menu[0][0] = "Powrot...";
                menu[0][1] = "Odswiez";
                Array.Copy(names, 0, menu[0], 2, names.Length);
                var res = (await GUIUtilities.Create2DMenu(this, stream, menu, new[] { "Publiczne lobby" }, defaulty: lastsel)).Item2;
                if (res == 0) {
                    return;
                } else if (res == 1) {
                    lobbies = MatchmakingStuff.GetPublic().ToArray();
                    names = lobbies.Select(x => x.Name + "[" + x.Identifier + "]").ToArray();
                } else {
                    var sel = res - 2;
                    await JoinLobbyProcess(stream, lobbies[sel]);
                }
                lastsel = res;
            }
        }
        public async Task JoinLobby(ConnectionHandler handler, NetworkStream stream) {
            //if (!string.IsNullOrWhiteSpace(handler.PlayerName)) return;
            bool invalid = false;
            while (true) {
                await handler.TryClear(stream);
                await handler.WriteASCII(stream, "Wpisz identyfikator lobby (5-7 znakow).\r\n\r\n");
                if (invalid) await handler.WriteASCII(stream, "Wpisano nieprawidlowy identyfikator.\r\n");
                await handler.WriteASCII(stream, "Identyfikator: ");
                string nam = await handler.Read(stream, new ReadConfiguration() {
                    Terminator = '\r',
                    Echo = true,
                    MaxLength = 10,
                    Whitelist = Utilities.ALPHANUMERIC,
                    FlushAfterReading = true,
                });
                string li = nam.Trim();
                for (int i = 0; i < MatchmakingStuff.AllLobbies.Count; i++) {
                    var itm = MatchmakingStuff.AllLobbies[i];
                    if (itm.Identifier == li) {
                        await TryClear(stream);
                        await WriteASCII(stream, "Czekaj....");
                        await JoinLobbyProcess(stream, itm);
                        return;
                    }
                }
                invalid = true;
                if (string.IsNullOrWhiteSpace(nam)) break;
            }
        }

        public async Task JoinLobbyProcess(NetworkStream stream, Lobby lobby) {
            QuizGamemode qg = lobby.Gamemode;
            if (lobby.PlayerLimit != -1 && lobby.Players.Count >= lobby.PlayerLimit) {
                await TryClear(stream);
                await WriteASCII(stream, "Lobby jest pelne.\r\n\r\nNacisnij dowolny klawisz aby kontynuowac...");
                await ReadChar(stream);
                Flush(stream);
                return;
            }
            if (!string.IsNullOrWhiteSpace(lobby.Password)) {
                await TryClear(stream);
                await WriteASCII(stream, "Haslo: ");
                var rc = ReadConfiguration.Default;
                rc.EchoPasswordChars = true;
                string pwd = await Read(stream, rc);
                if (!string.IsNullOrWhiteSpace(pwd)) {
                    if (lobby.Password != pwd) return;
                } else {
                    return;
                }
            }
            var ctx = new BaseQuizContext(this, stream, lobby.Subject);
            var game = qg.CreateCheckInstance(lobby.Subject, ctx);
            game.JoinExistingLobby(lobby);
            await game.Begin();
        }
        protected async Task DoQuizNewest(NetworkStream stream) {
            string qc = File.ReadAllText("Quizzes\\Quizhouse_poor\\quiz.jp2");
            QuizContainer quiz = QuizContainer.FromQuizhouse_poor(qc);
            BaseQuizContext bq = new BaseQuizContext(this, stream, quiz);
            await bq.Handle();
        }
        protected async Task HandleTerminalSetup(NetworkStream stream) {
            await TryClear(stream);
            char c = await Choice(stream, new[] { '4', '8', '1' }, "Czy to terminal z [4]0, [8]0, czy [1]20 kolumn?", def: '1');
            switch (c) {
                default:
                case '4':
                    Columns = 40;
                    break;
                case '8':
                    Columns = 80;
                    break;
                case '1':
                    Columns = 120;
                    break;
            }
            Flush(stream);
            await TryClear(stream);
            await WriteASCII(stream, "The bar below should fill the width of the console. If not, resize your console, or reconnect and try again.\r\n");
            await WriteBar(stream);
        }

        protected async Task DoQuizNew(NetworkStream stream) {
            var ct = File.ReadAllText("quiz.jp2");
            var quiz = QuizContainer.FromQuizhouse_poor(ct);
            async Task CW(object ln = null) => await WriteASCII(stream, ln + "\r\n");
            async Task<char> RK() { char c = await ReadChar(stream); Flush(stream); return c; }
            int wynik = 0;
            await TryClear(stream);
            await CW("Tytul quizu: " + quiz.Title);
            await CW("Autor quizu: " + quiz.Author);
            await CW("Dowolny klawisz aby rozpoczac....");
            await RK();
            

            for (int i = 0; i < quiz.Questions.Length; i++) {
                await TryClear(stream);
                var itm = quiz.Questions[i];
                await CW((i + 1) + ". " + itm.Content + "\r\n");
                char correctAnswer = 'A';
                for (int j = 0; j < itm.Answers.Length; j++) {
                    await CW(Utilities.NumberToLetter(j) + ") " + itm.Answers[j].Content);
                    if (itm.Answers[j].Correct) correctAnswer = Utilities.NumberToLetter(j)[0];
                }
                await WriteASCII(stream, "Twoja odpowiedz: ");
                char c = await ReadChar(stream, false);
                await CW(char.ToUpper(c));

                await CW();
                if (char.ToUpper(c) == correctAnswer) {
                    wynik++;
                    await CW("Dobrze! Zdobywasz punkt!");
                } else {
                    await CW("Zle! Prawidlowa odpowiedz to: " + correctAnswer);
                }
                await CW("Dowolny klawisz aby kontynuowac....");
                await RK();
            }
            await TryClear(stream);
            await CW("Twoj wynik: " + wynik + "/" + quiz.Questions.Length);
            await CW("Dowolny klawisz aby kontynuowac....");
            await RK();
        }

        protected async Task DoQuiz(NetworkStream stream) {
            bool twrd = false;
            await TryClear(stream);
            // await WriteBar(stream);
            //await WriteASCII(stream, "Tryb T W A R D O R D Z E N wcisnij \"T\", klasyczny wcisnij inny dowolny klawisz.\r\n");
            await WriteASCII(stream, "Tryb trudny wcisnij \"T\", klasyczny wcisnij inny dowolny klawisz.\r\n");
            //await WriteBar(stream);
            twrd = char.ToUpper(await ReadChar(stream)) == 'T';
            Flush(stream);
            Stream s = File.OpenRead("quiz.jp2");
            TextReader tr = new StreamReader(s);
            int wynik = 0;
            bool twrdis = false;
            int twr = 1;
            string qti = tr.ReadLine();
            string qau = tr.ReadLine();
            #region process
            for (int i = 0; i < 5; ++i) {
                await TryClear(stream);
                await WriteASCII(stream, (i == 0 ? "Tytul quizu: " : "") + qti + "\r\n");
                if (i == 0)
                    await WriteASCII(stream, "Autor quizu: " + qau + "\r\n");
                //await WriteBar(stream);
                await WriteASCII(stream, "\r\n\r\n");
                await WriteASCII(stream, (i + 1) + ". " + tr.ReadLine() + "\r\n\r\n");
                await WriteASCII(stream, "a) " + tr.ReadLine() + "\r\n");
                await WriteASCII(stream, "b) " + tr.ReadLine() + "\r\n");
                await WriteASCII(stream, "c) " + tr.ReadLine() + "\r\n");
                await WriteASCII(stream, "d) " + tr.ReadLine() + "\r\n");
                char op = tr.ReadLine()[0];
                await WriteASCII(stream, "\r\n? ");
                char rc = await ReadChar(stream, true);
                await WriteASCII(stream, "\r\n");
                if (char.ToUpper(op) == Char.ToUpper(rc)) {
                    await WriteASCII(stream, "Dobrze! Zdobywasz punkt!\r\n");

                    wynik++;
                } else {
                    if (twrd) {
                        if (!twrdis) {
                            //await WriteASCII(stream, "Tryb T W A R D O R D Z E N oznacza, ze niepoprawne odpowiedzi dodatkowo usuwaja twoje punkty. Kazda kolejna niepoprawna odpowiedz usuwa dwa razy wiecej punktow od poprzedniej, na przyklad pierwsza usuwa 1, druga 2 a trzecia 4.\r\n");
                            await WriteASCII(stream, "Tryb trudny: kazda kolejna niepoprawna odpowiedz usuwa dwa razy wiecej punktow od poprzedniej.\r\n");
                            twrdis = true;
                        }
                        await WriteASCII(stream, "Zle! Tracisz " + (twr == 1 ? "" : twr + "") + "punkt" + (
                            twr == 1 ? "" :
                            twr <= 5 ? "y"
                            : "ow" // max 31, 32 znowu sie odmienia na "-y" ale akurat -31 to max
                            ) + ".\r\n");
                        wynik -= twr;
                        twr <<= 1;
                    } else {
                        await WriteASCII(stream, "Zle! Poprawna odpowiedz to: " + op + "\r\n");
                    }
                }
                Flush(stream);
                if (wynik < 0) await WriteASCII(stream, "Uwaga: Twoj wynik jest ujemny! Masz [" + wynik + "] punktow.\r\n\r\n");
                await WriteASCII(stream, "Nacisnij dowolny klawisz aby kontynuowac....");
                await ReadChar(stream, true);
                Flush(stream);
            }
#endregion
            await TryClear(stream);
            await WriteASCII(stream, "Twoj wynik: " + wynik + "/5\r\n\r\nUdostepnic?\r\n- Jezeli tak, wpisz nick (max 30 znakow) i wcisnij enter.\r\n- Jezeli nie, nic nie wpisuj i wcisnij enter.\r\n\r\n");
            string nic = await Prompt(stream, "? ", new ReadConfiguration() { MaxLength = 30, Whitelist = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-_", Echo = true, Terminator = '\n' });
            if (!string.IsNullOrWhiteSpace(nic)) {
                File.AppendAllText("Hiscore.txt", nic + ":" + wynik + "\n");
            }
            Tuple<string, int>[] hs = Array.ConvertAll(File.ReadAllLines("Hiscore.txt"), (x) => {
                string nm = x.Split(':')[0];
                string trz = x.Split(':')[1];
                int ioi;
                if (!int.TryParse(trz, out ioi)) {
                    ioi = -123456789;
                }
                return new Tuple<string, int>(nm, ioi);
            });
            StringBuilder sb = new StringBuilder();
            hs = hs.OrderBy(x => -x.Item2).ToArray();
            bool ht = false;
            const int MAX_HISCORE = 15;
            for (int i = 0; i < (hs.Length < MAX_HISCORE? hs.Length : MAX_HISCORE); i++) {
                var item = hs[i];
                bool tho = item.Item1 == nic;
                ht |= tho;
                sb.Append(((i + 1) + ". " +  (tho? "-> " : "") + item.Item1).PadRight(38) + "  " + item.Item2 + "\r\n");
            }
            if (!ht && !string.IsNullOrWhiteSpace(nic)) {
                int ord = 0;
                for (int i = 0; i < hs.Length; i++) {
                    if (hs[i].Item1 == nic) { ord = i; break; }
                }
                sb.Append((ord + ". -> " + nic).PadRight(38) + "  " + wynik + "\r\n");
            }
            await TryClear(stream);
            //await WriteBar(stream);
            await WriteASCII(stream, "============= WYSOKI WYNIK: =============\r\n" + sb.ToString() + "\r\n");
            //await WriteBar(stream);
            await WriteASCII(stream, "\r\n");
            await WriteASCII(stream, "Nacisnij klawisz aby sie rozlaczyc.");
            await ReadChar(stream, true);
            Flush(stream);
        }
        
        }
}
 