using ASTM_D7896_Tester.Models;
using Modbus.Device;
using System;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace ASTM_D7896_Tester.Services;

public class PlcService : IPlcService
{
    private bool _isConnected = false;
    private readonly AppConfig _config;
    private TcpClient _tcpClient;
    private IModbusMaster _master;

    public PlcService(AppConfig config)
    {
        _config = config;
    }

    public Task<bool> ConnectAsync()
    {
        // 模拟连接
        _isConnected = true;
        return Task.FromResult(true);
    }

    public Task DisconnectAsync()
    {
        _isConnected = false;
        return Task.CompletedTask;
    }

    public Task<bool> IsConnectedAsync() => Task.FromResult(_isConnected);

    public async Task EnsureConnectedAsync(int retryCount = 3)
    {
        if (_tcpClient != null && _tcpClient.Connected)
            return;

        for (int i = 0; i < retryCount; i++)
        {
            try
            {
                _tcpClient?.Close();
                _tcpClient = new TcpClient();
                using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
                // 现在可以直接使用扩展方法
                await _tcpClient.ConnectAsync(_config.PlcConnection.IpAddress, _config.PlcConnection.Port).WithCancellation(cts.Token);
                _master = ModbusIpMaster.CreateIp(_tcpClient);
                await ConnectAsync();
                return;
            }
            catch (Exception ex) when (i < retryCount - 1)
            {
                await DisconnectAsync();
                System.Diagnostics.Debug.WriteLine($"连接失败，{500}ms 后重试... {ex.Message}");
                await Task.Delay(500);
            }
        }
        throw new Exception($"无法连接到 PLC ({_config.PlcConnection.IpAddress}:{_config.PlcConnection.Port})，请检查网络和 PLC 状态。");
    }

    // 读取两个连续的保持寄存器，转换为32位浮点数（假设大端模式）
    public async Task<float> ReadFloatAsync(ushort startAddress)
    {
        await EnsureConnectedAsync();
        var registers = await ReadHoldingRegistersAsync(startAddress, 2);
        return UshortToFloat(registers[1], registers[0]);
    }


    //public async Task<float> ReadPressureAsync() =>
    //    await ReadFloatAsync(_config.PressureRegister);

    public async Task<float> ReadWetFlowAsync(int stationId)
    {
        ushort startAddress = stationId switch
        {
            //1 => _config.WetFlowRegister,
            //2 => _config.WetFlowRegister2,
            //3 => _config.WetFlowRegister3,
        };
        return await ReadFloatAsync(startAddress);
    }

    public async Task<float> ReadDryFlowAsync(int stationId)
    {
        ushort startAddress = stationId switch
        {
            _ => throw new ArgumentException("Invalid station")
        };
        return await ReadFloatAsync(startAddress);
    }


    public async Task WriteCoilAsync(ushort coilAddress, bool value)
    {
        await EnsureConnectedAsync();
        await _master.WriteSingleCoilAsync(_config.PlcConnection.SlaveId, coilAddress, value);
    }

    public async Task WriteRegisterAsync(ushort registerAddress, ushort value)
    {
        await EnsureConnectedAsync();
        await Task.Delay(100);
        await _master.WriteSingleRegisterAsync(_config.PlcConnection.SlaveId, registerAddress, value);
    }


    public async Task<bool> ReadCoilAsync(ushort coilAddress)
    {
        await EnsureConnectedAsync();
        await Task.Delay(100);
        bool[] result = await _master?.ReadCoilsAsync(_config.PlcConnection.SlaveId, coilAddress, 1);
        return result[0];
    }

    public bool IsConnected => _tcpClient != null && _tcpClient.Connected;

    public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
    {
        await EnsureConnectedAsync();
        // await Task.Delay(100);
        return await _master.ReadHoldingRegistersAsync(_config.PlcConnection.SlaveId, startAddress, count);
    }

    public async Task WriteSingleRegisterAsync(ushort registerAddress, ushort value)
    {
        await EnsureConnectedAsync();
        int val = (int)value;
        await Task.Delay(100);
        await _master.WriteMultipleRegistersAsync(1, registerAddress, intToushorts(val));
    }

    public async Task WriteMultipleRegistersAsync(ushort registerAddress, float value)
    {
        await EnsureConnectedAsync();
        await Task.Delay(100);
        await _master.WriteMultipleRegistersAsync(_config.PlcConnection.SlaveId, registerAddress, SplitFloatToUShortArray((float)value));
    }

    /// <summary>
    /// Int转为ushort数组发送
    /// </summary>
    /// <param name="res"></param>
    /// <returns>返回ushort数组</returns>
    private ushort[] intToushorts(int res)
    {
        ushort ust1 = (ushort)(res >> 16);
        ushort ust2 = (ushort)res;
        return new ushort[] { ust2, ust1 };
    }


    /// <summary>
    /// Float转为Ushort数组发送
    /// </summary>
    /// <param name="value"></param>
    /// <returns>返回ushort数组</returns>
    public ushort[] SplitFloatToUShortArray(float value)
    {
        byte[] floatBytes = BitConverter.GetBytes(value);
        ushort[] ushortArray = new ushort[floatBytes.Length / 2];

        for (int i = 0, j = 0; i < floatBytes.Length; i += 2, j++)
        {
            ushortArray[j] = BitConverter.ToUInt16(floatBytes, i);
        }

        return ushortArray;
    }

    /// <summary>
    /// ushort转为float类型
    /// </summary>
    /// <param name="P1"></param>
    /// <param name="P2"></param>
    /// <returns>float型数据</returns>
    public float UshortToFloat(ushort P1, ushort P2)
    {
        int intSign, intSignRest, intExponent, intExponentRest;
        float faResult, faDigit;
        intSign = P1 / 32768;
        intSignRest = P1 % 32768;
        intExponent = intSignRest / 128;
        intExponentRest = intSignRest % 128;
        faDigit = (float)(intExponentRest * 65536 + P2) / 8388608;
        faResult = (float)Math.Pow(-1, intSign) * (float)Math.Pow(2, intExponent - 127) * (faDigit + 1);
        return faResult;
    }

    public void Dispose()
    {
        _master?.Dispose();
        _tcpClient?.Close();
        _tcpClient?.Dispose();
    }
}


public static class TaskExtensions
{
    public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
    {
        var tcs = new TaskCompletionSource<bool>();
        using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
        {
            if (task != await Task.WhenAny(task, tcs.Task))
                throw new OperationCanceledException(cancellationToken);
        }
        await task;
    }
}