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

#nullable disable

using System.Drawing;

namespace System.Windows.Forms
{
    public static partial class ControlPaint
    {
        /// <summary>
        ///  Logic copied from Windows sources to copy the lightening and darkening of colors.
        /// </summary>
        private readonly struct HLSColor
        {
            private const int ShadowAdjustment = -333;
            private const int HighlightAdjustment = 500;

            private const int Range = 240;
            private const int HLSMax = Range;
            private const int RGBMax = 255;
            private const int Undefined = HLSMax * 2 / 3;

            private readonly int _hue;
            private readonly int _saturation;
            private readonly bool _isSystemColors_Control;

            public HLSColor(Color color)
            {
                _isSystemColors_Control = color.ToKnownColor() == SystemColors.Control.ToKnownColor();

                ARGB argb = color;
                int r = argb.R;
                int g = argb.G;
                int b = argb.B;
                int max, min;
                int sum, dif;
                int Rdelta, Gdelta, Bdelta;  // intermediate value: % of spread from max

                // calculate lightness
                max = Math.Max(Math.Max(r, g), b);
                min = Math.Min(Math.Min(r, g), b);
                sum = max + min;

                Luminosity = ((sum * HLSMax) + RGBMax) / (2 * RGBMax);

                dif = max - min;
                if (dif == 0)
                {
                    // r=g=b --> achromatic case
                    _saturation = 0;
                    _hue = Undefined;
                }
                else
                {
                    // chromatic case
                    _saturation = Luminosity <= (HLSMax / 2)
                        ? ((dif * HLSMax) + (sum / 2)) / sum
                        : ((dif * HLSMax) + (2 * RGBMax - sum) / 2) / (2 * RGBMax - sum);

                    Rdelta = (((max - r) * (HLSMax / 6)) + (dif / 2)) / dif;
                    Gdelta = (((max - g) * (HLSMax / 6)) + (dif / 2)) / dif;
                    Bdelta = (((max - b) * (HLSMax / 6)) + (dif / 2)) / dif;

                    if (r == max)
                    {
                        _hue = Bdelta - Gdelta;
                    }
                    else if (g == max)
                    {
                        _hue = (HLSMax / 3) + Rdelta - Bdelta;
                    }
                    else
                    {
                        // B == cMax
                        _hue = (2 * HLSMax / 3) + Gdelta - Rdelta;
                    }

                    if (_hue < 0)
                    {
                        _hue += HLSMax;
                    }

                    if (_hue > HLSMax)
                    {
                        _hue -= HLSMax;
                    }
                }
            }

            public int Luminosity { get; }

            public Color Darker(float percDarker)
            {
                if (!_isSystemColors_Control)
                {
                    int zeroLum = NewLuma(ShadowAdjustment, true);
                    return ColorFromHLS(_hue, zeroLum - (int)(zeroLum * percDarker), _saturation);
                }
                else
                {
                    // With the usual color scheme, ControlDark/DarkDark is not exactly
                    // what we would otherwise calculate
                    if (percDarker == 0.0f)
                    {
                        return SystemColors.ControlDark;
                    }
                    else if (percDarker == 1.0f)
                    {
                        return SystemColors.ControlDarkDark;
                    }
                    else
                    {
                        ARGB dark = SystemColors.ControlDark;
                        ARGB darkDark = SystemColors.ControlDarkDark;

                        return Color.FromArgb(
                            (byte)(dark.R - (byte)((dark.R - darkDark.R) * percDarker)),
                            (byte)(dark.G - (byte)((dark.G - darkDark.G) * percDarker)),
                            (byte)(dark.B - (byte)((dark.B - darkDark.B) * percDarker)));
                    }
                }
            }

            public override bool Equals(object o)
            {
                if (!(o is HLSColor))
                {
                    return false;
                }

                HLSColor c = (HLSColor)o;
                return _hue == c._hue &&
                       _saturation == c._saturation &&
                       Luminosity == c.Luminosity &&
                       _isSystemColors_Control == c._isSystemColors_Control;
            }

            public static bool operator ==(HLSColor a, HLSColor b) => a.Equals(b);

            public static bool operator !=(HLSColor a, HLSColor b) => !a.Equals(b);

            public override int GetHashCode() => HashCode.Combine(_hue, _saturation, Luminosity);

            public Color Lighter(float percentLighter)
            {
                if (_isSystemColors_Control)
                {
                    // With the usual color scheme, ControlLight/LightLight is not exactly
                    // what we would otherwise calculate
                    if (percentLighter == 0.0f)
                    {
                        return SystemColors.ControlLight;
                    }
                    else if (percentLighter == 1.0f)
                    {
                        return SystemColors.ControlLightLight;
                    }
                    else
                    {
                        ARGB light = SystemColors.ControlLight;
                        ARGB lightLight = SystemColors.ControlLightLight;

                        return Color.FromArgb(
                            (byte)(light.R - (byte)((light.R - lightLight.R) * percentLighter)),
                            (byte)(light.G - (byte)((light.G - lightLight.G) * percentLighter)),
                            (byte)(light.B - (byte)((light.B - lightLight.B) * percentLighter)));
                    }
                }
                else
                {
                    int zeroLum = Luminosity;
                    int oneLum = NewLuma(HighlightAdjustment, true);
                    return ColorFromHLS(_hue, zeroLum + (int)((oneLum - zeroLum) * percentLighter), _saturation);
                }
            }

            private int NewLuma(int n, bool scale)
            {
                return NewLuma(Luminosity, n, scale);
            }

            private int NewLuma(int luminosity, int n, bool scale)
            {
                if (n == 0)
                {
                    return luminosity;
                }

                if (scale)
                {
                    return n > 0
                        ? (int)((luminosity * (1000 - n) + (Range + 1L) * n) / 1000)
                        : luminosity * (n + 1000) / 1000;
                }

                luminosity += (int)((long)n * Range / 1000);

                if (luminosity < 0)
                {
                    return 0;
                }
                else if (luminosity > HLSMax)
                {
                    return HLSMax;
                }

                return luminosity;
            }

            private Color ColorFromHLS(int hue, int luminosity, int saturation)
            {
                byte r, g, b;
                int magic1, magic2;

                if (saturation == 0)
                {
                    // achromatic case
                    r = g = b = (byte)(luminosity * RGBMax / HLSMax);
                    if (hue != Undefined)
                    {
                        /* ERROR */
                    }
                }
                else
                {
                    // chromatic case
                    if (luminosity <= (HLSMax / 2))
                    {
                        magic2 = ((luminosity * (HLSMax + saturation)) + (HLSMax / 2)) / HLSMax;
                    }
                    else
                    {
                        magic2 = luminosity + saturation - (((luminosity * saturation) + (HLSMax / 2)) / HLSMax);
                    }

                    magic1 = 2 * luminosity - magic2;

                    // get RGB, change units from HLSMax to RGBMax
                    r = (byte)((HueToRGB(magic1, magic2, hue + HLSMax / 3) * RGBMax + (HLSMax / 2)) / HLSMax);
                    g = (byte)((HueToRGB(magic1, magic2, hue) * RGBMax + (HLSMax / 2)) / HLSMax);
                    b = (byte)((HueToRGB(magic1, magic2, hue - HLSMax / 3) * RGBMax + (HLSMax / 2)) / HLSMax);
                }

                return Color.FromArgb(r, g, b);
            }

            private int HueToRGB(int n1, int n2, int hue)
            {
                // range check: note values passed add/subtract thirds of range

                /* The following is redundant for WORD (unsigned int) */
                if (hue < 0)
                {
                    hue += HLSMax;
                }

                if (hue > HLSMax)
                {
                    hue -= HLSMax;
                }

                // return r, g, or b value from this tridrant
                if (hue < (HLSMax / 6))
                {
                    return n1 + (((n2 - n1) * hue + (HLSMax / 12)) / (HLSMax / 6));
                }

                if (hue < (HLSMax / 2))
                {
                    return n2;
                }

                if (hue < (HLSMax * 2 / 3))
                {
                    return n1 + (((n2 - n1) * ((HLSMax * 2 / 3) - hue) + (HLSMax / 12)) / (HLSMax / 6));
                }
                else
                {
                    return n1;
                }
            }
        }
    }
}
