using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using DotNext;
using DotNext.Threading;
using Newtonsoft.Json;
using WebProtocol;

/// <summary> UDP接受数据包格式 </summary>
public class UDPData
{
    /// <summary>
    /// 接受到的时间
    /// </summary>
    public required DateTime DateTime { get; set; }
    /// <summary>
    /// 发送来源的IP地址
    /// </summary>
    public required string Address { get; set; }
    /// <summary>
    /// 发送来源的端口号
    /// </summary>
    public required int Port { get; set; }
    /// <summary>
    /// 接受到的数据
    /// </summary>
    public required byte[] Data { get; set; }
    /// <summary>
    /// 是否被读取过
    /// </summary>
    public required bool HasRead { get; set; }

    /// <summary>
    /// 深度拷贝对象
    /// </summary>
    /// <returns>UDPData</returns>
    public UDPData DeepClone()
    {
        var cloneData = new byte[this.Data.Length];
        Buffer.BlockCopy(this.Data, 0, cloneData, 0, this.Data.Length);
        return new UDPData()
        {
            DateTime = this.DateTime,
            Address = new string(this.Address),
            Port = this.Port,
            Data = cloneData,
            HasRead = this.HasRead
        };
    }

    /// <summary>
    /// 将UDP Data 转化为Json 格式字符串
    /// </summary>
    /// <returns>json字符串</returns>
    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

/// <summary>
/// UDP 服务器
/// </summary>
public class UDPServer
{
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    private static Dictionary<string, Queue<UDPData>> udpData = new Dictionary<string, Queue<UDPData>>();

    private int listenPort;
    private UdpClient listener;
    private IPEndPoint groupEP;

    private bool isRunning = false;
    /// <summary>
    /// 是否正在工作
    /// </summary>
    public bool IsRunning { get { return isRunning; } }

    /// <summary> UDP 服务器的错误代码 </summary>
    public enum ErrorCode
    {
        /// <summary> [TODO:description] </summary>
        Success = 0,
        /// <summary> [TODO:description] </summary>
        GetNoneAfterTimeout,
        /// <summary> [TODO:description] </summary>
        ResponseWrong,
        /// <summary> [TODO:description] </summary>
        NotRecvDataPackage,
    }

    /// <summary>
    /// Construct a udp server with fixed port
    /// </summary>
    /// <param name="port"> Device UDP Port </param>
    /// <returns> UDPServer class </returns>
    public UDPServer(int port)
    {
        // Construction
        listenPort = port;
        try
        {
            listener = new UdpClient(listenPort);
            groupEP = new IPEndPoint(IPAddress.Any, listenPort);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
            throw new ArgumentException(
                $"Failed to set up server with this port: {port}",
                nameof(port)
                );
        }
    }

    /// <summary>
    /// 异步寻找目标发送的内容
    /// </summary>
    /// <param name="ipAddr"> 目标IP地址 </param>
    /// <param name="timeout">超时时间</param>
    /// <param name="cycle">延迟时间</param>
    /// <param name="callerName">调用函数名称</param>
    /// <param name="callerLineNum">调用函数位置</param>
    /// <returns>
    /// 异步Optional 数据包：
    /// Optional 为空时，表明找不到数据；
    /// Optional 存在时，为最先收到的数据
    /// </returns>
    public async ValueTask<Optional<UDPData>> FindDataAsync(
         string ipAddr, int timeout = 1000, int cycle = 0,
         [CallerMemberName] string callerName = "",
         [CallerLineNumber] int callerLineNum = 0)
    {
        UDPData? data = null;

        logger.Debug($"Caller \"{callerName}|{callerLineNum}\": Try to find {ipAddr} UDP Data");

        var startTime = DateTime.Now;
        var isTimeout = false;
        var timeleft = TimeSpan.FromMilliseconds(timeout);
        while (!isTimeout)
        {
            var elapsed = DateTime.Now - startTime;
            isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
            if (isTimeout) break;

            timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
            using (await udpData.AcquireWriteLockAsync(timeleft))
            {
                if (udpData.ContainsKey(ipAddr) &&
                    udpData.TryGetValue(ipAddr, out var dataQueue) &&
                    dataQueue.Count > 0)
                {
                    data = dataQueue.Dequeue();
                    logger.Debug($"Find UDP Data: {data.ToString()}");
                    break;
                }
            }

            await Task.Delay(cycle);
        }

        if (data is null)
        {
            logger.Trace("Get nothing even after time out");
            return new(null);
        }
        else
        {
            return new(data.DeepClone());
        }
    }

    /// <summary>
    /// 获取还未被读取的数据列表
    /// </summary>
    /// <param name="ipAddr">IP地址</param>
    /// <param name="timeout">超时时间</param>
    /// <param name="cycle">延迟时间</param>
    /// <returns>数据列表</returns>
    public async ValueTask<Optional<List<UDPData>>> GetDataArrayAsync(string ipAddr, int timeout = 1000, int cycle = 0)
    {
        List<UDPData>? data = null;

        var startTime = DateTime.Now;
        var isTimeout = false;
        var timeleft = TimeSpan.FromMilliseconds(timeout);
        while (!isTimeout)
        {
            var elapsed = DateTime.Now - startTime;
            isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
            if (isTimeout) break;

            timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
            using (await udpData.AcquireReadLockAsync(timeleft))
            {
                if (udpData.ContainsKey(ipAddr) &&
                    udpData.TryGetValue(ipAddr, out var dataQueue) &&
                    dataQueue.Count > 0)
                {
                    data = dataQueue.ToList();
                    logger.Debug($"Find UDP Data Array: {JsonConvert.SerializeObject(data)}");
                    break;
                }
            }
        }

        if (data is null)
        {
            logger.Trace("Get nothing even after time out");
            return new(null);
        }
        else
        {
            return new(data);
        }
    }

    /// <summary>
    /// 异步等待写响应
    /// </summary>
    /// <param name="address">IP地址</param>
    /// <param name="port">UDP 端口</param>
    /// <param name="timeout">超时时间范围</param>
    /// <returns>接收响应包</returns>
    public async ValueTask<Result<WebProtocol.RecvRespPackage>> WaitForAckAsync
        (string address, int port = -1, int timeout = 1000)
    {
        var data = await FindDataAsync(address, timeout);
        if (!data.HasValue)
            return new(new Exception("Get None even after time out!"));

        var recvData = data.Value;
        if (recvData.Address != address || (port > 0 && recvData.Port != port))
            return new(new Exception("Receive Data From Wrong Board!"));

        var retPack = WebProtocol.RecvRespPackage.FromBytes(recvData.Data);
        if (!retPack.IsSuccessful)
            return new(new Exception("Not RecvDataPackage!", retPack.Error));

        return retPack.Value;
    }

    /// <summary>
    /// 异步等待数据
    /// </summary>
    /// <param name="address">IP地址</param>
    /// <param name="port">UDP 端口</param>
    /// <param name="timeout">超时时间范围</param>
    /// <returns>接收数据包</returns>
    public async ValueTask<Result<RecvDataPackage>> WaitForDataAsync
        (string address, int port = -1, int timeout = 1000)
    {
        var data = await FindDataAsync(address, timeout);
        if (!data.HasValue)
            return new(new Exception("Get None even after time out!"));

        var recvData = data.Value;
        if (recvData.Address != address || (port >= 0 && recvData.Port != port))
            return new(new Exception("Receive Data From Wrong Board!"));

        var retPack = WebProtocol.RecvDataPackage.FromBytes(recvData.Data);
        if (!retPack.IsSuccessful)
            return new(new Exception("Not RecvDataPackage!", retPack.Error));

        return retPack.Value;
    }

    private void ReceiveHandler(IAsyncResult res)
    {
        logger.Trace("Enter handler");
        var remoteEP = new IPEndPoint(IPAddress.Any, listenPort);
        byte[] bytes = listener.EndReceive(res, ref remoteEP);

        // Handle RemoteEP
        if (remoteEP is null)
        {
            logger.Debug($"Receive Data from Unknown at {DateTime.Now.ToString()}:");
            logger.Debug($"  Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}");
            goto BEGIN_RECEIVE;
        }


        // Handle Package
        var udpData = RecordUDPData(bytes, remoteEP);
        PrintData(udpData);

    BEGIN_RECEIVE:
        listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
    }

    private bool SendBytes(IPEndPoint endPoint, byte[] buf)
    {
        var sendLen = listener.Send(buf, endPoint);

        if (sendLen == buf.Length) { return true; }
        else { return false; }
    }

    private bool SendString(IPEndPoint endPoint, string text)
    {
        byte[] buf = Encoding.ASCII.GetBytes(text);
        var sendLen = listener.Send(buf, endPoint);

        if (sendLen == buf.Length) { return true; }
        else { return false; }
    }

    private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP)
    {
        var remoteAddress = remoteEP.Address.ToString();
        var remotePort = remoteEP.Port;
        var data = new UDPData()
        {
            Address = remoteAddress,
            Port = remotePort,
            Data = bytes,
            DateTime = DateTime.Now,
            HasRead = false,
        };

        using (udpData.AcquireWriteLock())
        {
            // Record UDP Receive Data
            if (udpData.ContainsKey(remoteAddress) && udpData.TryGetValue(remoteAddress, out var dataQueue))
            {
                dataQueue.Enqueue(data);
                logger.Trace("Receive data from old client");
            }
            else
            {
                var queue = new Queue<UDPData>();
                queue.Enqueue(data);
                udpData.Add(remoteAddress, queue);
                logger.Trace("Receive data from new client");
            }
        }

        return data;
    }

    /// <summary>
    /// 输出UDP Data到log中
    /// </summary>
    /// <param name="data">UDP数据</param>
    public void PrintData(UDPData data)
    {
        var bytes = data.Data;
        var sign = bytes[0];
        string recvData = "";
        if (sign == (byte)WebProtocol.PackSign.SendAddr)
        {
            var resData = WebProtocol.SendAddrPackage.FromBytes(bytes);
            if (resData.IsSuccessful)
                recvData = resData.Value.ToString();
            else
                recvData = resData.Error.ToString();
        }
        else if (sign == (byte)WebProtocol.PackSign.SendData) { }
        else if (sign == (byte)WebProtocol.PackSign.RecvData)
        {
            var resData = WebProtocol.RecvDataPackage.FromBytes(bytes);
            if (resData.IsSuccessful)
                recvData = resData.Value.Options.ToString();
            else
                recvData = resData.Error.ToString();
        }
        else if (sign == (byte)WebProtocol.PackSign.RecvResp)
        {
            var resData = WebProtocol.RecvRespPackage.FromBytes(bytes);
            if (resData.IsSuccessful)
                recvData = resData.Value.Options.ToString();
            else
                recvData = resData.Error.ToString();
        }
        else
        {
            recvData = Encoding.ASCII.GetString(bytes, 0, bytes.Length);
        }

        logger.Debug($"Receive Data from {data.Address}:{data.Port} at {data.DateTime.ToString()}:");
        logger.Debug($"  Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}");
        if (recvData.Length != 0) logger.Debug($"  Decoded Data  : {recvData}");
    }

    /// <summary>
    /// 将所有数据输出到log中
    /// </summary>
    /// <returns> void </returns>
    public void PrintAllData()
    {
        using (udpData.AcquireReadLock())
        {
            logger.Debug("Ready Data:");

            foreach (var ip in udpData)
            {
                foreach (var data in ip.Value)
                {
                    logger.Debug(data.ToString());
                }
            }
        }
    }

    /// <summary>
    /// 清空指定IP地址的数据
    /// </summary>
    /// <param name="ipAddr">IP地址</param>
    /// <returns>无</returns>
    public async Task ClearUDPData(string ipAddr)
    {
        using (await udpData.AcquireWriteLockAsync())
        {
            if (udpData.ContainsKey(ipAddr) &&
                udpData.TryGetValue(ipAddr, out var dataQueue) &&
                dataQueue.Count > 0)
            {
                dataQueue.Clear();
            }
        }
    }


    /// <summary>
    /// Start UDP Server
    /// </summary>
    /// <returns>None</returns>
    public void Start()
    {
        try
        {
            this.listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
        finally
        {
            this.isRunning = true;
        }
    }

    /// <summary>
    /// Close UDP Server
    /// </summary>
    /// <returns>None</returns>
    public void Stop()
    {
        this.listener.Close();
        this.isRunning = false;
    }
}

