// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

namespace SharpEmu.Libs.Agc;

internal enum Gen5ShaderEncoding
{
    Sop1,
    Sop2,
    Sopc,
    Sopp,
    Sopk,
    Smrd,
    Smem,
    Mubuf,
    Mtbuf,
    Vop1,
    Vop2,
    Vopc,
    Vop3,
    Vintrp,
    Ds,
    Flat,
    Vop3p,
    Mimg,
    Exp,
}

internal enum Gen5OperandKind
{
    ScalarRegister,
    VectorRegister,
    EncodedConstant,
    LiteralConstant,
}

internal enum Gen5ShaderResourceKind
{
    ReadOnlyTexture,
    ReadWriteTexture,
    Sampler,
    ConstantBuffer,
}

internal enum Gen5PixelOutputKind
{
    Float,
    Uint,
    Sint,
}

internal enum Gen5SpirvStage
{
    Vertex,
    Pixel,
    Compute,
}

internal sealed record Gen5SpirvShader(
    byte[] Spirv,
    IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
    IReadOnlyList<Gen5ImageBinding> ImageBindings,
    uint AttributeCount,
    IReadOnlyList<Gen5VertexInputBinding> VertexInputs);

internal readonly record struct Gen5ShaderResourceMapping(
    Gen5ShaderResourceKind Kind,
    uint Slot,
    uint OffsetDwords,
    bool SizeFlag);

internal sealed record Gen5ShaderMetadata(
    uint ExtendedUserDataSizeDwords,
    uint ShaderResourceTableSizeDwords,
    IReadOnlyDictionary<uint, uint> DirectResources,
    IReadOnlyList<Gen5ShaderResourceMapping> Resources);

internal readonly record struct Gen5ComputeSystemRegisters(
    uint? WorkGroupXRegister,
    uint? WorkGroupYRegister,
    uint? WorkGroupZRegister,
    uint? ThreadGroupSizeRegister)
{
    public bool TryGetExpression(uint scalarRegister, out string expression)
    {
        if (WorkGroupXRegister == scalarRegister)
        {
            expression = "gl_WorkGroupID.x";
            return true;
        }

        if (WorkGroupYRegister == scalarRegister)
        {
            expression = "gl_WorkGroupID.y";
            return true;
        }

        if (WorkGroupZRegister == scalarRegister)
        {
            expression = "gl_WorkGroupID.z";
            return true;
        }

        if (ThreadGroupSizeRegister == scalarRegister)
        {
            expression = "(gl_WorkGroupSize.x * gl_WorkGroupSize.y * gl_WorkGroupSize.z)";
            return true;
        }

        expression = string.Empty;
        return false;
    }

    public void ClearStaticValues(Span<uint> scalarRegisters)
    {
        ClearStaticValue(scalarRegisters, WorkGroupXRegister);
        ClearStaticValue(scalarRegisters, WorkGroupYRegister);
        ClearStaticValue(scalarRegisters, WorkGroupZRegister);
        ClearStaticValue(scalarRegisters, ThreadGroupSizeRegister);
    }

    private static void ClearStaticValue(Span<uint> scalarRegisters, uint? scalarRegister)
    {
        if (scalarRegister is { } register && register < scalarRegisters.Length)
        {
            scalarRegisters[(int)register] = 0;
        }
    }
}

internal sealed record Gen5ShaderState(
    Gen5ShaderProgram Program,
    IReadOnlyList<uint> UserData,
    Gen5ShaderMetadata? Metadata,
    Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
    uint UserDataScalarRegisterBase = 0);

internal readonly record struct Gen5Operand(Gen5OperandKind Kind, uint Value)
{
    public static Gen5Operand Scalar(uint index) =>
        new(Gen5OperandKind.ScalarRegister, index);

    public static Gen5Operand Vector(uint index) =>
        new(Gen5OperandKind.VectorRegister, index);

    public static Gen5Operand Source(uint encoded, uint? literal = null)
    {
        if (encoded >= 256)
        {
            return Vector(encoded - 256);
        }

        if (encoded is 249 or 255 && literal.HasValue)
        {
            return new(Gen5OperandKind.LiteralConstant, literal.Value);
        }

        if (encoded <= 105 || encoded is 106 or 107 or 124 or 126 or 127)
        {
            return Scalar(encoded);
        }

        return new(Gen5OperandKind.EncodedConstant, encoded);
    }

    public override string ToString() => Kind switch
    {
        Gen5OperandKind.ScalarRegister => $"s{Value}",
        Gen5OperandKind.VectorRegister => $"v{Value}",
        Gen5OperandKind.LiteralConstant => $"0x{Value:X8}",
        _ => $"src[{Value}]",
    };
}

internal abstract record Gen5InstructionControl;

internal sealed record Gen5ImageControl(
    uint Dmask,
    uint VectorAddress,
    IReadOnlyList<uint> AddressRegisters,
    uint VectorData,
    uint ScalarResource,
    uint ScalarSampler,
    uint Dimension,
    bool IsArray,
    bool Glc,
    bool Slc) : Gen5InstructionControl
{
    public uint GetAddressRegister(int component) =>
        component < AddressRegisters.Count
            ? AddressRegisters[component]
            : VectorAddress + (uint)component;
}

internal sealed record Gen5GlobalMemoryControl(
    uint DwordCount,
    uint VectorAddress,
    uint VectorData,
    uint ScalarAddress,
    int OffsetBytes,
    bool Glc,
    bool Slc) : Gen5InstructionControl;

internal sealed record Gen5BufferMemoryControl(
    uint DwordCount,
    uint VectorAddress,
    uint VectorData,
    uint ScalarResource,
    int OffsetBytes,
    bool IndexEnabled,
    bool OffsetEnabled,
    bool Glc,
    bool Slc) : Gen5InstructionControl;

internal sealed record Gen5ExportControl(
    uint Target,
    uint EnableMask,
    bool Compressed,
    bool Done,
    bool ValidMask) : Gen5InstructionControl;

internal sealed record Gen5InterpolationControl(
    uint Attribute,
    uint Channel) : Gen5InstructionControl;

internal sealed record Gen5Vop3Control(
    uint AbsoluteMask,
    uint NegateMask,
    uint OutputModifier,
    bool Clamp,
    uint? ScalarDestination) : Gen5InstructionControl;

internal sealed record Gen5SdwaControl(
    uint DestinationSelect,
    uint Source0Select,
    uint Source1Select,
    uint AbsoluteMask,
    uint NegateMask,
    uint OutputModifier,
    bool Clamp) : Gen5InstructionControl;

internal sealed record Gen5DppControl(
    uint Control,
    bool FetchInactive,
    bool BoundControl,
    uint AbsoluteMask,
    uint NegateMask,
    uint BankMask,
    uint RowMask) : Gen5InstructionControl;

internal sealed record Gen5ScalarMemoryControl(
    uint DestinationCount,
    int ImmediateOffsetBytes,
    uint? DynamicOffsetRegister) : Gen5InstructionControl;

internal sealed record Gen5DataShareControl(
    uint Offset0,
    uint Offset1,
    bool Gds) : Gen5InstructionControl;

internal sealed record Gen5ImageBinding(
    uint Pc,
    string Opcode,
    Gen5ImageControl Control,
    IReadOnlyList<uint> ResourceDescriptor,
    IReadOnlyList<uint> SamplerDescriptor,
    uint? MipLevel);

internal sealed record Gen5GlobalMemoryBinding(
    uint ScalarAddress,
    ulong BaseAddress,
    IReadOnlyList<uint> InstructionPcs,
    byte[] Data);

internal sealed record Gen5VertexInputBinding(
    uint Pc,
    uint Location,
    uint ComponentCount,
    uint DataFormat,
    uint NumberFormat,
    ulong BaseAddress,
    uint Stride,
    uint OffsetBytes,
    byte[] Data);

internal sealed record Gen5ShaderEvaluation(
    IReadOnlyList<uint> InitialScalarRegisters,
    IReadOnlyList<uint> ScalarRegisters,
    IReadOnlyDictionary<uint, IReadOnlyList<uint>> ScalarRegistersByPc,
    IReadOnlyList<Gen5ImageBinding> ImageBindings,
    IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
    Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
    IReadOnlySet<uint>? RuntimeScalarRegisters = null,
    IReadOnlyList<Gen5VertexInputBinding>? VertexInputs = null);

internal sealed record Gen5ShaderInstruction(
    uint Pc,
    Gen5ShaderEncoding Encoding,
    string Opcode,
    IReadOnlyList<uint> Words,
    IReadOnlyList<Gen5Operand> Sources,
    IReadOnlyList<Gen5Operand> Destinations,
    Gen5InstructionControl? Control);

internal sealed record Gen5ShaderProgram(
    ulong Address,
    IReadOnlyList<Gen5ShaderInstruction> Instructions)
{
    public IEnumerable<Gen5ImageControl> ImageResources =>
        Instructions
            .Select(instruction => instruction.Control)
            .OfType<Gen5ImageControl>();
}
