// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using Xunit;

namespace System.Windows.Forms.Tests
{
    public class ColorDialogTests : IClassFixture<ThreadExceptionFixture>
    {
        [WinFormsFact]
        public void ColorDialog_Ctor_Default()
        {
            using var dialog = new SubColorDialog();
            Assert.True(dialog.AllowFullOpen);
            Assert.False(dialog.AnyColor);
            Assert.True(dialog.CanRaiseEvents);
            Assert.Equal(Color.Black, dialog.Color);
            Assert.Equal(Enumerable.Repeat(0x00FFFFFF, 16).ToArray(), dialog.CustomColors);
            Assert.NotSame(dialog.CustomColors, dialog.CustomColors);
            Assert.Null(dialog.Container);
            Assert.False(dialog.DesignMode);
            Assert.NotNull(dialog.Events);
            Assert.Same(dialog.Events, dialog.Events);
            Assert.NotEqual(IntPtr.Zero, dialog.Instance);
            Assert.Equal(dialog.Instance, dialog.Instance);
            Assert.False(dialog.FullOpen);
            Assert.Equal(0, dialog.Options);
            Assert.False(dialog.ShowHelp);
            Assert.False(dialog.SolidColorOnly);
            Assert.Null(dialog.Site);
            Assert.Null(dialog.Tag);
        }

        [WinFormsFact]
        public void ColorDialog_Ctor_Default_OverridenReset()
        {
            using var dialog = new EmptyResetColorDialog();
            Assert.True(dialog.AllowFullOpen);
            Assert.False(dialog.AnyColor);
            Assert.True(dialog.CanRaiseEvents);
            Assert.Equal(Color.Empty, dialog.Color);
            Assert.Equal(new int[16], dialog.CustomColors);
            Assert.NotSame(dialog.CustomColors, dialog.CustomColors);
            Assert.Null(dialog.Container);
            Assert.False(dialog.DesignMode);
            Assert.NotNull(dialog.Events);
            Assert.Same(dialog.Events, dialog.Events);
            Assert.False(dialog.FullOpen);
            Assert.NotEqual(IntPtr.Zero, dialog.Instance);
            Assert.Equal(dialog.Instance, dialog.Instance);
            Assert.Equal(0, dialog.Options);
            Assert.False(dialog.ShowHelp);
            Assert.False(dialog.SolidColorOnly);
            Assert.Null(dialog.Site);
            Assert.Null(dialog.Tag);
        }

        [WinFormsTheory]
        [InlineData(true, 0, 0x4)]
        [InlineData(false, 0x4, 0)]
        public void ColorDialog_AllowFullOpen_Set_GetReturnsExpected(bool value, int expectedOptions, int expectedOptionsAfter)
        {
            using var dialog = new SubColorDialog
            {
                AllowFullOpen = value
            };
            Assert.Equal(value, dialog.AllowFullOpen);
            Assert.Equal(expectedOptions, dialog.Options);

            // Set same.
            dialog.AllowFullOpen = value;
            Assert.Equal(value, dialog.AllowFullOpen);
            Assert.Equal(expectedOptions, dialog.Options);

            // Set different.
            dialog.AllowFullOpen = !value;
            Assert.Equal(!value, dialog.AllowFullOpen);
            Assert.Equal(expectedOptionsAfter, dialog.Options);
        }

        [WinFormsTheory]
        [InlineData(true, 0x100, 0)]
        [InlineData(false, 0, 0x100)]
        public void ColorDialog_AnyColor_Set_GetReturnsExpected(bool value, int expectedOptions, int expectedOptionsAfter)
        {
            using var dialog = new SubColorDialog
            {
                AnyColor = value
            };
            Assert.Equal(value, dialog.AnyColor);
            Assert.Equal(expectedOptions, dialog.Options);

            // Set same.
            dialog.AnyColor = value;
            Assert.Equal(value, dialog.AnyColor);
            Assert.Equal(expectedOptions, dialog.Options);

            // Set different.
            dialog.AnyColor = !value;
            Assert.Equal(!value, dialog.AnyColor);
            Assert.Equal(expectedOptionsAfter, dialog.Options);
        }

        public static IEnumerable<object[]> Color_Set_TestData()
        {
            yield return new object[] { Color.Empty, Color.Black };
            yield return new object[] { Color.Red, Color.Red };
        }

        [WinFormsTheory]
        [MemberData(nameof(Color_Set_TestData))]
        public void ColorDialog_Color_Set_GetReturnsExpected(Color value, Color expected)
        {
            using var dialog = new ColorDialog
            {
                Color = value
            };
            Assert.Equal(expected, dialog.Color);

            // Set same.
            dialog.Color = value;
            Assert.Equal(expected, dialog.Color);
        }

        [WinFormsTheory]
        [MemberData(nameof(Color_Set_TestData))]
        public void ColorDialog_Color_SetWithCustomOldValue_GetReturnsExpected(Color value, Color expected)
        {
            using var dialog = new ColorDialog
            {
                Color = Color.Blue
            };

            dialog.Color = value;
            Assert.Equal(expected, dialog.Color);

            // Set same.
            dialog.Color = value;
            Assert.Equal(expected, dialog.Color);
        }

        [WinFormsFact]
        public void ColorDialog_Color_ResetValue_Success()
        {
            PropertyDescriptor property = TypeDescriptor.GetProperties(typeof(ColorDialog))[nameof(ColorDialog.Color)];
            using var dialog = new ColorDialog();
            Assert.False(property.CanResetValue(dialog));

            dialog.Color = Color.Red;
            Assert.Equal(Color.Red, dialog.Color);
            Assert.True(property.CanResetValue(dialog));

            property.ResetValue(dialog);
            Assert.Equal(Color.Black, dialog.Color);
            Assert.False(property.CanResetValue(dialog));
        }

        [WinFormsFact]
        public void ColorDialog_Color_ShouldSerializeValue_Success()
        {
            PropertyDescriptor property = TypeDescriptor.GetProperties(typeof(ColorDialog))[nameof(ColorDialog.Color)];
            using var dialog = new ColorDialog();
            Assert.False(property.ShouldSerializeValue(dialog));

            dialog.Color = Color.Red;
            Assert.Equal(Color.Red, dialog.Color);
            Assert.True(property.ShouldSerializeValue(dialog));

            property.ResetValue(dialog);
            Assert.Equal(Color.Black, dialog.Color);
            Assert.False(property.ShouldSerializeValue(dialog));
        }

        [WinFormsFact]
        public void ColorDialog_CustomColors_Get_ReturnsClone()
        {
            using var dialog = new ColorDialog();
            int[] value1 = dialog.CustomColors;
            int[] value2 = dialog.CustomColors;
            Assert.NotSame(value1, value2);
            Assert.Equal(value1, value2);
            value1[0] = 1;
            Assert.Equal(0x00FFFFFF, value2[0]);
            Assert.Equal(0x00FFFFFF, dialog.CustomColors[0]);
        }

        public static IEnumerable<object[]> CustomColors_Set_TestData()
        {
            yield return new object[] { null, Enumerable.Repeat(0x00FFFFFF, 16).ToArray() };
            yield return new object[] { Array.Empty<int>(), Enumerable.Repeat(0x00FFFFFF, 16).ToArray() };
            yield return new object[] { new int[] { 1, 2, 3 }, new int[] { 1, 2, 3 }.Concat(Enumerable.Repeat(0x00FFFFFF, 13)).ToArray() };
            yield return new object[] { Enumerable.Repeat(0, 16).ToArray(), Enumerable.Repeat(0, 16).ToArray() };
            yield return new object[] { Enumerable.Repeat(unchecked((int)0xFFFFFFFF), 16).ToArray(), Enumerable.Repeat(unchecked((int)0xFFFFFFFF), 16).ToArray() };
            yield return new object[] { Enumerable.Repeat(1, 16).ToArray(), Enumerable.Repeat(1, 16).ToArray() };
            yield return new object[] { Enumerable.Repeat(1, 20).ToArray(), Enumerable.Repeat(1, 16).ToArray() };
        }

        [WinFormsTheory]
        [MemberData(nameof(CustomColors_Set_TestData))]
        public void ColorDialog_CustomColors_Set_GetReturnsExpected(int[] value, int[] expected)
        {
            using var dialog = new ColorDialog
            {
                CustomColors = value
            };
            Assert.Equal(expected, dialog.CustomColors);
            Assert.NotSame(value, dialog.CustomColors);
            Assert.NotSame(dialog.CustomColors, dialog.CustomColors);

            // Set same.
            dialog.CustomColors = value;
            Assert.Equal(expected, dialog.CustomColors);
            Assert.NotSame(value, dialog.CustomColors);
            Assert.NotSame(dialog.CustomColors, dialog.CustomColors);
        }

        [WinFormsTheory]
        [MemberData(nameof(CustomColors_Set_TestData))]
        public void ColorDialog_CustomColors_SetWithCustomOldValue_GetReturnsExpected(int[] value, int[] expected)
        {
            using var dialog = new ColorDialog
            {
                CustomColors = new int[1]
            };

            dialog.CustomColors = value;
            Assert.Equal(expected, dialog.CustomColors);
            Assert.NotSame(value, dialog.CustomColors);
            Assert.NotSame(dialog.CustomColors, dialog.CustomColors);

            // Set same.
            dialog.CustomColors = value;
            Assert.Equal(expected, dialog.CustomColors);
            Assert.NotSame(value, dialog.CustomColors);
            Assert.NotSame(dialog.CustomColors, dialog.CustomColors);
        }

        [WinFormsFact]
        public void ColorDialog_CustomColors_Set_GetReturnsClone()
        {
            int[] value = Enumerable.Repeat(1, 16).ToArray();
            using var dialog = new ColorDialog
            {
                CustomColors = value
            };
            value[0] = 0;
            Assert.Equal(Enumerable.Repeat(1, 16), dialog.CustomColors);
        }

        [WinFormsTheory]
        [InlineData(true, 0x2, 0)]
        [InlineData(false, 0, 0x2)]
        public void ColorDialog_FullOpen_Set_GetReturnsExpected(bool value, int expectedOptions, int expectedOptionsAfter)
        {
            using var dialog = new SubColorDialog
            {
                FullOpen = value
            };
            Assert.Equal(value, dialog.FullOpen);
            Assert.Equal(expectedOptions, dialog.Options);

            // Set same.
            dialog.FullOpen = value;
            Assert.Equal(value, dialog.FullOpen);
            Assert.Equal(expectedOptions, dialog.Options);

            // Set different.
            dialog.FullOpen = !value;
            Assert.Equal(!value, dialog.FullOpen);
            Assert.Equal(expectedOptionsAfter, dialog.Options);
        }

        [WinFormsTheory]
        [InlineData(true, 0x8, 0)]
        [InlineData(false, 0, 0x8)]
        public void ColorDialog_ShowHelp_Set_GetReturnsExpected(bool value, int expectedOptions, int expectedOptionsAfter)
        {
            using var dialog = new SubColorDialog
            {
                ShowHelp = value
            };
            Assert.Equal(value, dialog.ShowHelp);
            Assert.Equal(expectedOptions, dialog.Options);

            // Set same.
            dialog.ShowHelp = value;
            Assert.Equal(value, dialog.ShowHelp);
            Assert.Equal(expectedOptions, dialog.Options);

            // Set different.
            dialog.ShowHelp = !value;
            Assert.Equal(!value, dialog.ShowHelp);
            Assert.Equal(expectedOptionsAfter, dialog.Options);
        }

        [WinFormsTheory]
        [InlineData(true, 0x80, 0)]
        [InlineData(false, 0, 0x80)]
        public void ColorDialog_SolidColorOnly_Set_GetReturnsExpected(bool value, int expectedOptions, int expectedOptionsAfter)
        {
            using var dialog = new SubColorDialog
            {
                SolidColorOnly = value
            };
            Assert.Equal(value, dialog.SolidColorOnly);
            Assert.Equal(expectedOptions, dialog.Options);

            // Set same.
            dialog.SolidColorOnly = value;
            Assert.Equal(value, dialog.SolidColorOnly);
            Assert.Equal(expectedOptions, dialog.Options);

            // Set different.
            dialog.SolidColorOnly = !value;
            Assert.Equal(!value, dialog.SolidColorOnly);
            Assert.Equal(expectedOptionsAfter, dialog.Options);
        }

        [WinFormsFact]
        public void ColorDialog_Reset_Invoke_Success()
        {
            using var dialog = new SubColorDialog
            {
                AllowFullOpen = false,
                AnyColor = true,
                Color = Color.Red,
                CustomColors = new int[] { 1, 2, 3 },
                FullOpen = true,
                ShowHelp = true,
                SolidColorOnly = true,
                Tag = "Tag",
            };

            dialog.Reset();
            Assert.True(dialog.AllowFullOpen);
            Assert.False(dialog.AnyColor);
            Assert.True(dialog.CanRaiseEvents);
            Assert.Equal(Color.Black, dialog.Color);
            Assert.Equal(Enumerable.Repeat(0x00FFFFFF, 16).ToArray(), dialog.CustomColors);
            Assert.NotSame(dialog.CustomColors, dialog.CustomColors);
            Assert.Null(dialog.Container);
            Assert.False(dialog.DesignMode);
            Assert.NotNull(dialog.Events);
            Assert.Same(dialog.Events, dialog.Events);
            Assert.False(dialog.FullOpen);
            Assert.NotEqual(IntPtr.Zero, dialog.Instance);
            Assert.Equal(dialog.Instance, dialog.Instance);
            Assert.Equal(0, dialog.Options);
            Assert.False(dialog.ShowHelp);
            Assert.False(dialog.SolidColorOnly);
            Assert.Null(dialog.Site);
            Assert.Equal("Tag", dialog.Tag);
        }

        [WinFormsFact]
        public void ColorDialog_ToString_Invoke_ReturnsExpected()
        {
            using var dialog = new SubColorDialog();
            Assert.Equal("System.Windows.Forms.Tests.ColorDialogTests+SubColorDialog,  Color: Color [Black]", dialog.ToString());
        }

        private class SubColorDialog : ColorDialog
        {
            public new bool CanRaiseEvents => base.CanRaiseEvents;

            public new bool DesignMode => base.DesignMode;

            public new EventHandlerList Events => base.Events;

            public new IntPtr Instance => base.Instance;

            public new int Options => base.Options;
        }

        private class EmptyResetColorDialog : SubColorDialog
        {
            public override void Reset()
            {
            }
        }
    }
}
