using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace MegaRobo.C00225155.Entities;

public sealed class HttpListenerEx : IDisposable
{
    private Task _listener;

    private readonly TcpListenerAdapter _tcpListener;

    private CancellationTokenSource _cts;

    private bool _disposedValue;

    private bool _isListening;

    public bool IsListening => _isListening;

    public Socket Socket => _tcpListener.Socket;

    public IPEndPoint LocalEndpoint { get; }

    public event EventHandler<HttpListenerRequestEventArgs> Request;

    private HttpListener()
    {
        _cts = null;
    }

    public HttpListener(IPAddress address, int port)
        : this()
    {
        LocalEndpoint = new IPEndPoint(address, port);
        _tcpListener = new TcpListenerAdapter(LocalEndpoint);
    }

    public HttpListener(IPEndPoint endpoint)
        : this()
    {
        _tcpListener = new TcpListenerAdapter(endpoint);
    }

    public void Start()
    {
        if (_disposedValue)
        {
            throw new ObjectDisposedException("Object has been disposed.");
        }

        if (_cts != null)
        {
            throw new InvalidOperationException("HttpListener is already running.");
        }

        _cts = new CancellationTokenSource();
        _isListening = true;
        _listener = Task.Run((Func<Task?>)StartListenerAsync, _cts.Token);
    }

    private async Task StartListenerAsync()
    {
        try
        {
            _tcpListener.Start();
            while (_isListening)
            {
                TcpClientAdapter client = await _tcpListener.AcceptTcpClientAsync().ConfigureAwait(continueOnCapturedContext: false);
                HttpListenerRequest request = new HttpListenerRequest(client);
                Task.Run(async delegate
                {
                    HttpListenerResponse response = new HttpListenerResponse(request, client);
                    try
                    {
                        await request.ProcessAsync();
                        response.Initialize();
                        if (this.Request == null)
                        {
                            response.NotFound();
                            response.Close();
                        }
                        else
                        {
                            this.Request(this, new HttpListenerRequestEventArgs(request, response));
                        }
                    }
                    catch (Exception)
                    {
                        response.CloseSocket();
                    }
                });
            }
        }
        finally
        {
            _isListening = false;
            _cts = null;
        }
    }

    public void Close()
    {
        if (_cts == null)
        {
            throw new InvalidOperationException("HttpListener is not running.");
        }

        this.Request = null;
        _cts.Cancel();
        _cts = null;
        _isListening = false;
        _tcpListener.Stop();
        try
        {
            _listener.Wait(TimeSpan.FromMilliseconds(1.0));
        }
        catch (Exception)
        {
        }
    }

    public Task<HttpListenerContext> GetContextAsync()
    {
        TaskCompletionSource<HttpListenerContext> tcs = new TaskCompletionSource<HttpListenerContext>();
        EventHandler<HttpListenerRequestEventArgs> requestHandler = null;
        requestHandler = delegate (object sender, HttpListenerRequestEventArgs evArgs)
        {
            HttpListenerContext result = new HttpListenerContext(evArgs.Request, evArgs.Response);
            tcs.SetResult(result);
            Request -= requestHandler;
        };
        Request += requestHandler;
        return tcs.Task;
    }

    private void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            Close();
            _disposedValue = true;
        }
    }

    public void Dispose()
    {
        Dispose(disposing: true);
    }
}

