// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Drawing;
using System.Globalization;
using Microsoft.Win32;

namespace System.Windows.Forms;

internal static class LinkUtilities
{
    // IE fonts and colors
    private static Color s_ielinkColor = Color.Empty;
    private static Color s_ieactiveLinkColor = Color.Empty;
    private static Color s_ievisitedLinkColor = Color.Empty;

    private const string IESettingsRegPath = "Software\\Microsoft\\Internet Explorer\\Settings";
    public const string IEMainRegPath = "Software\\Microsoft\\Internet Explorer\\Main";
    private const string IEAnchorColor = "Anchor Color";
    private const string IEAnchorColorVisited = "Anchor Color Visited";
    private const string IEAnchorColorHover = "Anchor Color Hover";

    /// <summary>
    ///  Retrieves a named IE color from the registry. There are constants at the top
    ///  of this file of the valid names to retrieve.
    /// </summary>
    private static Color GetIEColor(string name)
    {
        RegistryKey? key = Registry.CurrentUser.OpenSubKey(IESettingsRegPath);

        if (key is not null)
        {
            // Since this comes from the registry, be very careful about its contents.
            string? s = (string?)key.GetValue(name);
            key.Close();

            if (s is not null)
            {
                Span<Range> rgbs = stackalloc Range[4];
                int rgbsCount = s.AsSpan().Split(rgbs, ',');
                Span<int> rgb = stackalloc int[3];
                rgb.Clear();

                int nMax = Math.Min(rgb.Length, rgbsCount);

                // NOTE: if we can't parse rgbs[i], rgb[i] will be set to 0.
                for (int i = 0; i < nMax; i++)
                {
                    int.TryParse(s.AsSpan(rgbs[i]), out rgb[i]);
                }

                return Color.FromArgb(rgb[0], rgb[1], rgb[2]);
            }
        }

        if (string.Equals(name, IEAnchorColor, StringComparison.OrdinalIgnoreCase))
        {
            return Color.Blue;
        }
        else if (string.Equals(name, IEAnchorColorVisited, StringComparison.OrdinalIgnoreCase))
        {
            return Color.Purple;
        }
        else if (string.Equals(name, IEAnchorColorHover, StringComparison.OrdinalIgnoreCase))
        {
            return Color.Red;
        }
        else
        {
            return Color.Red;
        }
    }

    public static Color IELinkColor
    {
        get
        {
            if (s_ielinkColor.IsEmpty)
            {
                s_ielinkColor = GetIEColor(IEAnchorColor);
            }

            return s_ielinkColor;
        }
    }

    public static Color IEActiveLinkColor
    {
        get
        {
            if (s_ieactiveLinkColor.IsEmpty)
            {
                s_ieactiveLinkColor = GetIEColor(IEAnchorColorHover);
            }

            return s_ieactiveLinkColor;
        }
    }

    public static Color IEVisitedLinkColor
    {
        get
        {
            if (s_ievisitedLinkColor.IsEmpty)
            {
                s_ievisitedLinkColor = GetIEColor(IEAnchorColorVisited);
            }

            return s_ievisitedLinkColor;
        }
    }

    /// <summary>
    ///  Gets a light blue link color suitable for dark mode backgrounds.
    /// </summary>
    public static Color DarkModeLinkColor => Color.FromArgb(102, 178, 255);

    /// <summary>
    ///  Gets a light red active link color suitable for dark mode backgrounds.
    /// </summary>
    public static Color DarkModeActiveLinkColor => Color.FromArgb(255, 128, 128);

    /// <summary>
    ///  Gets a light purple visited link color suitable for dark mode backgrounds.
    /// </summary>
    public static Color DarkModeVisitedLinkColor => Color.FromArgb(200, 162, 255);

    /// Produces a color for visited links using SystemColors
    public static Color GetVisitedLinkColor()
    {
        int r = (SystemColors.Window.R + SystemColors.WindowText.R + 1) / 2;
        int g = SystemColors.WindowText.G;
        int b = (SystemColors.Window.B + SystemColors.WindowText.B + 1) / 2;

        return Color.FromArgb(r, g, b);
    }

    /// <summary>
    ///  Retrieves the IE settings for link behavior from the registry.
    /// </summary>
    public static LinkBehavior GetIELinkBehavior()
    {
        RegistryKey? key = null;
        try
        {
            key = Registry.CurrentUser.OpenSubKey(IEMainRegPath);
        }
        catch (Security.SecurityException)
        {
            // User does not have right to access Registry path HKCU\\Software\\Microsoft\\Internet Explorer\\Main.
            // Catch SecurityException silently and let the return value fallback to AlwaysUnderline.
        }

        if (key is not null)
        {
            string? s = (string?)key.GetValue("Anchor Underline");
            key.Close();

            if (s is not null && string.Compare(s, "no", true, CultureInfo.InvariantCulture) == 0)
            {
                return LinkBehavior.NeverUnderline;
            }

            if (s is not null && string.Compare(s, "hover", true, CultureInfo.InvariantCulture) == 0)
            {
                return LinkBehavior.HoverUnderline;
            }
            else
            {
                return LinkBehavior.AlwaysUnderline;
            }
        }

        return LinkBehavior.AlwaysUnderline;
    }

    public static void EnsureLinkFonts(
        Font baseFont,
        LinkBehavior link,
        [AllowNull] ref Font linkFont,
        [AllowNull] ref Font hoverLinkFont)
    {
        if (linkFont is not null && hoverLinkFont is not null)
        {
            return;
        }

        bool underlineLink = true;
        bool underlineHover = true;

        if (link == LinkBehavior.SystemDefault)
        {
            link = GetIELinkBehavior();
        }

        switch (link)
        {
            case LinkBehavior.AlwaysUnderline:
                underlineLink = true;
                underlineHover = true;
                break;
            case LinkBehavior.HoverUnderline:
                underlineLink = false;
                underlineHover = true;
                break;
            case LinkBehavior.NeverUnderline:
                underlineLink = false;
                underlineHover = false;
                break;
        }

        Font f = baseFont;

        // We optimize for the "same" value (never & always) to avoid creating an extra font object.
        if (underlineHover == underlineLink)
        {
            FontStyle style = f.Style;
            if (underlineHover)
            {
                style |= FontStyle.Underline;
            }
            else
            {
                style &= ~FontStyle.Underline;
            }

            hoverLinkFont = new Font(f, style);
            linkFont = hoverLinkFont;
        }
        else
        {
            FontStyle hoverStyle = f.Style;
            if (underlineHover)
            {
                hoverStyle |= FontStyle.Underline;
            }
            else
            {
                hoverStyle &= ~FontStyle.Underline;
            }

            hoverLinkFont = new Font(f, hoverStyle);

            FontStyle linkStyle = f.Style;
            if (underlineLink)
            {
                linkStyle |= FontStyle.Underline;
            }
            else
            {
                linkStyle &= ~FontStyle.Underline;
            }

            linkFont = new Font(f, linkStyle);
        }
    }
}
