// 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;

namespace System.Windows.Forms.UITests;

public class TabControlTests : ControlTestBase
{
    public TabControlTests(ITestOutputHelper testOutputHelper)
        : base(testOutputHelper)
    {
    }

    [WinFormsFact]
    public async Task TabControl_TabPage_IsHoveredWithMouse_IsTrue_WhenMouseIsOn_FirstTabAsync()
    {
        await RunTestAsync(async (form, tabControl) =>
        {
            // TODO: find the way to determin the tab's dimensions and use those instead of (10, 10)
            bool result = await IsHoveredWithMouseAsync(form, tabControl, new(10, 10));

            Assert.True(result);
        });
    }

    [WinFormsFact]
    public async Task TabControl_TabPage_IsHoveredWithMouse_IsTrue_WhenMouseIsOn_SecondTabAsync()
    {
        await RunTestAsync(async (form, tabControl) =>
        {
            // TODO: find the way to determin the tab's dimensions and use those instead of (60, 10)
            bool result = await IsHoveredWithMouseAsync(form, tabControl, new(60, 10));

            Assert.True(result);
        });
    }

    [WinFormsFact]
    public async Task TabControl_TabPage_IsHoveredWithMouse_IsFalse_WhenMouseIs_OutsideControlAsync()
    {
        await RunTestAsync(async (form, tabControl) =>
        {
            // TODO: find the way to determin the tab's dimensions and use those instead of (300, 300)
            bool result = await IsHoveredWithMouseAsync(form, tabControl, new(300, 300));

            Assert.False(result);
        });
    }

    [WinFormsFact]
    public async Task TabControl_TabPage_IsHoveredWithMouse_IsFalse_WhenMouseIs_OutsideMainScreenAsync()
    {
        await RunTestAsync(async (form, tabControl) =>
        {
            TestOutputHelper.WriteLine($"Primary screen: {Screen.PrimaryScreen?.WorkingArea}");

            // We can't physically move the mouse outside the desktop area,
            // so the mouse cursor will be stuck at the bottom right corner of the desktop.
            bool result = await IsHoveredWithMouseAsync(form, tabControl, new(1000, 1000), assertCorrectLocation: false);

            Assert.False(result);
        });
    }

    private async Task<bool> IsHoveredWithMouseAsync(Form form, TabControl tabControl, Point point, bool assertCorrectLocation = true)
    {
        await MoveMouseAsync(form, tabControl.PointToScreen(point), assertCorrectLocation);

        bool result = ((IKeyboardToolTip)tabControl).IsHoveredWithMouse();
        bool resultOfPage1 = ((IKeyboardToolTip)tabControl.TabPages[0]).IsHoveredWithMouse();
        bool resultOfPage2 = ((IKeyboardToolTip)tabControl.TabPages[1]).IsHoveredWithMouse();
        return result && resultOfPage1 && resultOfPage2;
    }

    private async Task RunTestAsync(Func<Form, TabControl, Task> runTest)
    {
        await RunSingleControlTestAsync(
            testDriverAsync: runTest,
            createControl: () =>
            {
                TabControl tabControl = new()
                {
                    Location = new Point(0, 0)
                };

                TabPage tabPage1 = new();
                TabPage tabPage2 = new();
                tabControl.TabPages.Add(tabPage1);
                tabControl.TabPages.Add(tabPage2);

                return tabControl;
            });
    }

    [WinFormsFact]
    public async Task TabControl_ControlsDoNotReorderWhenSelectedIndexChanges()
    {
        // Validates the following bug fix: https://github.com/dotnet/winforms/issues/7837
        await RunSingleControlTestAsync(
            testDriverAsync: async (form, tabControl) =>
            {
                var originalPages = tabControl.TabPages.Cast<TabPage>().ToArray();

                tabControl.SelectedIndex--;
                await Task.Yield();

                // The changes we made to SelectedIndex will result in a WM_WINDOWPOSCHANGED message which will
                // fire TabPage.WmWindowPosChanged, which will call TabControl.UpdateChildControlIndex.

                // This test ensures that the latter method short-circuits and does not call Controls.SetChildIndex
                // which would inappropriately reorder the Controls collection, resulting in TabPages and Controls
                // becoming out of sync.

                for (int i = 0; i < originalPages.Length; i++)
                {
                    Assert.Equal(tabControl.Controls[i], tabControl.TabPages[i]);
                }

                // The following test proves that it *matters* that the Controls and TabPages collection remain in sync.

                // Remove the first page by position:
                tabControl.TabPages.RemoveAt(0);

                // We should be left with pages[1] and pages[2]. This will not be the case if the Controls and TabPages
                // collections are out of sync.
                Assert.Equal(originalPages[1], tabControl.TabPages[0]);
                Assert.Equal(originalPages[2], tabControl.TabPages[1]);
            },
            createControl: () =>
            {
                SubclassedTabControl tabControl = new()
                {
                    Location = new Point(0, 0)
                };

                TabPage tabPage1 = new() { Text = "Page 1" };
                TabPage tabPage2 = new() { Text = "Page 2" };
                TabPage tabPage3 = new() { Text = "Page 3" };

                tabControl.TabPages.Add(tabPage1);
                tabControl.TabPages.Add(tabPage2);
                tabControl.TabPages.Add(tabPage3);

                // Start with the Selected Page at the end:
                tabControl.SelectedIndex = tabControl.TabPages.Count - 1;

                return tabControl;
            });
    }

    // Bug https://github.com/dotnet/winforms/issues/7837 occured only when TabControl was subclassed.
    private class SubclassedTabControl : TabControl { }
}
