dorisoy_HardwareMonitor/LibreHardwareMonitorLib/Hardware/Controller/Heatmaster/HeatmasterGroup.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.IO.Ports;
using System.Security;
using System.Text;
using System.Threading;
using Microsoft.Win32;

namespace LibreHardwareMonitor.Hardware.Controller.Heatmaster
{
    internal class HeatmasterGroup : IGroup
    {
        private readonly List<Heatmaster> _hardware = new List<Heatmaster>();
        private readonly StringBuilder _report = new StringBuilder();

        public HeatmasterGroup(ISettings settings)
        {
            // No implementation for Heatmaster on Unix systems
            if (Software.OperatingSystem.IsUnix)
                return;


            string[] portNames = GetRegistryPortNames();
            for (int i = 0; i < portNames.Length; i++)
            {
                bool isValid = false;
                try
                {
                    using (SerialPort serialPort = new SerialPort(portNames[i], 38400, Parity.None, 8, StopBits.One))
                    {
                        serialPort.NewLine = ((char)0x0D).ToString();
                        _report.Append("Port Name: ");
                        _report.AppendLine(portNames[i]);
                        try
                        {
                            serialPort.Open();
                        }
                        catch (UnauthorizedAccessException)
                        {
                            _report.AppendLine("Exception: Access Denied");
                        }

                        if (serialPort.IsOpen)
                        {
                            serialPort.DiscardInBuffer();
                            serialPort.DiscardOutBuffer();
                            serialPort.Write(new byte[] { 0xAA }, 0, 1);

                            int j = 0;
                            while (serialPort.BytesToRead == 0 && j < 10)
                            {
                                Thread.Sleep(20);
                                j++;
                            }

                            if (serialPort.BytesToRead > 0)
                            {
                                bool flag = false;
                                while (serialPort.BytesToRead > 0 && !flag)
                                {
                                    flag |= serialPort.ReadByte() == 0xAA;
                                }

                                if (flag)
                                {
                                    serialPort.WriteLine("[0:0]RH");
                                    try
                                    {
                                        int k = 0;
                                        int revision = 0;
                                        while (k < 5)
                                        {
                                            string line = ReadLine(serialPort, 100);
                                            if (line.StartsWith("-[0:0]RH:", StringComparison.Ordinal))
                                            {
                                                revision = int.Parse(line.Substring(9), CultureInfo.InvariantCulture);
                                                break;
                                            }

                                            k++;
                                        }

                                        isValid = revision == 770;
                                        if (!isValid)
                                        {
                                            _report.Append("Status: Wrong Hardware Revision " + revision.ToString(CultureInfo.InvariantCulture));
                                        }
                                    }
                                    catch (TimeoutException)
                                    {
                                        _report.AppendLine("Status: Timeout Reading Revision");
                                    }
                                }
                                else
                                {
                                    _report.AppendLine("Status: Wrong Startflag");
                                }
                            }
                            else
                            {
                                _report.AppendLine("Status: No Response");
                            }

                            serialPort.DiscardInBuffer();
                        }
                        else
                        {
                            _report.AppendLine("Status: Port not Open");
                        }
                    }
                }
                catch (Exception e)
                {
                    _report.AppendLine(e.ToString());
                }

                if (isValid)
                {
                    _report.AppendLine("Status: OK");
                    _hardware.Add(new Heatmaster(portNames[i], settings));
                }

                _report.AppendLine();
            }
        }

        public IReadOnlyList<IHardware> Hardware => _hardware;

        public string GetReport()
        {
            if (_report.Length > 0)
            {
                StringBuilder r = new StringBuilder();
                r.AppendLine("Serial Port Heatmaster");
                r.AppendLine();
                r.Append(_report);
                r.AppendLine();
                return r.ToString();
            }

            return null;
        }

        public void Close()
        {
            foreach (Heatmaster heatmaster in _hardware)
                heatmaster.Close();
        }

        private static string ReadLine(SerialPort port, int timeout)
        {
            int i = 0;
            StringBuilder builder = new StringBuilder();
            while (i < timeout)
            {
                while (port.BytesToRead > 0)
                {
                    byte b = (byte)port.ReadByte();
                    switch (b)
                    {
                        case 0xAA: return ((char)b).ToString();
                        case 0x0D: return builder.ToString();
                        default:
                            builder.Append((char)b);
                            break;
                    }
                }

                i++;
                Thread.Sleep(1);
            }

            throw new TimeoutException();
        }

        private static string[] GetRegistryPortNames()
        {
            List<string> result = new List<string>();
            string[] paths = { string.Empty, "&MI_00" };
            try
            {
                foreach (string path in paths)
                {
                    RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Enum\USB\VID_10C4&PID_EA60" + path);
                    if (key != null)
                    {
                        foreach (string subKeyName in key.GetSubKeyNames())
                        {
                            RegistryKey subKey = key.OpenSubKey(subKeyName + "\\" + "Device Parameters");
                            if (subKey?.GetValue("PortName") is string name && !result.Contains(name))
                                result.Add(name);
                        }
                    }
                }
            }
            catch (SecurityException)
            { }

            return result.ToArray();
        }
    }
}
