namespace Universal.IO;
internal class IOTcpClient : BaseFilter, ITcpClient
{

    #region Internal Variables
    public string? Name { get; internal set; }

    bool _bInitialize = false;
    volatile bool _bOpen = false;
    System.Net.Sockets.TcpClient? _tcpClient = null;
    Task? _readTask = null;
    TcpConnection? _tcpConnection = null;
    #endregion

    bool ITcpClient.Initiallize(string name)
    {
        lock (this)
        {
            if (_bInitialize)
                return false;

            this._bInitialize = true;
            this.Name = name;
            return true;
        }
    }

    bool ITcpClient.Open(string remoteHost, ushort remotePort, out TcpConnection? tcpConnection, string? localHost, ushort localPort, bool noDelay, int timeout, int receiveBufferSize)
    {
        tcpConnection = null;

        lock (this)
        {
            if (!_bInitialize)
                return false;

            if (_bOpen)
                return false;


            if (!IPAddress.TryParse(remoteHost, out IPAddress? remoteIPAddress) || remoteIPAddress is null)
                return false;

            AutoResetEvent? allowOpenEvent = new(false);
            bool result = false;
            try
            {
                _tcpClient = new() { NoDelay = noDelay };

                if (string.IsNullOrEmpty(localHost) || !IPAddress.TryParse(localHost, out IPAddress? localIP) || localIP is null)
                {
                    _tcpClient = new() { NoDelay = noDelay };
                }
                else
                {
                    IPEndPoint localEP = new(localIP, localPort);
                    _tcpClient = new(localEP) { NoDelay = noDelay };
                }


                using CancellationTokenSource cancellationTokenSource = new();
                Task valueTask = _tcpClient.ConnectAsync(remoteIPAddress, remotePort);

                if (!valueTask.Wait(timeout))
                {
                    cancellationTokenSource.Cancel();
                    return false;
                }

                TcpConnection? connection = default;

                _readTask = Task.Factory.StartNew(async () =>
                {
                    try
                    {
                        if (_tcpClient.Client.LocalEndPoint is not IPEndPoint || _tcpClient.Client.RemoteEndPoint is not IPEndPoint)
                            return;

                        connection = new((IPEndPoint)_tcpClient.Client.LocalEndPoint, (IPEndPoint)_tcpClient.Client.RemoteEndPoint, null!);
                        _tcpConnection = connection;
                        NetworkStream networkStream = _tcpClient.GetStream();

                        Thread.Sleep(0);

                        try
                        {
                            _bOpen = true;
                            base.PrevConnector?.Connected(connection);
                            result = true;
                        }
                        finally { allowOpenEvent.Set(); }

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

                            if (bytesRead <= 0)
                                break;

                            byte[] cache = new byte[bytesRead];
                            Array.Copy(buffer, cache, bytesRead);

                            Data receivedData = new(cache, connection, receiveTime);

                            try { base.PrevReceiver?.Receive(receivedData); } catch { }
                        }

                        try { _tcpClient?.Close(); } catch { }
                        try { base.PrevConnector?.Disconnected(connection); } catch { }
                        _tcpClient = null;
                        _bOpen = false;
                    }
                    catch { }
                }, TaskCreationOptions.LongRunning);
            }
            catch
            {
                _tcpClient = null;
                return false;
            }

            allowOpenEvent.WaitOne();
            tcpConnection = _tcpConnection;

            if (result)
                return true;

            _tcpClient = null;
            return false;
        }
    }

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

            if (!_bOpen)
                return true;

            try { _tcpClient?.Close(); } catch { }
            _readTask?.Wait();
            _readTask = null;

            _bOpen = false;

            return true;
        }
    }


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

        if (!_bOpen)
            return false;

        if (_tcpClient?.Connected != true)
            return false;

        if (data.RawData is null || data.RawData.Length == 0)
            return false;

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

        return true;
    }

    void IDisposable.Dispose()
    {
        (this as ITcpClient).Close();
    }
}