#include "gpu_ball_buffer.hpp"

#include <SDL3/SDL_log.h>
#include <algorithm>  // std::min
#include <cstring>    // memcpy

bool GpuBallBuffer::init(SDL_GPUDevice* device) {
    Uint32 buf_size = static_cast<Uint32>(MAX_BALLS) * sizeof(BallGPUData);

    // GPU vertex buffer (instance-rate data read by the ball instanced shader)
    SDL_GPUBufferCreateInfo buf_info = {};
    buf_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
    buf_info.size  = buf_size;
    gpu_buf_ = SDL_CreateGPUBuffer(device, &buf_info);
    if (!gpu_buf_) {
        SDL_Log("GpuBallBuffer: GPU buffer creation failed: %s", SDL_GetError());
        return false;
    }

    // Transfer buffer (upload staging, cycled every frame)
    SDL_GPUTransferBufferCreateInfo tb_info = {};
    tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
    tb_info.size  = buf_size;
    transfer_buf_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
    if (!transfer_buf_) {
        SDL_Log("GpuBallBuffer: transfer buffer creation failed: %s", SDL_GetError());
        return false;
    }

    SDL_Log("GpuBallBuffer: initialized (capacity %d balls, %.1f MB VRAM)",
            MAX_BALLS, buf_size / (1024.0f * 1024.0f));
    return true;
}

void GpuBallBuffer::destroy(SDL_GPUDevice* device) {
    if (!device) return;
    if (transfer_buf_) { SDL_ReleaseGPUTransferBuffer(device, transfer_buf_); transfer_buf_ = nullptr; }
    if (gpu_buf_)      { SDL_ReleaseGPUBuffer(device, gpu_buf_);              gpu_buf_      = nullptr; }
    count_ = 0;
}

bool GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd,
                            const BallGPUData* data, int count) {
    if (!data || count <= 0) { count_ = 0; return false; }
    count = std::min(count, MAX_BALLS);

    Uint32 upload_size = static_cast<Uint32>(count) * sizeof(BallGPUData);

    void* ptr = SDL_MapGPUTransferBuffer(device, transfer_buf_, true /* cycle */);
    if (!ptr) {
        SDL_Log("GpuBallBuffer: transfer buffer map failed: %s", SDL_GetError());
        return false;
    }
    memcpy(ptr, data, upload_size);
    SDL_UnmapGPUTransferBuffer(device, transfer_buf_);

    SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
    SDL_GPUTransferBufferLocation src = { transfer_buf_, 0 };
    SDL_GPUBufferRegion           dst = { gpu_buf_,      0, upload_size };
    SDL_UploadToGPUBuffer(copy, &src, &dst, true /* cycle */);
    SDL_EndGPUCopyPass(copy);

    count_ = count;
    return true;
}
