using JdModbusV1.Attributes;
using JdModbusV1.Configures;
//using JdModbusV1.Models;
using NModbus;
using NModbus.Serial;
using System.IO.Ports;
using System.Net.Sockets;

namespace JdModbusV1;

public class JdModbus : IDisposable
{
    

    private readonly RtuConfigure? _rtuConfigure;
    private readonly TcpConfigure? _tcpConfigure;
    private IModbusMaster? _client;

    private readonly string _comPort;
    public JdModbus(string comPort, RtuConfigure? rtuConfigure)
    {
        _comPort = comPort;
        _rtuConfigure = rtuConfigure ?? new();

    }

    public JdModbus(string ip, int port = 502)
    {
        _tcpConfigure = new(ip, port);
    }

    public void Connect()
    {
        if (_client != null) return;

        if (_rtuConfigure != null)
        {
            var serialPort = new SerialPort(_comPort)
            {
                BaudRate = _rtuConfigure.BaudRate,
                Parity = _rtuConfigure.Parity,
                StopBits = _rtuConfigure.StopBits,
                DataBits = _rtuConfigure.DataBits,
            };

            serialPort.Open();
            _client = new ModbusFactory().CreateRtuMaster(serialPort);
            return;
        }

        if (_tcpConfigure != null)
        {
            var tcpClient = new TcpClient(_tcpConfigure.Ip, _tcpConfigure.Port);
            _client = new ModbusFactory().CreateMaster(tcpClient);
            return;
        }
    }


    public bool ReadCoil<TAddress>(TAddress address, byte slave=1) where TAddress : Enum
    {
        ThrowIfNullClient();

        var attr = GetAddressAttribute<CoilAttribute>(address);
        var result = _client.ReadCoils(slave, attr.Address, 1);

        return result[0];
    }

    public void WriteCoil<TAddress>(TAddress address, bool value, byte slave = 1) where TAddress : Enum
    {
        ThrowIfNullClient();

        var attr = GetAddressAttribute<CoilAttribute>(address);
        _client.WriteSingleCoil(slave, attr.Address, value);
    }

    public void WriteCoils<TAdddress>(TAdddress address, bool[] value, byte slave = 1) where TAdddress : Enum
    {
        ThrowIfNullClient();

        var attr = GetAddressAttribute<CoilAttribute> (address);
        _client.WriteMultipleCoils(slave, attr.Address, value);
    }

    public bool[] ReadCoils<TAddress>(TAddress address, ushort numOfCoils, byte slave=1) where TAddress : Enum
    {
        ThrowIfNullClient();

        var attr = GetAddressAttribute<CoilAttribute>(address);
        var result = _client.ReadCoils(slave, attr.Address, numOfCoils);

        return result;
    }

    public ushort ReadHoldingRegister<TAddress>(TAddress address, byte slave = 1) where TAddress : Enum
    {
        ThrowIfNullClient();

        var attr = GetAddressAttribute<HoldingAttribute>(address);
        var result = _client.ReadHoldingRegisters(slave, attr.Address, 1);

        return result[0];
    }

    public ushort[] ReadHoldingRegisters<TAddress>(TAddress address, ushort numOfCoils, byte slave = 1) where TAddress : Enum
    {
        ThrowIfNullClient();

        var attr = GetAddressAttribute<HoldingAttribute>(address);
        var result = _client.ReadHoldingRegisters(slave, attr.Address, numOfCoils);

        return result;
    }

    public void WriteHoldingRegister<TAddress>(TAddress address, ushort value, byte slave = 1) where TAddress : Enum
    {
        ThrowIfNullClient();

        var attr = GetAddressAttribute<HoldingAttribute>(address);
        _client.WriteSingleRegister(slave, attr.Address, value);
    }

    public void WriteHoldingRegisters<TAddress>(TAddress address, ushort[] value, byte slave = 1) where TAddress : Enum
    {
        ThrowIfNullClient();

        var attr = GetAddressAttribute<HoldingAttribute>(address);
        _client.WriteMultipleRegisters(slave, attr.Address, value);
    }

    private static TAttribute GetAddressAttribute<TAttribute>(Enum value) where TAttribute : ModbusAddressAttribute
    {
        var fieldInfo = value.GetType().GetField(value.ToString());
        var attribute = fieldInfo.GetCustomAttributes(typeof(TAttribute), false);

        if (attribute.Length == 0)
            throw new ArgumentException($"`{value}`는 {nameof(ModbusAddressAttribute)} 속성을 지정하지 않았습니다.");

        return (TAttribute)attribute[0];
    }

    public void Dispose() => _client?.Dispose();


    private void ThrowIfNullClient()
    {
        if (_client == null) 
            throw new NullReferenceException("Modbus Client가 초기화 되지 않았습니다.");
    }
}
