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

#nullable disable

using System.Drawing;
using System.Runtime.CompilerServices;

namespace System.Windows.Forms.Tests;

public partial class ControlTests
{
    [WinFormsFact]
    public void Control_GetHandleInternalShouldBeZero()
    {
        using Control control = new();

        IntPtr intptr = control.HandleInternal;

        Assert.Equal(IntPtr.Zero, intptr);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [BoolData]
    public void Control_IsTopMdiWindowClosingGetSet(bool expected)
    {
        using Control control = new()
        {
            IsTopMdiWindowClosing = expected
        };

        Assert.Equal(expected, control.IsTopMdiWindowClosing);
    }

    [WinFormsTheory]
    [EnumData<BoundsSpecified>]
    public void Control_RequiredScaling_Set_GetReturnsExpected(BoundsSpecified value)
    {
        using Control control = new()
        {
            RequiredScaling = value
        };
        Assert.Equal(value, control.RequiredScaling);

        // Set same.
        control.RequiredScaling = value;
        Assert.Equal(value, control.RequiredScaling);
    }

    [WinFormsTheory]
    [BoolData]
    public void Control_RequiredScalingEnabled_Get_ReturnsExpected(bool value)
    {
        using Control control = new()
        {
            RequiredScalingEnabled = value
        };
        Assert.Equal(value, control.RequiredScalingEnabled);

        // Set same.
        control.RequiredScalingEnabled = value;
        Assert.Equal(value, control.RequiredScalingEnabled);

        // Set different.
        control.RequiredScalingEnabled = !value;
        Assert.Equal(!value, control.RequiredScalingEnabled);
    }

    [WinFormsTheory]
    [BoolData]
    public void Control_TabStopInternal_Set_GetReturnsExpected(bool value)
    {
        using Control control = new()
        {
            TabStopInternal = value
        };
        Assert.Equal(value, control.TabStopInternal);

        // Set same.
        control.TabStopInternal = value;
        Assert.Equal(value, control.TabStopInternal);

        // Set different.
        control.TabStopInternal = value;
        Assert.Equal(value, control.TabStopInternal);
    }

    [WinFormsTheory]
    [BoolData]
    public void Control_TabStopInternal_SetWithHandle_GetReturnsExpected(bool value)
    {
        using Control control = new();
        Assert.NotEqual(IntPtr.Zero, control.Handle);

        control.TabStopInternal = value;
        Assert.Equal(value, control.TabStopInternal);

        // Set same.
        control.TabStopInternal = value;
        Assert.Equal(value, control.TabStopInternal);

        // Set different.
        control.TabStopInternal = value;
        Assert.Equal(value, control.TabStopInternal);
    }

    [WinFormsFact]
    public void Control_TabStopInternal_SetWithHandler_DoesNotCallTabStopChanged()
    {
        using Control control = new()
        {
            TabStop = true
        };
        int callCount = 0;
        EventHandler handler = (sender, e) =>
        {
            Assert.Same(control, sender);
            Assert.Same(EventArgs.Empty, e);
            callCount++;
        };
        control.TabStopChanged += handler;

        // Set different.
        control.TabStopInternal = false;
        Assert.False(control.TabStopInternal);
        Assert.Equal(0, callCount);

        // Set same.
        control.TabStopInternal = false;
        Assert.False(control.TabStopInternal);
        Assert.Equal(0, callCount);

        // Set different.
        control.TabStopInternal = true;
        Assert.True(control.TabStopInternal);
        Assert.Equal(0, callCount);

        // Remove handler.
        control.TabStopChanged -= handler;
        control.TabStopInternal = false;
        Assert.False(control.TabStopInternal);
        Assert.Equal(0, callCount);
    }

    [WinFormsTheory]
    [InlineData(true, true)]
    [InlineData(false, true)] // setting is impossible; default is false
    // SupportsUseCompatibleTextRendering is always false
    public void Control_UseCompatibleTextRenderingIntGetSet(bool given, bool expected)
    {
        using Control control = new()
        {
            UseCompatibleTextRenderingInternal = given
        };

        Assert.Equal(expected, control.UseCompatibleTextRenderingInternal);
    }

    [WinFormsTheory]
    [BoolData]
    public void Control_ValidationCancelledGetSet(bool expected)
    {
        using Control control = new()
        {
            ValidationCancelled = expected
        };

        Assert.Equal(expected, control.ValidationCancelled);
    }

    [WinFormsTheory]
    [IntegerData<int>]
    public void Control_ApplyBoundsConstraints(int expected)
    {
        using Control control = new();
        Rectangle expectedBounds = new(expected, expected, expected, expected);

        Rectangle actualBounds = control.ApplyBoundsConstraints(expected, expected, expected, expected);

        Assert.Equal(expectedBounds, actualBounds);
    }

    [WinFormsTheory]
    [IntegerData<int>]
    public void Control_ApplySizeConstraints(int expected)
    {
        using Control control = new();
        Size expectedSize = new(expected, expected);

        Size actualSize = control.ApplySizeConstraints(expected, expected);

        Assert.Equal(expectedSize, actualSize);
    }

    [WinFormsTheory]
    [CommonMemberData(typeof(CommonTestHelper), nameof(CommonTestHelper.GetSizeTheoryData))]
    public void Control_ApplySizeConstraintsSize_Invoke_ReturnsExpected(Size expectedSize)
    {
        using Control control = new();
        Size actualSize = control.ApplySizeConstraints(expectedSize);
        Assert.Equal(expectedSize, actualSize);
    }

    [WinFormsFact]
    public void Control_AssignParent()
    {
        using Control control = new();
        Control parent = new();

        control.AssignParent(parent);

        Assert.Equal(parent, control.Parent);
    }

    [WinFormsFact]
    public void Control_ParentChangedFromAssign()
    {
        bool wasChanged = false;
        using Control control = new();
        control.ParentChanged += (sender, args) => wasChanged = true;
        Control parent = new();

        control.AssignParent(parent);

        Assert.True(wasChanged);
    }

    [WinFormsFact]
    public void Control_CanProcessMnemonic()
    {
        using Control control = new();

        // act and assert
        Assert.True(control.CanProcessMnemonic());
    }

    [WinFormsFact]
    public void Control_CanProcessMnemonicNotEnabled()
    {
        using Control control = new()
        {
            Enabled = false
        };

        // act and assert
        Assert.False(control.CanProcessMnemonic());
    }

    [WinFormsFact]
    public void Control_CanProcessMnemonicNotVisible()
    {
        using Control control = new()
        {
            Visible = false
        };

        // act and assert
        Assert.False(control.CanProcessMnemonic());
    }

    [WinFormsFact]
    public void Control_CanProcessMnemonicParent()
    {
        using Control control = new();
        Control parent = new();
        control.AssignParent(parent);

        // act and assert
        Assert.True(control.CanProcessMnemonic());
    }

    [WinFormsTheory]
    [BoolData]
    public void Control_CreateControlInternal(bool fIgnoreVisible)
    {
        using Control control = new();

        control.CreateControl(fIgnoreVisible);

        Assert.True(control.Created);
    }

    [WinFormsFact]
    public void Control_GetChildControlsInTabOrder()
    {
        using Control control = new();
        Control first = new()
        {
            TabIndex = 0
        };
        Control second = new()
        {
            TabIndex = 1
        };
        Control third = new()
        {
            TabIndex = 2
        };
        var ordered = new Control[]
        {
            first,
            second,
            third
        };
        var unordered = new Control[]
        {
            second,
            first,
            third
        };
        control.Controls.AddRange(unordered);

        Control[] tabOrderedChildren = control.GetChildControlsInTabOrder(false);

        Assert.Equal(ordered, tabOrderedChildren);
    }

    [WinFormsFact]
    public void Control_GetChildControlsInTabOrderHandlesOnly()
    {
        using Control control = new();
        Control first = new()
        {
            TabIndex = 0
        };
        Control second = new()
        {
            TabIndex = 1
        };
        Control third = new()
        {
            TabIndex = 2
        };
        var unordered = new Control[]
        {
            second,
            first,
            third
        };
        control.Controls.AddRange(unordered);

        Control[] tabOrderedChildrenWithhandlesOnly = control.GetChildControlsInTabOrder(true);

        Assert.Empty(tabOrderedChildrenWithhandlesOnly);
    }

    [WinFormsFact]
    public void Control_GetFirstChildControlInTabOrder()
    {
        using Control control = new();
        Control first = new()
        {
            TabIndex = 0
        };
        Control second = new()
        {
            TabIndex = 1
        };
        Control third = new()
        {
            TabIndex = 2
        };
        var tabOrder = new Control[]
        {
            second,
            first,
            third
        };
        control.Controls.AddRange(tabOrder);

        // act and assert
        Assert.Equal(first, control.GetFirstChildControlInTabOrder(true));
    }

    [WinFormsFact]
    public void Control_GetFirstChildControlInTabOrderReverse()
    {
        using Control control = new();
        Control first = new()
        {
            TabIndex = 0
        };
        Control second = new()
        {
            TabIndex = 1
        };
        Control third = new()
        {
            TabIndex = 2
        };
        var tabOrder = new Control[]
        {
            second,
            first,
            third
        };
        control.Controls.AddRange(tabOrder);

        // act and assert
        Assert.Equal(third, control.GetFirstChildControlInTabOrder(false));
    }

    [WinFormsFact]
    public void Control_ReflectParentDoesNotRootParent()
    {
        using Control control = new();
        CreateAndDispose(control);
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true);
        Assert.Null(control.TestAccessor.Dynamic.ReflectParent);

        [MethodImpl(MethodImplOptions.NoInlining)]
        static void CreateAndDispose(Control control)
        {
            // Note that rooting of locals changes between release and debug. Having a separate method here
            // ensures that we'll always get it collected when doing GC.Collect out in the caller.
            using Form form = new();
            form.Controls.Add(control);
            form.Show();
            form.Controls.Remove(control);
            Assert.NotNull(control.TestAccessor.Dynamic.ReflectParent);
        }
    }

    [WinFormsFact]
    public void Control_WndProc_WmDpiChangedBeforeParent_DpiChanged_RescaleConstantsForDpiReceivesDistinctValues()
    {
        using IDisposable dpiScope = ScaleHelper.EnterDpiAwarenessScope(DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
        using DpiRescaleTrackingControl control = new();
        Assert.NotEqual(IntPtr.Zero, control.Handle);
        int initialRescaleCallCount = control.RescaleConstantsForDpiCallCount;

        int newDpi = control.DeviceDpi == 96 ? 120 : 96;
        Message message = Message.Create((HWND)control.Handle, PInvokeCore.WM_DPICHANGED_BEFOREPARENT, (WPARAM)newDpi, (LPARAM)0);

        control.WndProc(ref message);

        Assert.Equal(initialRescaleCallCount + 1, control.RescaleConstantsForDpiCallCount);
        Assert.Equal(newDpi, control.DeviceDpiNew);
        Assert.NotEqual(control.DeviceDpiOld, control.DeviceDpiNew);
    }

    private sealed class DpiRescaleTrackingControl : Control
    {
        public int DeviceDpiOld { get; private set; }

        public int DeviceDpiNew { get; private set; }

        public int RescaleConstantsForDpiCallCount { get; private set; }

        protected override void RescaleConstantsForDpi(int deviceDpiOld, int deviceDpiNew)
        {
            DeviceDpiOld = deviceDpiOld;
            DeviceDpiNew = deviceDpiNew;
            RescaleConstantsForDpiCallCount++;

            base.RescaleConstantsForDpi(deviceDpiOld, deviceDpiNew);
        }

        public new void WndProc(ref Message m) => base.WndProc(ref m);
    }
}
