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

namespace LibreHardwareMonitor.Windows.Forms.UI;

public class SensorNotifyIcon : IDisposable
{
    private readonly UnitManager _unitManager;
    private readonly NotifyIconAdv _notifyIcon;
    private readonly Bitmap _bitmap;
    private readonly Graphics _graphics;
    private Color _color;
    private Color _darkColor;
    private Brush _brush;
    private Brush _darkBrush;
    private readonly Pen _pen;
    private readonly Font _font;
    private readonly Font _smallFont;

    public SensorNotifyIcon(SystemTray sensorSystemTray, ISensor sensor, PersistentSettings settings, UnitManager unitManager)
    {
        _unitManager = unitManager;
        Sensor = sensor;
        _notifyIcon = new NotifyIconAdv();

        Color defaultColor = Color.White;
        if (sensor.SensorType == SensorType.Load || sensor.SensorType == SensorType.Control || sensor.SensorType == SensorType.Level)
            defaultColor = Color.FromArgb(0xff, 0x70, 0x8c, 0xf1);

        Color = settings.GetValue(new Identifier(sensor.Identifier, "traycolor").ToString(), defaultColor);

        _pen = new Pen(Color.FromArgb(96, Color.Black));
        ContextMenuStrip contextMenuStrip = new ContextMenuStrip();
        ToolStripItem hideShowItem = new ToolStripMenuItem("Hide/Show");
        hideShowItem.Click += delegate
        {
            sensorSystemTray.SendHideShowCommand();
        };
        contextMenuStrip.Items.Add(hideShowItem);
        contextMenuStrip.Items.Add(new ToolStripSeparator());
        ToolStripItem removeItem = new ToolStripMenuItem("Remove Sensor");
        removeItem.Click += delegate
        {
            sensorSystemTray.Remove(Sensor);
        };
        contextMenuStrip.Items.Add(removeItem);
        ToolStripItem colorItem = new ToolStripMenuItem("Change Color...");
        colorItem.Click += delegate
        {
            ColorDialog dialog = new ColorDialog { Color = Color };
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                Color = dialog.Color;
                settings.SetValue(new Identifier(sensor.Identifier,
                                                 "traycolor").ToString(), Color);
            }
        };
        contextMenuStrip.Items.Add(colorItem);
        contextMenuStrip.Items.Add(new ToolStripSeparator());
        ToolStripItem exitItem = new ToolStripMenuItem("Exit");
        exitItem.Click += delegate
        {
            sensorSystemTray.SendExitCommand();
        };
        contextMenuStrip.Items.Add(exitItem);
        _notifyIcon.ContextMenuStrip = contextMenuStrip;
        _notifyIcon.DoubleClick += delegate
        {
            sensorSystemTray.SendHideShowCommand();
        };

        // get the default dpi to create an icon with the correct size
        float dpiX, dpiY;
        using (Bitmap b = new Bitmap(1, 1, PixelFormat.Format32bppArgb))
        {
            dpiX = b.HorizontalResolution;
            dpiY = b.VerticalResolution;
        }

        // adjust the size of the icon to current dpi (default is 16x16 at 96 dpi)
        int width = (int)Math.Round(16 * dpiX / 96);
        int height = (int)Math.Round(16 * dpiY / 96);

        // make sure it does never get smaller than 16x16
        width = width < 16 ? 16 : width;
        height = height < 16 ? 16 : height;

        // adjust the font size to the icon size
        FontFamily family = SystemFonts.MessageBoxFont.FontFamily;
        float baseSize;
        switch (family.Name)
        {
            case "Segoe UI": baseSize = 12; break;
            case "Tahoma": baseSize = 11; break;
            default: baseSize = 12; break;
        }

        _font = new Font(family, baseSize * width / 16.0f, GraphicsUnit.Pixel);
        _smallFont = new Font(family, 0.75f * baseSize * width / 16.0f, GraphicsUnit.Pixel);

        _bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
        _graphics = Graphics.FromImage(_bitmap);
        if (Environment.OSVersion.Version.Major > 5)
        {
            _graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
            _graphics.SmoothingMode = SmoothingMode.HighQuality;
        }
    }

    public ISensor Sensor { get; }

    public Color Color
    {
        get { return _color; }
        set
        {
            _color = value;
            _darkColor = Color.FromArgb(255, _color.R / 3, _color.G / 3, _color.B / 3);
            Brush brush = _brush;
            _brush = new SolidBrush(_color);
            brush?.Dispose();
            Brush darkBrush = _darkBrush;
            _darkBrush = new SolidBrush(_darkColor);
            darkBrush?.Dispose();
        }
    }

    public void Dispose()
    {
        Icon icon = _notifyIcon.Icon;
        _notifyIcon.Icon = null;
        icon?.Destroy();
        _notifyIcon.Dispose();

        _brush?.Dispose();
        _darkBrush?.Dispose();
        _pen.Dispose();
        _graphics.Dispose();
        _bitmap.Dispose();
        _font.Dispose();
        _smallFont.Dispose();
    }

    private string GetString()
    {
        if (!Sensor.Value.HasValue)
            return "-";

        switch (Sensor.SensorType)
        {
            case SensorType.Temperature:
                return _unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit ? $"{UnitManager.CelsiusToFahrenheit(Sensor.Value):F0}" : $"{Sensor.Value:F0}";
            case SensorType.TimeSpan:
                return $"{TimeSpan.FromSeconds(Sensor.Value.Value):g}";
            case SensorType.Timing:
                return $"{Sensor.Value.Value:F3}";
            case SensorType.Clock:
            case SensorType.Fan:
            case SensorType.Flow:
                return $"{1e-3f * Sensor.Value:F1}";
            case SensorType.Voltage:
            case SensorType.Current:
            case SensorType.SmallData:
            case SensorType.Factor:
            case SensorType.Throughput:
            case SensorType.Conductivity:
                return $"{Sensor.Value:F1}";
            case SensorType.Control:
            case SensorType.Frequency:
            case SensorType.Level:
            case SensorType.Power:
            case SensorType.Data:
            case SensorType.Load:
            case SensorType.Energy:
            case SensorType.Noise:
            case SensorType.Humidity:
                return $"{Sensor.Value:F0}";
            default:
                return "-";
        }
    }

    private Icon CreateTransparentIcon()
    {
        string text = GetString();
        int count = 0;
        for (int i = 0; i < text.Length; i++)
            if ((text[i] >= '0' && text[i] <= '9') || text[i] == '-')
                count++;
        bool small = count > 2;

        _graphics.Clear(Color.Black);
        Rectangle bounds = new Rectangle(Point.Empty, _bitmap.Size);
        TextRenderer.DrawText(_graphics, text, small ? _smallFont : _font,
            bounds, Color.White, Color.Black, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
        
        BitmapData data = _bitmap.LockBits(
            new Rectangle(0, 0, _bitmap.Width, _bitmap.Height),
            ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

        IntPtr Scan0 = data.Scan0;

        int numBytes = _bitmap.Width * _bitmap.Height * 4;
        byte[] bytes = new byte[numBytes];
        Marshal.Copy(Scan0, bytes, 0, numBytes);
        _bitmap.UnlockBits(data);

        byte red, green, blue;
        for (int i = 0; i < bytes.Length; i += 4)
        {
            blue = bytes[i];
            green = bytes[i + 1];
            red = bytes[i + 2];

            bytes[i] = _color.B;
            bytes[i + 1] = _color.G;
            bytes[i + 2] = _color.R;
            bytes[i + 3] = (byte)(0.3 * red + 0.59 * green + 0.11 * blue);
        }

        return IconFactory.Create(bytes, _bitmap.Width, _bitmap.Height,
            PixelFormat.Format32bppArgb);
    }

    private Icon CreatePercentageIcon()
    {
        try
        {
            _graphics.Clear(Color.Transparent);
        }
        catch (ArgumentException)
        {
            _graphics.Clear(Color.Black);
        }
        _graphics.FillRectangle(_darkBrush, 0.5f, -0.5f, _bitmap.Width - 2, _bitmap.Height);
        float value = Sensor.Value.GetValueOrDefault();
        float y = (float)(_bitmap.Height * 0.01f) * (100 - value);
        _graphics.FillRectangle(_brush, 0.5f, -0.5f + y, _bitmap.Width - 2, _bitmap.Height - y);
        _graphics.DrawRectangle(_pen, 1, 0, _bitmap.Width - 3, _bitmap.Height - 1);

        return IconFactory.Create(_bitmap);
    }

    public void Update()
    {
        Icon icon = _notifyIcon.Icon;

        switch (Sensor.SensorType)
        {
            case SensorType.Load:
            case SensorType.Control:
            case SensorType.Level:
                _notifyIcon.Icon = CreatePercentageIcon();
                break;
            default:
                _notifyIcon.Icon = CreateTransparentIcon();
                break;
        }

        icon?.Destroy();

        string format = "";
        switch (Sensor.SensorType)
        {
            case SensorType.Voltage: format = "\n{0}: {1:F2} V"; break;
            case SensorType.Current: format = "\n{0}: {1:F2} A"; break;
            case SensorType.Clock: format = "\n{0}: {1:F0} MHz"; break;
            case SensorType.Load: format = "\n{0}: {1:F1} %"; break;
            case SensorType.Temperature: format = "\n{0}: {1:F1} °C"; break;
            case SensorType.Fan: format = "\n{0}: {1:F0} RPM"; break;
            case SensorType.Flow: format = "\n{0}: {1:F0} L/h"; break;
            case SensorType.Control: format = "\n{0}: {1:F1} %"; break;
            case SensorType.Level: format = "\n{0}: {1:F1} %"; break;
            case SensorType.Power: format = "\n{0}: {1:F0} W"; break;
            case SensorType.Data: format = "\n{0}: {1:F0} GB"; break;
            case SensorType.Factor: format = "\n{0}: {1:F3} GB"; break;
            case SensorType.Energy: format = "\n{0}: {0:F0} mWh"; break;
            case SensorType.Noise: format = "\n{0}: {0:F0} dBA"; break;
            case SensorType.Conductivity: format = "\n{0}: {0:F1} µS/cm"; break;
            case SensorType.Humidity: format = "\n{0}: {0:F0} %"; break;
            case SensorType.Timing: format = "\n{0}: {0:F3} ns"; break;
        }

        string formattedValue = string.Format(format, Sensor.Name, Sensor.Value);

        if (Sensor.SensorType == SensorType.Temperature && _unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit)
        {
            format = "\n{0}: {1:F1} °F";
            formattedValue = string.Format(format, Sensor.Name, UnitManager.CelsiusToFahrenheit(Sensor.Value));
        }

        string hardwareName = Sensor.Hardware.Name;
        hardwareName = hardwareName.Substring(0, Math.Min(63 - formattedValue.Length, hardwareName.Length));
        string text = hardwareName + formattedValue;
        if (text.Length > 63)
            text = null;

        _notifyIcon.Text = text;
        _notifyIcon.Visible = true;
    }
}
