using BPASmartClient.Helper;
using BPASmartClient.Message;
using NModbus;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BPASmartClient.Modbus
{
    public class ModbusTcp
    {

        //private volatile static ModbusTcp _Instance;
        //public static ModbusTcp GetInstance => _Instance ?? (_Instance = new ModbusTcp());
        //private ModbusTcp() { }

        private ModbusFactory modbusFactory;
        private IModbusMaster master;
        private TcpClient tcpClient;
        private ManualResetEvent mre = new ManualResetEvent(false);
        public string IPAdress;
        public int Port;

        public Action ConnectOk { get; set; }

        public Action ConnectFail { get; set; }

        public Action Disconnect { get; set; }

        public bool IsReconnect { get; set; } = true; //true=启用重连，false=禁用重连

        /// <summary>
        /// 判断是否连接成功
        /// </summary>
        public bool Connected => tcpClient == null ? false : tcpClient.Connected;

        /// <summary>
        /// ModbusTcp 连接设备
        /// </summary>
        /// <param name="ip">ip 地址</param>
        /// <param name="port">端口号，默认502</param>
        public void ModbusTcpConnect(string ip, int port = 502)
        {
            MessageLog.GetInstance.Show($"设备【{ip}:{port}】连接中。。。。");
            IPAdress = ip;
            Port = port;
            modbusFactory = new ModbusFactory();
            Connect();
            if (Connected)
            {
                master.Transport.ReadTimeout = 2000;//读取超时时间
                master.Transport.WriteTimeout = 2000;//写入超时时间
                master.Transport.Retries = 10;//重试次数
                ConnectOk?.Invoke();
                MessageLog.GetInstance.Show($"设备【{ip}:{port}】连接成功");
            }
            else
            {
                ConnectFail?.Invoke();
            }

        }

        public void Dispose()
        {
            master.Dispose();
            Disconnect?.Invoke();
        }

        private void Connect()
        {
            bool ErrorFlag = false;
            while (!Connected)
            {
                try
                {
                    tcpClient = new TcpClient(IPAdress, Port);

                    //mre.Reset();
                    //tcpClient.BeginConnect(IPAdress, Port, (res) => { mre.Set(); }, null);
                    //if (!mre.WaitOne(1500, false)) break;

                    master = modbusFactory.CreateMaster(tcpClient);
                }
                catch (Exception ex)
                {
                    if (!ErrorFlag)
                    {
                        MessageLog.GetInstance.ShowEx($"ModbusTcp 连接失败，IP = {IPAdress},Port = {Port}");
                        MessageLog.GetInstance.ShowEx(ex.ToString());
                        ErrorFlag = true;
                    }
                    if (!IsReconnect) break;
                    Thread.Sleep(3000);
                }
            }
            if (ErrorFlag && IsReconnect) MessageLog.GetInstance.Show("ModbusTcp 重连成功！");
        }

        /// <summary>
        /// 获取 M 区 和 VW 区的Modbus的地址
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public int GetAddress(string address)
        {
            if (address == null) return -1;
            if (address.Length > 0)
            {
                if (address.ToUpper().Contains("M") && address.Length >= 4)
                {
                    var res = address.Substring(1).Split('.');
                    if (res != null && res.Length == 2)
                    {
                        if (int.TryParse(res[0], out int firstAddress) && int.TryParse(res[1], out int ExitAddress))
                        {
                            if (ExitAddress >= 0 && ExitAddress <= 7)
                            {
                                return (firstAddress * 8) + 320 + ExitAddress;
                            }
                        }
                    }
                }
                else if (address.ToUpper().Contains("VW") && address.Length >= 3)
                {
                    var res = address.Substring(2);
                    if (res != null && int.TryParse(res, out int tempAddress))
                    {
                        return (tempAddress / 2) + 100;
                    }
                }
                else if (address.ToUpper().Contains("LW") && address.Length >= 3)
                {
                    var res = address.Substring(2);
                    if (res != null && int.TryParse(res, out int LwAddress))
                    {
                        return LwAddress;
                    }
                }
            }
            return -1;
        }

        private void ExceptionHandling(Exception ex)
        {
            if (ex.InnerException is SocketException)
            {
                if (IsReconnect)
                {
                    tcpClient = null;
                    Connect();
                }
                Disconnect?.Invoke();
            }
        }

        public object Read(string address, ushort len = 1, byte slaveAddress = 1)
        {
            if (address == null || tcpClient == null) return default(object);
            ushort startAddress = (ushort)GetAddress(address);
            CommandType commandType = CommandType.Coils;
            try
            {
                if (address.ToUpper().Contains("M"))
                {
                    commandType = CommandType.Coils;
                    return master.ReadCoils(slaveAddress, startAddress, len);
                }
                else if (address.ToUpper().Contains("VW") || address.ToUpper().Contains("LW"))
                {
                    commandType = CommandType.HoldingRegisters;
                    return master.ReadHoldingRegisters(slaveAddress, startAddress, len);
                }
                else if (address.ToUpper().Contains("I"))
                {
                    commandType = CommandType.Inputs;
                    return master.ReadInputs(slaveAddress, startAddress, len);
                }
            }
            catch (Exception ex)
            {
                MessageLog.GetInstance.ShowEx($"读取地址：【{address}：= {startAddress}】,读取类型：【{commandType.ToString()}】出错，{ex.ToString()}");
                ExceptionHandling(ex);
            }
            return default(object);
        }

        public void Write<T>(string address, T value, byte slaveAddress = 1)
        {
            if (address == null || tcpClient == null) return;
            ushort startAddress = (ushort)GetAddress(address);
            CommandType commandType = CommandType.Coils;
            try
            {
                if (address.ToUpper().Contains("M"))
                {
                    commandType = CommandType.Coils;
                    if (value is bool boolValue)
                        master.WriteSingleCoil(slaveAddress, startAddress, boolValue);
                    else if (value is bool[] boolsValue)
                        master.WriteMultipleCoils(slaveAddress, startAddress, boolsValue);
                }
                else if (address.ToUpper().Contains("VW") || address.ToUpper().Contains("LW"))
                {
                    commandType = CommandType.HoldingRegisters;
                    if (value is ushort ushortValue)
                        master.WriteSingleRegister(slaveAddress, startAddress, ushortValue);
                    else if (value is ushort[] ushortsValue)
                    {
                        int len = 100;
                        if (ushortsValue.Length > len)
                        {
                            List<ushort[]> ushortLists = new List<ushort[]>();
                            for (int i = 0; i < ushortsValue.Length / len; i++)
                            {
                                ushortLists.Add(ushortsValue.Skip(0).Take(len).ToArray());
                            }
                            int y = ushortsValue.Length % len;

                            if (y > 0)
                            {
                                ushortLists.Add(ushortsValue.Skip(ushortsValue.Length - y).Take(y).ToArray());
                            }
                            foreach (var item in ushortLists)
                            {
                                master.WriteMultipleRegisters(slaveAddress, startAddress, item);
                                startAddress += (ushort)item.Length;
                            }
                        }
                        else
                        {
                            master.WriteMultipleRegisters(slaveAddress, startAddress, ushortsValue);
                        }
                    }
                }
                else if (address.ToUpper().Contains("I"))
                {
                    commandType = CommandType.Inputs;
                }
            }
            catch (Exception ex)
            {
                MessageLog.GetInstance.ShowEx($"写入地址：【{address}：= {startAddress}】,写入类型：【{commandType.ToString()}】出错，{ex.ToString()}");
                ExceptionHandling(ex);
            }
        }


        #region 字符串数据读写
        /// <summary>
        /// 赋值string
        /// </summary>
        /// <param name="StartAddress"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public void SetString(string StartAddress, string value)
        {
            //获取指定的编码不存在的时候需要安装 System.Text.Encoding.CodePages nuget包 
            //然后使用下面的代码就可以获取了
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            var bytes = Encoding.GetEncoding("gb2312")?.GetBytes(value);
            Write(StartAddress, bytes.BytesToUshorts());
        }

        /// <summary>
        /// 获取string
        /// </summary>
        /// <param name="StartAddress"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public string GetString(string StartAddress, ushort len)
        {
            var res = Read(StartAddress, len);
            if (res != null && res is ushort[] ushorts)
            {
                Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
                return Encoding.GetEncoding("gb2312")?.GetString(ushorts.UshortsToBytes()).Trim(new char[] { '\0' });
            }
            return String.Empty;
        }

        #endregion

        #region 浮点数数据读写
        /// <summary>
        /// 赋值Real类型数据
        /// </summary>
        /// <param name="StartAddress"></param>
        /// <param name="value"></param>
        public void SetReal(string StartAddress, float value)
        {
            var bytes = BitConverter.GetBytes(value);
            Write(StartAddress, bytes.BytesToUshorts());
        }

        /// <summary>
        /// 获取float类型数据
        /// </summary>
        /// <param name="StartAddress"></param>
        /// <returns></returns>
        public float GetReal(string StartAddress)
        {
            var res = Read(StartAddress, 2);
            if (res != null && res is ushort[] ushorts)
            {
                return BitConverter.ToSingle(ushorts.UshortsToBytes(), 0);
            }
            return 0;
        }
        #endregion

        #region 整数数据读写
        /// <summary>
        /// 赋值Real类型数据
        /// </summary>
        /// <param name="StartAddress"></param>
        /// <param name="value"></param>
        public void SetUint(string StartAddress, uint value)
        {
            var bytes = BitConverter.GetBytes(value);
            Write(StartAddress, bytes.BytesToUshorts());
        }

        /// <summary>
        /// 获取float类型数据
        /// </summary>
        /// <param name="StartAddress"></param>
        /// <returns></returns>
        public uint GetUint(string StartAddress)
        {
            var res = Read(StartAddress, 2);
            if (res != null && res is ushort[] ushorts)
            {
                return BitConverter.ToUInt32(ushorts.UshortsToBytes(), 0);
            }
            return 0;
        }
        #endregion

        #region 批量数据读取
        /// <summary>
        /// 读取多个线圈
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="num"></param>
        /// <param name="slaveAddress"></param>
        /// <returns></returns>
        public bool[] ReadCoils(ushort startAddress, ushort num, byte slaveAddress = 1)
        {
            return master.ReadCoils(slaveAddress, startAddress, num);
        }

        /// <summary>
        /// 读取多个输入线圈
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="num"></param>
        /// <param name="slaveAddress"></param>
        /// <returns></returns>
        public bool[] ReadInputs(ushort startAddress, ushort num, byte slaveAddress = 1)
        {
            return master.ReadInputs(slaveAddress, startAddress, num);
        }

        /// <summary>
        /// 读取多个保持寄存器
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="num"></param>
        /// <param name="slaveAddress"></param>
        /// <returns></returns>
        public ushort[] ReadHoldingRegisters(ushort startAddress, ushort num, byte slaveAddress = 1)
        {
            return master.ReadHoldingRegisters(slaveAddress, startAddress, num);
        }

        /// <summary>
        /// 读取多个输入寄存器
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="num"></param>
        /// <param name="slaveAddress"></param>
        /// <returns></returns>
        public ushort[] ReadInputRegisters(ushort startAddress, ushort num, byte slaveAddress = 1)
        {
            return master.ReadInputRegisters(slaveAddress, startAddress, num);
        }
        #endregion

        #region 批量数据写入
        /// <summary>
        /// 写入多个线圈
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="value"></param>
        /// <param name="slaveAddress"></param>
        public void WriteMultipleCoils(ushort startAddress, bool[] value, byte slaveAddress = 1)
        {
            master.WriteMultipleCoils(slaveAddress, startAddress, value);
        }

        /// <summary>
        /// 写入多个寄存器
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="value"></param>
        /// <param name="slaveAddress"></param>
        public void WriteMultipleRegisters(ushort startAddress, ushort[] value, byte slaveAddress = 1)
        {
            master.WriteMultipleRegisters(slaveAddress, startAddress, value);
        }
        #endregion

        #region 单个数据读取
        /// <summary>
        /// 读取单个线圈
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="slaveAddress"></param>
        /// <returns></returns>
        public bool ReadCoils(ushort startAddress, byte slaveAddress = 1)
        {
            var result = master.ReadCoils(slaveAddress, startAddress, 1);
            if (result == null) return false;
            if (result.Length == 1) return result[0];
            return false;
            //return master.ReadCoils(slaveAddress, startAddress, 1)[0];
        }

        /// <summary>
        /// 读取单个输入线圈
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="slaveAddress"></param>
        /// <returns></returns>
        public bool ReadInputs(ushort startAddress, byte slaveAddress = 1)
        {
            var result = master.ReadInputs(slaveAddress, startAddress, 1);
            if (result == null) return false;
            if (result.Length == 1) return result[0];
            return false;
            //return master.ReadInputs(slaveAddress, startAddress, 1)[0];
        }

        /// <summary>
        /// 读取单个保持寄存器
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="slaveAddress"></param>
        /// <returns></returns>
        public ushort ReadHoldingRegisters(ushort startAddress, byte slaveAddress = 1)
        {
            if (tcpClient == null) return 0;
            ushort[] result = null;
            try
            {
                result = master.ReadHoldingRegisters(slaveAddress, startAddress, 1);
            }
            catch (Exception ex)
            {
                MessageLog.GetInstance.Show(ex.ToString());
                tcpClient = null;
                Connect();
            }

            if (result == null) return 0;
            if (result.Length == 1) return result[0];
            return 0;
            //return master.ReadHoldingRegisters(slaveAddress, startAddress, 1)[0];
        }

        /// <summary>
        /// 读取单个输入寄存器
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="slaveAddress"></param>
        /// <returns></returns>
        public ushort ReadInputRegisters(ushort startAddress, byte slaveAddress = 1)
        {
            var result = master.ReadInputRegisters(slaveAddress, startAddress, 1);
            if (result == null) return 0;
            if (result.Length == 1) return result[0];
            return 0;
            //return master.ReadInputRegisters(slaveAddress, startAddress, 1)[0];
        }
        #endregion

        #region 单个数据写入
        /// <summary>
        /// 写入单个线圈
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="value"></param>
        /// <param name="slaveAddress"></param>
        public void WriteSingleCoil(ushort startAddress, bool value, byte slaveAddress = 1)
        {
            master.WriteSingleCoil(slaveAddress, startAddress, value);
        }

        /// <summary>
        /// 写入单个寄存器
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="value"></param>
        /// <param name="slaveAddress"></param>
        public void WriteSingleRegister(ushort startAddress, ushort value, byte slaveAddress = 1)
        {
            master.WriteSingleRegister(slaveAddress, startAddress, value);
        }
        #endregion
    }

    public enum CommandType
    {
        /// <summary>
        /// 线圈操作
        /// </summary>
        Coils,
        /// <summary>
        /// 输入线圈操作
        /// </summary>
        Inputs,
        /// <summary>
        /// 保持寄存器操作
        /// </summary>
        HoldingRegisters,
        /// <summary>
        /// 输入寄存器操作
        /// </summary>
        InputRegisters,
    }

}
