namespace UniversalNetFrame451.IO;

internal class IOTcpServer : BaseFilter, ITcpServer
{
    #region Internal Variables
    public string Name { get; set; }
    private volatile bool _bInitialize = false;
    private volatile bool _bOpen = false;
    private volatile bool _bExit = false;
    private bool _noDelay = false;
    private int _receiveBufferSize = 0;
    private TcpListener _tcpListener = null;
    private readonly HashSet<TcpClient> _clients = [];
    #endregion

    bool ITcpServer.Initialize(string name)
    {
        lock (this)
        {
            if (_bInitialize)
                return false;

            Name = name;
            _bInitialize = true;

            return true;
        }
    }
    bool ITcpServer.Open(string ip, ushort port, bool noDelay, int receiveBufferSize)
    {
        lock (this)
        {
            if (!_bInitialize)
                return false;

            if (_bOpen)
                return false;

            _bExit = false;

            IPAddress address = IPAddress.Any;

            if (!string.IsNullOrWhiteSpace(ip))
                if (!IPAddress.TryParse(ip, out address))
                    return false;

            TcpListener tcpListener = null;
            try
            {
                tcpListener = new(address, port);
                
                tcpListener.Start();
            }
            catch
            {
                return false;
            }

            _noDelay = noDelay;
            _receiveBufferSize = receiveBufferSize;
            _tcpListener = tcpListener;

            TaskCompletionSource<bool> taskCompletionSource = new();
            Task.Factory.StartNew(() => WaitForConnection(taskCompletionSource), TaskCreationOptions.LongRunning);
            if (!taskCompletionSource.Task.Result)
                return false;

            _bOpen = true;
            return true;
        }
    }

    async Task WaitForConnection(TaskCompletionSource<bool> taskCompletionSource = null)
    {
        if (_bExit)
            return;

        Task<System.Net.Sockets.TcpClient> taskAcceptTcpClientAsync = default;
        try
        {
            taskAcceptTcpClientAsync = _tcpListener!.AcceptTcpClientAsync();
            taskCompletionSource.SetResult(true);
        }
        catch
        {
            try { taskCompletionSource.SetResult(false); } catch { }
        }

        try
        {
            System.Net.Sockets.TcpClient tcpClient = await taskAcceptTcpClientAsync!;
            tcpClient.NoDelay = _noDelay;
            tcpClient.SendTimeout = 200;
            tcpClient.ReceiveTimeout = 200;
            if (_bExit)
            {
                tcpClient.Close();
                return;
            }

            NetworkStream networkStream = tcpClient.GetStream();
            if (tcpClient.Client.LocalEndPoint is not IPEndPoint || tcpClient.Client.RemoteEndPoint is not IPEndPoint)
                return;

            TcpConnection connection = new(
                (IPEndPoint)tcpClient.Client.LocalEndPoint!,
                (IPEndPoint)tcpClient.Client.RemoteEndPoint!,
                tcpClient);

            lock (_clients)
            {

                _clients.Add(tcpClient);
            }

            _ = Task.Factory.StartNew(async () => { try { await WaitForConnection(); } catch (Exception) { } }, TaskCreationOptions.LongRunning);

            try
            {
                try { PrevConnector.Connected(connection); } catch { }

                for (byte[] buffer = new byte[_receiveBufferSize]; ;)
                {
                    int bytesRead = 0;
                    DateTime receiveTime;
                    try
                    {
                        bytesRead = await networkStream.ReadAsync(buffer, 0, _receiveBufferSize);
                        receiveTime = DateTime.Now;
                    }
                    catch
                    {
                        break;
                    }

                    if (bytesRead <= 0)
                    {
                        lock (_clients)
                            if (_clients.Contains(tcpClient))
                                _clients.Remove(tcpClient);
                        break;
                    }
                    byte[] cache = new byte[bytesRead];
                    Array.Copy(buffer, cache, bytesRead);


                    try { this.PrevReceiver.Receive(new(cache, connection, receiveTime)); } catch { }
                }

                try
                {
                    tcpClient.Close();
                    PrevConnector.Disconnected(connection);
                }
                catch { }
            }
            catch { }
        }
        catch { }
    }

    public override bool Send(Data data)
    {
        if (!_bInitialize)
            return false;

        if (!_bOpen)
            return false;

        if (data.Connection is not TcpConnection tcp)
            return false;

        if (tcp.TcpClient is null)
            return false;

        try
        {
            if (!tcp.TcpClient!.Connected)
                return false;
            if (data.RawData == null || data.RawData.Length == 0)
                return false;

            NetworkStream networkStream = tcp.TcpClient.GetStream();
            networkStream.Write([.. data.RawData], 0, data.RawData.Length);
            networkStream.Flush();
            data.DateTime = DateTime.Now;
        }
        catch
        {
            return false;
        }

        return true;
    }

    bool ITcpServer.Close()
    {
        lock (this)
        {
            if (!_bInitialize)
                return false;

            if (!_bOpen)
                return true;

            _bExit = true;

            try { _tcpListener.Stop(); } catch { }

            lock (_clients)
            {
                foreach (System.Net.Sockets.TcpClient tcpClient in _clients)
                    tcpClient.Close();
                _clients.Clear();
            }

            _bOpen = false;

            return true;
        }
    }

    public void Dispose()
    {
        (this as ITcpServer).Close();
    }
}
