// 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.Drawing;
using System.Globalization;
using System.Text;
using LibreHardwareMonitor.Hardware;
using LibreHardwareMonitor.Windows.Forms.Utilities;

namespace LibreHardwareMonitor.Windows.Forms.UI;

public class SensorNode : Node
{
    private readonly PersistentSettings _settings;
    private readonly UnitManager _unitManager;
    private Color? _penColor;
    private bool _plot;

    public SensorNode(ISensor sensor, PersistentSettings settings, UnitManager unitManager)
    {
        Sensor = sensor;
        _settings = settings;
        _unitManager = unitManager;

        switch (sensor.SensorType)
        {
            case SensorType.Voltage:
                Format = "{0:F3} V";
                break;
            case SensorType.Current:
                Format = "{0:F3} A";
                break;
            case SensorType.Clock:
                Format = "{0:F1} MHz";
                break;
            case SensorType.Load:
                Format = "{0:F1} %";
                break;
            case SensorType.Temperature:
                Format = "{0:F1} °C";
                break;
            case SensorType.Fan:
                Format = "{0:F0} RPM";
                break;
            case SensorType.Flow:
                Format = "{0:F1} L/h";
                break;
            case SensorType.Control:
                Format = "{0:F1} %";
                break;
            case SensorType.Level:
                Format = "{0:F1} %";
                break;
            case SensorType.Power:
                Format = "{0:F1} W";
                break;
            case SensorType.Data:
                Format = "{0:F1} GB";
                break;
            case SensorType.SmallData:
                Format = "{0:F1} MB";
                break;
            case SensorType.Factor:
                Format = "{0:F3}";
                break;
            case SensorType.Frequency:
                Format = "{0:F1} Hz";
                break;
            case SensorType.Throughput:
                Format = "{0:F1} B/s";
                break;
            case SensorType.TimeSpan:
                Format = "{0:g}";
                break;
            case SensorType.Timing:
                Format = "{0:F3} ns";
                break;
            case SensorType.Energy:
                Format = "{0:F0} mWh";
                break;
            case SensorType.Noise:
                Format = "{0:F0} dBA";
                break;
            case SensorType.Conductivity:
                Format = "{0:F1} µS/cm";
                break;
            case SensorType.Humidity:
                Format = "{0:F0} %";
                break;
        }

        bool hidden = settings.GetValue(new Identifier(sensor.Identifier, "hidden").ToString(), sensor.IsDefaultHidden);
        base.IsVisible = !hidden;
        Plot = settings.GetValue(new Identifier(sensor.Identifier, "plot").ToString(), false);
        string id = new Identifier(sensor.Identifier, "penColor").ToString();

        if (settings.Contains(id))
            PenColor = settings.GetValue(id, Color.Black);
    }

    public event EventHandler PlotSelectionChanged;

    public string Format { get; set; } = "";

    public override bool IsVisible
    {
        get { return base.IsVisible; }
        set
        {
            base.IsVisible = value;
            _settings.SetValue(new Identifier(Sensor.Identifier, "hidden").ToString(), !value);
        }
    }

    public string Max
    {
        get { return ValueToString(Sensor.Max); }
    }

    public string Min
    {
        get { return ValueToString(Sensor.Min); }
    }

    public Color? PenColor
    {
        get { return _penColor; }
        set
        {
            _penColor = value;

            string id = new Identifier(Sensor.Identifier, "penColor").ToString();
            if (value.HasValue)
                _settings.SetValue(id, value.Value);
            else
                _settings.Remove(id);

            PlotSelectionChanged?.Invoke(this, null);
        }
    }

    public bool Plot
    {
        get { return _plot; }
        set
        {
            _plot = value;
            _settings.SetValue(new Identifier(Sensor.Identifier, "plot").ToString(), value);
            PlotSelectionChanged?.Invoke(this, null);
        }
    }

    public ISensor Sensor { get; }

    public override string Text
    {
        get { return Sensor.Name; }
        set { Sensor.Name = value; }
    }

    public override string ToolTip
    {
        get
        {
            StringBuilder stringBuilder = new();

            if (Sensor is ICriticalSensorLimits criticalSensorLimits)
                OptionallyAppendCriticalRange(stringBuilder, criticalSensorLimits.CriticalLowLimit, criticalSensorLimits.CriticalHighLimit, "critical");

            if (Sensor is ISensorLimits sensorLimits)
                OptionallyAppendCriticalRange(stringBuilder, sensorLimits.LowLimit, sensorLimits.HighLimit, "normal");

            return stringBuilder.ToString();
        }
    }

    public string Value
    {
        get { return ValueToString(Sensor.Value); }
    }

    public string ValueToString(float? value)
    {
        if (value.HasValue)
        {
            switch (Sensor.SensorType)
            {
                case SensorType.Temperature when _unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit:
                    {
                        return $"{value * 1.8 + 32:F1} °F";
                    }
                case SensorType.Throughput:
                    {
                        string result;
                        switch (Sensor.Name)
                        {
                            case "Connection Speed":
                                {
                                    switch (value)
                                    {
                                        case 100000000:
                                            {
                                                result = "100Mbps";
                                                break;
                                            }
                                        case 1000000000:
                                            {
                                                result = "1Gbps";
                                                break;
                                            }
                                        default:
                                            {
                                                if (value < 1024)
                                                    result = $"{value:F0} bps";
                                                else if (value < 1048576)
                                                    result = $"{value / 1024:F1} Kbps";
                                                else if (value < 1073741824)
                                                    result = $"{value / 1048576:F1} Mbps";
                                                else
                                                    result = $"{value / 1073741824:F1} Gbps";
                                            }

                                            break;
                                    }

                                    break;
                                }
                            default:
                                {
                                    const int _1MB = 1048576;

                                    result = value < _1MB ? $"{value / 1024:F1} KB/s" : $"{value / _1MB:F1} MB/s";

                                    break;
                                }
                        }

                        return result;
                    }
                case SensorType.TimeSpan:
                    {
                        return string.Format(Format, TimeSpan.FromSeconds(value.Value));
                    }
                default:
                    {
                        return string.Format(Format, value);
                    }
            }
        }

        return "-";
    }

    private void OptionallyAppendCriticalRange(StringBuilder str, float? min, float? max, string kind)
    {
        if (min.HasValue)
        {
            str.AppendLine(max.HasValue
                               ? $"{CultureInfo.CurrentUICulture.TextInfo.ToTitleCase(kind)} range: {ValueToString(min)} to {ValueToString(max)}."
                               : $"Minimal {kind} value: {ValueToString(min)}.");
        }
        else if (max.HasValue)
        {
            str.AppendLine($"Maximal {kind} value: {ValueToString(max)}.");
        }
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        if (!(obj is SensorNode s))
            return false;


        return (Sensor == s.Sensor);
    }

    public override int GetHashCode()
    {
        return Sensor.GetHashCode();
    }
}
