using System.Numerics;
using System.Runtime.InteropServices;
using WellspringCS;

namespace Nerfed.Runtime.Graphics;

public unsafe class TextBatch : GraphicsResource
{
    public const int INITIAL_CHAR_COUNT = 64;
    public const int INITIAL_VERTEX_COUNT = INITIAL_CHAR_COUNT * 4;
    public const int INITIAL_INDEX_COUNT = INITIAL_CHAR_COUNT * 6;

    public IntPtr Handle { get; }
    public Buffer VertexBuffer { get; protected set; } = null;
    public Buffer IndexBuffer { get; protected set; } = null;
    public uint PrimitiveCount { get; protected set; }
    public Font CurrentFont { get; private set; }

    private readonly GraphicsDevice graphicsDevice;
    private readonly int stringBytesLength;
    private TransferBuffer TransferBuffer;
    private byte* stringBytes;

    public TextBatch(GraphicsDevice device) : base(device)
    {
        graphicsDevice = device;
        Handle = Wellspring.Wellspring_CreateTextBatch();

        stringBytesLength = 128;
        stringBytes = (byte*)NativeMemory.Alloc((nuint)stringBytesLength);

        VertexBuffer = Buffer.Create<FontVertex>(graphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT);
        IndexBuffer = Buffer.Create<uint>(graphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT);

        TransferBuffer = TransferBuffer.Create<byte>(
            graphicsDevice,
            TransferBufferUsage.Upload,
            VertexBuffer.Size + IndexBuffer.Size
        );
    }

    // Call this to initialize or reset the batch.
    public void Start(Font font)
    {
        Wellspring.Wellspring_StartTextBatch(Handle, font.Handle);
        CurrentFont = font;
        PrimitiveCount = 0;
    }

    // Add text with size and color to the batch
    public unsafe bool Add(
        string text,
        int pixelSize,
        Color color,
        HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
        VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
    )
    {
        int byteCount = System.Text.Encoding.UTF8.GetByteCount(text);

        if (stringBytesLength < byteCount)
        {
            stringBytes = (byte*)NativeMemory.Realloc(stringBytes, (nuint)byteCount);
        }

        fixed (char* chars = text)
        {
            System.Text.Encoding.UTF8.GetBytes(chars, text.Length, stringBytes, byteCount);

            byte result = Wellspring.Wellspring_AddToTextBatch(
                Handle,
                pixelSize,
                new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
                (Wellspring.HorizontalAlignment)horizontalAlignment,
                (Wellspring.VerticalAlignment)verticalAlignment,
                (IntPtr)stringBytes,
                (uint)byteCount
            );

            if (result == 0)
            {
                Log.Warning("Could not decode string: " + text);
                return false;
            }
        }

        return true;
    }

    // Call this after you have made all the Add calls you want, but before beginning a render pass.
    public unsafe void UploadBufferData(CommandBuffer commandBuffer)
    {
        Wellspring.Wellspring_GetBufferData(
            Handle,
            out uint vertexCount,
            out IntPtr vertexDataPointer,
            out uint vertexDataLengthInBytes,
            out IntPtr indexDataPointer,
            out uint indexDataLengthInBytes
        );

        Span<byte> vertexSpan = new Span<byte>((void*)vertexDataPointer, (int)vertexDataLengthInBytes);
        Span<byte> indexSpan = new Span<byte>((void*)indexDataPointer, (int)indexDataLengthInBytes);

        bool newTransferBufferNeeded = false;

        if (VertexBuffer.Size < vertexDataLengthInBytes)
        {
            VertexBuffer.Dispose();
            VertexBuffer = new Buffer(graphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
            newTransferBufferNeeded = true;
        }

        if (IndexBuffer.Size < indexDataLengthInBytes)
        {
            IndexBuffer.Dispose();
            IndexBuffer = new Buffer(graphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes);
            newTransferBufferNeeded = true;
        }

        if (newTransferBufferNeeded)
        {
            TransferBuffer.Dispose();
            TransferBuffer = new TransferBuffer(
                graphicsDevice,
                TransferBufferUsage.Upload,
                VertexBuffer.Size + IndexBuffer.Size
            );
        }

        if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
        {
            TransferBuffer.SetData(vertexSpan, true);
            TransferBuffer.SetData(indexSpan, (uint)vertexSpan.Length, false);

            CopyPass copyPass = commandBuffer.BeginCopyPass();
            copyPass.UploadToBuffer(
                new TransferBufferLocation(TransferBuffer),
                new BufferRegion(VertexBuffer, 0, (uint)vertexSpan.Length),
                true
            );
            copyPass.UploadToBuffer(
                new TransferBufferLocation(TransferBuffer, (uint)vertexSpan.Length),
                new BufferRegion(IndexBuffer, 0, (uint)indexSpan.Length),
                true
            );
            commandBuffer.EndCopyPass(copyPass);
        }

        PrimitiveCount = vertexCount / 2;
    }

    // Call this AFTER binding your text pipeline!
    public void Render(CommandBuffer commandBuffer, RenderPass renderPass, Matrix4x4 transformMatrix)
    {
        commandBuffer.PushVertexUniformData(transformMatrix);
        commandBuffer.PushFragmentUniformData(CurrentFont.DistanceRange);

        renderPass.BindFragmentSampler(
            new TextureSamplerBinding(
                CurrentFont.Texture,
                graphicsDevice.LinearSampler
            )
        );
        renderPass.BindVertexBuffer(VertexBuffer);
        renderPass.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo);

        renderPass.DrawIndexedPrimitives(
            0,
            0,
            PrimitiveCount
        );
    }

    protected override void Dispose(bool disposing)
    {
        if (!IsDisposed)
        {
            if (disposing)
            {
                VertexBuffer.Dispose();
                IndexBuffer.Dispose();
            }

            NativeMemory.Free(stringBytes);
            Wellspring.Wellspring_DestroyTextBatch(Handle);
        }

        base.Dispose(disposing);
    }
}