using System.Diagnostics;
using System.IO.Ports;

namespace UHFHelper
{
    public class UHF_RS485_Helper
    {
        private SerialPort? _serialPort;
        private SerialParam? _serialParam;

        /// <summary>
        /// 单例模式
        /// </summary>
        private static UHF_RS485_Helper? Instance { get; set; }
        public static UHF_RS485_Helper GetInstance()
        {
            if (Instance == null)
            {
                Instance = new UHF_RS485_Helper();
            }
            return Instance;
        }
        /// <summary>
        /// 打开串口
        /// </summary>
        /// <param name="param"></param>
        public void Open(SerialParam param)
        {
            _serialParam = param;
            _serialPort = new SerialPort(_serialParam.PortName, _serialParam.BaudRate, _serialParam.PortParity, _serialParam.DataBits);
            _serialPort.Open();
            
        }
        /// <summary>
        /// 获取串口状态
        /// </summary>
        /// <returns></returns>
        public bool GetSerialPortState()
        {
            if (_serialPort!=null)
            {
                return _serialPort.IsOpen;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 关闭串口
        /// </summary>
        public void Close()
        {
            if (_serialPort == null) return;
            _serialPort.Close();
            _serialPort.Dispose();
            _serialPort = null;
        }
        /// <summary>
        /// 读卡方法，返回读卡器数据
        /// </summary>
        /// <param name="adr">设备读取地址</param>
        /// <returns></returns>
        public async Task<DKoutput?> ReadCard(int adr)
        {
            DKoutput dKoutput = new();
            var readByte = ReadByte(adr);
            var result = await SendMessageAsync(readByte, TimeSpan.FromSeconds(1), 18);
            if (result == null)
            {
                return null;
            }
            else
            {
                //获取校验码
                var crc = result.Skip(16).Take(2).ToArray();
                //获取卡号
                var cardNo = result.Skip(6).Take(16).ToArray();
                //获取读卡器数据
                var readData = result.Skip(0).Take(16).ToArray();
                //获取读卡器地址
                var address = result.Skip(1).Take(1).ToArray();
                //判断数据是否合法
                var temcrc = CRC16.ToCRC16(readData);
                if (crc.ByteArrayToHexString() == temcrc.ByteArrayToHexString())
                {
                    dKoutput.Address = address.ByteArrayToHexString();
                    dKoutput.ResData = cardNo.ByteArrayToHexString();
                    return dKoutput;
                }
                else
                {
                    return null;
                }
            }
        }
        /// <summary>
        /// 生成读取命令
        /// </summary>
        /// <returns></returns>
        private byte[] ReadByte(int adr)
        {
            //根据读取地址生成读取命令
            byte[] data1 = new byte[3] { 04, (byte)adr, 01 };
            //生成crc校验码
            byte[] data2 = CRC16.ToCRC16(data1);
            byte[] data3 = new byte[data1.Length + data2.Length];
            data1.CopyTo(data3, 0);
            data2.CopyTo(data3, data1.Length);
            return data3;
        }
        /// <summary>
        /// 获取本机可用串口
        /// </summary>
        /// <returns></returns>
        public string[] GetLocalSerialPortNames()
        {
            return SerialPort.GetPortNames();
        }
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="msg">数据</param>
        /// <param name="timeout">超时时间</param>
        /// <param name="count">串口回复字节数</param>
        /// <returns></returns>
        public byte[]? SendMessage(byte[] msg, TimeSpan timeout, int count)
        {
            if (_serialPort == null) return null;
            var stopwatch = Stopwatch.StartNew();
            var originalWriteTimeout = _serialPort.WriteTimeout;
            var originalReadTimeout = _serialPort.ReadTimeout;
            try
            {

                _serialPort.WriteTimeout = (int)Math.Max((timeout - stopwatch.Elapsed).TotalMilliseconds, 0);
                _serialPort.Write(msg, 0, msg.Length);

                var tmpCount = count;
                byte[] buffer = new byte[tmpCount];
                int offset = 0;
                while (tmpCount > 0)
                {
                    _serialPort.ReadTimeout = (int)Math.Max((timeout - stopwatch.Elapsed).TotalMilliseconds, 0);
                     var readCount = _serialPort.Read(buffer, offset, tmpCount); 
                    if (readCount==6)
                    {
                        tmpCount = 0;
                        continue;
                    }
                    offset += readCount;
                    tmpCount -= readCount;
                }
                _serialPort.DiscardInBuffer();
                return buffer;
            }catch(Exception ex)
            {
                return null;
            }
            finally
            {
                _serialPort.ReadTimeout = originalReadTimeout;
                _serialPort.WriteTimeout = originalWriteTimeout;
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="msg">数据</param>
        /// <param name="timeout">超时时间</param>
        /// <param name="count">串口回复字节数</param>
        /// <returns></returns>
        public async Task<byte[]?> SendMessageAsync(byte[] msg, TimeSpan timeout, int count)
        {
            var sendTask = Task.Run(() => SendMessage(msg, timeout, count));
            try
            {
                await Task.WhenAny(sendTask, Task.Delay(timeout));
            }
            catch (TaskCanceledException)
            {
                throw new TimeoutException();
            }
            return await sendTask;
        }

        /// <summary>
        /// 发出蜂鸣声音3次,代表扣款成功!
        /// </summary>
        public async void OpenBeep(int addr)
        {
            byte[] sendData = new byte[]{ 0x07, (byte)addr, 0x33, 0x08, 0x02, 0x03, 0x33,0xB1 };
            await SendMessageAsync(sendData, TimeSpan.FromSeconds(1), 6);
        }
    }

    public class SerialParam
    {
        public string PortName { get; set; } = "COM3";
        public int BaudRate { get; set; } = 57600;
        public int DataBits { get; set; } = 8;
        public Parity PortParity { get; set; } = Parity.None;
        public StopBits PortStopBits { get; set; } = StopBits.One;
    }
}
