using NModbus;
using NModbus.Serial;
using System;
using System.IO.Ports;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace DeviceCommand.Base
{
    public class ModbusRtu:IModbusDevice
    {
        public string PortName { get; private set; } = "COM1";
        public int BaudRate { get; private set; } = 9600;
        public int DataBits { get; private set; } = 8;
        public StopBits StopBits { get; private set; } = StopBits.One;
        public Parity Parity { get; private set; } = Parity.None;
        public int ReadTimeout { get; private set; } = 3000;
        public int WriteTimeout { get; private set; } = 3000;

        public SerialPort SerialPort { get;  set; } = new SerialPort();
        public TcpClient TcpClient { get; set; }
        public IModbusMaster Modbus { get;  set; }

        protected readonly SemaphoreSlim _commLock = new(1, 1);

        public void ConfigureDevice(string portName, int baudRate, int dataBits = 8,    StopBits stopBits = StopBits.One, Parity parity = Parity.None,  int readTimeout = 3000, int writeTimeout = 3000)
        {
            PortName = portName;
            BaudRate = baudRate;
            DataBits = dataBits;
            StopBits = stopBits;
            Parity = parity;
            ReadTimeout = readTimeout;
            WriteTimeout = writeTimeout;
        }

        public virtual async Task<bool> ConnectAsync(CancellationToken ct = default)
        {
            await _commLock.WaitAsync(ct);
            try
            {
                if (SerialPort.IsOpen)
                    SerialPort.Close();

                SerialPort.PortName = PortName;
                SerialPort.BaudRate = BaudRate;
                SerialPort.DataBits = DataBits;
                SerialPort.StopBits = StopBits;
                SerialPort.Parity = Parity;
                SerialPort.ReadTimeout = ReadTimeout;
                SerialPort.WriteTimeout = WriteTimeout;
                SerialPort.Open();

                Modbus = new ModbusFactory().CreateRtuMaster(SerialPort);
                return true;
            }
            finally
            {
                _commLock.Release();
            }
        }

        public virtual void Close()
        {
            if (SerialPort.IsOpen)
                SerialPort.Close();
        }

        public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default)
        {
            await _commLock.WaitAsync(ct);
            try
            {
                var writeTask = Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value).WaitAsync(ct);
                var timeoutTask = Task.Delay(WriteTimeout, ct);
                var completedTask = await Task.WhenAny(writeTask, timeoutTask);
                if (completedTask == timeoutTask)
                    throw new TimeoutException("ModbusRTU写单寄存器超时");
                await writeTask;
            }
            finally
            {
                _commLock.Release();
            }
        }

        public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default)
        {
            await _commLock.WaitAsync(ct);
            try
            {
                var writeTask = Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values).WaitAsync(ct);
                var timeoutTask = Task.Delay(WriteTimeout, ct);
                var completedTask = await Task.WhenAny(writeTask, timeoutTask);
                if (completedTask == timeoutTask)
                    throw new TimeoutException("ModbusRTU写多寄存器超时");
                await writeTask;
            }
            finally
            {
                _commLock.Release();
            }
        }

        public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
        {
            await _commLock.WaitAsync(ct);
            try
            {
                var readTask = Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
                var timeoutTask = Task.Delay(ReadTimeout, ct);
                var completedTask = await Task.WhenAny(readTask, timeoutTask);
                if (completedTask == timeoutTask)
                    throw new TimeoutException("ModbusRTU读取保持寄存器超时");
                return await readTask;
            }
            finally
            {
                _commLock.Release();
            }
        }

        public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default)
        {
            await _commLock.WaitAsync(ct);
            try
            {
                var writeTask = Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value).WaitAsync(ct);
                var timeoutTask = Task.Delay(WriteTimeout, ct);
                var completedTask = await Task.WhenAny(writeTask, timeoutTask);
                if (completedTask == timeoutTask)
                    throw new TimeoutException("ModbusRTU写单线圈超时");
                await writeTask;
            }
            finally
            {
                _commLock.Release();
            }
        }

        public async Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
        {
            await _commLock.WaitAsync(ct);
            try
            {
                var readTask = Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
                var timeoutTask = Task.Delay(ReadTimeout, ct);
                var completedTask = await Task.WhenAny(readTask, timeoutTask);
                if (completedTask == timeoutTask)
                    throw new TimeoutException("ModbusRTU读取线圈超时");
                return await readTask;
            }
            finally
            {
                _commLock.Release();
            }
        }

        public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
        {
            await _commLock.WaitAsync(ct);
            try
            {
                var readTask = Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
                var timeoutTask = Task.Delay(ReadTimeout, ct);
                var completedTask = await Task.WhenAny(readTask, timeoutTask);
                if (completedTask == timeoutTask)
                    throw new TimeoutException("ModbusRTU读取输入寄存器超时");
                return await readTask;
            }
            finally
            {
                _commLock.Release();
            }
        }
    }
}
