using System.Net;
using System.Net.Sockets;
using System.Text;
using DotNext;
using WebProtocol;

/// <summary>
/// UDP客户端发送池
/// </summary>
public class UDPClientPool
{
    private static IPAddress localhost = IPAddress.Parse("127.0.0.1");

    /// <summary>
    /// 发送字符串
    /// </summary>
    /// <param name="endPoint">IP端点（IP地址与端口）</param>
    /// <param name="stringArray">字符串数组</param>
    /// <returns>是否成功</returns>
    public static bool SendString(IPEndPoint endPoint, string[] stringArray)
    {
        var isSuccessful = true;
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        foreach (var str in stringArray)
        {
            byte[] sendbuf = Encoding.ASCII.GetBytes(str);
            var sendLen = socket.SendTo(sendbuf, endPoint);
            if (str.Length != sendLen) isSuccessful = false;
        }

        socket.Close();

        if (isSuccessful) { return true; }
        else { return false; }
    }

    /// <summary>
    /// 异步发送字符串
    /// </summary>
    /// <param name="endPoint">IP端点（IP地址与端口）</param>
    /// <param name="stringArray">字符串数组</param>
    /// <returns>是否成功</returns>
    public async static ValueTask<bool> SendStringAsync(IPEndPoint endPoint, string[] stringArray)
    {
        return await Task.Run(() => { return SendString(endPoint, stringArray); });
    }

    /// <summary>
    /// 发送二进制字节
    /// </summary>
    /// <param name="endPoint">IP端点（IP地址与端口）</param>
    /// <param name="buf">二进制字节</param>
    /// <returns>是否成功</returns>
    public static bool SendBytes(IPEndPoint endPoint, byte[] buf)
    {
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        var sendLen = socket.SendTo(buf, endPoint);
        socket.Close();

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

    /// <summary>
    /// 异步发送二进制字节
    /// </summary>
    /// <param name="endPoint">IP端点（IP地址与端口）</param>
    /// <param name="buf">二进制字节</param>
    /// <returns>是否成功</returns>
    public async static ValueTask<bool> SendBytesAsync(IPEndPoint endPoint, byte[] buf)
    {
        return await Task.Run(() => { return SendBytes(endPoint, buf); });
    }

    /// <summary>
    /// 发送地址包
    /// </summary>
    /// <param name="endPoint">IP端点（IP地址与端口）</param>
    /// <param name="pkg">地址包</param>
    /// <returns>是否成功</returns>
    public static bool SendAddrPack(IPEndPoint endPoint, WebProtocol.SendAddrPackage pkg)
    {
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        var sendBytes = pkg.ToBytes();
        var sendLen = socket.SendTo(sendBytes, endPoint);
        socket.Close();

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

    /// <summary>
    /// 异步发送地址包
    /// </summary>
    /// <param name="endPoint">IP端点（IP地址与端口）</param>
    /// <param name="pkg">地址包</param>
    /// <returns>是否成功</returns>
    public async static ValueTask<bool> SendAddrPackAsync(IPEndPoint endPoint, WebProtocol.SendAddrPackage pkg)
    {
        return await Task.Run(() => { return SendAddrPack(endPoint, pkg); });
    }


    /// <summary>
    /// 发送数据包
    /// </summary>
    /// <param name="endPoint">IP端点（IP地址与端口）</param>
    /// <param name="pkg">数据包</param>
    /// <returns>是否成功</returns>
    public static bool SendDataPack(IPEndPoint endPoint, WebProtocol.SendDataPackage pkg)
    {
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        var sendBytes = pkg.ToBytes();
        var sendLen = socket.SendTo(sendBytes, endPoint);
        socket.Close();

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

    /// <summary>
    /// 异步发送数据包
    /// </summary>
    /// <param name="endPoint">IP端点（IP地址与端口）</param>
    /// <param name="pkg">数据包</param>
    /// <returns>是否成功</returns>
    public async static ValueTask<bool> SendDataPackAsync(IPEndPoint endPoint, WebProtocol.SendDataPackage pkg)
    {
        return await Task.Run(() => { return SendDataPack(endPoint, pkg); });
    }

    /// <summary>
    /// 发送字符串到本地
    /// </summary>
    /// <param name="port">端口</param>
    /// <param name="stringArray">字符串数组</param>
    /// <returns>是否成功</returns>
    public static bool SendStringLocalHost(int port, string[] stringArray)
    {
        return SendString(new IPEndPoint(localhost, port), stringArray);
    }

    /// <summary>
    /// 循环发送字符串到本地
    /// </summary>
    /// <param name="times">发送总次数</param>
    /// <param name="sleepMilliSeconds">间隔时间</param>
    /// <param name="port">端口</param>
    /// <param name="stringArray">字符串数组</param>
    /// <returns>是否成功</returns>
    public static bool CycleSendStringLocalHost(int times, int sleepMilliSeconds, int port, string[] stringArray)
    {
        var isSuccessful = true;

        while (times-- >= 0)
        {
            isSuccessful = SendStringLocalHost(port, stringArray);
            if (!isSuccessful) break;

            Thread.Sleep(sleepMilliSeconds);
        }

        return isSuccessful;
    }

    /// <summary>
    /// [TODO:description]
    /// </summary>
    /// <param name="endPoint">[TODO:parameter]</param>
    /// <param name="devAddr">[TODO:parameter]</param>
    /// <param name="timeout">[TODO:parameter]</param>
    /// <returns>[TODO:return]</returns>
    public static async ValueTask<Result<RecvDataPackage>> ReadAddr(
            IPEndPoint endPoint, uint devAddr, int timeout = 1000)
    {
        var ret = false;
        var opts = new SendAddrPackOptions();

        opts.BurstType = BurstType.FixedBurst;
        opts.BurstLength = 0;
        opts.CommandID = 0;
        opts.Address = devAddr;

        // Read Jtag State Register
        opts.IsWrite = false;
        ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts));
        if (!ret) return new(new Exception("Send Address Package Failed!"));

        // Wait for Read Ack
        if (!MsgBus.IsRunning)
            return new(new Exception("Message Bus not Working!"));

        var retPack = await MsgBus.UDPServer.WaitForDataAsync(
                endPoint.Address.ToString(), endPoint.Port, timeout);
        if (!retPack.IsSuccessful) return new(retPack.Error);
        else if (!retPack.Value.IsSuccessful)
            return new(new Exception("Send address package failed"));

        var retPackOpts = retPack.Value.Options;
        if (retPackOpts.Data is null)
            return new(new Exception($"Data is Null, package: {retPackOpts.ToString()}"));

        return retPack;
    }

    /// <summary>
    /// [TODO:description]
    /// </summary>
    /// <param name="endPoint">[TODO:parameter]</param>
    /// <param name="devAddr">[TODO:parameter]</param>
    /// <param name="result">[TODO:parameter]</param>
    /// <param name="resultMask">[TODO:parameter]</param>
    /// <param name="timeout">[TODO:parameter]</param>
    /// <returns>[TODO:return]</returns>
    public static async ValueTask<Result<bool>> ReadAddr(
            IPEndPoint endPoint, uint devAddr, UInt32 result, UInt32 resultMask, int timeout = 1000)
    {
        var address = endPoint.Address.ToString();

        var ret = await ReadAddr(endPoint, devAddr, timeout);
        if (!ret.IsSuccessful) return new(ret.Error);
        if (!ret.Value.IsSuccessful)
            return new(new Exception($"Read device {address} address {devAddr} failed"));

        var retData = ret.Value.Options.Data;
        if (retData is null)
            return new(new Exception($"Device {address} receive none"));
        if (retData.Length != 4)
            return new(new Exception(
                $"Device {address} receive data is {retData.Length} bytes instead of 4 bytes"));

        // Check result
        try
        {
            var retCode = Convert.ToUInt32(Common.Number.BytesToUInt64(retData).Value);
            return Common.Number.BitsCheck(retCode, result, resultMask);
        }
        catch (Exception error)
        {
            return new(error);
        }
    }

    /// <summary>
    /// [TODO:description]
    /// </summary>
    /// <param name="endPoint">[TODO:parameter]</param>
    /// <param name="devAddr">[TODO:parameter]</param>
    /// <param name="result">[TODO:parameter]</param>
    /// <param name="resultMask">[TODO:parameter]</param>
    /// <param name="timeout">[TODO:parameter]</param>
    /// <returns>[TODO:return]</returns>
    public static async ValueTask<Result<bool>> ReadAddrWithWait(
            IPEndPoint endPoint, uint devAddr, UInt32 result, UInt32 resultMask, int timeout = 1000)
    {
        var address = endPoint.Address.ToString();

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

            try
            {
                var ret = await ReadAddr(endPoint, devAddr, Convert.ToInt32(timeleft.TotalMilliseconds));
                if (!ret.IsSuccessful) return new(ret.Error);
                if (!ret.Value.IsSuccessful)
                    return new(new Exception($"Read device {address} address {devAddr} failed"));

                var retData = ret.Value.Options.Data;
                if (retData is null)
                    return new(new Exception($"Device {address} receive none"));
                if (retData.Length != 4)
                    return new(new Exception(
                        $"Device {address} receive data is {retData.Length} bytes instead of 4 bytes"));

                // Check result
                var retCode = Convert.ToUInt32(Common.Number.BytesToUInt64(retData).Value);
                if (Common.Number.BitsCheck(retCode, result, resultMask)) return true;
            }
            catch (Exception error)
            {
                return new(error);
            }
        }

        return false;
    }


    /// <summary>
    /// [TODO:description]
    /// </summary>
    /// <param name="endPoint">[TODO:parameter]</param>
    /// <param name="devAddr">[TODO:parameter]</param>
    /// <param name="data">[TODO:parameter]</param>
    /// <param name="timeout">[TODO:parameter]</param>
    /// <returns>[TODO:return]</returns>
    public static async ValueTask<Result<bool>> WriteAddr(
            IPEndPoint endPoint, UInt32 devAddr, UInt32 data, int timeout = 1000)
    {
        var ret = false;
        var opts = new SendAddrPackOptions();

        opts.BurstType = BurstType.FixedBurst;
        opts.BurstLength = 0;
        opts.CommandID = 0;
        opts.Address = devAddr;

        // Write Jtag State Register
        opts.IsWrite = true;
        ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts));
        if (!ret) return new(new Exception("Send 1st address package failed!"));
        // Send Data Package
        ret = await UDPClientPool.SendDataPackAsync(endPoint,
                new SendDataPackage(Common.Number.NumberToBytes(data, 4).Value));
        if (!ret) return new(new Exception("Send data package failed!"));

        // Check Msg Bus
        if (!MsgBus.IsRunning)
            return new(new Exception("Message bus not working!"));

        // Wait for Write Ack
        var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(
                endPoint.Address.ToString(), endPoint.Port, timeout);
        if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);

        return udpWriteAck.Value.IsSuccessful;
    }

    /// <summary>
    /// [TODO:description]
    /// </summary>
    /// <param name="endPoint">[TODO:parameter]</param>
    /// <param name="devAddr">[TODO:parameter]</param>
    /// <param name="dataArray">[TODO:parameter]</param>
    /// <param name="timeout">[TODO:parameter]</param>
    /// <returns>[TODO:return]</returns>
    public static async ValueTask<Result<bool>> WriteAddr(IPEndPoint endPoint, UInt32 devAddr, byte[] dataArray, int timeout = 1000)
    {
        var ret = false;
        var opts = new SendAddrPackOptions();


        opts.BurstType = BurstType.FixedBurst;
        opts.CommandID = 0;
        opts.Address = devAddr;

        // Check Msg Bus
        if (!MsgBus.IsRunning)
            return new(new Exception("Message bus not working!"));

        opts.IsWrite = true;
        var hasRest = dataArray.Length % (256 * (32 / 8)) != 0;
        var writeTimes = hasRest ?
            dataArray.Length / (256 * (32 / 8)) + 1 :
            dataArray.Length / (256 * (32 / 8));
        for (var i = 0; i < writeTimes; i++)
        {
            // Sperate Data Array
            var isLastData = i == writeTimes - 1;
            var sendDataArray =
                isLastData ?
                dataArray[(i * (256 * (32 / 8)))..] :
                dataArray[(i * (256 * (32 / 8)))..((i + 1) * (256 * (32 / 8)))];

            // Write Jtag State Register
            opts.BurstLength = ((byte)(sendDataArray.Length / 4 - 1));
            ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts));
            if (!ret) return new(new Exception("Send 1st address package failed!"));

            // Send Data Package
            ret = await UDPClientPool.SendDataPackAsync(endPoint, new SendDataPackage(sendDataArray));
            if (!ret) return new(new Exception("Send data package failed!"));

            // Wait for Write Ack
            var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(endPoint.Address.ToString(), endPoint.Port, timeout);
            if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);

            if (!udpWriteAck.Value.IsSuccessful)
                return false;
        }

        return true;
    }

}
