/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*/

// Regression test for issue #63: VulkanBuffer::Resize used to unconditionally
// destroy + reallocate. It now reuses the existing allocation in place when the
// new request still fits within the created capacity AND the immutable-at-create
// properties match (usage flags are fixed at create; the chosen memory type must
// still satisfy the required property flags). The reuse path only shrinks `size`
// and issues no Vulkan call.
//
// The guard is pure logic over recorded fields, so this test drives it directly
// with no GPU device: it stamps a fake non-null buffer handle plus capacity /
// usage / chosen-flags, then calls Resize. With Device::device == VK_NULL_HANDLE
// any real destroy/reallocate would dereference a null dispatch handle and
// crash — so reaching the line after a reuse Resize is the assertion that the
// in-place path was taken. The realloc path (flag/size mismatch) is intentionally
// not exercised here because it would issue real Vulkan calls.

#include <cstdint>
#include <cstdlib>
#include "vulkan/vulkan.h"

import Crafter.Graphics;
import std;
using namespace Crafter;

namespace {

int failures = 0;

void Check(bool ok, std::string_view what) {
    std::println("{}  {}", ok ? "PASS" : "FAIL", what);
    if (!ok) ++failures;
}

constexpr auto DEVICE_LOCAL  = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
constexpr auto HOST_VISIBLE  = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
constexpr auto HOST_COHERENT = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;

constexpr VkBufferUsageFlags2 USAGE =
    VK_BUFFER_USAGE_2_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT;

// A fake non-null, non-dereferenced handle: Resize's reuse path never touches it.
VkBuffer FakeHandle() {
    return reinterpret_cast<VkBuffer>(static_cast<std::uintptr_t>(0x1));
}

// Stamp a buffer into the "already created with capacity C" state without
// touching the GPU, then neutralise it so the destructor's Clear() is skipped.
template <typename Buf>
void NeutraliseHandle(Buf& buf) {
    buf.buffer = VK_NULL_HANDLE;
}

} // namespace

int main() {
    Check(Device::device == VK_NULL_HANDLE,
          "no Vulkan device created — a real reallocate would fault");

    {
        // Shrink within capacity, flags match → reuse in place, only size shrinks.
        VulkanBuffer<float, false> buf;
        buf.buffer = FakeHandle();
        buf.capacity = 16 * sizeof(float);
        buf.size = 16 * sizeof(float);
        buf.usageFlagsCreated = USAGE;
        buf.memoryPropertyFlagsChosen = HOST_VISIBLE | HOST_COHERENT;

        buf.Resize(USAGE, HOST_VISIBLE, 4); // 4 floats <= 16-float capacity
        Check(buf.buffer == FakeHandle(),
              "fitting Resize reuses the existing handle (no realloc)");
        Check(buf.size == 4 * sizeof(float),
              "reuse updates size to the new logical extent");
        Check(buf.capacity == 16 * sizeof(float),
              "reuse leaves capacity at the original allocation size");
        NeutraliseHandle(buf);
    }

    {
        // Exact-fit request (size == capacity) is still a reuse.
        VulkanBuffer<float, false> buf;
        buf.buffer = FakeHandle();
        buf.capacity = 8 * sizeof(float);
        buf.size = 2 * sizeof(float);
        buf.usageFlagsCreated = USAGE;
        buf.memoryPropertyFlagsChosen = HOST_VISIBLE;

        buf.Resize(USAGE, HOST_VISIBLE, 8);
        Check(buf.buffer == FakeHandle() && buf.size == 8 * sizeof(float),
              "exact-capacity Resize reuses the allocation");
        NeutraliseHandle(buf);
    }

    {
        // Reuse requires the chosen memory type to still satisfy the required
        // flags. Chosen type carries DEVICE_LOCAL, so a request for it is
        // satisfied and reuse is allowed.
        VulkanBuffer<float, false> buf;
        buf.buffer = FakeHandle();
        buf.capacity = 8 * sizeof(float);
        buf.size = 8 * sizeof(float);
        buf.usageFlagsCreated = USAGE;
        buf.memoryPropertyFlagsChosen = HOST_VISIBLE | DEVICE_LOCAL;

        buf.Resize(USAGE, DEVICE_LOCAL, 4);
        Check(buf.buffer == FakeHandle() && buf.size == 4 * sizeof(float),
              "reuse allowed when chosen memory type satisfies required flags");
        NeutraliseHandle(buf);
    }

    if (failures != 0) {
        std::println("{} check(s) failed", failures);
        return EXIT_FAILURE;
    }
    std::println("all checks passed");
    return EXIT_SUCCESS;
}
