dorisoy_HardwareMonitor/LibreHardwareMonitorLib/Hardware/Cpu/CpuGroup.cs
// 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.Collections.Generic;
using System.Globalization;
using System.Text;

namespace LibreHardwareMonitor.Hardware.CPU
{
    internal class CpuGroup : IGroup
    {
        private readonly List<GenericCpu> _hardware = new List<GenericCpu>();
        private readonly CpuId[][][] _threads;

        public CpuGroup(ISettings settings)
        {
            CpuId[][] processorThreads = GetProcessorThreads();
            _threads = new CpuId[processorThreads.Length][][];

            int index = 0;
            foreach (CpuId[] threads in processorThreads)
            {
                if (threads.Length == 0)
                    continue;


                CpuId[][] coreThreads = GroupThreadsByCore(threads);
                _threads[index] = coreThreads;

                switch (threads[0].Vendor)
                {
                    case Vendor.Intel:
                        _hardware.Add(new IntelCpu(index, coreThreads, settings));
                        break;
                    case Vendor.AMD:
                        switch (threads[0].Family)
                        {
                            case 0x0F:
                                _hardware.Add(new Amd0FCpu(index, coreThreads, settings));
                                break;
                            case 0x10:
                            case 0x11:
                            case 0x12:
                            case 0x14:
                            case 0x15:
                            case 0x16:
                                _hardware.Add(new Amd10Cpu(index, coreThreads, settings));
                                break;
                            case 0x17:
                            case 0x19:
                                _hardware.Add(new Amd17Cpu(index, coreThreads, settings));
                                break;
                            default:
                                _hardware.Add(new GenericCpu(index, coreThreads, settings));
                                break;
                        }

                        break;
                    default:
                        _hardware.Add(new GenericCpu(index, coreThreads, settings));
                        break;
                }

                index++;
            }
        }

        public IReadOnlyList<IHardware> Hardware => _hardware;

        public string GetReport()
        {
            if (_threads == null)
                return null;


            StringBuilder r = new StringBuilder();
            r.AppendLine("CPUID");
            r.AppendLine();
            for (int i = 0; i < _threads.Length; i++)
            {
                r.AppendLine("Processor " + i);
                r.AppendLine();
                r.AppendFormat("Processor Vendor: {0}{1}", _threads[i][0][0].Vendor, Environment.NewLine);
                r.AppendFormat("Processor Brand: {0}{1}", _threads[i][0][0].BrandString, Environment.NewLine);
                r.AppendFormat("Family: 0x{0}{1}", _threads[i][0][0].Family.ToString("X", CultureInfo.InvariantCulture), Environment.NewLine);
                r.AppendFormat("Model: 0x{0}{1}", _threads[i][0][0].Model.ToString("X", CultureInfo.InvariantCulture), Environment.NewLine);
                r.AppendFormat("Stepping: 0x{0}{1}", _threads[i][0][0].Stepping.ToString("X", CultureInfo.InvariantCulture), Environment.NewLine);
                r.AppendLine();

                r.AppendLine("CPUID Return Values");
                r.AppendLine();
                for (int j = 0; j < _threads[i].Length; j++)
                {
                    for (int k = 0; k < _threads[i][j].Length; k++)
                    {
                        r.AppendLine(" CPU Group: " + _threads[i][j][k].Group);
                        r.AppendLine(" CPU Thread: " + _threads[i][j][k].Thread);
                        r.AppendLine(" APIC ID: " + _threads[i][j][k].ApicId);
                        r.AppendLine(" Processor ID: " + _threads[i][j][k].ProcessorId);
                        r.AppendLine(" Core ID: " + _threads[i][j][k].CoreId);
                        r.AppendLine(" Thread ID: " + _threads[i][j][k].ThreadId);
                        r.AppendLine();
                        r.AppendLine(" Function  EAX       EBX       ECX       EDX");
                        AppendCpuidData(r, _threads[i][j][k].Data, CpuId.CPUID_0);
                        AppendCpuidData(r, _threads[i][j][k].ExtData, CpuId.CPUID_EXT);
                        r.AppendLine();
                    }
                }
            }

            return r.ToString();
        }

        public void Close()
        {
            foreach (GenericCpu cpu in _hardware)
            {
                cpu.Close();
            }
        }

        private static CpuId[][] GetProcessorThreads()
        {
            List<CpuId> threads = new List<CpuId>();
            
            for (int i = 0; i < ThreadAffinity.ProcessorGroupCount; i++)
            {
                for (int j = 0; j < 64; j++)
                {
                    try
                    {
                        if (!ThreadAffinity.IsValid(GroupAffinity.Single((ushort)i, j)))
                            continue;


                        var cpuid = CpuId.Get(i, j);
                        if (cpuid != null)
                            threads.Add(cpuid);
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        // All cores found.
                        break;
                    }
                }
            }


            SortedDictionary<uint, List<CpuId>> processors = new SortedDictionary<uint, List<CpuId>>();
            foreach (CpuId thread in threads)
            {
                processors.TryGetValue(thread.ProcessorId, out List<CpuId> list);
                if (list == null)
                {
                    list = new List<CpuId>();
                    processors.Add(thread.ProcessorId, list);
                }

                list.Add(thread);
            }

            CpuId[][] processorThreads = new CpuId[processors.Count][];
            int index = 0;
            foreach (List<CpuId> list in processors.Values)
            {
                processorThreads[index] = list.ToArray();
                index++;
            }

            return processorThreads;
        }

        private static CpuId[][] GroupThreadsByCore(IEnumerable<CpuId> threads)
        {
            SortedDictionary<uint, List<CpuId>> cores = new SortedDictionary<uint, List<CpuId>>();
            foreach (CpuId thread in threads)
            {
                cores.TryGetValue(thread.CoreId, out List<CpuId> coreList);
                if (coreList == null)
                {
                    coreList = new List<CpuId>();
                    cores.Add(thread.CoreId, coreList);
                }

                coreList.Add(thread);
            }

            CpuId[][] coreThreads = new CpuId[cores.Count][];
            int index = 0;
            foreach (List<CpuId> list in cores.Values)
            {
                coreThreads[index] = list.ToArray();
                index++;
            }

            return coreThreads;
        }

        private static void AppendCpuidData(StringBuilder r, uint[,] data, uint offset)
        {
            for (int i = 0; i < data.GetLength(0); i++)
            {
                r.Append(" ");
                r.Append((i + offset).ToString("X8", CultureInfo.InvariantCulture));
                for (int j = 0; j < 4; j++)
                {
                    r.Append("  ");
                    r.Append(data[i, j].ToString("X8", CultureInfo.InvariantCulture));
                }

                r.AppendLine();
            }
        }
    }
}
