using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Numerics;
using System.Runtime.CompilerServices;

namespace Dalamud.Interface;

/// <summary>
/// Class containing various methods for manipulating colors.
/// </summary>
public static class ColorHelpers
{
    /// <summary>
    /// Pack a vector4 color into a uint for use in ImGui APIs.
    /// </summary>
    /// <param name="color">The color to pack.</param>
    /// <returns>The packed color.</returns>
    public static uint RgbaVector4ToUint(Vector4 color)
    {
        var r = (byte)(color.X * 255);
        var g = (byte)(color.Y * 255);
        var b = (byte)(color.Z * 255);
        var a = (byte)(color.W * 255);

        return (uint)((a << 24) | (b << 16) | (g << 8) | r);
    }

    /// <summary>
    /// Convert a RGBA color in the range of 0.f to 1.f to a uint.
    /// </summary>
    /// <param name="color">The color to pack.</param>
    /// <returns>The packed color.</returns>
    public static Vector4 RgbaUintToVector4(uint color)
    {
        var r = (color & 0x000000FF) / 255f;
        var g = ((color & 0x0000FF00) >> 8) / 255f;
        var b = ((color & 0x00FF0000) >> 16) / 255f;
        var a = ((color & 0xFF000000) >> 24) / 255f;

        return new Vector4(r, g, b, a);
    }

    /// <summary>
    /// Convert a RGBA color in the range of 0.f to 1.f to a HSV color.
    /// </summary>
    /// <param name="color">The color to convert.</param>
    /// <returns>The color in a HSV representation.</returns>
    public static HsvaColor RgbaToHsv(Vector4 color)
    {
        var r = color.X;
        var g = color.Y;
        var b = color.Z;

        var max = Math.Max(r, Math.Max(g, b));
        var min = Math.Min(r, Math.Min(g, b));

        var h = max;
        var s = max;
        var v = max;

        var d = max - min;
        s = max == 0 ? 0 : d / max;

        if (max == min)
        {
            h = 0; // achromatic
        }
        else
        {
            if (max == r)
            {
                h = ((g - b) / d) + (g < b ? 6 : 0);
            }
            else if (max == g)
            {
                h = ((b - r) / d) + 2;
            }
            else if (max == b)
            {
                h = ((r - g) / d) + 4;
            }

            h /= 6;
        }

        return new HsvaColor(h, s, v, color.W);
    }

    /// <summary>
    /// Convert a HSV color to a RGBA color in the range of 0.f to 1.f.
    /// </summary>
    /// <param name="hsv">The color to convert.</param>
    /// <returns>The RGB color.</returns>
    public static Vector4 HsvToRgb(HsvaColor hsv)
    {
        var h = hsv.H;
        var s = hsv.S;
        var v = hsv.V;

        var r = 0f;
        var g = 0f;
        var b = 0f;

        var i = (int)Math.Floor(h * 6);
        var f = (h * 6) - i;
        var p = v * (1 - s);
        var q = v * (1 - (f * s));
        var t = v * (1 - ((1 - f) * s));

        switch (i % 6)
        {
            case 0:
                r = v;
                g = t;
                b = p;
                break;

            case 1:
                r = q;
                g = v;
                b = p;
                break;

            case 2:
                r = p;
                g = v;
                b = t;
                break;

            case 3:
                r = p;
                g = q;
                b = v;
                break;

            case 4:
                r = t;
                g = p;
                b = v;
                break;

            case 5:
                r = v;
                g = p;
                b = q;
                break;
        }

        return new Vector4(r, g, b, hsv.A);
    }

    /// <summary>
    /// Performs a swap of endianness
    /// Exmaple:
    /// (FFXIV) RGBA to ABGR (ImGui).
    /// </summary>
    /// <param name="rgba">Color value in byte order X Y Z W.</param>
    /// <returns>Endian swapped color value with new byte order W Z Y X.</returns>
    public static uint SwapEndianness(uint rgba)
        => BinaryPrimitives.ReverseEndianness(rgba);

    /// <summary>
    /// Lighten a color.
    /// </summary>
    /// <param name="color">The color to lighten.</param>
    /// <param name="amount">The amount to lighten.</param>
    /// <returns>The lightened color.</returns>
    public static Vector4 Lighten(this Vector4 color, float amount)
    {
        var hsv = RgbaToHsv(color);
        hsv.V += amount;
        return HsvToRgb(hsv);
    }

    /// <summary>
    /// Lighten a color.
    /// </summary>
    /// <param name="color">The color to lighten.</param>
    /// <param name="amount">The amount to lighten.</param>
    /// <returns>The lightened color.</returns>
    public static uint Lighten(uint color, float amount)
        => RgbaVector4ToUint(Lighten(RgbaUintToVector4(color), amount));

    /// <summary>
    /// Darken a color.
    /// </summary>
    /// <param name="color">The color to lighten.</param>
    /// <param name="amount">The amount to lighten.</param>
    /// <returns>The darkened color.</returns>
    public static Vector4 Darken(this Vector4 color, float amount)
    {
        var hsv = RgbaToHsv(color);
        hsv.V -= amount;
        return HsvToRgb(hsv);
    }

    /// <summary>
    /// Darken a color.
    /// </summary>
    /// <param name="color">The color to lighten.</param>
    /// <param name="amount">The amount to lighten.</param>
    /// <returns>The darkened color.</returns>
    public static uint Darken(uint color, float amount)
        => RgbaVector4ToUint(Darken(RgbaUintToVector4(color), amount));

    /// <summary>
    /// Saturate a color.
    /// </summary>
    /// <param name="color">The color to lighten.</param>
    /// <param name="amount">The amount to lighten.</param>
    /// <returns>The saturated color.</returns>
    public static Vector4 Saturate(this Vector4 color, float amount)
    {
        var hsv = RgbaToHsv(color);
        hsv.S += amount;
        return HsvToRgb(hsv);
    }

    /// <summary>
    /// Saturate a color.
    /// </summary>
    /// <param name="color">The color to lighten.</param>
    /// <param name="amount">The amount to lighten.</param>
    /// <returns>The saturated color.</returns>
    public static uint Saturate(uint color, float amount)
        => RgbaVector4ToUint(Saturate(RgbaUintToVector4(color), amount));

    /// <summary>
    /// Desaturate a color.
    /// </summary>
    /// <param name="color">The color to lighten.</param>
    /// <param name="amount">The amount to lighten.</param>
    /// <returns>The desaturated color.</returns>
    public static Vector4 Desaturate(this Vector4 color, float amount)
    {
        var hsv = RgbaToHsv(color);
        hsv.S -= amount;
        return HsvToRgb(hsv);
    }

    /// <summary>
    /// Desaturate a color.
    /// </summary>
    /// <param name="color">The color to lighten.</param>
    /// <param name="amount">The amount to lighten.</param>
    /// <returns>The desaturated color.</returns>
    public static uint Desaturate(uint color, float amount)
        => RgbaVector4ToUint(Desaturate(RgbaUintToVector4(color), amount));

    /// <summary>Applies the given opacity value ranging from 0 to 1 to an uint value containing a RGBA value.</summary>
    /// <param name="rgba">RGBA value to transform.</param>
    /// <param name="opacity">Opacity to apply.</param>
    /// <returns>Transformed value.</returns>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static uint ApplyOpacity(uint rgba, float opacity) =>
        ((uint)MathF.Round((rgba >> 24) * opacity) << 24) | (rgba & 0xFFFFFFu);

    /// <summary>Swaps red and blue channels of a given color in ARGB(BB GG RR AA) and ABGR(RR GG BB AA).</summary>
    /// <param name="x">Color to process.</param>
    /// <returns>Swapped color.</returns>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static uint SwapRedBlue(uint x) => (x & 0xFF00FF00u) | ((x >> 16) & 0xFF) | ((x & 0xFF) << 16);

    /// <summary>
    /// Fade a color.
    /// </summary>
    /// <param name="color">The color to lighten.</param>
    /// <param name="amount">The amount to lighten.</param>
    /// <returns>The faded color.</returns>
    public static Vector4 Fade(this Vector4 color, float amount)
    {
        var hsv = RgbaToHsv(color);
        hsv.A -= amount;
        return HsvToRgb(hsv);
    }

    /// <summary>
    /// Set alpha of a color.
    /// </summary>
    /// <param name="color">The color.</param>
    /// <param name="alpha">The alpha value to set.</param>
    /// <returns>The color with the set alpha value.</returns>
    public static Vector4 WithAlpha(this Vector4 color, float alpha)
        => color with { W = alpha };

    /// <summary>
    /// Fade a color.
    /// </summary>
    /// <param name="color">The color to lighten.</param>
    /// <param name="amount">The amount to lighten.</param>
    /// <returns>The faded color.</returns>
    public static uint Fade(uint color, float amount)
        => RgbaVector4ToUint(Fade(RgbaUintToVector4(color), amount));

    /// <summary>
    /// Convert a KnownColor to a RGBA vector with values between 0.0f and 1.0f.
    /// </summary>
    /// <param name="knownColor">Known Color to convert.</param>
    /// <returns>RGBA Vector with values between 0.0f and 1.0f.</returns>
    public static Vector4 Vector(this KnownColor knownColor)
    {
        var rgbColor = Color.FromKnownColor(knownColor);
        return new Vector4(rgbColor.R, rgbColor.G, rgbColor.B, rgbColor.A) / 255.0f;
    }

    /// <summary>
    /// Normalizes a Vector4 with RGBA 255 color values to values between 0.0f and 1.0f
    /// If values are out of RGBA 255 range, the original value is returned.
    /// </summary>
    /// <param name="color">The color vector to convert.</param>
    /// <returns>A vector with values between 0.0f and 1.0f.</returns>
    public static Vector4 NormalizeToUnitRange(this Vector4 color) => color switch
    {
        // If any components are out of range, return original value.
        { W: > 255.0f or < 0.0f } or { X: > 255.0f or < 0.0f } or { Y: > 255.0f or < 0.0f } or { Z: > 255.0f or < 0.0f } => color,

        // If all components are already unit range, return original value.
        { W: >= 0.0f and <= 1.0f, X: >= 0.0f and <= 1.0f, Y: >= 0.0f and <= 1.0f, Z: >= 0.0f and <= 1.0f } => color,

        _ => color / 255.0f,
    };
    
    /// <summary>
    /// A struct representing a color using HSVA coordinates.
    /// </summary>
    /// <param name="H">The hue represented by this struct.</param>
    /// <param name="S">The saturation represented by this struct.</param>
    /// <param name="V">The value represented by this struct.</param>
    /// <param name="A">The alpha represented by this struct.</param>
    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter",
                     Justification = "I don't like it.")]
    public record struct HsvaColor(float H, float S, float V, float A);
}
