using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TELTest {
    public abstract class ConnectionHandler {
        public static List<ConnectionHandler> AllHandlers { get; } = new List<ConnectionHandler>();
        public ConnectionHandler() {
            ID = AllHandlers.Count;
            AllHandlers.Add(this);
        }
        public int ID { get; protected set; }
        public abstract Task HandleMe(NetworkStream stream);
        public async Task Handle(NetworkStream stream) {
            await Task.Delay(500); // PBX flashbacks
            await WriteASCII(stream, "Processing.\r\n");
            await Task.Delay(100);
            Flush(stream);
            //await ReadTimeout(stream, 100); // Catch loose buffered chars
            await TryClear(stream);
            await WriteASCII(stream, "Hello!\r\n");
            await WriteASCII(stream, (ID + 1) + " concurrent connections right now.\r\n");
            await WriteASCII(stream, $"Server time is {DateTime.Now}\r\n");

            await HandleMe(stream);
            await WriteASCII(stream, "\r\n\r\nBye!\r\n");
            AllHandlers.Remove(this);
        }
        public void Flush(NetworkStream stream) {
            while (stream.DataAvailable) stream.ReadByte();
        }
        public int ReadOneIfAvailable(NetworkStream stream) {
            if (!stream.DataAvailable) return -1;
            return stream.ReadByte();
        }
        public async Task TryClear(NetworkStream stream) {
            await WriteASCII(stream, "\x1b[2J\x1b[H");
        }
        public async Task<char> Choice(NetworkStream stream, char[] choice, string caption = null,
            string prompt = null, char? def = null, bool echo = true, bool noNewLine = false,
            bool caseSensitive = false, CancellationToken? token = null) {
            if (choice.Length <= 0) throw new ArgumentOutOfRangeException(nameof(choice));
            
            if (prompt == null) {
                StringBuilder sb = new StringBuilder();
                sb.Append('[');
                for (int i = 0; i < choice.Length; i++) {
                    var item = choice[i];
                    string cs = (item + (i == choice.Length - 1 ? "" : "/"));
                    cs = caseSensitive ? cs : cs.ToUpper();
                    sb.Append(cs);
                }
                sb.Append(']');
                if (def != null) {
                    string cs = " (" + def.Value + ")";
                    cs = caseSensitive ? cs : cs.ToUpper();
                    sb.Append(cs);
                }
                sb.Append(" > ");
                prompt = sb.ToString();
            }
            char selection = '\0';
            while (true) {
                if (caption != null)
                    await WriteASCII(stream, "\r\n" + caption + "\r\n");
                

                await WriteASCII(stream, prompt);
                char c = await ReadChar(stream,echo);
                if (!noNewLine) await WriteASCII(stream, "\r\n");

                for (int i = 0; i < choice.Length; i++) {
                    if (caseSensitive) {
                        if (choice[i] == c) {
                            selection = c;
                            goto ChoiceDone;
                        }
                    } else {
                        if (Char.ToUpper(choice[i]) == Char.ToUpper(c)) {
                            selection = choice[i];
                            goto ChoiceDone;
                        }
                    }
                }
                if (def != null) {
                    if (c == '\r' || c == '\n') {
                        selection = def.Value;
                        //while (stream.DataAvailable) stream.ReadByte();
                        goto ChoiceDone;
                    }
                }
                Flush(stream);
            }
        ChoiceDone:;
            Flush(stream);
            return selection;
        }
        public async Task<string> Prompt(NetworkStream stream, string prompt, ReadConfiguration? config = null, Nullable<CancellationToken> token = null) {
            await WriteASCII(stream, prompt);
            if (config == null) config = ReadConfiguration.Default;
            string ret = await Read(stream, config.Value, token);
            await WriteASCII(stream, "\r\n");
            return ret;
        }
        public async Task<string> Read(NetworkStream stream, char terminator = '\r', bool echo = true, Nullable<CancellationToken> token = null) {
            ReadConfiguration rc = ReadConfiguration.Default;
            rc.Terminator = terminator;
            rc.Echo = echo;
            return await Read(stream, rc, token);
        }
        public async Task<string> Read(NetworkStream stream, ReadConfiguration config, Nullable<CancellationToken> token = null) {
            byte[] buf = new byte[512];
            int i = 0;
            int con = 0;
            while (true) {
                if (token == null)
                    await stream.ReadAsync(buf, i, 1);
                else
                    await stream.ReadAsync(buf, i, 1, token.Value);
                if (buf[i] == 0x7F) {
                    buf[i] = (byte)'\b';
                    await WriteASCII(stream, "\b");
                }

                if (buf[i] == (byte)config.Terminator) { i++;  break; }


                if (buf[i] == (byte)'\b') {
                    i--;
                    con--;
                    i = i < 0 ? 0 : i;
                    

                    if (con >= 0) {
                        await WriteASCII(stream, " \b");
                    } else if (con < 0) {
                        await WriteASCII(stream, " ");
                        con++;
                    }
                    continue;
                } else {
                    i++;
                    
                    if (!config.Echo) {
                        await WriteASCII(stream, "\b \b");
                    } else {
                        con++;
                    }
                }
                char cb = (char)buf[i - 1];
                if (config.Whitelist != null && config.Whitelist.IndexOf(cb) == -1 && (cb != '\b' && cb != '\n' && cb != '\r')) {
                    //Console.WriteLine("Forbidden char - \"" + (char)buf[i] + "\"");
                    await WriteASCII(stream, "\b \b");
                    i--;
                    con--;
                    continue;
                }
                if (config.Echo && config.EchoPasswordChars) {
                    await WriteASCII(stream, "\b" + config.PasswordChar);
                }
                if (config.MaxLength + 1 == i) {
                    i--;
                    con--;
                    await WriteASCII(stream, "\b \b");
                }
                //Console.WriteLine(buf[i > 0 ? i - 1 : 0].ToString("X") + ", " + i + " curs " + con);
            }
            string ret = System.Text.Encoding.ASCII.GetString(buf, 0, i - 1);
            if (config.FlushAfterReading) Flush(stream);
            return ret.Trim();
        }
        protected int Columns { get; set; } = 120;
        public String PlayerName { get; set; }
        public bool PlayerReady { get; set; }
        public object SharedTempUserObject { get; set; }

        public async Task<string> ReadTimeout(NetworkStream stream, int timeout, string def = null, char terminator = '\n') {
            CancellationTokenSource cts = new CancellationTokenSource();

            Task<string> ts = Read(stream, terminator, true, cts.Token);
            await Task.WhenAny(ts, Task.Delay(timeout));
            cts.Cancel();
            cts.Dispose();
            return ts.IsCompleted ? ts.Result : def;
        }
        public async Task<char> ReadChar(NetworkStream stream, bool echo = true) {
            byte[] buf = new byte[512];
            await stream.ReadAsync(buf, 0, 1);
            if (!echo && buf[0] != 8) await WriteASCII(stream, "\b \b");
            //string ret = System.Text.Encoding.ASCII.GetString(buf, 0, i - 1);
            return (char)buf[0];
        }
        public async Task WriteBar(NetworkStream stream, char chr = '=') {
            await WriteASCII(stream, new string(chr, Columns) + "\r\n");
        }
        public async Task WriteASCII(NetworkStream stream, string text) {
            var bytes = Encoding.ASCII.GetBytes(text);
            await stream.WriteAsync(bytes, 0, bytes.Length);
        }
    }
}
