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

using System.Runtime.CompilerServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Versioning;

namespace System.Windows.Forms.Primitives;

// Borrowed from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs
internal static partial class LocalAppContextSwitches
{
    // Enabling switches in Core is different from Framework. See https://learn.microsoft.com/dotnet/core/runtime-config/
    // for details on how to set switches.

    // Switch names declared internal below are used in unit/integration tests. Refer to
    // https://github.com/dotnet/winforms/blob/tree/main/docs/design/anchor-layout-changes-in-net80.md
    // for more details on how to enable these switches in the application.
    private const string ScaleTopLevelFormMinMaxSizeForDpiSwitchName = "System.Windows.Forms.ScaleTopLevelFormMinMaxSizeForDpi";
    internal const string AnchorLayoutV2SwitchName = "System.Windows.Forms.AnchorLayoutV2";
    internal const string ApplyParentFontToMenusSwitchName = "System.Windows.Forms.ApplyParentFontToMenus";
    internal const string ServicePointManagerCheckCrlSwitchName = "System.Windows.Forms.ServicePointManagerCheckCrl";
    internal const string TrackBarModernRenderingSwitchName = "System.Windows.Forms.TrackBarModernRendering";
    private const string DoNotCatchUnhandledExceptionsSwitchName = "System.Windows.Forms.DoNotCatchUnhandledExceptions";
    internal const string DataGridViewUIAStartRowCountAtZeroSwitchName = "System.Windows.Forms.DataGridViewUIAStartRowCountAtZero";
    internal const string NoClientNotificationsSwitchName = "Switch.System.Windows.Forms.AccessibleObject.NoClientNotifications";
    internal const string EnableMsoComponentManagerSwitchName = "Switch.System.Windows.Forms.EnableMsoComponentManager";
    internal const string TreeNodeCollectionAddRangeRespectsSortOrderSwitchName = "System.Windows.Forms.TreeNodeCollectionAddRangeRespectsSortOrder";
    internal const string ClipboardDragDropEnableUnsafeBinaryFormatterSerializationSwitchName = "Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization";
    internal const string ClipboardDragDropEnableNrbfSerializationSwitchName = "Windows.ClipboardDragDrop.EnableNrbfSerialization";

    private static int s_scaleTopLevelFormMinMaxSizeForDpi;
    private static int s_anchorLayoutV2;
    private static int s_applyParentFontToMenus;
    private static int s_servicePointManagerCheckCrl;
    private static int s_trackBarModernRendering;
    private static int s_doNotCatchUnhandledExceptions;
    private static int s_dataGridViewUIAStartRowCountAtZero;
    private static int s_noClientNotifications;
    private static int s_enableMsoComponentManager;
    private static int s_treeNodeCollectionAddRangeRespectsSortOrder;
    private static int s_clipboardDragDropEnableUnsafeBinaryFormatterSerialization;
    private static int s_clipboardDragDropEnableNrbfSerialization;

    private static FrameworkName? s_targetFrameworkName;

    /// <summary>
    ///  When there is no exception handler registered for a thread, re-throws the exception. The exception will
    ///  not be presented in a dialog or swallowed when not in interactive mode. This is always opt-in and is
    ///  intended for scenarios where setting handlers for threads isn't practical.
    /// </summary>
    public static bool DoNotCatchUnhandledExceptions
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(DoNotCatchUnhandledExceptionsSwitchName, ref s_doNotCatchUnhandledExceptions);
    }

    /// <summary>
    ///  The <see cref="TargetFrameworkAttribute"/> value for the entry assembly, if any.
    /// </summary>
    public static FrameworkName? TargetFrameworkName
    {
        get
        {
            s_targetFrameworkName ??= AppContext.TargetFrameworkName is { } name ? new(name) : null;
            return s_targetFrameworkName;
        }
    }

    private static bool GetCachedSwitchValue(string switchName, ref int cachedSwitchValue)
    {
        // The cached switch value has 3 states: 0 - unknown, 1 - true, -1 - false
        if (cachedSwitchValue < 0)
        {
            return false;
        }

        if (cachedSwitchValue > 0)
        {
            return true;
        }

        return GetSwitchValue(switchName, ref cachedSwitchValue);
    }

    private static bool GetSwitchValue(string switchName, ref int cachedSwitchValue)
    {
        bool hasSwitch = AppContext.TryGetSwitch(switchName, out bool isSwitchEnabled);
        if (!hasSwitch)
        {
            isSwitchEnabled = GetSwitchDefaultValue(switchName);
        }

        AppContext.TryGetSwitch("TestSwitch.LocalAppContext.DisableCaching", out bool disableCaching);
        if (!disableCaching)
        {
            cachedSwitchValue = isSwitchEnabled ? 1 /*true*/ : -1 /*false*/;
        }
        else if (!hasSwitch)
        {
            AppContext.SetSwitch(switchName, isSwitchEnabled);
        }

        return isSwitchEnabled;
    }

    private static bool GetSwitchDefaultValue(string switchName)
    {
        if (switchName == TreeNodeCollectionAddRangeRespectsSortOrderSwitchName)
        {
            return true;
        }

        if (switchName == ClipboardDragDropEnableNrbfSerializationSwitchName)
        {
            return true;
        }

        if (TargetFrameworkName is not { } framework)
        {
            return false;
        }

        if (framework.Version.Major >= 8)
        {
            // Behavior changes added in .NET 8

            if (switchName == ScaleTopLevelFormMinMaxSizeForDpiSwitchName)
            {
                return true;
            }

            if (switchName == TrackBarModernRenderingSwitchName)
            {
                return true;
            }

            if (switchName == ServicePointManagerCheckCrlSwitchName)
            {
                return true;
            }
        }

        return false;
    }

    /// <summary>
    ///  Indicates whether AnchorLayoutV2 feature is enabled.
    /// </summary>
    /// <devdoc>
    ///  Returns AnchorLayoutV2 switch value from runtimeconfig.json. Defaults to false.
    ///  Refer to
    ///  https://github.com/dotnet/winforms/blob/tree/main/docs/design/anchor-layout-changes-in-net80.md for more details.
    /// </devdoc>
    public static bool AnchorLayoutV2
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(AnchorLayoutV2SwitchName, ref s_anchorLayoutV2);
    }

    /// <summary>
    ///  Gets or sets a value indicating whether the parent font (as set by
    ///  <see cref="M:System.Windows.Forms.Application.SetDefaultFont(System.Drawing.Font)" />
    ///  or by the parent control or form's font) is applied to menus.
    /// </summary>
    public static bool ApplyParentFontToMenus
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(ApplyParentFontToMenusSwitchName, ref s_applyParentFontToMenus);
    }

    public static bool ScaleTopLevelFormMinMaxSizeForDpi
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(ScaleTopLevelFormMinMaxSizeForDpiSwitchName, ref s_scaleTopLevelFormMinMaxSizeForDpi);
    }

    public static bool TrackBarModernRendering
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(TrackBarModernRenderingSwitchName, ref s_trackBarModernRendering);
    }

    /// <summary>
    ///  Indicates whether certificates are checked against the certificate authority revocation list.
    ///  If true, revoked certificates will not be accepted by WebRequests and WebClients as valid.
    ///  Otherwise, revoked certificates will be accepted as valid.
    /// </summary>
    public static bool ServicePointManagerCheckCrl
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(ServicePointManagerCheckCrlSwitchName, ref s_servicePointManagerCheckCrl);
    }

    public static bool DataGridViewUIAStartRowCountAtZero
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(DataGridViewUIAStartRowCountAtZeroSwitchName, ref s_dataGridViewUIAStartRowCountAtZero);
    }

    public static bool NoClientNotifications
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(NoClientNotificationsSwitchName, ref s_noClientNotifications);
    }

    /// <summary>
    ///  If <see langword="true"/> Windows Forms threads will register with existing IMsoComponentManager instances.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   See <see href="https://learn.microsoft.com/previous-versions/office/developer/office-2007/ff518974(v=office.12)">
    ///   Component API Reference for the 2007 Office System</see> for more information on the IMsoComponentManager.
    ///  </para>
    /// </remarks>
    public static bool EnableMsoComponentManager
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(EnableMsoComponentManagerSwitchName, ref s_enableMsoComponentManager);
    }

    /// <summary>
    ///  When set to (default), API will insert nodes in the sorted order.
    ///  To get behavior compatible with the previous versions of .NET and .NET Framework, set this switch to.
    /// </summary>
    public static bool TreeNodeCollectionAddRangeRespectsSortOrder
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(TreeNodeCollectionAddRangeRespectsSortOrderSwitchName, ref s_treeNodeCollectionAddRangeRespectsSortOrder);
    }

    /// <summary>
    ///  If <see langword="true"/>, then Clipboard and DataObject Get and Set methods will attempts to serialize or deserialize
    ///  binary formatted content using either <see cref="BinaryFormatter"/> or System.Windows.Forms.BinaryFormat.Deserializer.
    ///  To use <see cref="BinaryFormatter"/>, application should also opt in into the
    ///  "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization" option and reference the out-of-band
    ///  "System.Runtime.Serialization.Formatters" NuGet package and opt out from using the System.Windows.Forms.BinaryFormat.Deserializer
    ///  by setting "Windows.ClipboardDragDrop.EnableNrbfSerialization" to <see langword="true"/>
    /// </summary>
    public static bool ClipboardDragDropEnableUnsafeBinaryFormatterSerialization
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(ClipboardDragDropEnableUnsafeBinaryFormatterSerializationSwitchName,
            ref s_clipboardDragDropEnableUnsafeBinaryFormatterSerialization);
    }

    /// <summary>
    ///  If <see langword="true"/>, then Clipboard Get methods will prefer System.Windows.Forms.BinaryFormat.Deserializer
    ///  to deserialize the payload, if needed. If <see langword="false"/>, then <see cref="BinaryFormatter"/> is used
    ///  to get full compatibility with the downlevel versions of .NET.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   This switch has no effect if "Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization"
    ///   is set to <see langword="false"/>.
    ///  </para>
    /// </remarks>
    public static bool ClipboardDragDropEnableNrbfSerialization
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => GetCachedSwitchValue(ClipboardDragDropEnableNrbfSerializationSwitchName, ref s_clipboardDragDropEnableNrbfSerialization);
    }
}
