using System.Net.Sockets;
using System.Net;
using System.Text;

namespace Tests.UnitTests.WeighingTests {
    public abstract class MockScale : IDisposable {

        protected readonly TcpListener Server;

        public int IdentNr { get; set; } = 0;
        public int Tare { get; set; } = 0;
        public int Weight { get; set; } = 0;
        public string? Error { get; set; } = null;

        protected MockScale(int port) {
            Server = new TcpListener(IPAddress.Loopback, port);
            Server.Start(4);
        }

        public void Dispose() {
            Server.Dispose();
            GC.SuppressFinalize(this);
        }
    }

    public class CommandMockScale : MockScale {

        private readonly Func<string, int, int, string?, int, (string, bool)> Handler;
        private readonly Thread ServerThread;
        private bool IsRunning = true;

        public CommandMockScale(int port, Func<string, int, int, string?, int, (string, bool)> handler) :
            base(port) {
            Handler = handler;
            ServerThread = new Thread(new ParameterizedThreadStart(Serve));
            ServerThread.Start();
        }

        private async void Serve(object? parameters) {
            byte[] buffer = new byte[16];
            while (IsRunning) {
                try {
                    using var client = await Server.AcceptTcpClientAsync();
                    if (client == null) continue;
                    using var stream = client.GetStream();
                    while (true) {
                        int read = await stream.ReadAsync(buffer);
                        var (res, inc) = Handler(Encoding.ASCII.GetString(buffer, 0, read), Weight, Tare, Error, IdentNr + 1);
                        if (inc) IdentNr++;
                        await stream.WriteAsync(Encoding.ASCII.GetBytes(res));
                    }
                } catch (Exception) {
                    // ignore
                }
            }
        }

        public new void Dispose() {
            IsRunning = false;
            ServerThread.Interrupt();
            ServerThread.Join();
            base.Dispose();
        }
    }

    public class EventMockScale : MockScale {

        private readonly Func<int, int, string?, int, (string, bool)> Handler;
        private readonly Thread ServerThread;
        private TcpClient? Client;
        private bool IsRunning = true;

        public EventMockScale(int port, Func<int, int, string?, int, (string, bool)> handler) :
            base(port) {
            Handler = handler;
            ServerThread = new Thread(new ParameterizedThreadStart(Serve));
            ServerThread.Start();
        }

        private async void Serve(object? parameters) {
            while (IsRunning) {
                try {
                    Client = await Server.AcceptTcpClientAsync();
                } catch (Exception) {
                    // ignore
                }
            }
        }

        public async Task Weigh(int weight, int tare = 0) {
            Weight = weight;
            Tare = tare;
            await Weigh();
        }

        public async Task Weigh() {
            var (res, inc) = Handler(Weight, Tare, Error, IdentNr + 1);
            if (inc) IdentNr++;
            await Client!.GetStream().WriteAsync(Encoding.ASCII.GetBytes(res));
        }

        public new void Dispose() {
            Client?.Dispose();
            IsRunning = false;
            ServerThread.Interrupt();
            ServerThread.Join();
            base.Dispose();
        }
    }
}
