using System.Threading.Tasks;

namespace TLVProtocal;

internal class TlvCommunicatorClient : BaseFilter, ITlvCommunicatorClient
{
    private ITcpClient _tcpClient;
    private ITlvProvider _provider;

    private MessageQueue _messageQueue;
    private MessageReceiveQueue _receiveQueue;

    private TlvAckLayer _ackLayer;
    private TlvPackLayer _packLayer;
    private TlvContentLayer _contentLayer;

    private RawDataLog _dataLogLayer;

    private ushort _requestID = 0;
    private int _timeoutTimer = 0;
    private readonly ConcurrentDictionary<ushort, SyncMessage> _requests = [];

    bool ITlvCommunicatorClient.Initialize(ITlvProvider provider, bool ConvertToBigEnd, int timeoutTime)
    {
        if (this._tcpClient is not null)
            return false;

        this._tcpClient = IOFactory.GetTcpClient();
        this._provider = provider;
        this._timeoutTimer = timeoutTime;

        this._ackLayer = new(ConvertToBigEnd, timeoutTime);
        this._packLayer = new(ConvertToBigEnd);
        this._contentLayer = new(ConvertToBigEnd);

        this._messageQueue = new();
        this._messageQueue.Initialize();

        this._receiveQueue = new();
        this._receiveQueue.Initialize();

        this._dataLogLayer = new();
        string filePath = Path.Combine(Environment.CurrentDirectory, "Log", "TlvCommunicatorClient.log");
        this._dataLogLayer.Initialize(filePath);

        _ = this >> this._contentLayer >> this._receiveQueue >> this._ackLayer >> this._packLayer >> this._messageQueue >>  (BaseFilter)this._tcpClient;
        //this._dataLogLayer >>
        return true;
    }


    bool ITlvCommunicatorClient.Open(string ipAddress, ushort port)
    {
        if (this._tcpClient is null)
            return false;

        this._tcpClient.Initiallize($"TlvCommunicatorClient {ipAddress}:{port}");
        return this._tcpClient.Open(ipAddress, port, out _);
    }

    bool ITlvCommunicatorClient.SendNotify(TlvData tlvData)
    {
        return base.Send(tlvData);
    }

    bool ITlvCommunicatorClient.SendRequest(TlvData request, out TlvData reply)
    {
        reply = null;
        SyncMessage syncMessage = new();

        lock (_requests)
        {
            do request.RequestID = this.GetNextID();
            while (!this._requests.TryAdd(request.RequestID.Value, syncMessage));
        }

        Task.Factory.StartNew(() =>
        {
            if (!base.Send(request))
            {
                syncMessage.WaitEvent.Set();
                this._requests.TryRemove(request.RequestID.Value, out _);
            }
        });

        if (!syncMessage.WaitEvent.WaitOne(this._timeoutTimer))
        {
            this._requests.TryRemove(request.RequestID.Value, out _);
            return false;
        }

        if (!this._requests.TryRemove(request.RequestID.Value, out _))
            return false;

        reply = syncMessage.TlvData;

        return true;
    }

    public override bool Receive(Data data)
    {
        if (data is not TlvData tlv)
            return false;

        if (tlv.RequestID is null)
        {
            this._provider?.Received(tlv);
            return true;
        }

        if (!this._requests.TryGetValue(tlv.RequestID.Value, out SyncMessage syncMessage))
            return false;

        syncMessage.TlvData = tlv;
        syncMessage.WaitEvent.Set();
        return true;
    }

    public override bool Send(Data data)
    {
        return false;
    }

    public override void Connected(Connection connection)
    {
        if (connection is not TcpConnection tcpConnection)
            return;

        this._provider?.Connected(tcpConnection);
    }

    public override void Disconnected(Connection connection)
    {
        if (connection is not TcpConnection tcpConnection)
            return;

        this._provider?.Disconnected(tcpConnection);
    }

    private ushort GetNextID()
    {
        if (this._requestID == ushort.MaxValue)
            return 0;

        return ++_requestID;
    }

    void IDisposable.Dispose()
    {
        this._tcpClient?.Close();
        this._tcpClient = null;

        ((IDisposable)this._messageQueue).Dispose();
        this._messageQueue = null;
        this._packLayer = null;
        this._contentLayer = null;
        this._ackLayer = null;
    }


}

internal class SyncMessage
{
    public TlvData TlvData { get; set; }
    public readonly AutoResetEvent WaitEvent = new(false);
}