/*
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 the ranged FlushDevice (UI descriptor registration):
// WriteBufferDescriptor / WriteSampledImageDescriptor / RegisterSampler used to
// FlushDevice() the whole multi-KB descriptor heap after writing one few-byte
// descriptor. They now flush only the written byte range via
// VulkanBuffer::FlushDevice(offset, bytes), which rounds the range outward to
// VkPhysicalDeviceLimits::nonCoherentAtomSize (vkFlushMappedMemoryRanges rejects
// an unaligned sub-buffer offset/size).
//
// The rounding lives in AlignMappedFlushRange — pure math with no Vulkan
// dependency — so this drives it directly with no GPU device, and also confirms
// the ranged FlushDevice keeps the issue-#60 coherent-memory gate (a real flush
// would fault with Device::device == VK_NULL_HANDLE).

#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 HOST_VISIBLE  = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
constexpr auto HOST_COHERENT = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
constexpr auto DEVICE_LOCAL  = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;

} // namespace

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

    // ── AlignMappedFlushRange: the spec demands offset and size be multiples
    //    of nonCoherentAtomSize (atom). Start rounds down, end rounds up. ──
    {
        // A descriptor wholly inside one atom expands to cover that atom.
        auto r = AlignMappedFlushRange(/*offset*/ 80, /*bytes*/ 16,
                                       /*atom*/ 64, /*mappingSize*/ 4096);
        Check(r.offset == 64 && r.size == 64,
              "sub-atom range rounds out to the enclosing atom [64,128)");
        Check(r.offset % 64 == 0 && r.size % 64 == 0,
              "rounded offset and size are atom multiples");
    }
    {
        // A range straddling an atom boundary expands both ways.
        auto r = AlignMappedFlushRange(/*offset*/ 60, /*bytes*/ 8,
                                       /*atom*/ 64, /*mappingSize*/ 4096);
        Check(r.offset == 0 && r.size == 128,
              "straddling range rounds out to [0,128)");
    }
    {
        // Already-aligned range is left exactly as is.
        auto r = AlignMappedFlushRange(/*offset*/ 128, /*bytes*/ 64,
                                       /*atom*/ 64, /*mappingSize*/ 4096);
        Check(r.offset == 128 && r.size == 64,
              "already atom-aligned range is unchanged");
    }
    {
        // atom == 1 (no coherence granularity): range passes through verbatim.
        auto r = AlignMappedFlushRange(/*offset*/ 80, /*bytes*/ 16,
                                       /*atom*/ 1, /*mappingSize*/ 4096);
        Check(r.offset == 80 && r.size == 16,
              "atom==1 leaves the range exact");
    }
    {
        // Near the end of a non-atom-aligned mapping: the rounded-up end is
        // clamped to mappingSize so offset+size == mapping size (the spec's
        // exception that permits a non-atom-multiple size).
        auto r = AlignMappedFlushRange(/*offset*/ 4030, /*bytes*/ 8,
                                       /*atom*/ 64, /*mappingSize*/ 4040);
        Check(r.offset == 3968 && r.offset + r.size == 4040,
              "tail range clamps its end to the mapping size");
        Check(r.offset % 64 == 0,
              "clamped range still has an atom-aligned offset");
    }

    // ── Ranged FlushDevice keeps the coherent-memory gate (issue #60). ──
    {
        VulkanBuffer<std::uint8_t, true> buf;
        buf.memoryPropertyFlagsChosen = HOST_VISIBLE | HOST_COHERENT;
        buf.mappedSize = 4096;
        buf.FlushDevice(/*offset*/ 80, /*bytes*/ 16); // must early-return
        Check(true, "ranged FlushDevice skips the Vulkan call on coherent memory");
    }
    {
        VulkanBuffer<std::uint8_t, true> buf;
        buf.memoryPropertyFlagsChosen = HOST_VISIBLE | DEVICE_LOCAL;
        Check(!(buf.memoryPropertyFlagsChosen & HOST_COHERENT),
              "non-coherent chosen type is not gated as coherent (ranged flush still required)");
    }

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