using Common;
using DotNext;
using Newtonsoft.Json;

namespace WebProtocol
{

    /// <summary> The Sign of Package </summary>
    public enum PackSign
    {
        /// <summary> Package: Send Read or Write Address to Device </summary>
        SendAddr = 0x00,
        /// <summary> Package: Send Data Which Update the flash of Device</summary>
        SendData = 0xFF,
        /// <summary>  </summary>
        RecvData = 0x0F,
        /// <summary>  </summary>
        RecvResp = 0xF0,
    }

    /// <summary> Package Burst Type </summary>
    public enum BurstType
    {
        /// <summary> Fixed Type </summary>
        FixedBurst = 0b00,
        /// <summary> Extended Type </summary>
        ExtendBurst = 0b01,
    }


    /// <summary> Package options which to send address to read or write </summary>
    public class SendAddrPackOptions
    {

        /// <summary>
        /// 突发类型
        /// </summary>
        /// <example>0</example>
        public required BurstType BurstType { get; set; }

        /// <summary>
        /// 任务ID
        /// </summary>
        /// <example>1</example>
        public required byte CommandID { get; set; }

        /// <summary>
        /// 标识写入还是读取
        /// </summary>
        /// <example>true</example>
        public required bool IsWrite { get; set; }

        /// <summary>
        /// 突发长度：0是32bits，255是32bits x 256
        /// </summary>
        /// <example>255</example>
        public required byte BurstLength { get; set; }


        /// <summary>
        /// 目标地址
        /// </summary>
        /// <example>0</example>
        public required UInt32 Address { get; set; }

        /// <summary>
        /// 转换为Json格式字符串
        /// </summary>
        /// <returns>Json String</returns>
        public override string ToString()
        {
            return JsonConvert.SerializeObject(this);
        }
    }

    /// <summary> Package Options which to receive from boards </summary>
    public class RecvPackOptions
    {
        /// <summary> 数据包类型 </summary>
        public enum PackType
        {
            /// <summary> 读响应包 </summary>
            ReadResp,
            /// <summary> 写响应包 </summary>
            WriteResp
        };

        /// <summary>
        /// 时间戳
        /// </summary>
        /// <example>1234567</example>
        public required UInt32 Timestamp { get; set; }

        /// <summary>
        /// 数据包类型
        /// </summary>
        /// <example>0</example>
        public required PackType Type { get; set; }

        /// <summary>
        /// Task ID
        /// </summary>
        /// <example>0</example>
        public required byte CommandID { get; set; }

        /// <summary>
        /// Whether is succeed to finish command
        /// </summary>
        /// <example>true</example>
        public required bool IsSuccess { get; set; }

        /// <summary>
        /// Return Data
        /// </summary>
        /// <example>[]</example>
        public byte[]? Data { get; set; }

        /// <summary>
        /// Convert to Json String
        /// </summary>
        /// <returns> Json String </returns>
        public override string ToString()
        {
            return JsonConvert.SerializeObject(this);
        }
    }

    /// <summary> Server->FPGA 地址包 </summary>
    public class SendAddrPackage
    {
        readonly byte sign = (byte)PackSign.SendAddr;
        readonly byte commandType;
        readonly byte burstLength;
        readonly byte _reserved = 0;
        readonly UInt32 address;

        /// <summary>
        /// 使用地址包选项构造地址包
        /// </summary>
        /// <param name="opts"> 地址包选项 </param>
        public SendAddrPackage(SendAddrPackOptions opts)
        {
            byte byteBurstType = Convert.ToByte((byte)opts.BurstType << 6);
            byte byteCommandID = Convert.ToByte((opts.CommandID & 0x03) << 4);
            byte byteIsWrite = (opts.IsWrite ? (byte)0x01 : (byte)0x00);
            this.commandType = Convert.ToByte(byteBurstType | byteCommandID | byteIsWrite);
            this.burstLength = opts.BurstLength;
            this.address = opts.Address;
        }

        /// <summary>
        /// 使用完整参数构造地址包
        /// </summary>
        /// <param name="burstType"> 突发类型 </param>
        /// <param name="commandID"> 任务ID </param>
        /// <param name="isWrite"> 是否是写数据 </param>
        /// <param name="burstLength"> 突发长度 </param>
        /// <param name="address"> 设备地址 </param>
        public SendAddrPackage(BurstType burstType, byte commandID, bool isWrite, byte burstLength, UInt32 address)
        {
            byte byteBurstType = Convert.ToByte((byte)burstType << 6);
            byte byteCommandID = Convert.ToByte((commandID & 0x03) << 4);
            byte byteIsWrite = (isWrite ? (byte)0x01 : (byte)0x00);
            this.commandType = Convert.ToByte(byteBurstType | byteCommandID | byteIsWrite);
            this.burstLength = burstLength;
            this.address = address;
        }

        /// <summary>
        /// 使用二进制参数构建地址包
        /// </summary>
        /// <param name="commandType">二进制命令类型</param>
        /// <param name="burstLength">突发长度</param>
        /// <param name="address">写入或读取的地址</param>
        public SendAddrPackage(byte commandType, byte burstLength, UInt32 address)
        {
            this.commandType = commandType;
            this.burstLength = burstLength;
            this.address = address;
        }

        /// <summary>
        /// 获取对应地址包选项
        /// </summary>
        public SendAddrPackOptions Options
        {
            get
            {
                return new SendAddrPackOptions()
                {
                    Address = this.address,
                    BurstLength = this.burstLength,
                    BurstType = (BurstType)(this.commandType >> 6),
                    CommandID = Convert.ToByte((this.commandType >> 4) & 0b11),
                    IsWrite = Convert.ToBoolean(this.commandType & 1)
                };
            }
        }

        /// <summary>
        /// 将地址包转化为字节数组
        /// </summary>
        /// <returns> 字节数组 </returns>
        public byte[] ToBytes()
        {
            var arr = new byte[8];
            arr[0] = sign;
            arr[1] = commandType;
            arr[2] = burstLength;
            arr[3] = _reserved;

            var bytesAddr = Common.Number.NumberToBytes(address, 4).Value;
            Array.Copy(bytesAddr, 0, arr, 4, bytesAddr.Length);

            return arr;
        }

        /// <summary>
        /// 讲地址包转化为Json格式的地址包选项
        /// </summary>
        /// <returns> 字符串 </returns>
        public override string ToString()
        {
            var opts = new SendAddrPackOptions()
            {
                BurstType = (BurstType)(commandType >> 6),
                CommandID = Convert.ToByte((commandType >> 4) & 0b0011),
                IsWrite = Convert.ToBoolean(commandType & 0x01),
                BurstLength = burstLength,
                Address = address,
            };

            return JsonConvert.SerializeObject(opts);
        }

        /// <summary>
        /// 根据字节数组构建地址包
        /// </summary>
        /// <param name="bytes">字节数组</param>
        /// <param name="checkSign">是否校验地址包包头</param>
        /// <returns>地址包</returns>
        public static Result<SendAddrPackage> FromBytes(byte[] bytes, bool checkSign = true)
        {
            if (bytes.Length != 8)
            {
                return new(new ArgumentException(
                    "Bytes are not equal to 8 bytes.",
                    nameof(bytes)
                ));
            }

            if (checkSign && bytes[0] != (byte)PackSign.SendAddr)
            {
                return new(new ArgumentException(
                            "The sign of bytes is not SendAddr Package, but you can disable it by set checkSign false.",
                            nameof(bytes)
                            ));
            }

            var address = Common.Number.BytesToUInt64(bytes[4..]).Value;
            return new SendAddrPackage(bytes[1], bytes[2], Convert.ToUInt32(address));
        }
    }

    /// <summary> 数据包 </summary>
    public struct SendDataPackage
    {
        readonly byte sign = (byte)PackSign.SendData;
        readonly byte[] _reserved = new byte[3];
        readonly byte[] bodyData;

        /// <summary>
        /// 根据数据内容构建数据包
        /// </summary>
        /// <param name="bodyData">数据</param>
        public SendDataPackage(byte[] bodyData)
        {
            if (bodyData.Length > 256 * (32 / 8))
                throw new Exception("The data of SendDataPackage can't over 256 * 32bits");

            if (bodyData.Length % 4 != 0)
                throw new Exception("The data of SendDataPackage should be divided by 4");

            this.bodyData = bodyData;

            _ = _reserved;
        }

        /// <summary>
        /// 将数据包转化为字节数组
        /// </summary>
        /// <returns>字节数组</returns>
        public byte[] ToBytes()
        {
            var bodyDataLen = bodyData.Length;
            var arr = new byte[4 + bodyDataLen];

            arr[0] = sign;

            Array.Copy(bodyData, 0, arr, 4, bodyDataLen);

            return arr;
        }


    }

    /// <summary> FPGA->Server 读响应包 </summary>
    public class RecvDataPackage
    {
        readonly UInt32 timestamp;
        readonly byte sign = (byte)PackSign.RecvData;
        readonly byte commandID;
        readonly byte resp;
        readonly byte _reserved = 0;
        readonly byte[] bodyData;

        /// <summary>
        ///  FPGA->Server 读响应包 
        ///  构造函数
        /// </summary>
        /// <param name="timestamp"> 时间戳 </param>
        /// <param name="commandID"> 任务ID号 </param>
        /// <param name="resp"> 读响应包响应 </param>
        /// <param name="bodyData"> 数据 </param>
        public RecvDataPackage(UInt32 timestamp, byte commandID, byte resp, byte[] bodyData)
        {
            this.timestamp = timestamp;
            this.commandID = commandID;
            this.resp = resp;
            this.bodyData = bodyData;

            _ = this.sign;
            _ = this._reserved;
        }

        /// <summary>
        /// 通过接受包选项构建读响应包
        /// </summary>
        /// <param name="opts">接收包（读响应包和写响应包）选项</param>
        public RecvDataPackage(RecvPackOptions opts)
        {
            this.timestamp = opts.Timestamp;
            this.commandID = opts.CommandID;
            this.resp = Convert.ToByte(opts.IsSuccess ? 0b10 : 0b00);
            this.bodyData = opts.Data ?? (byte[])[0, 0, 0, 0];
        }

        /// <summary>
        /// 获取读响应包选项
        /// </summary>
        public RecvPackOptions Options
        {
            get
            {
                var opts = new RecvPackOptions()
                {
                    Timestamp = this.timestamp,
                    Type = RecvPackOptions.PackType.ReadResp,
                    CommandID = this.commandID,
                    IsSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? false : true),
                    Data = this.bodyData,
                };

                return opts;
            }
        }

        /// <summary>
        /// 读取是否成功
        /// </summary>
        public bool IsSuccessful
        {
            get { return Convert.ToBoolean((this.resp >> 1) == 0b01 ? false : true); }
        }

        /// <summary>
        /// 从字节数组构建读响应包
        /// </summary>
        /// <param name="bytes">字节数组</param>
        /// <returns>读响应包</returns>
        public static Result<RecvDataPackage> FromBytes(byte[] bytes)
        {
            if (bytes[4] != (byte)PackSign.RecvData)
                return new(new ArgumentException(
                    $"The sign of bytes is not RecvData Package, Sign: 0x{BitConverter.ToString([bytes[0]])}",
                    nameof(bytes)
                ));
            return new RecvDataPackage(
                Number.BytesToUInt32(bytes[..4]).Value,
                bytes[5],
                bytes[6],
                bytes[8..]);
        }

        /// <summary>
        /// [TODO:description]
        /// </summary>
        /// <param name="bytes">[TODO:parameter]</param>
        /// <returns>[TODO:return]</returns>
        public static bool IsRecvDataPackage(byte[] bytes)
        {
            return bytes[4] == (byte)PackSign.RecvData;
        }

        /// <summary>
        /// 将数据包转化为字节数组
        /// </summary>
        /// <returns>字节数组</returns>
        public byte[] ToBytes()
        {
            var bodyDataLen = bodyData.Length;
            var arr = new byte[8 + bodyDataLen];

            Buffer.BlockCopy(
                Number.UInt32ArrayToBytes([this.timestamp]).Value, 0, arr, 0, 4);
            arr[4] = this.sign;
            arr[5] = this.commandID;
            arr[6] = this.resp;
            arr[7] = this.resp;

            Array.Copy(bodyData, 0, arr, 8, bodyDataLen);

            return arr;
        }

    }

    /// <summary> 写响应包 </summary>
    public class RecvRespPackage
    {
        readonly UInt32 timestamp;
        readonly byte sign = (byte)PackSign.RecvResp;
        readonly byte commandID;
        readonly byte resp;
        readonly byte _reserved = 0;

        /// <summary>
        /// 构建写响应包
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <param name="commandID">任务ID</param>
        /// <param name="resp">写响应</param>
        public RecvRespPackage(UInt32 timestamp, byte commandID, byte resp)
        {
            this.timestamp = timestamp;
            this.commandID = commandID;
            this.resp = resp;

            _ = this.sign;
            _ = this._reserved;
        }

        /// <summary>
        /// 通过接受包选项构建写响应包
        /// </summary>
        /// <param name="opts">接收包（读响应包和写响应包）选项</param>
        public RecvRespPackage(RecvPackOptions opts)
        {
            this.timestamp = opts.Timestamp;
            this.commandID = opts.CommandID;
            this.resp = Convert.ToByte(opts.IsSuccess ? 0b10 : 0b00);
        }

        /// <summary>
        /// 获取写响应包选项
        /// </summary>
        public RecvPackOptions Options
        {
            get
            {
                var opts = new RecvPackOptions()
                {
                    Timestamp = this.timestamp,
                    Type = RecvPackOptions.PackType.WriteResp,
                    CommandID = commandID,
                    IsSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? false : true),
                    Data = null,
                };

                return opts;
            }
        }

        /// <summary>
        /// 写入是否成功
        /// </summary>
        public bool IsSuccessful
        {
            get { return Convert.ToBoolean((this.resp >> 1) == 0b01 ? false : true); }
        }

        /// <summary>
        /// 从字节数组构建写响应包
        /// </summary>
        /// <param name="bytes">字节数组</param>
        /// <returns>写响应包</returns>
        public static Result<RecvRespPackage> FromBytes(byte[] bytes)
        {
            if (bytes[4] != (byte)PackSign.RecvResp)
                return new(new ArgumentException(
                    $"The sign of bytes is not RecvResp Package, Sign: 0x{BitConverter.ToString([bytes[4]])}",
                    nameof(bytes)
                ));
            var timestamp = Number.BytesToUInt32(bytes[..4]).Value;
            return new RecvRespPackage(timestamp, bytes[5], bytes[6]);
        }

        /// <summary>
        /// [TODO:description]
        /// </summary>
        /// <param name="bytes">[TODO:parameter]</param>
        /// <returns>[TODO:return]</returns>
        public static bool IsRecvRespPackage(byte[] bytes)
        {
            return bytes[4] == (byte)PackSign.RecvResp;
        }

        /// <summary>
        /// 将数据包转化为字节数组
        /// </summary>
        /// <returns>字节数组</returns>
        public byte[] ToBytes()
        {
            var arr = new byte[8];
            Buffer.BlockCopy(
                Number.UInt32ArrayToBytes([this.timestamp]).Value, 0, arr, 0, 4);
            arr[4] = this.sign;
            arr[5] = this.commandID;
            arr[6] = this.resp;
            arr[7] = this._reserved;

            return arr;
        }
    }
}
