// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using LibreHardwareMonitor.PawnIo;

namespace LibreHardwareMonitor.Hardware.Cpu;

internal sealed class IntelCpu : GenericCpu
{
    private readonly Sensor _busClock;
    private readonly Sensor _coreAvg;
    private readonly Sensor[] _coreClocks;
    private readonly Sensor _coreMax;
    private readonly Sensor[] _coreTemperatures;
    private readonly Sensor[] _coreVIDs;
    private readonly Sensor _coreVoltage;
    private readonly Sensor[] _distToTjMaxTemperatures;

    private readonly string[] _coreNames;

    private readonly uint[] _energyStatusMsrs = { MSR_PKG_ENERGY_STATUS, MSR_PP0_ENERGY_STATUS, MSR_PP1_ENERGY_STATUS, MSR_DRAM_ENERGY_STATUS, MSR_PLATFORM_ENERGY_STATUS };
    private readonly uint[] _lastEnergyConsumed;
    private readonly DateTime[] _lastEnergyTime;

    private readonly MicroArchitecture _microArchitecture;
    private readonly Sensor _packageTemperature;
    private readonly Sensor[] _powerSensors;
    private readonly double _timeStampCounterMultiplier;

    private readonly IntelMsr _pawnModule;

    public IntelCpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : base(processorIndex, cpuId, settings)
    {
        if (Software.OperatingSystem.IsUnix)
            return;

        _pawnModule = new IntelMsr();

        uint eax;

        // set tjMax
        float[] tjMax;
        switch (_family)
        {
            case 0x06:
                {
                    switch (_model)
                    {
                        case 0x0F: // Intel Core 2 (65nm)
                            _microArchitecture = MicroArchitecture.Core;
                            tjMax = _stepping switch
                            {
                                // B2
                                0x06 => _coreCount switch
                                {
                                    2 => Floats(80 + 10),
                                    4 => Floats(90 + 10),
                                    _ => Floats(85 + 10)
                                },
                                // G0
                                0x0B => Floats(90 + 10),
                                // M0
                                0x0D => Floats(85 + 10),
                                _ => Floats(85 + 10)
                            };
                            break;

                        case 0x17: // Intel Core 2 (45nm)
                            _microArchitecture = MicroArchitecture.Core;
                            tjMax = Floats(100);
                            break;

                        case 0x1C: // Intel Atom (45nm)
                            _microArchitecture = MicroArchitecture.Atom;
                            tjMax = _stepping switch
                            {
                                // C0
                                0x02 => Floats(90),
                                // A0, B0
                                0x0A => Floats(100),
                                _ => Floats(90)
                            };
                            break;

                        case 0x1A: // Intel Core i7 LGA1366 (45nm)
                        case 0x1E: // Intel Core i5, i7 LGA1156 (45nm)
                        case 0x1F: // Intel Core i5, i7
                        case 0x25: // Intel Core i3, i5, i7 LGA1156 (32nm)
                        case 0x2C: // Intel Core i7 LGA1366 (32nm) 6 Core
                        case 0x2E: // Intel Xeon Processor 7500 series (45nm)
                        case 0x2F: // Intel Xeon Processor (32nm)
                            _microArchitecture = MicroArchitecture.Nehalem;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x2A: // Intel Core i5, i7 2xxx LGA1155 (32nm)
                        case 0x2D: // Next Generation Intel Xeon, i7 3xxx LGA2011 (32nm)
                            _microArchitecture = MicroArchitecture.SandyBridge;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x3A: // Intel Core i5, i7 3xxx LGA1155 (22nm)
                        case 0x3E: // Intel Core i7 4xxx LGA2011 (22nm)
                            _microArchitecture = MicroArchitecture.IvyBridge;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x3C: // Intel Core i5, i7 4xxx LGA1150 (22nm)
                        case 0x3F: // Intel Xeon E5-2600/1600 v3, Core i7-59xx
                        // LGA2011-v3, Haswell-E (22nm)
                        case 0x45: // Intel Core i5, i7 4xxxU (22nm)
                        case 0x46:
                            _microArchitecture = MicroArchitecture.Haswell;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x3D: // Intel Core M-5xxx (14nm)
                        case 0x47: // Intel i5, i7 5xxx, Xeon E3-1200 v4 (14nm)
                        case 0x4F: // Intel Xeon E5-26xx v4
                        case 0x56: // Intel Xeon D-15xx
                            _microArchitecture = MicroArchitecture.Broadwell;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x36: // Intel Atom S1xxx, D2xxx, N2xxx (32nm)
                            _microArchitecture = MicroArchitecture.Atom;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x37: // Intel Atom E3xxx, Z3xxx (22nm)
                        case 0x4A:
                        case 0x4D: // Intel Atom C2xxx (22nm)
                        case 0x5A:
                        case 0x5D:
                            _microArchitecture = MicroArchitecture.Silvermont;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x4E:
                        case 0x5E: // Intel Core i5, i7 6xxxx LGA1151 (14nm)
                        case 0x55: // Intel Core X i7, i9 7xxx LGA2066 (14nm)
                            _microArchitecture = MicroArchitecture.Skylake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x4C: // Intel Airmont (Cherry Trail, Braswell)
                            _microArchitecture = MicroArchitecture.Airmont;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x8E: // Intel Core i5, i7 7xxxx (14nm) (Kaby Lake) and 8xxxx (14nm++) (Coffee Lake)
                        case 0x9E:
                            _microArchitecture = MicroArchitecture.KabyLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x5C: // Goldmont (Apollo Lake)
                        case 0x5F: // (Denverton)
                            _microArchitecture = MicroArchitecture.Goldmont;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x7A: // Goldmont plus (Gemini Lake)
                            _microArchitecture = MicroArchitecture.GoldmontPlus;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x66: // Intel Core i3 8xxx (10nm) (Cannon Lake)
                            _microArchitecture = MicroArchitecture.CannonLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x7D: // Intel Core i3, i5, i7 10xxx (10nm) (Ice Lake)
                        case 0x7E:
                        case 0x6A: // Ice Lake server
                        case 0x6C:
                            _microArchitecture = MicroArchitecture.IceLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0xA5:
                        case 0xA6: // Intel Core i3, i5, i7 10xxxU (14nm)
                            _microArchitecture = MicroArchitecture.CometLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x86: // Tremont (10nm) (Elkhart Lake, Skyhawk Lake)
                            _microArchitecture = MicroArchitecture.Tremont;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x8C: // Tiger Lake (Intel 10 nm SuperFin, Gen. 11)
                        case 0x8D:
                            _microArchitecture = MicroArchitecture.TigerLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x97: // Alder Lake (Intel 7 (10ESF), Gen. 12)
                        case 0x9A: // Alder Lake-L (Intel 7 (10ESF), Gen. 12)
                        case 0xBE: // Alder Lake-N (Intel 7 (10ESF), Gen. 12)
                            _microArchitecture = MicroArchitecture.AlderLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0xB7: // Raptor Lake (Intel 7 (10ESF), Gen. 13)
                        case 0xBA: // Raptor Lake-P (Intel 7 (10ESF), Gen. 13)
                        case 0xBF: // Raptor Lake-N (Intel 7 (10ESF), Gen. 13)
                            _microArchitecture = MicroArchitecture.RaptorLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0xAC: // Meteor Lake (Intel 4, TSMC N5/N6, Gen. 14)
                        case 0xAA: // Meteor Lake-L (Intel 4, TSMC N5/N6, Gen. 14)
                            _microArchitecture = MicroArchitecture.MeteorLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x9C: // Jasper Lake (10nm)
                            _microArchitecture = MicroArchitecture.JasperLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0xA7: // Intel Core i5, i6, i7 11xxx (14nm) (Rocket Lake)
                            _microArchitecture = MicroArchitecture.RocketLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0xB5: // Intel Core Ultra 5/7 200 Series ArrowLake-U
                        case 0xC5: // Intel Core Ultra 9 200 Series ArrowLake
                        case 0xC6: // Intel Core Ultra 7 200 Series ArrowLake
                            _microArchitecture = MicroArchitecture.ArrowLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0xBD: // Intel Core Ultra 5/7 200 Series LunarLake
                            _microArchitecture = MicroArchitecture.LunarLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x8F: // Intel Xeon W5-3435X // SapphireRapids 
                            _microArchitecture = MicroArchitecture.SapphireRapids;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0x96: // Intel Celeron ElkhartLake 
                            _microArchitecture = MicroArchitecture.ElkhartLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        case 0xCC: // Intel Panther Lake.
                            _microArchitecture = MicroArchitecture.PantherLake;
                            tjMax = GetTjMaxFromMsr();
                            break;

                        default:
                            _microArchitecture = MicroArchitecture.Unknown;
                            tjMax = Floats(100);
                            break;
                    }
                }

                break;
            case 0x0F:
                switch (_model)
                {
                    case 0x00: // Pentium 4 (180nm)
                    case 0x01: // Pentium 4 (130nm)
                    case 0x02: // Pentium 4 (130nm)
                    case 0x03: // Pentium 4, Celeron D (90nm)
                    case 0x04: // Pentium 4, Pentium D, Celeron D (90nm)
                    case 0x06: // Pentium 4, Pentium D, Celeron D (65nm)
                        _microArchitecture = MicroArchitecture.NetBurst;
                        tjMax = Floats(100);
                        break;

                    default:
                        _microArchitecture = MicroArchitecture.Unknown;
                        tjMax = Floats(100);
                        break;
                }

                break;
            default:
                _microArchitecture = MicroArchitecture.Unknown;
                tjMax = Floats(100);
                break;
        }

        // set timeStampCounterMultiplier
        switch (_microArchitecture)
        {
            case MicroArchitecture.Atom:
            case MicroArchitecture.Core:
            case MicroArchitecture.NetBurst:
                if (_pawnModule.ReadMsr(IA32_PERF_STATUS, out uint _, out uint edx))
                    _timeStampCounterMultiplier = ((edx >> 8) & 0x1f) + (0.5 * ((edx >> 14) & 1));

                break;
            case MicroArchitecture.Airmont:
            case MicroArchitecture.AlderLake:
            case MicroArchitecture.ArrowLake:
            case MicroArchitecture.Broadwell:
            case MicroArchitecture.CannonLake:
            case MicroArchitecture.CometLake:
            case MicroArchitecture.Goldmont:
            case MicroArchitecture.GoldmontPlus:
            case MicroArchitecture.Haswell:
            case MicroArchitecture.IceLake:
            case MicroArchitecture.IvyBridge:
            case MicroArchitecture.JasperLake:
            case MicroArchitecture.KabyLake:
            case MicroArchitecture.LunarLake:
            case MicroArchitecture.Nehalem:
            case MicroArchitecture.MeteorLake:
            case MicroArchitecture.PantherLake:
            case MicroArchitecture.RaptorLake:
            case MicroArchitecture.RocketLake:
            case MicroArchitecture.SandyBridge:
            case MicroArchitecture.Silvermont:
            case MicroArchitecture.Skylake:
            case MicroArchitecture.TigerLake:
            case MicroArchitecture.SapphireRapids:
            case MicroArchitecture.ElkhartLake:
            case MicroArchitecture.Tremont:
                if (_pawnModule.ReadMsr(MSR_PLATFORM_INFO, out eax, out uint _))
                    _timeStampCounterMultiplier = (eax >> 8) & 0xff;

                break;
            default:
                _timeStampCounterMultiplier = 0;
                break;
        }

        // Initialize core names
        _coreNames = new string[_coreCount];
        int pCoreIndex = 1;
        int eCoreIndex = 1;

        for (int i = 0; i < _coreCount; i++)
        {
            if (_cpuId.Length > i && _cpuId[i].Length > 0)
            {
                CoreType coreType = _cpuId[i][0].CoreType;

                _coreNames[i] = coreType switch
                {
                    CoreType.Performance => $"P-Core #{pCoreIndex++}",
                    CoreType.Efficient => $"E-Core #{eCoreIndex++}",
                    _ => CoreString(i)
                };
            }
            else
            {
                _coreNames[i] = CoreString(i);
            }
        }

        int coreSensorId = 0;

        //core temp avg and max value
        //is only available when the cpu has more than 1 core
        if (cpuId[0][0].Data.GetLength(0) > 6 && (cpuId[0][0].Data[6, 0] & 0x40) != 0 && _microArchitecture != MicroArchitecture.Unknown && _coreCount > 1)
        {
            _coreMax = new Sensor("Core Max", coreSensorId, SensorType.Temperature, this, settings);
            ActivateSensor(_coreMax);
            coreSensorId++;

            _coreAvg = new Sensor("Core Average", coreSensorId, SensorType.Temperature, this, settings);
            ActivateSensor(_coreAvg);
            coreSensorId++;
        }
        else
        {
            _coreMax = null;
            _coreAvg = null;
        }

        // check if processor supports a digital thermal sensor at core level
        if (cpuId[0][0].Data.GetLength(0) > 6 && (cpuId[0][0].Data[6, 0] & 1) != 0 && _microArchitecture != MicroArchitecture.Unknown)
        {
            _coreTemperatures = new Sensor[_coreCount];
            for (int i = 0; i < _coreTemperatures.Length; i++)
            {
                _coreTemperatures[i] = new Sensor(_coreNames[i],
                    coreSensorId,
                    SensorType.Temperature,
                    this,
                    [
                        new ParameterDescription("TjMax [°C]", "TjMax temperature of the core sensor.\n" + "Temperature = TjMax - TSlope * Value.", tjMax[i]),
                        new ParameterDescription("TSlope [°C]", "Temperature slope of the digital thermal sensor.\n" + "Temperature = TjMax - TSlope * Value.", 1)
                    ],
                    settings);

                ActivateSensor(_coreTemperatures[i]);
                coreSensorId++;
            }
        }
        else
        {
            _coreTemperatures = [];
        }

        // check if processor supports a digital thermal sensor at package level
        if (cpuId[0][0].Data.GetLength(0) > 6 && (cpuId[0][0].Data[6, 0] & 0x40) != 0 && _microArchitecture != MicroArchitecture.Unknown)
        {
            _packageTemperature = new Sensor("CPU Package",
                                             coreSensorId,
                                             SensorType.Temperature,
                                             this,
                                             [
                                                 new ParameterDescription("TjMax [°C]", "TjMax temperature of the package sensor.\n" + "Temperature = TjMax - TSlope * Value.", tjMax[0]),
                                                 new ParameterDescription("TSlope [°C]", "Temperature slope of the digital thermal sensor.\n" + "Temperature = TjMax - TSlope * Value.", 1)
                                             ],
                                             settings);

            ActivateSensor(_packageTemperature);
            coreSensorId++;
        }

        // dist to tjmax sensor
        if (cpuId[0][0].Data.GetLength(0) > 6 && (cpuId[0][0].Data[6, 0] & 1) != 0 && _microArchitecture != MicroArchitecture.Unknown)
        {
            _distToTjMaxTemperatures = new Sensor[_coreCount];
            for (int i = 0; i < _distToTjMaxTemperatures.Length; i++)
            {
                _distToTjMaxTemperatures[i] = new Sensor(_coreNames[i] + " Distance to TjMax", coreSensorId, SensorType.Temperature, this, settings);
                ActivateSensor(_distToTjMaxTemperatures[i]);
                coreSensorId++;
            }
        }
        else
        {
            _distToTjMaxTemperatures = [];
        }

        _busClock = new Sensor("Bus Speed", 0, SensorType.Clock, this, settings);
        _coreClocks = new Sensor[_coreCount];
        for (int i = 0; i < _coreClocks.Length; i++)
        {
            _coreClocks[i] = new Sensor(_coreNames[i], i + 1, SensorType.Clock, this, settings);
            if (HasTimeStampCounter && _microArchitecture != MicroArchitecture.Unknown)
                ActivateSensor(_coreClocks[i]);
        }

        if (_microArchitecture is MicroArchitecture.Airmont or
            MicroArchitecture.AlderLake or
            MicroArchitecture.ArrowLake or
            MicroArchitecture.Broadwell or
            MicroArchitecture.CannonLake or
            MicroArchitecture.CometLake or
            MicroArchitecture.Goldmont or
            MicroArchitecture.GoldmontPlus or
            MicroArchitecture.Haswell or
            MicroArchitecture.IceLake or
            MicroArchitecture.IvyBridge or
            MicroArchitecture.JasperLake or
            MicroArchitecture.KabyLake or
            MicroArchitecture.LunarLake or
            MicroArchitecture.MeteorLake or
            MicroArchitecture.PantherLake or
            MicroArchitecture.RaptorLake or
            MicroArchitecture.RocketLake or
            MicroArchitecture.SandyBridge or
            MicroArchitecture.Silvermont or
            MicroArchitecture.Skylake or
            MicroArchitecture.TigerLake or
            MicroArchitecture.SapphireRapids or
            MicroArchitecture.ElkhartLake or
            MicroArchitecture.Tremont)
        {
            _powerSensors = new Sensor[_energyStatusMsrs.Length];
            _lastEnergyTime = new DateTime[_energyStatusMsrs.Length];
            _lastEnergyConsumed = new uint[_energyStatusMsrs.Length];

            if (_pawnModule.ReadMsr(MSR_RAPL_POWER_UNIT, out eax, out uint _))
            {
                EnergyUnitsMultiplier = _microArchitecture switch
                {
                    MicroArchitecture.Silvermont or MicroArchitecture.Airmont => 1.0e-6f * (1 << (int)((eax >> 8) & 0x1F)),
                    _ => 1.0f / (1 << (int)((eax >> 8) & 0x1F))
                };
            }

            if (EnergyUnitsMultiplier != 0)
            {
                string[] powerSensorLabels = ["CPU Package", "CPU Cores", "CPU Graphics", "CPU Memory", "CPU Platform"];

                for (int i = 0; i < _energyStatusMsrs.Length; i++)
                {
                    if (!_pawnModule.ReadMsr(_energyStatusMsrs[i], out eax, out uint _))
                        continue;

                    // Don't show the "GPU Graphics" sensor on windows, it will show up under the GPU instead.
                    if (i == 2 && !Software.OperatingSystem.IsUnix)
                        continue;

                    _lastEnergyTime[i] = DateTime.UtcNow;
                    _lastEnergyConsumed[i] = eax;
                    _powerSensors[i] = new Sensor(powerSensorLabels[i],
                                                  i,
                                                  SensorType.Power,
                                                  this,
                                                  settings);

                    ActivateSensor(_powerSensors[i]);
                }
            }
        }

        if (_pawnModule.ReadMsr(IA32_PERF_STATUS, out eax, out uint _) && ((eax >> 32) & 0xFFFF) > 0)
        {
            _coreVoltage = new Sensor("CPU Core", 0, SensorType.Voltage, this, settings);
            ActivateSensor(_coreVoltage);
        }

        _coreVIDs = new Sensor[_coreCount];
        for (int i = 0; i < _coreVIDs.Length; i++)
        {
            _coreVIDs[i] = new Sensor(_coreNames[i], i + 1, SensorType.Voltage, this, settings);
            ActivateSensor(_coreVIDs[i]);
        }

        Update();
    }

    public float EnergyUnitsMultiplier { get; }

    private float[] Floats(float f)
    {
        float[] result = new float[_coreCount];
        for (int i = 0; i < _coreCount; i++)
            result[i] = f;

        return result;
    }

    private float[] GetTjMaxFromMsr()
    {
        float[] result = new float[_coreCount];
        for (int i = 0; i < _coreCount; i++)
        {
            if (_pawnModule.ReadMsr(IA32_TEMPERATURE_TARGET, out uint eax, out uint _, _cpuId[i][0].Affinity))
                result[i] = (eax >> 16) & 0xFF;
            else
                result[i] = 100;
        }

        return result;
    }

    public override string GetReport()
    {
        StringBuilder r = new();
        r.Append(base.GetReport());
        r.Append("MicroArchitecture: ");
        r.AppendLine(_microArchitecture.ToString());
        r.Append("Time Stamp Counter Multiplier: ");
        r.AppendLine(_timeStampCounterMultiplier.ToString(CultureInfo.InvariantCulture));
        r.AppendLine();
        return r.ToString();
    }

    /// <inheritdoc />
    public override void Close()
    {
        base.Close();
        _pawnModule.Close();
    }

    public override void Update()
    {
        base.Update();

        float coreMax = float.MinValue;
        float coreAvg = 0;
        uint eax;

        for (int i = 0; i < _coreTemperatures?.Length; i++)
        {
            // if reading is valid
            if (_pawnModule.ReadMsr(IA32_THERM_STATUS_MSR, out eax, out _, _cpuId[i][0].Affinity) && (eax & 0x80000000) != 0)
            {
                // get the dist from tjMax from bits 22:16
                float deltaT = (eax & 0x007F0000) >> 16;
                float tjMax = _coreTemperatures[i].Parameters[0].Value;
                float tSlope = _coreTemperatures[i].Parameters[1].Value;
                _coreTemperatures[i].Value = tjMax - (tSlope * deltaT);

                coreAvg += (float)_coreTemperatures[i].Value;
                if (coreMax < _coreTemperatures[i].Value)
                    coreMax = (float)_coreTemperatures[i].Value;

                _distToTjMaxTemperatures[i].Value = deltaT;
            }
            else
            {
                _coreTemperatures[i].Value = null;
                _distToTjMaxTemperatures[i].Value = null;
            }
        }

        //calculate average cpu temperature over all cores
        if (_coreMax != null && coreMax != float.MinValue)
        {
            _coreMax.Value = coreMax;
            coreAvg /= _coreTemperatures.Length;
            _coreAvg.Value = coreAvg;
        }

        if (_packageTemperature != null)
        {
            // if reading is valid
            if (_pawnModule.ReadMsr(IA32_PACKAGE_THERM_STATUS, out eax, out _, _cpuId[0][0].Affinity) && (eax & 0x80000000) != 0)
            {
                // get the dist from tjMax from bits 22:16
                float deltaT = (eax & 0x007F0000) >> 16;
                float tjMax = _packageTemperature.Parameters[0].Value;
                float tSlope = _packageTemperature.Parameters[1].Value;
                _packageTemperature.Value = tjMax - (tSlope * deltaT);
            }
            else
            {
                _packageTemperature.Value = null;
            }
        }

        if (HasTimeStampCounter && _timeStampCounterMultiplier > 0)
        {
            double newBusClock = 0;
            for (int i = 0; i < _coreClocks.Length; i++)
            {
                System.Threading.Thread.Sleep(1);
                if (_pawnModule.ReadMsr(IA32_PERF_STATUS, out eax, out _, _cpuId[i][0].Affinity))
                {
                    newBusClock = TimeStampCounterFrequency / _timeStampCounterMultiplier;
                    switch (_microArchitecture)
                    {
                        case MicroArchitecture.Nehalem:
                            _coreClocks[i].Value = (float)((eax & 0xff) * newBusClock);
                            break;
                        case MicroArchitecture.Airmont:
                        case MicroArchitecture.AlderLake:
                        case MicroArchitecture.ArrowLake:
                        case MicroArchitecture.Broadwell:
                        case MicroArchitecture.CannonLake:
                        case MicroArchitecture.CometLake:
                        case MicroArchitecture.Goldmont:
                        case MicroArchitecture.GoldmontPlus:
                        case MicroArchitecture.Haswell:
                        case MicroArchitecture.IceLake:
                        case MicroArchitecture.IvyBridge:
                        case MicroArchitecture.JasperLake:
                        case MicroArchitecture.KabyLake:
                        case MicroArchitecture.LunarLake:
                        case MicroArchitecture.MeteorLake:
                        case MicroArchitecture.PantherLake:
                        case MicroArchitecture.RaptorLake:
                        case MicroArchitecture.RocketLake:
                        case MicroArchitecture.SandyBridge:
                        case MicroArchitecture.Silvermont:
                        case MicroArchitecture.Skylake:
                        case MicroArchitecture.TigerLake:
                        case MicroArchitecture.SapphireRapids:
                        case MicroArchitecture.ElkhartLake:
                        case MicroArchitecture.Tremont:
                            _coreClocks[i].Value = (float)(((eax >> 8) & 0xff) * newBusClock);
                            break;
                        default:
                            _coreClocks[i].Value = (float)((((eax >> 8) & 0x1f) + (0.5 * ((eax >> 14) & 1))) * newBusClock);
                            break;
                    }
                }
                else
                {
                    // if IA32_PERF_STATUS is not available, assume TSC frequency
                    _coreClocks[i].Value = (float)TimeStampCounterFrequency;
                }
            }

            if (newBusClock > 0)
            {
                _busClock.Value = (float)newBusClock;
                ActivateSensor(_busClock);
            }
        }

        if (_powerSensors != null)
        {
            foreach (Sensor sensor in _powerSensors)
            {
                if (sensor == null)
                    continue;

                if (!_pawnModule.ReadMsr(_energyStatusMsrs[sensor.Index], out eax, out _))
                    continue;

                DateTime time = DateTime.UtcNow;
                uint energyConsumed = eax;
                float deltaTime = (float)(time - _lastEnergyTime[sensor.Index]).TotalSeconds;
                if (deltaTime < 0.01)
                    continue;

                sensor.Value = EnergyUnitsMultiplier * unchecked(energyConsumed - _lastEnergyConsumed[sensor.Index]) / deltaTime;
                _lastEnergyTime[sensor.Index] = time;
                _lastEnergyConsumed[sensor.Index] = energyConsumed;
            }
        }

        if (_coreVoltage != null && _pawnModule.ReadMsr(IA32_PERF_STATUS, out eax, out uint edx))
        {
            uint vidBits = edx & 0xFFFF;        
            if (vidBits == 0)
                vidBits = edx & 0xFFFF;
        
            if (vidBits > 0)
            {
                _coreVoltage.Value = vidBits / 8192.0f;
            }
            else
            {
                _coreVoltage.Value = 0;
            }
        }

        for (int i = 0; i < _coreVIDs?.Length; i++)
        {

            if (_pawnModule.ReadMsr(IA32_PERF_STATUS, out eax, out edx, _cpuId[i][0].Affinity))
            {
                uint vidBitsLoop = edx & 0xFFFF;        
                if (vidBitsLoop == 0)
                    vidBitsLoop = eax & 0xFFFF;
        
                if (vidBitsLoop > 0)
                {
                    _coreVIDs[i].Value = vidBitsLoop / 8192.0f;
                    ActivateSensor(_coreVIDs[i]);
                }
                else
                {
                    DeactivateSensor(_coreVIDs[i]);
                }
            }
            else
            {
                DeactivateSensor(_coreVIDs[i]);
            }
        }
    }

    [SuppressMessage("ReSharper", "IdentifierTypo")]
    private enum MicroArchitecture
    {
        Airmont,
        AlderLake,
        Atom,
        ArrowLake, // Gen. 15 (0xC6, -H = 0xC5)
        Broadwell,
        CannonLake,
        CometLake,
        Core,
        Goldmont,
        GoldmontPlus,
        Haswell,
        IceLake,
        IvyBridge,
        JasperLake,
        KabyLake,
        LunarLake,
        Nehalem,
        NetBurst,
        MeteorLake,
        PantherLake,
        RocketLake,
        SandyBridge,
        Silvermont,
        Skylake,
        TigerLake,
        Tremont,
        RaptorLake,
        SapphireRapids,
        ElkhartLake,
        Unknown
    }

    // ReSharper disable InconsistentNaming
    private const uint IA32_PACKAGE_THERM_STATUS = 0x1B1;
    private const uint IA32_PERF_STATUS = 0x0198;
    private const uint IA32_TEMPERATURE_TARGET = 0x01A2;
    private const uint IA32_THERM_STATUS_MSR = 0x019C;

    private const uint MSR_DRAM_ENERGY_STATUS = 0x619;
    private const uint MSR_PKG_ENERGY_STATUS = 0x611;
    private const uint MSR_PLATFORM_INFO = 0xCE;
    private const uint MSR_PP0_ENERGY_STATUS = 0x639;
    private const uint MSR_PP1_ENERGY_STATUS = 0x641;
    private const uint MSR_PLATFORM_ENERGY_STATUS = 0x64D;

    private const uint MSR_RAPL_POWER_UNIT = 0x606;
    // ReSharper restore InconsistentNaming
}
