using FluentModbus;
using ProtocalGeneral;
using System.Net;
using System.Runtime.InteropServices;

namespace ModBusTcp;

public class Modbus_Tcp : IModBus<byte[]?>
{
    private ModbusTcpClient? _master;
    private IPEndPoint? EndPoint;
    private string? _name;
    private string _ip = string.Empty;
    private ushort _port;
    private ITcpConnectNority? _connect;

    public bool Initialize(string name, ITcpConnectNority tcpConnect)
    {
        if (!string.IsNullOrEmpty(this._name))
            return false;
        this._name = name;
        this._name ??= "Default";
        this._connect = tcpConnect;
        return true;
    }

    public bool Open(string ip, ushort port, bool notify = true)
    {
        if (this._master is not null)
            return false;
        if (!IPAddress.TryParse(ip, out IPAddress? address) || address is null)
            return false;
        try
        {
            IPEndPoint endPoint = new(address, port);

            ModbusTcpClient client = new()
            {
                ReadTimeout = 500,
                ConnectTimeout = 500
            };
            if (notify)
                client.Connect(endPoint);
            this._ip = ip;
            this._port = port;
            this.EndPoint = endPoint;
            this._master = client;
        }
        catch
        {
            return false;
        }
        return true;
    }

    public byte[]? GetBuffer(ushort index, ushort count, byte slaveAddress = 1)
    {
        if (this._master is null)
            return null;

        try
        {
            lock (this)
            {
                Span<byte> bytes = this._master.ReadHoldingRegisters(slaveAddress, index, count);
                if (bytes == null)
                    return null;
                return bytes.ToArray();
            }
        }
        catch 
        {
            TryReconnect();
            return null;
        }
    }

    public unsafe bool SetFloat(ushort index, float value, byte slaveAddress = 1)
    {
        if (this._master is null)
            return false;

        byte[] bytes = new byte[4];
        float* pSource = &value;
        byte* pByte = (byte*)pSource;
        bytes[0] = pByte[3];
        bytes[1] = pByte[2];
        bytes[2] = pByte[1];
        bytes[3] = pByte[0];

        try
        {
            lock (this)
                this._master.WriteMultipleRegisters(slaveAddress, (int)index, bytes);
        }
        catch
        {
            return false;
        }

        return true;
    }

    public unsafe bool SetValue<T>(ushort index, T value, byte slaveAddress = 1) where T : struct
    {
        if (this._master is null)
            return false;
        int size = Marshal.SizeOf<T>();

        if (size % 4 != 0)
            return false;

        byte[] bytes = new byte[size];
#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
        T* pSource = &value;
#pragma warning restore CS8500
        byte* pByte = (byte*)pSource;
        for (int i = 0; i < size; i += 4)
        {
            bytes[i] = pByte[i + 3];
            bytes[i + 1] = pByte[i + 2];
            bytes[i + 2] = pByte[i + 1];
            bytes[i + 3] = pByte[i];
        }
        try
        {
            lock (this)
                this._master.WriteMultipleRegisters(slaveAddress, (int)index, bytes);
        }
        catch
        {
            return false;
        }
        return true;
    }

    public unsafe bool SetUshort(ushort index, ushort value, byte slaveAddress = 1)
    {
        if (this._master is null)
            return false;
        ushort* pUshort = &value;
        byte* pByte = (byte*)pUshort;
        byte[] result = [pByte[1], pByte[0]];
        try
        {
            lock (this)
                this._master.WriteSingleRegister(slaveAddress, index, result);
        }
        catch
        {
            return false;
        }
        return true;
    }

    public bool Close()
    {
        this._master?.Dispose();
        this._master = null;
        this.IsCanceled = true;
        return true;
    }

    private volatile bool IsCanceled = false;

    private readonly Semaphore semaphore = new(1, 1);

    private void TryReconnect()
    {
        if (!semaphore.WaitOne(0))
            return;
        this._master?.Disconnect();
        this._connect?.DisConnect(this._ip, this._port);
        Task.Factory.StartNew(() =>
        {
            IsCanceled = false;
            this._master ??= new()
            {
                ReadTimeout = 500,
                ConnectTimeout = 500
            };

            try
            {
                while (true)
                {
                    if (this.IsCanceled)
                        return;
                    try
                    {
                        this._master.Connect(EndPoint!);
                        break;
                    }
                    catch
                    {
                        Thread.Sleep(1000);
                    }
                }

                this._connect?.Connect(this._ip, this._port);
            }
            finally
            {
                semaphore.Release();
            }
        });
    }
}
