#nullable enable

using System;
using System.Globalization;
using LibreHardwareMonitor.Hardware;

public class Telemetry : IDisposable
{
    private const int UpdateRateDefaultMs = 1000;
    public int UpdateRateMs => UpdateRateDefaultMs;

    private readonly UdpSender udp = new UdpSender();
    private Computer computer = new Computer();

    private IHardware? cpuHw;
    private IHardware? gpuHw;
    private IHardware? memHw;

    private ISensor[] cpuLoadSensors = Array.Empty<ISensor>();
    private ISensor? cpuTempSensor;

    private ISensor? gpu3DLoadSensor;
    private ISensor? gpuTempSensor;
    private ISensor? gpuVramUsedSensor;
    private ISensor? gpuVramTotalSensor;

    private ISensor? memUsedSensor;
    private ISensor? memAvailSensor;

    private static readonly CultureInfo CI = CultureInfo.InvariantCulture;

    // Restart LHM every 30 minutes
    private readonly TimeSpan restartInterval = TimeSpan.FromMinutes(30);
    private DateTime lastRestart = DateTime.UtcNow;

    public void Initialize()
    {
        ConfigureComputer();
        computer.Open();
        CacheHardwareAndSensors();
    }

    private void ConfigureComputer()
    {
        computer.IsCpuEnabled = true;
        computer.IsGpuEnabled = true;
        computer.IsMemoryEnabled = true;

        // True minimal mode
        computer.IsMotherboardEnabled = false;
        computer.IsControllerEnabled = false;
        computer.IsNetworkEnabled = false;
        computer.IsStorageEnabled = false;
        computer.IsBatteryEnabled = false;
    }

    private void RestartComputerIfNeeded()
    {
        if (DateTime.UtcNow - lastRestart < restartInterval)
            return;

        lastRestart = DateTime.UtcNow;

        try
        {
            computer.Close();
        }
        catch
        {
            // ignore
        }

        computer = new Computer();
        ConfigureComputer();
        computer.Open();
        CacheHardwareAndSensors();
    }

    private void CacheHardwareAndSensors()
    {
        cpuHw = null;
        gpuHw = null;
        memHw = null;

        cpuLoadSensors = Array.Empty<ISensor>();
        cpuTempSensor = null;

        gpu3DLoadSensor = null;
        gpuTempSensor = null;
        gpuVramUsedSensor = null;
        gpuVramTotalSensor = null;

        memUsedSensor = null;
        memAvailSensor = null;

        foreach (var hw in computer.Hardware)
        {
            hw.Update();

            switch (hw.HardwareType)
            {
                case HardwareType.Cpu:
                    cpuHw = hw;
                    CacheCpuSensors(hw);
                    break;

                case HardwareType.GpuNvidia:
                case HardwareType.GpuAmd:
                case HardwareType.GpuIntel:
                    gpuHw = hw;
                    CacheGpuSensors(hw);
                    break;

                case HardwareType.Memory:
                    memHw = hw;
                    CacheMemorySensors(hw);
                    break;
            }
        }
    }

    private void CacheCpuSensors(IHardware hw)
    {
        var loads = new System.Collections.Generic.List<ISensor>();

        foreach (var s in hw.Sensors)
        {
            if (s.SensorType == SensorType.Load &&
                s.Name.Contains("CPU Core"))
                loads.Add(s);

            if (s.SensorType == SensorType.Temperature)
            {
                if (s.Name == "Core (Tctl/Tdie)")
                    cpuTempSensor = s;
                else if (cpuTempSensor == null)
                    cpuTempSensor = s;
            }
        }

        cpuLoadSensors = loads.ToArray();
    }

    private void CacheGpuSensors(IHardware hw)
    {
        foreach (var s in hw.Sensors)
        {
            if (s.SensorType == SensorType.Load)
            {
                if (s.Name == "D3D 3D")
                    gpu3DLoadSensor = s;
                else if (gpu3DLoadSensor == null && s.Name == "GPU Core")
                    gpu3DLoadSensor = s;
            }

            if (s.SensorType == SensorType.Temperature)
            {
                if (s.Name == "GPU Core")
                    gpuTempSensor = s;
                else if (gpuTempSensor == null)
                    gpuTempSensor = s;
            }

            if (s.SensorType == SensorType.SmallData)
            {
                if (s.Name == "GPU Memory Used")
                    gpuVramUsedSensor = s;

                if (s.Name == "GPU Memory Total")
                    gpuVramTotalSensor = s;
            }
        }
    }

    private void CacheMemorySensors(IHardware hw)
    {
        foreach (var s in hw.Sensors)
        {
            if (s.SensorType == SensorType.Data && s.Name == "Memory Used")
                memUsedSensor = s;

            if (s.SensorType == SensorType.Data && s.Name == "Memory Available")
                memAvailSensor = s;
        }
    }

    public void UpdateAndSend()
    {
        RestartComputerIfNeeded();

        //cpuHw?.Update();
        //gpuHw?.Update();
        //memHw?.Update();
		// Single-pass update: update ALL enabled hardware in one loop
		foreach (var hw in computer.Hardware)
			hw.Update();

        float cpu = GetCpuLoadPercent();
        float cpuTemp = GetCpuTemperaturePercent();
        float mem = GetMemoryUsagePercent();
        float gpu3d = GetGpu3DLoad();
        float gpuTemp = GetGpuTemperaturePercent();
        float vram = GetGpuVramPercent();

        float[] packet =
        {
            cpu,
            cpuTemp,
            mem,
            gpu3d,
            gpuTemp,
            vram,
            0f,
            0f
        };

        udp.SendFloats(packet);
    }

    private float GetCpuLoadPercent()
    {
        if (cpuLoadSensors.Length == 0) return 0;

        float total = 0;
        int count = 0;

        foreach (var s in cpuLoadSensors)
        {
            total += s.Value ?? 0;
            count++;
        }

        return count == 0 ? 0 : total / count;
    }

    private float GetCpuTemperaturePercent()
    {
        float t = cpuTempSensor?.Value ?? 0;
        return Math.Clamp(t, 0, 100);
    }

    private float GetGpu3DLoad()
    {
        return gpu3DLoadSensor?.Value ?? 0;
    }

    private float GetGpuTemperaturePercent()
    {
        float t = gpuTempSensor?.Value ?? 0;
        return Math.Clamp(t, 0, 100);
    }

    private float GetGpuVramPercent()
    {
        float used = gpuVramUsedSensor?.Value ?? 0;
        float total = gpuVramTotalSensor?.Value ?? 0;

        if (total <= 0) return 0;
        return (used / total) * 100f;
    }

    private float GetMemoryUsagePercent()
    {
        float used = memUsedSensor?.Value ?? 0;
        float avail = memAvailSensor?.Value ?? 0;

        float total = used + avail;
        if (total <= 0) return 0;

        return (used / total) * 100f;
    }

    public void Dispose()
    {
        udp.Dispose();
        computer.Close();
    }
}
