using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Buffers.Binary;
using System.Buffers;

namespace JT809.Protocol.JT809Extensions
{
    public static class JT809BinaryExtensions
    {
        /// <summary>
        /// 日期限制于2000年
        /// </summary>
        private const int DateLimitYear = 2000;

        private const ushort cnCRC_CCITT = 0x1021; //CRC校验多项式

        private static ulong[] CRC = new ulong[256]; //建立CRC16表 

        private static readonly DateTime UTCBaseTime = new DateTime(1970, 1, 1);

        static JT809BinaryExtensions()
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            encoding = Encoding.GetEncoding("GBK");
            ushort i, j;
            ushort nData;
            ushort nAccum;
            for (i = 0; i < 256; i++)
            {
                nData = (ushort)(i << 8);
                nAccum = 0;
                for (j = 0; j < 8; j++)
                {
                    if (((nData ^ nAccum) & 0x8000) > 0)
                        nAccum = (ushort)((nAccum << 1) ^ cnCRC_CCITT);
                    else
                        nAccum <<= 1;
                    nData <<= 1;
                }
                CRC[i] = (ulong)nAccum;
            }
        }

        private static Encoding encoding;

        public static int ReadBCD32(this byte data, byte dig)
        {
            int result = Convert.ToInt32(data.ToString("X"));
            return result * (int)Math.Pow(100, dig - 1);
        }

        public static int ReadBCD32(ReadOnlySpan<byte> read, ref int offset, int len)
        {
            int result = Convert.ToInt32(read[offset].ToString("X"));
            offset += len;
            return result * (int)Math.Pow(100, len - 1);
        }


        public static long ReadBCD64(this byte data, byte dig)
        {
            long result = Convert.ToInt64(data.ToString("X"));
            return result * (long)Math.Pow(100, dig - 1);
        }

        public static string ReadStringLittle(ReadOnlySpan<byte> read, ref int offset, int len)
        {
            string value = encoding.GetString(read.Slice(offset, len).ToArray());
            offset += len;
            return value.Trim('\0');
        }

        public static string ReadStringLittle(ReadOnlySpan<byte> read, ref int offset)
        {
            string value = encoding.GetString(read.Slice(offset).ToArray());
            offset += value.Length;
            return value.Trim('\0');
        }

        public static long ReadBCD(ReadOnlySpan<byte> buf, ref int offset, int len)
        {
            long result = 0;
            try
            {
                for (int i = offset; i < offset + len; i++)
                {
                    result += buf[i].ReadBCD64((byte)(offset + len - i));
                }
            }
            catch
            {
            }
            offset = offset + len;
            return result;
        }

        public static DateTime ReadDateTimeLittle(ReadOnlySpan<byte> buf, ref int offset)
        {
            DateTime dateTime = new DateTime(
                (buf[offset]).ReadBCD32(1) + DateLimitYear,
                (buf[offset + 1]).ReadBCD32(1),
                (buf[offset + 2]).ReadBCD32(1),
                (buf[offset + 3]).ReadBCD32(1),
                (buf[offset + 4]).ReadBCD32(1),
                (buf[offset + 5]).ReadBCD32(1));
            offset = offset + 6;
            return dateTime;
        }

        public static DateTime ReadDateLittle(ReadOnlySpan<byte> buf, ref int offset)
        {
            DateTime dateTime = new DateTime(
                    ((buf[offset] << 8) | (buf[offset + 1])),
                    (buf[offset + 2]).ReadBCD32(1),
                    (buf[offset + 3]).ReadBCD32(1));
            offset = offset + 4;
            return dateTime;
        }

        public static DateTime ReadUTCDateTimeLittle(ReadOnlySpan<byte> buf, ref int offset)
        {
            ulong result = 0;
            for (int i = 0; i < 8; i++)
            {
                ulong currentData = (ulong)buf[offset+i] << (8 * (8 - i - 1));
                result += currentData;
            }
            offset += 8;
            return UTCBaseTime.AddSeconds(result).AddHours(8);
        }

        public static int ReadInt32Little(ReadOnlySpan<byte> read, ref int offset)
        {
            int value = (read[offset] << 24) | (read[offset + 1] << 16) | (read[offset + 2] << 8) | read[offset + 3];
            offset = offset + 4;
            return value;
        }

        public static uint ReadUInt32Little(ReadOnlySpan<byte> read, ref int offset)
        {
            uint value = (uint)((read[offset] << 24) | (read[offset + 1] << 16) | (read[offset + 2] << 8) | read[offset + 3]);
            offset = offset + 4;
            return value;
        }

        public static ulong ReadUInt64Little(ReadOnlySpan<byte> read, ref int offset)
        {
            ulong value = (ulong)(
                (read[offset] << 56) |
                (read[offset + 1] << 48) |
                (read[offset + 2] << 40) |
                (read[offset + 3] << 32) |
                (read[offset + 4] << 24) |
                (read[offset + 5] << 16) |
                (read[offset + 6] << 8) |
                 read[offset + 7]);
            offset = offset + 8;
            return value;
        }

        public static ushort ReadUInt16Little(ReadOnlySpan<byte> read, ref int offset)
        {
            ushort value = (ushort)((read[offset] << 8) | (read[offset + 1]));
            offset = offset + 2;
            return value;
        }

        public static byte ReadByteLittle(ReadOnlySpan<byte> read, ref int offset)
        {
            byte value = read[offset];
            offset = offset + 1;
            return value;
        }

        public static byte[] ReadBytesLittle(ReadOnlySpan<byte> read, ref int offset, int len)
        {
            ReadOnlySpan<byte> temp = read.Slice(offset, len);
            offset = offset + len;
            return temp.ToArray();
        }

        /// <summary>
        /// 数字编码 大端模式、高位在前
        /// </summary>
        /// <param name="read"></param>
        /// <param name="offset"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static string ReadBigNumberLittle(ReadOnlySpan<byte> read, ref int offset, int len)
        {
            ulong result = 0;
            for (int i = 0; i < len; i++)
            {
                ulong currentData = (ulong)read[offset+i] << (8 * (len - i - 1));
                result += currentData;
            }
            offset += len;
            return result.ToString();
        }

        /// <summary>
        /// 数字编码 小端模式、低位在前
        /// </summary>
        /// <param name="read"></param>
        /// <param name="offset"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static string ReadLowNumberLittle(ReadOnlySpan<byte> read, ref int offset, int len)
        {
            ulong result = 0;
            for (int i = 0; i < len; i++)
            {
                ulong currentData = (ulong)read[offset+i] << (8 * i);
                result += currentData;
            }
            offset += len;
            return result.ToString();
        }

        public static int WriteDateTime6Little(IMemoryOwner<byte> memoryOwner, int offset, DateTime date)
        {
            memoryOwner.Memory.Span[offset] = ((byte)(date.Year - DateLimitYear)).ToBcdByte();
            memoryOwner.Memory.Span[offset + 1] = ((byte)(date.Month)).ToBcdByte();
            memoryOwner.Memory.Span[offset + 2] = ((byte)(date.Day)).ToBcdByte();
            memoryOwner.Memory.Span[offset + 3] = ((byte)(date.Hour)).ToBcdByte();
            memoryOwner.Memory.Span[offset + 4] = ((byte)(date.Minute)).ToBcdByte();
            memoryOwner.Memory.Span[offset + 5] = ((byte)(date.Second)).ToBcdByte();
            return 6;
        }

        public static int WriteDateTime4Little(IMemoryOwner<byte> memoryOwner, int offset, DateTime date)
        {
            memoryOwner.Memory.Span[offset] = (byte)(date.Year >> 8);
            memoryOwner.Memory.Span[offset + 1] = (byte)date.Year;
            memoryOwner.Memory.Span[offset + 2] = ((byte)(date.Month)).ToBcdByte();
            memoryOwner.Memory.Span[offset + 3] = ((byte)(date.Day)).ToBcdByte();
            return 4;
        }

        public static int WriteInt32Little(IMemoryOwner<byte> memoryOwner, int offset, int data)
        {
            memoryOwner.Memory.Span[offset] = (byte)(data >> 24);
            memoryOwner.Memory.Span[offset + 1] = (byte)(data >> 16);
            memoryOwner.Memory.Span[offset + 2] = (byte)(data >> 8);
            memoryOwner.Memory.Span[offset + 3] = (byte)data;
            return 4;
        }

        public static int WriteUInt32Little(IMemoryOwner<byte> memoryOwner, int offset, uint data)
        {
            memoryOwner.Memory.Span[offset] = (byte)(data >> 24);
            memoryOwner.Memory.Span[offset + 1] = (byte)(data >> 16);
            memoryOwner.Memory.Span[offset + 2] = (byte)(data >> 8);
            memoryOwner.Memory.Span[offset + 3] = (byte)data;
            return 4;
        }

        public static int WriteUInt64Little(IMemoryOwner<byte> memoryOwner, int offset, ulong data)
        {
            memoryOwner.Memory.Span[offset] = (byte)(data >> 56);
            memoryOwner.Memory.Span[offset + 1] = (byte)(data >> 48);
            memoryOwner.Memory.Span[offset + 2] = (byte)(data >> 40);
            memoryOwner.Memory.Span[offset + 3] = (byte)(data >> 32);
            memoryOwner.Memory.Span[offset + 4] = (byte)(data >> 24);
            memoryOwner.Memory.Span[offset + 5] = (byte)(data >> 16);
            memoryOwner.Memory.Span[offset + 6] = (byte)(data >> 8);
            memoryOwner.Memory.Span[offset + 7] = (byte)data;
            return 8;
        }

        public static int WriteUInt16Little(IMemoryOwner<byte> memoryOwner, int offset, ushort data)
        {
            memoryOwner.Memory.Span[offset] = (byte)(data >> 8);
            memoryOwner.Memory.Span[offset + 1] = (byte)data;
            return 2;
        }

        public static int WriteByteLittle(IMemoryOwner<byte> memoryOwner, int offset, byte data)
        {
            memoryOwner.Memory.Span[offset] = data;
            return 1;
        }

        public static int WriteBytesLittle(IMemoryOwner<byte> memoryOwner, int offset, byte[] data)
        {
            CopyTo(data, memoryOwner.Memory.Span, offset);
            return data.Length;
        }

        public static int WriteStringLittle(IMemoryOwner<byte> memoryOwner, int offset, string data)
        {
            byte[] codeBytes = encoding.GetBytes(data);
            CopyTo(codeBytes, memoryOwner.Memory.Span, offset);
            return codeBytes.Length;
        }

        public static int WriteStringLittle(IMemoryOwner<byte> memoryOwner, int offset, string data, int len)
        {
            byte[] bytes = null;
            if (string.IsNullOrEmpty(data))
            {
                bytes = new byte[0];
            }
            else
            {
                bytes = encoding.GetBytes(data);
            }
            byte[] rBytes = new byte[len];
            for (int i = 0; i < bytes.Length; i++)
            {
                if (i >= len) break;
                rBytes[i] = bytes[i];
            }
            CopyTo(rBytes, memoryOwner.Memory.Span, offset);
            return rBytes.Length;
        }

        public static int WriteStringPadLeftLittle(IMemoryOwner<byte> memoryOwner, int offset, string data, int len)
        {
            data = data.PadLeft(len, '\0');
            byte[] codeBytes = encoding.GetBytes(data);
            CopyTo(codeBytes, memoryOwner.Memory.Span, offset);
            return codeBytes.Length;
        }

        public static int WriteStringPadRightLittle(IMemoryOwner<byte> memoryOwner, int offset, string data, int len)
        {
            data = data.PadRight(len, '\0');
            byte[] codeBytes = encoding.GetBytes(data);
            CopyTo(codeBytes, memoryOwner.Memory.Span, offset);
            return codeBytes.Length;
        }

        public static int WriteBCDLittle(IMemoryOwner<byte> memoryOwner, int offset, string data, int digit, int len)
        {
            ReadOnlySpan<char> bcd = data.PadLeft(len, '0').AsSpan();
            for (int i = 0; i < digit; i++)
            {
                memoryOwner.Memory.Span[offset + i] = Convert.ToByte(bcd.Slice(i * 2, 2).ToString(), 16);
            }
            return digit;
        }

        /// <summary>
        /// 数字编码 大端模式、高位在前
        /// </summary>
        /// <param name="memoryOwner"></param>
        /// <param name="offset"></param>
        /// <param name="data"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static int WriteBigNumberLittle(IMemoryOwner<byte> memoryOwner, int offset, string data, int len)
        {
            ulong number = string.IsNullOrEmpty(data) ? 0 : (ulong)double.Parse(data);
            for (int i = len - 1; i >= 0; i--)
            {
                memoryOwner.Memory.Span[offset+i] = (byte)(number & 0xFF);  //取低8位
                number = number >> 8;
            }
            return len;
        }

        /// <summary>
        /// 数字编码 小端模式、低位在前
        /// </summary>
        /// <param name="memoryOwner"></param>
        /// <param name="offset"></param>
        /// <param name="data"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static int WriteLowNumberLittle(IMemoryOwner<byte> memoryOwner, int offset, string data, int len)
        {
            ulong number = string.IsNullOrEmpty(data) ? 0 : (ulong)double.Parse(data);
            for (int i = 0; i < len; i++)
            {
                memoryOwner.Memory.Span[offset + i] = (byte)(number & 0xFF); //取低8位
                number = number >> 8;
            }
            return len;
        }

        public static int WriteUTCDateTimeLittle(IMemoryOwner<byte> memoryOwner, int offset, DateTime date)
        {
            ulong totalSecends = (ulong)(date.AddHours(-8) - UTCBaseTime).TotalSeconds;
            //高位在前
            for (int i = 7; i >= 0; i--)
            {
                memoryOwner.Memory.Span[offset+i] = (byte)(totalSecends & 0xFF);  //取低8位
                totalSecends = totalSecends >> 8;
            }
            return 8;
        }

        public static IEnumerable<byte> ToBytes(this string data, Encoding coding)
        {
            return coding.GetBytes(data);
        }

        public static IEnumerable<byte> ToBytes(this string data)
        {
            return ToBytes(data, encoding);
        }

        public static IEnumerable<byte> ToBytes(this int data, int len)
        {
            List<byte> bytes = new List<byte>();
            int n = 1;
            for (int i = 0; i < len; i++)
            {
                bytes.Add((byte)(data >> 8 * (len - n)));
                n++;
            }
            return bytes;
        }

        /// <summary>
        /// 从数据头到校验码前的 CRC 1 G-CCITT 的校验值，遵循人端排序方式的规定。
        /// </summary>
        /// <param name="packege"></param>
        /// <param name="ucbuf"></param>
        /// <param name="offset"></param>
        /// <param name="iLen"></param>
        /// <returns></returns>
        public static ushort ToCRC16_CCITT(this Span<byte> ucbuf, int offset, int iLen)
        {
            ushort checkCode = 0xFFFF;
            for (int j = offset; j < iLen; ++j)
            {
                checkCode = (ushort)((checkCode << 8) ^ (ushort)CRC[(checkCode >> 8) ^ ucbuf[j]]);
            }
            return checkCode;
        }

        /// <summary>
        /// 从数据头到校验码前的 CRC 1 G-CCITT 的校验值，遵循人端排序方式的规定。
        /// </summary>
        /// <param name="packege"></param>
        /// <param name="ucbuf"></param>
        /// <param name="offset"></param>
        /// <param name="iLen"></param>
        /// <returns></returns>
        public static ushort ToCRC16_CCITT(this ReadOnlySpan<byte> ucbuf, int offset, int iLen)
        {
            ushort checkCode = 0xFFFF;
            for (int j = offset; j < iLen; ++j)
            {
                checkCode = (ushort)((checkCode << 8) ^ (ushort)CRC[(checkCode >> 8) ^ ucbuf[j]]);
            }
            return checkCode;
        }

        public static byte ToBcdByte(this byte buf)
        {
            return (byte)Convert.ToInt32(buf.ToString(), 16);
        }

        /// <summary>
        /// 经纬度
        /// </summary>
        /// <param name="latlng"></param>
        /// <returns></returns>
        public static double ToLatLng(this int latlng)
        {
            return Math.Round(latlng / Math.Pow(10, 6), 6);
        }

        public static void CopyTo(Span<byte> source, Span<byte> destination, int offset)
        {
            for (int i = 0; i < source.Length; i++)
            {
                destination[offset + i] = source[i];
            }
        }

        /// <summary>
        /// 字节数组转16进制字符串
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="separator">默认 " "</param>
        /// <returns></returns>
        public static string ToHexString(this byte[] bytes, string separator = " ")
        {
            return string.Join(separator, bytes.Select(s => s.ToString("X2")));
        }

        /// <summary>
        /// 16进制字符串转16进制数组
        /// </summary>
        /// <param name="hexString"></param>
        /// <param name="separator"></param>
        /// <returns></returns>
        public static byte[] ToHexBytes(this string hexString, string separator = " ")
        {
            return hexString.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries).Select(s => Convert.ToByte(s, 16)).ToArray();
        }

        /// <summary>
        /// 16进制字符串转16进制数组
        /// </summary>
        /// <param name="hexString"></param>
        /// <returns></returns>
        public static byte[] ToStr2HexBytes(this string hexString)
        {
            //byte[] buf = new byte[hexString.Length / 2];
            //for (int i = 0; i < hexString.Length; i++)
            //{
            //    if (i % 2 == 0)
            //    {
            //        buf[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16) ;
            //    }

            //}
            byte[] buf = new byte[hexString.Length / 2];
            ReadOnlySpan<char> readOnlySpan = hexString.AsSpan();
            for (int i = 0; i < hexString.Length; i++)
            {
                if (i % 2 == 0)
                {
                    buf[i / 2] = Convert.ToByte(readOnlySpan.Slice(i, 2).ToString(), 16);
                }
            }
            return buf;
            //List<byte> bytes = new List<byte>();
            //while (hexString.Length>0)
            //{
            //    bytes.Add(Convert.ToByte(hexString.AsSpan(0, 2).ToString(), 16));
            //    hexString = hexString.Remove(0,2);
            //}
            //return Regex.Replace(hexString, @"(\w{2})", "$1 ").Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries).Select(s => Convert.ToByte(s, 16)).ToArray();
        }
    }
}
