// 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.Windows.Forms.ButtonInternal;

namespace System.Windows.Forms.Tests;

public class CheckBoxBaseAdapterTests : IDisposable
{
    private TestCheckBox? _checkBox;

    private (TestCheckBoxBaseAdapter, TestCheckBox) CreateAdapter(CheckState checkState)
    {
        _checkBox = new TestCheckBox
        {
            CheckState = checkState
        };

        TestCheckBoxBaseAdapter adapter = new(_checkBox);

        return (adapter, _checkBox);
    }

    public void Dispose() => _checkBox?.Dispose();

    private class TestCheckBox : CheckBox
    {
        public new bool Enabled
        {
            get => base.Enabled;
            set => base.Enabled = value;
        }

        public new CheckState CheckState
        {
            get => base.CheckState;
            set => base.CheckState = value;
        }
    }

    private class TestCheckBoxBaseAdapter : CheckBoxBaseAdapter
    {
        public TestCheckBoxBaseAdapter(ButtonBase control) : base(control) { }

        public void CallDrawCheckFlat(
            PaintEventArgs e,
            LayoutData layout,
            Color checkColor,
            Color checkBackground,
            Color checkBorder,
            ColorData colors)
            => DrawCheckFlat(e, layout, checkColor, checkBackground, checkBorder, colors);

        protected override LayoutOptions Layout(PaintEventArgs e)
            => new LayoutOptions();

        protected override ButtonBaseAdapter CreateButtonAdapter()
            => new StubButtonBaseAdapter(Control);

        private class StubButtonBaseAdapter : ButtonBaseAdapter
        {
            public StubButtonBaseAdapter(ButtonBase control) : base(control) { }
            internal override void PaintUp(PaintEventArgs e, CheckState state) { }
            internal override void PaintDown(PaintEventArgs e, CheckState state) { }
            internal override void PaintOver(PaintEventArgs e, CheckState state) { }
            protected override LayoutOptions Layout(PaintEventArgs e) => new LayoutOptions();
        }

        internal override void PaintOver(PaintEventArgs e, CheckState state)
        {
        }

        internal override void PaintDown(PaintEventArgs e, CheckState state)
        {
        }

        internal override void PaintUp(PaintEventArgs e, CheckState state)
        {
        }
    }

    private static ButtonBaseAdapter.LayoutData CreateLayoutData(bool dotNetOneButtonCompat = false)
    {
        ButtonBaseAdapter.LayoutData layout = new(
            new ButtonBaseAdapter.LayoutOptions
            {
                DotNetOneButtonCompat = dotNetOneButtonCompat
            })
        {
            CheckBounds = new Rectangle(2, 2, 11, 11)
        };

        return layout;
    }

    private static ButtonBaseAdapter.ColorData CreateColorData(Graphics graphics)
    {
        ButtonBaseAdapter.ColorOptions options = new(
            graphics,
            Color.Black,
            Color.White
        );

        ButtonBaseAdapter.ColorData colorData = new(options)
        {
            ButtonFace = Color.LightGray,
            ButtonShadow = Color.Gray,
            Highlight = Color.Yellow
        };

        return colorData;
    }

    [WinFormsTheory]
    [InlineData(CheckState.Unchecked, false)]
    [InlineData(CheckState.Checked, false)]
    [InlineData(CheckState.Indeterminate, false)]
    [InlineData(CheckState.Unchecked, true)]
    [InlineData(CheckState.Checked, true)]
    [InlineData(CheckState.Indeterminate, true)]
    public void DrawCheckFlat_DoesNotThrow(CheckState checkState, bool dotNetOneButtonCompat)
    {
        (TestCheckBoxBaseAdapter adapter, TestCheckBox checkBox) = CreateAdapter(checkState);
        using Bitmap bitmap = new(20, 20);
        using Graphics graphics = Graphics.FromImage(bitmap);
        using PaintEventArgs e = new(graphics, new Rectangle(0, 0, 20, 20));
        ButtonBaseAdapter.LayoutData layout = CreateLayoutData(dotNetOneButtonCompat);
        Color checkColor = Color.Black;
        Color checkBackground = Color.White;
        Color checkBorder = Color.Blue;
        ButtonBaseAdapter.ColorData colors = CreateColorData(graphics);

        Exception? ex = Record.Exception(() =>
            adapter.CallDrawCheckFlat(e, layout, checkColor, checkBackground, checkBorder, colors));

        ex.Should().BeNull();
    }

    [WinFormsTheory]
    [InlineData(false, CheckState.Unchecked, false, 0)]
    [InlineData(false, CheckState.Unchecked, true, 1)]
    [InlineData(true, CheckState.Indeterminate, true, 2)]
    public void DrawCheckBackground_CoversAllBranches(
        bool controlEnabled,
        CheckState checkState,
        bool disabledColors,
        int expectedBranch)
    {
        Rectangle bounds = new(1, 2, 3, 4);
        using Bitmap bitmap = new(10, 10);
        using Graphics graphics = Graphics.FromImage(bitmap);
        Color checkBackground = expectedBranch == 2 ? SystemColors.Window : Color.Red;

        Exception? ex = Record.Exception(() =>
            CheckBoxBaseAdapter.DrawCheckBackground(
                controlEnabled,
                checkState,
                graphics,
                bounds,
                checkBackground,
                disabledColors));

        ex.Should().BeNull();
    }

    [WinFormsTheory]
    [InlineData(CheckState.Checked, true, true)]
    [InlineData(CheckState.Checked, false, true)]
    [InlineData(CheckState.Indeterminate, true, true)]
    [InlineData(CheckState.Indeterminate, false, true)]
    [InlineData(CheckState.Unchecked, true, false)]
    public void DrawCheckOnly_Protected_DoesNotThrow(
        CheckState checkState,
        bool enabled,
        bool isChecked)
    {
        (TestCheckBoxBaseAdapter adapter, TestCheckBox checkBox) = CreateAdapter(checkState);
        checkBox.Enabled = enabled;
        checkBox.Checked = isChecked;

        using Bitmap bitmap = new(20, 20);
        using Graphics graphics = Graphics.FromImage(bitmap);
        using PaintEventArgs e = new(graphics, new Rectangle(0, 0, 20, 20));
        ButtonBaseAdapter.LayoutData layout = CreateLayoutData();
        ButtonBaseAdapter.ColorData colors = CreateColorData(graphics);
        Color checkColor = Color.Black;

        Exception? ex = Record.Exception(() =>
            adapter.TestAccessor.Dynamic.DrawCheckOnly(
                e,
                layout,
                colors,
                checkColor));

        ex.Should().BeNull();
    }

    [WinFormsFact]
    public void DrawPopupBorder_DoesNotThrow_AndInflatesRectangle()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics graphics = Graphics.FromImage(bitmap);
        Rectangle original = new(2, 2, 10, 10);

        ButtonBaseAdapter.ColorData colors = CreateColorData(graphics);

        Rectangle result = CheckBoxBaseAdapter.DrawPopupBorder(graphics, original, colors);

        Rectangle expected = original;
        expected.Inflate(-1, -1);

        result.Should().Be(expected);
    }

    [WinFormsFact]
    public void DrawPopupBorder_PaintEventArgs_DoesNotThrow_AndInflatesRectangle()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics graphics = Graphics.FromImage(bitmap);
        Rectangle original = new(3, 3, 8, 8);

        ButtonBaseAdapter.ColorData colors = CreateColorData(graphics);

        using PaintEventArgs e = new(graphics, new Rectangle(0, 0, 20, 20));
        Rectangle result = CheckBoxBaseAdapter.DrawPopupBorder(e, original, colors);

        Rectangle expected = original;
        expected.Inflate(-1, -1);

        result.Should().Be(expected);
    }

    [WinFormsFact]
    public void DrawPopupBorder_HDC_DoesNotThrow_AndInflatesRectangle()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics graphics = Graphics.FromImage(bitmap);
        Rectangle original = new(4, 4, 6, 6);

        ButtonBaseAdapter.ColorData colors = CreateColorData(graphics);

        IntPtr hdcPtr = graphics.GetHdc();
        try
        {
            Rectangle result = CheckBoxBaseAdapter.DrawPopupBorder(
                new HDC(hdcPtr),
                original,
                colors);

            Rectangle expected = original;
            expected.Inflate(-1, -1);

            result.Should().Be(expected);
        }
        finally
        {
            graphics.ReleaseHdc(hdcPtr);
        }
    }

    [Theory]
    [InlineData(null, true, 2, 2, 10, 10)]
    [InlineData("", true, 2, 2, 10, 10)]
    [InlineData(null, false, 2, 2, 10, 10)]
    [InlineData("", false, 2, 2, 10, 10)]
    public void AdjustFocusRectangle_SetsFocusAsExpected(
        string? text,
        bool autoSize,
        int checkX, int checkY, int checkW, int checkH)
    {
        (TestCheckBoxBaseAdapter adapter, TestCheckBox checkBox) = CreateAdapter(CheckState.Unchecked);
        checkBox.Text = text;
        checkBox.AutoSize = autoSize;

        ButtonBaseAdapter.LayoutData layout = new(new ButtonBaseAdapter.LayoutOptions())
        {
            CheckBounds = new Rectangle(checkX, checkY, checkW, checkH),
            Field = new Rectangle(5, 5, 20, 20)
        };

        adapter.TestAccessor.Dynamic.AdjustFocusRectangle(layout);

        if (string.IsNullOrEmpty(text))
        {
            if (autoSize)
            {
                Rectangle expected = Rectangle.Inflate(layout.CheckBounds, -2, -2);
                layout.Focus.Should().Be(expected);
            }
            else
            {
                layout.Focus.Should().Be(new Rectangle(5, 5, 20, 20));
            }
        }
        else
        {
            layout.Focus.Should().Be(default);
        }
    }
}
