using System;
using System.Linq;
using System.Runtime.CompilerServices;

namespace Glamourer.Customization;

public enum CustomizeIndex : byte
{
    Race,
    Gender,
    BodyType,
    Height,
    Clan,
    Face,
    Hairstyle,
    Highlights,
    SkinColor,
    EyeColorRight,
    HairColor,
    HighlightsColor,
    FacialFeature1,
    FacialFeature2,
    FacialFeature3,
    FacialFeature4,
    FacialFeature5,
    FacialFeature6,
    FacialFeature7,
    LegacyTattoo,
    TattooColor,
    Eyebrows,
    EyeColorLeft,
    EyeShape,
    SmallIris,
    Nose,
    Jaw,
    Mouth,
    Lipstick,
    LipColor,
    MuscleMass,
    TailShape,
    BustSize,
    FacePaint,
    FacePaintReversed,
    FacePaintColor,
}

public static class CustomizationExtensions
{
    public const int NumIndices = (int)CustomizeIndex.FacePaintColor + 1;

    public static readonly CustomizeIndex[] All = Enum.GetValues<CustomizeIndex>()
        .Where(v => v is not CustomizeIndex.Race and not CustomizeIndex.BodyType).ToArray();

    public static readonly CustomizeIndex[] AllBasic = All
        .Where(v => v is not CustomizeIndex.Gender and not CustomizeIndex.Clan).ToArray();

    [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
    public static (int ByteIdx, byte Mask) ToByteAndMask(this CustomizeIndex index)
        => index switch
        {
            CustomizeIndex.Race              => (0, 0xFF),
            CustomizeIndex.Gender            => (1, 0xFF),
            CustomizeIndex.BodyType          => (2, 0xFF),
            CustomizeIndex.Height            => (3, 0xFF),
            CustomizeIndex.Clan              => (4, 0xFF),
            CustomizeIndex.Face              => (5, 0xFF),
            CustomizeIndex.Hairstyle         => (6, 0xFF),
            CustomizeIndex.Highlights        => (7, 0x80),
            CustomizeIndex.SkinColor         => (8, 0xFF),
            CustomizeIndex.EyeColorRight     => (9, 0xFF),
            CustomizeIndex.HairColor         => (10, 0xFF),
            CustomizeIndex.HighlightsColor   => (11, 0xFF),
            CustomizeIndex.FacialFeature1    => (12, 0x01),
            CustomizeIndex.FacialFeature2    => (12, 0x02),
            CustomizeIndex.FacialFeature3    => (12, 0x04),
            CustomizeIndex.FacialFeature4    => (12, 0x08),
            CustomizeIndex.FacialFeature5    => (12, 0x10),
            CustomizeIndex.FacialFeature6    => (12, 0x20),
            CustomizeIndex.FacialFeature7    => (12, 0x40),
            CustomizeIndex.LegacyTattoo      => (12, 0x80),
            CustomizeIndex.TattooColor       => (13, 0xFF),
            CustomizeIndex.Eyebrows          => (14, 0xFF),
            CustomizeIndex.EyeColorLeft      => (15, 0xFF),
            CustomizeIndex.EyeShape          => (16, 0x7F),
            CustomizeIndex.SmallIris         => (16, 0x80),
            CustomizeIndex.Nose              => (17, 0xFF),
            CustomizeIndex.Jaw               => (18, 0xFF),
            CustomizeIndex.Mouth             => (19, 0x7F),
            CustomizeIndex.Lipstick          => (19, 0x80),
            CustomizeIndex.LipColor          => (20, 0xFF),
            CustomizeIndex.MuscleMass        => (21, 0xFF),
            CustomizeIndex.TailShape         => (22, 0xFF),
            CustomizeIndex.BustSize          => (23, 0xFF),
            CustomizeIndex.FacePaint         => (24, 0x7F),
            CustomizeIndex.FacePaintReversed => (24, 0x80),
            CustomizeIndex.FacePaintColor    => (25, 0xFF),
            _                                => (0, 0x00),
        };

    [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
    public static CustomizeFlag ToFlag(this CustomizeIndex index)
        => (CustomizeFlag)(1ul << (int)index);

    public static string ToDefaultName(this CustomizeIndex customizeIndex)
        => customizeIndex switch
        {
            CustomizeIndex.Race              => "Race",
            CustomizeIndex.Gender            => "Gender",
            CustomizeIndex.BodyType          => "Body Type",
            CustomizeIndex.Height            => "Height",
            CustomizeIndex.Clan              => "Clan",
            CustomizeIndex.Face              => "Head Style",
            CustomizeIndex.Hairstyle         => "Hair Style",
            CustomizeIndex.Highlights        => "Highlights",
            CustomizeIndex.SkinColor         => "Skin Color",
            CustomizeIndex.EyeColorRight     => "Right Eye Color",
            CustomizeIndex.HairColor         => "Hair Color",
            CustomizeIndex.HighlightsColor   => "Highlights Color",
            CustomizeIndex.TattooColor       => "Tattoo Color",
            CustomizeIndex.Eyebrows          => "Eyebrow Style",
            CustomizeIndex.EyeColorLeft      => "Left Eye Color",
            CustomizeIndex.EyeShape          => "Small Pupils",
            CustomizeIndex.Nose              => "Nose Style",
            CustomizeIndex.Jaw               => "Jaw Style",
            CustomizeIndex.Mouth             => "Mouth Style",
            CustomizeIndex.MuscleMass        => "Muscle Tone",
            CustomizeIndex.TailShape         => "Tail Shape",
            CustomizeIndex.BustSize          => "Bust Size",
            CustomizeIndex.FacePaint         => "Face Paint",
            CustomizeIndex.FacePaintColor    => "Face Paint Color",
            CustomizeIndex.LipColor          => "Lip Color",
            CustomizeIndex.FacialFeature1    => "Facial Feature 1",
            CustomizeIndex.FacialFeature2    => "Facial Feature 2",
            CustomizeIndex.FacialFeature3    => "Facial Feature 3",
            CustomizeIndex.FacialFeature4    => "Facial Feature 4",
            CustomizeIndex.FacialFeature5    => "Facial Feature 5",
            CustomizeIndex.FacialFeature6    => "Facial Feature 6",
            CustomizeIndex.FacialFeature7    => "Facial Feature 7",
            CustomizeIndex.LegacyTattoo      => "Legacy Tattoo",
            CustomizeIndex.SmallIris         => "Small Iris",
            CustomizeIndex.Lipstick          => "Enable Lipstick",
            CustomizeIndex.FacePaintReversed => "Reverse Face Paint",
            _                                => string.Empty,
        };

    [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
    public static unsafe CustomizeValue Get(this in Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index)
    {
        var (offset, mask) = index.ToByteAndMask();
        return (CustomizeValue)(data.Data[offset] & mask);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
    public static unsafe bool Set(this ref Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index, CustomizeValue value)
    {
        var (offset, mask) = index.ToByteAndMask();
        return mask != 0xFF
            ? SetIfDifferentMasked(ref data.Data[offset], value, mask)
            : SetIfDifferent(ref data.Data[offset], value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
    private static bool SetIfDifferentMasked(ref byte oldValue, CustomizeValue newValue, byte mask)
    {
        var tmp = (byte)(newValue.Value & mask);
        tmp = (byte)(tmp | (oldValue & ~mask));
        if (oldValue == tmp)
            return false;

        oldValue = tmp;
        return true;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
    private static bool SetIfDifferent(ref byte oldValue, CustomizeValue newValue)
    {
        if (oldValue == newValue.Value)
            return false;

        oldValue = newValue.Value;
        return true;
    }
}
