using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using NModbus;

namespace Team.Communicate.Modbus
{
    public class ModbusTcpMaster : IModbusTcpMaster
    {
        private readonly string _ip;
        private readonly int _port;
        public DataFormat DataFormat { get; set; } = DataFormat.CDAB;
        public int ConnectTimeOut { get; set; }
        public string Ip { get => _ip;  }
        public int Port { get => _port; }


        public bool IsConnect { get; private set; }
        private IModbusMaster _modbusMaster;
        private TcpClient _client;

        public event EventHandler<bool> ConnectChanged;

        public ModbusTcpMaster(string ip, int port)
        {
            _ip = ip;
            _port = port;
            var modbusFactory = new ModbusFactory();
            _client = new TcpClient();
            _modbusMaster = modbusFactory.CreateMaster(_client);
        }
        /// <summary>
        /// client must is connected
        /// </summary>
        /// <param name="client"></param>
        public ModbusTcpMaster(TcpClient client)
        {
            if (client == null)
                throw new ArgumentNullException("client");
            if (!client.Connected) throw new ArgumentNullException("client is not connected");
            var iPEndPoint= client.Client.RemoteEndPoint as IPEndPoint;
            _ip = iPEndPoint.Address.ToString();
            _port= iPEndPoint.Port;
            //_port = port;
            var modbusFactory = new ModbusFactory();
            _client = client;
            _modbusMaster = modbusFactory.CreateMaster(_client);
           
        }
        public void Connect()
        {
            try
            {
                _client.Connect(_ip, _port);
                IsConnect = true;
                OnConnectChanged(true);
            }
            catch (Exception e)
            {
                OnConnectChanged(false);
                IsConnect = false;
            }
        }
        public byte SlaveId { get; set; } = 1;
        public async Task ConnectAsync()
        {
            try
            {
                if (_client.Connected)
                {
                    return;
                }
                await _client.ConnectAsync(_ip, _port);
                OnConnectChanged(true);
                IsConnect = true;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                OnConnectChanged(false);
                IsConnect = false;
            }

        }
        
        public async Task<bool> ReConnect()
        {
            if (_client != null)
            {
                _client.Dispose();
            }

            try
            {
                _client = new TcpClient();
                if (_client.Connected)
                {
                    return true;
                }
                await _client.ConnectAsync(_ip, _port);
                var modbusFactory = new ModbusFactory();
                _modbusMaster = modbusFactory.CreateMaster(_client);
                OnConnectChanged(true);
                IsConnect = true;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                OnConnectChanged(false);
                IsConnect = false;
            }
            return IsConnect;
        }
        public ushort[] ReadBytes(ushort start, ushort length)
        {
            var byteData = _modbusMaster.ReadHoldingRegisters(SlaveId, start, length);
            return byteData;
        }
        /// <summary>
        /// 获取单个位状态
        /// </summary>
        /// <param name="start">从PLC里读出第几个字节</param>
        /// <param name="index">第几位</param>
        /// <returns></returns>
        public bool GetBool(ushort start, ushort index)
        {
            try
            {
                var byteData = _modbusMaster.ReadHoldingRegisters(SlaveId, start, 1);
                var num = byteData[0];
                var bitGroup = new bool[16];
                for (ushort i = 0; i < 16; i++)
                {
                    bitGroup[i] = ((num >> i) & 1) == 1;
                }

                return bitGroup[index];
            }
            catch (IOException)
            {
                OnConnectChanged(false);
                IsConnect = false;
                throw;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }

        }

        public List<KeyValuePair<string, bool>> GetBoolList(IEnumerable<string> addresses)
        {
            var boolList = new List<KeyValuePair<string, bool>>();
            var byteData = _modbusMaster.ReadHoldingRegisters(SlaveId, 0, 50);
            foreach (var address in addresses)
            {
                var stringSplits = address.Split('.');

                var byteIndex = ushort.Parse(stringSplits[0]);
                var byteIndex1 = byteIndex % 2 == 0 ? byteIndex / 2: byteIndex-1 / 2;
                var index = int.Parse(stringSplits[1]);


                var num = byteData[byteIndex1];
                var bitGroup = new bool[16];
                for (ushort i = 0; i < 16; i++)
                {
                    bitGroup[i] = ((num >> i) & 1) == 1;
                }
                var addressValue = bitGroup[index];
                boolList.Add(new KeyValuePair<string, bool>(address, addressValue));
            }
            return boolList;
        }

        /// <summary>
        /// 获取单个位状态
        /// </summary>
        /// <param name="start">从PLC里读出第几个字节</param>
        /// <param name="index" >第几位</param>
        /// <returns></returns>
        public async Task<bool> GetBoolAsync(ushort start, ushort index)
        {
            try
            {
                var byteData = await _modbusMaster.ReadHoldingRegistersAsync(SlaveId, start, 1);
                var num = byteData[0];
                var tempBytes = BitConverter.GetBytes(num);
                var revertBytes = tempBytes.Reverse().ToArray();
                var realValue = BitConverter.ToUInt16(revertBytes, 0);
                var bitGroup = new bool[16];
                for (ushort i = 0; i < 16; i++)
                {
                    bitGroup[i] = ((realValue >> i) & 1) == 1;
                }

                return bitGroup[index];
            }
            catch (IOException)
            {
                OnConnectChanged(false);
                IsConnect = false;
                throw;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }

        }

        /// <summary>
        /// 写modbus位
        /// </summary>
        /// <param name="start"></param>
        /// <param name="index"></param>
        /// <param name="flag"></param>
        public void WriteBool(ushort start, int index, bool flag)
        {
            try
            {
                var doCh = new bool[16];
                var currentValue = GetShort(start);
                for (ushort i = 0; i < 16; i++)
                {
                    doCh[i] = ((currentValue >> i) & 1) == 1; //获取PLC当前状态
                }

                ushort outValue = 0;
                doCh[index] = flag;
                var front = new bool[8];
                var back = new bool[8];
                Array.Copy(doCh, 0, front, 0, 8);
                Array.Copy(doCh, 8, back, 0, 8);
                for (ushort i = 0; i < 16; i++)
                {
                    outValue |= (ushort) (Convert.ToUInt16(doCh[i]) << i);
                }

                _modbusMaster.WriteMultipleRegisters(SlaveId, start, new[] {outValue});
            }
            catch (IOException)
            {
                OnConnectChanged(false);
                IsConnect = false;
                throw;
            }
        }

        /// <summary>
        /// 写modbus位
        /// </summary>
        /// <param name="start"></param>
        /// <param name="index"></param>
        /// <param name="flag"></param>
        public async Task WriteBoolAsync(ushort start, int index, bool flag)
        {
            try
            {
                var doCh = new bool[16];
                var currentValue = await GetShortAsync(start);
                for (ushort i = 0; i < 16; i++)
                {
                    doCh[i] = ((currentValue >> i) & 1) == 1; //获取PLC当前状态
                }

                ushort outValue = 0;
                doCh[index] = flag;
                var front = new bool[8];
                var back = new bool[8];
                Array.Copy(doCh, 0, front, 0, 8);
                Array.Copy(doCh, 8, back, 0, 8);
                for (ushort i = 0; i < 16; i++)
                {
                    outValue |= (ushort) (Convert.ToUInt16(doCh[i]) << i);
                }

                var reverseValue = BitConverter.GetBytes(outValue);
                var outValue1 = BitConverter.ToUInt16(reverseValue, 0);
                await _modbusMaster.WriteMultipleRegistersAsync(SlaveId, start, new[] {outValue1});
            }
            catch (IOException)
            {
                OnConnectChanged(false);
                IsConnect = false;
                throw;
            }
        }

        /// <summary>
        /// 写modbus位
        /// </summary>
        /// <param name="start"></param>
        /// <param name="index"></param>
        /// <param name="value"></param>
        public void WriteByte(ushort start, int index, byte value)
        {
            try
            {
                var currentValue = GetShort(start);
                var bytes = BitConverter.GetBytes(currentValue);
                bytes[index] = value;
                var writeValue = BitConverter.ToUInt16(bytes, 0);
                _modbusMaster.WriteMultipleRegisters(SlaveId, start, new[] {writeValue});
            }
            catch (IOException)
            {
                OnConnectChanged(false);
                IsConnect = false;
                throw;
            }
        }


        /// <summary>
        /// 写modbus位
        /// </summary>
        /// <param name="start"></param>
        /// <param name="index"></param>
        /// <param name="value"></param>
        public async Task WriteByteAsync(ushort start, int index, byte value)//todo need to test
        {
            try
            {
                var currentValue = await GetShortAsync(start);
                var bytes = BitConverter.GetBytes(currentValue);
                bytes[index] = value;
                var writeValue = BitConverter.ToUInt16(bytes, 0);
                await _modbusMaster.WriteSingleRegisterAsync(SlaveId, start, writeValue);
            }
            catch (IOException)
            {
                OnConnectChanged(false);
                IsConnect = false;
                throw;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }

        }

        /// <summary>
        /// 获取浮点数值
        /// </summary>
        /// <param name="index">数据索引</param>
        /// <returns></returns>
        public float GetFloat(ushort index)
        {
            try
            {
                var shortData = _modbusMaster.ReadHoldingRegisters(SlaveId, index, 2);
                var bytes1 = BitConverter.GetBytes(shortData[0]);
                var bytes2 = BitConverter.GetBytes(shortData[1]);
                var mergeBytes = bytes1.Concat(bytes2);
                return BitConverter.ToSingle(mergeBytes.ToArray(), 0);
            }
            catch (IOException)
            {
                OnConnectChanged(false);
                IsConnect = false;
                throw;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }

        }

        /// <summary>
        /// 获取浮点数值
        /// </summary>
        /// <param name="index">数据索引</param>
        /// <returns></returns>
        public async Task<float> GetFloatAsync(ushort index)
        {
            try
            {
                var shortData = await _modbusMaster.ReadHoldingRegistersAsync(SlaveId, index, 2);
                var bytes1 = BitConverter.GetBytes(shortData[0]);
                var bytes2 = BitConverter.GetBytes(shortData[1]);
                var mergeBytes = bytes1.Concat(bytes2);
                return BitConverter.ToSingle(mergeBytes.ToArray(), 0);
            }
            catch (IOException)
            {
                OnConnectChanged(false);
                IsConnect = false;
                throw;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }

        }

        /// <summary>
        /// 获取字
        /// </summary>
        /// <param name="index">数据索引</param>
        /// <returns></returns>
        public ushort GetShort(ushort index)
        {
            try
            {
                
                var shortData = _modbusMaster.ReadHoldingRegisters(SlaveId, index, 1);
                var shortBytes = BitConverter.GetBytes(shortData[0]);
                var realBytes = shortBytes;
                var realValue = BitConverter.ToUInt16(realBytes, 0);
                return realValue;
            }
            catch (IOException)
            {
                OnConnectChanged(false);
                IsConnect = false;
                throw;
            }
        }

        /// <summary>
        /// 获取字
        /// </summary>
        /// <param name="index">数据索引</param>
        /// <returns></returns>
        public async Task<ushort> GetShortAsync(ushort index)
        {
            try
            {
                var shortData = await _modbusMaster.ReadHoldingRegistersAsync(SlaveId, index, 1);
                var shortBytes = BitConverter.GetBytes(shortData[0]);
                var realValue = BitConverter.ToUInt16(shortBytes, 0);
                return realValue;
            }
            catch (IOException)
            {
                IsConnect = false;
                throw;
            }
        }

        /// <summary>
        /// 获取整型数值
        /// </summary>
        /// 4字节整数
        /// <param name="index">数据索引</param>
        /// <returns></returns>
        public int GetInteger(ushort index)
        {
            try
            {
                var shortData = _modbusMaster.ReadHoldingRegisters(SlaveId, index, 2);
                var bytes1 = BitConverter.GetBytes(shortData[0]);
                var bytes2 = BitConverter.GetBytes(shortData[1]);
                var mergeBytes = bytes1.Concat(bytes2);
                return BitConverter.ToInt32(mergeBytes.ToArray(), 0);
            }
            catch (IOException)
            {
                IsConnect = false;
                throw;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }

        }

        /// <summary>
        /// 异步获取整型数值
        /// </summary>
        /// 4字节整数
        /// <param name="index">数据索引</param>
        /// <returns></returns>
        public async Task<int> GetIntegerAsync(ushort index)
        {
            try
            {
                var shortData = await _modbusMaster.ReadHoldingRegistersAsync(SlaveId, index, 2);
                var bytes1 = BitConverter.GetBytes(shortData[0]);
                var bytes2 = BitConverter.GetBytes(shortData[1]);
                var mergeBytes = bytes1.Concat(bytes2);
                return BitConverter.ToInt32(mergeBytes.ToArray(), 0);
            }
            catch (IOException)
            {
                IsConnect = false;
                OnConnectChanged(false);
                throw;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }

        }

        /// <summary>
        /// 读字节
        /// </summary>
        /// <param name="start">开始字索引</param>
        /// <param name="index">第几个字节</param>
        /// <returns></returns>
        public async Task<byte> GetByteAsync(ushort start, int index)
        {
            try
            {
                var shortData = await _modbusMaster.ReadHoldingRegistersAsync(SlaveId, start, 1);
                var bytes1 = BitConverter.GetBytes(shortData[0]).Reverse().ToArray();
                return bytes1[index];
            }
            catch (IOException)
            {
                IsConnect = false;
                OnConnectChanged(false);
                throw;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }

        }

        /// <summary>
        /// 读字节
        /// </summary>
        /// <param name="start">开始字索引</param>
        /// <param name="index">第几个字节</param>
        /// <returns></returns>
        public byte GetByte(ushort start, int index)
        {
            try
            {
                var shortData = _modbusMaster.ReadHoldingRegisters(SlaveId, start, 1);
                var bytes1 = BitConverter.GetBytes(shortData[0]).Reverse().ToArray();
                return bytes1[index];
            }
            catch (IOException)
            {
                OnConnectChanged(false);
                IsConnect = false;
                throw;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

      
        /// <summary>
        /// 反转多字节的数据信息
        /// </summary>
        /// <param name="value">数据字节</param>
        /// <param name="index">起始索引,默认值为0</param>
        /// <returns>实际字节信息</returns>
        protected byte[] ByteTransDataFormat2(byte[] value, int index = 0)
        {
            var buffer = new byte[2];
            switch (DataFormat)
            {
                case DataFormat.ABCD:
                {
                    buffer[0] = value[index + 1];
                    buffer[1] = value[index + 0];
                    break;
                }
                case DataFormat.BADC:
                {
                    buffer[0] = value[index + 0];
                    buffer[1] = value[index + 1];
                    break;
                }

                case DataFormat.CDAB:
                {
                    buffer[0] = value[index + 1];
                    buffer[1] = value[index + 0];
                    break;
                }
                case DataFormat.DCBA:
                {
                    buffer[0] = value[index + 0];
                    buffer[1] = value[index + 1];
                    break;
                }
            }

            return buffer;
        }
        /// <summary>
        /// 反转多字节的数据信息
        /// </summary>
        /// <param name="value">数据字节</param>
        /// <param name="index">起始索引,默认值为0</param>
        /// <returns>实际字节信息</returns>
        protected byte[] ByteTransDataFormat8(byte[] value, int index = 0)
        {
            byte[] buffer = new byte[8];
            switch (DataFormat)
            {
                case DataFormat.ABCD:
                    {
                        buffer[0] = value[index + 7];
                        buffer[1] = value[index + 6];
                        buffer[2] = value[index + 5];
                        buffer[3] = value[index + 4];
                        buffer[4] = value[index + 3];
                        buffer[5] = value[index + 2];
                        buffer[6] = value[index + 1];
                        buffer[7] = value[index + 0];
                        break;
                    }
                case DataFormat.BADC:
                    {
                        buffer[0] = value[index + 6];
                        buffer[1] = value[index + 7];
                        buffer[2] = value[index + 4];
                        buffer[3] = value[index + 5];
                        buffer[4] = value[index + 2];
                        buffer[5] = value[index + 3];
                        buffer[6] = value[index + 0];
                        buffer[7] = value[index + 1];
                        break;
                    }

                case DataFormat.CDAB:
                    {
                        buffer[0] = value[index + 1];
                        buffer[1] = value[index + 0];
                        buffer[2] = value[index + 3];
                        buffer[3] = value[index + 2];
                        buffer[4] = value[index + 5];
                        buffer[5] = value[index + 4];
                        buffer[6] = value[index + 7];
                        buffer[7] = value[index + 6];
                        break;
                    }
                case DataFormat.DCBA:
                    {
                        buffer[0] = value[index + 0];
                        buffer[1] = value[index + 1];
                        buffer[2] = value[index + 2];
                        buffer[3] = value[index + 3];
                        buffer[4] = value[index + 4];
                        buffer[5] = value[index + 5];
                        buffer[6] = value[index + 6];
                        buffer[7] = value[index + 7];
                        break;
                    }
            }

            return buffer;
        }

        /// <summary>
        /// 反转多字节的数据信息
        /// </summary>
        /// <param name="value">数据字节</param>
        /// <param name="index">起始索引,默认值为0</param>
        /// <returns>实际字节信息</returns>
        protected byte[] ByteTransDataFormat4(byte[] value, int index = 0)
        {
            var buffer = new byte[4];
            switch (DataFormat)
            {
                case DataFormat.ABCD:
                {
                    buffer[0] = value[index + 3];
                    buffer[1] = value[index + 2];
                    buffer[2] = value[index + 1];
                    buffer[3] = value[index + 0];
                    break;
                }
                case DataFormat.BADC:
                {
                    buffer[0] = value[index + 2];
                    buffer[1] = value[index + 3];
                    buffer[2] = value[index + 0];
                    buffer[3] = value[index + 1];
                    break;
                }

                case DataFormat.CDAB:
                {
                    buffer[0] = value[index + 1];
                    buffer[1] = value[index + 0];
                    buffer[2] = value[index + 3];
                    buffer[3] = value[index + 2];
                    break;
                }
                case DataFormat.DCBA:
                {
                    buffer[0] = value[index + 0];
                    buffer[1] = value[index + 1];
                    buffer[2] = value[index + 2];
                    buffer[3] = value[index + 3];
                    break;
                }
            }

            return buffer;
        }

        public virtual byte[] TransByte(int[] values)
        {
            if (values == null) return null;

            byte[] buffer = new byte[values.Length * 4];
            for (int i = 0; i < values.Length; i++)
            {
                ByteTransDataFormat4(BitConverter.GetBytes(values[i])).CopyTo(buffer, 4 * i);
            }

            return buffer;
        }

        public virtual byte[] TransByte(ushort[] values)
        {
            if (values == null) return null;

            byte[] buffer = new byte[values.Length * 2];
            for (int i = 0; i < values.Length; i++)
            {
                BitConverter.GetBytes(values[i]).CopyTo(buffer, 2 * i);
            }
            return buffer;
        }

        public void WriteDouble(ushort start, double value)
        {
            var convertBytes = BitConverter.GetBytes(value);
            var writeValues = new ushort[2];
            for (var i = 0; i < 4; i++)
            {
                var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
                writeValues[i] = BitConverter.ToUInt16(bytes, 0);
            }
            _modbusMaster.WriteMultipleRegisters(SlaveId, start, writeValues);
        }

        public async Task WriteDoubleAsync(ushort start, double value)
        {
            var convertBytes = BitConverter.GetBytes(value);
            var writeValues = new ushort[4];
            for (var i = 0; i < 4; i++)
            {
                var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
                writeValues[i] = BitConverter.ToUInt16(bytes, 0);
            }

            await _modbusMaster.WriteMultipleRegistersAsync(SlaveId, start, writeValues);
        }


        public void WriteFloat(ushort start, float value)
        {
            var convertBytes = BitConverter.GetBytes(value);
            var writeValues = new ushort[2];
            for (int i = 0; i < 4; i++)
            {
                var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
                writeValues[i] = BitConverter.ToUInt16(bytes, 0);
            }
            _modbusMaster.WriteMultipleRegisters(SlaveId, start, writeValues);
        }

        public async Task WriteFloatAsync(ushort start, float value)
        {
            var convertBytes = BitConverter.GetBytes(value);
            var writeValues = new ushort[2];
            for (var i = 0; i < 2; i++)
            {
                var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
                writeValues[i] = BitConverter.ToUInt16(bytes, 0);
            }

            await _modbusMaster.WriteMultipleRegistersAsync(SlaveId, start, writeValues);
        }

        public void WriteInt32(ushort start, int value)
        {
            var convertBytes = BitConverter.GetBytes(value);
            var writeValues = new ushort[2];
            for (int i = 0; i < 2; i++)
            {
                var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
                writeValues[i] = BitConverter.ToUInt16(bytes, 0);
            }

            _modbusMaster.WriteMultipleRegisters(SlaveId, start, writeValues);
        }
        public async Task WriteInt32Async(ushort start, int value)
        {
            var convertBytes = BitConverter.GetBytes(value);
            var writeValues = new ushort[2];
            for (var i = 0; i < 2; i++)
            {
                var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
                writeValues[i] = BitConverter.ToUInt16(bytes, 0);
            }

            await _modbusMaster.WriteMultipleRegistersAsync(SlaveId, start, writeValues);
        }

        public ushort[] GetBytes(ushort start, ushort length)
        {
        
            var nums= _modbusMaster.ReadInputRegisters(1,start, length);
            return nums;
        }

        public Task<ushort[]> GetBytesAsync(ushort start, ushort length)
        {
            return _modbusMaster.ReadInputRegistersAsync(SlaveId, start, length);
        }

        private void OnConnectChanged(bool state)
        {
            ConnectChanged?.Invoke(this, state);
        }
    }
}