/*
    * LimineProtocol.cpp
    * Montauk Boot Contract adapter for the Limine boot protocol
    * Copyright (c) 2026 Daniel Hammer, Limine Contributors (request layout)
*/

// ====================================================================
// Limine boot-protocol adapter.
//
// This is the ONLY translation unit in the kernel that includes <limine.h>
// or knows anything about Limine.  It does two jobs:
//
//   1. Emits the Limine request structures (the markers, base revision, and
//      one request per piece of data we need).  Limine fills in the
//      `.response` pointers before jumping to the kernel.
//
//   2. Implements montauk::boot::Acquire(), translating those native
//      responses into the bootloader-agnostic BootInfo contract.
//
// To port MontaukOS to a different bootloader, write a sibling file in this
// directory that emits that loader's handshake and implements Acquire(), and
// build against it instead of this one.  Nothing else in the kernel changes.
// ====================================================================

#include "../BootProtocol.hpp"
#include <limine.h>

// --------------------------------------------------------------------
// Limine requests.  These were historically in Platform/Limine.hpp; they
// now live here so the rest of the kernel never sees Limine types.
//
// Requests must survive dead-code elimination and live in the
// .limine_requests section, hence "used" + the section attribute.  The
// start/end markers and base-revision tag must appear exactly once in the
// linked image -- this file is that one place.
// --------------------------------------------------------------------

namespace {

    __attribute__((used, section(".limine_requests")))
    volatile LIMINE_BASE_REVISION(3);

    __attribute__((used, section(".limine_requests")))
    volatile limine_framebuffer_request framebuffer_request = {
        .id = LIMINE_FRAMEBUFFER_REQUEST,
        .revision = 0,
        .response = nullptr
    };

    __attribute__((used, section(".limine_requests")))
    volatile limine_efi_system_table_request system_table_request = {
        .id = LIMINE_EFI_SYSTEM_TABLE_REQUEST,
        .revision = 0,
        .response = nullptr
    };

    __attribute__((used, section(".limine_requests")))
    volatile limine_hhdm_request hhdm_request = {
        .id = LIMINE_HHDM_REQUEST,
        .revision = 0,
        .response = nullptr
    };

    __attribute__((used, section(".limine_requests")))
    volatile limine_memmap_request memmap_request = {
        .id = LIMINE_MEMMAP_REQUEST,
        .revision = 0,
        .response = nullptr
    };

    __attribute__((used, section(".limine_requests")))
    volatile limine_efi_memmap_request efi_memmap_request = {
        .id = LIMINE_EFI_MEMMAP_REQUEST,
        .revision = 0,
        .response = nullptr
    };

    __attribute__((used, section(".limine_requests")))
    volatile limine_rsdp_request rsdp_request = {
        .id = LIMINE_RSDP_REQUEST,
        .revision = 0,
        .response = nullptr
    };

    __attribute__((used, section(".limine_requests")))
    volatile limine_module_request module_request = {
        .id = LIMINE_MODULE_REQUEST,
        .revision = 1,
        .response = nullptr,
        .internal_module_count = 0,
        .internal_modules = nullptr
    };

    __attribute__((used, section(".limine_requests")))
    volatile limine_mp_request mp_request = {
        .id = LIMINE_MP_REQUEST,
        .revision = 0,
        .response = nullptr,
        .flags = 0
    };

    __attribute__((used, section(".limine_requests_start")))
    volatile LIMINE_REQUESTS_START_MARKER;

    __attribute__((used, section(".limine_requests_end")))
    volatile LIMINE_REQUESTS_END_MARKER;

}

// --------------------------------------------------------------------
// Static, bootloader-independent storage for the translated contract arrays.
//
// Acquire() runs before the page-frame allocator and heap exist, so it
// cannot allocate.  We translate Limine's native arrays into these fixed
// buffers (kernel .bss).  Caps are generous relative to real hardware; if a
// machine ever exceeds them we clamp (and the SMP path clamps again to
// MaxCPUs), which is far better than allocating from a non-existent heap.
// --------------------------------------------------------------------

namespace {
    using namespace montauk::boot;

    constexpr uint64_t kMaxRegions = 512;  // UEFI maps are typically < 256
    constexpr uint64_t kMaxModules = 32;
    constexpr uint64_t kMaxCpus    = 256;  // SmpBoot re-clamps to MaxCPUs (64)

    MemoryRegion g_regions[kMaxRegions];
    Module       g_modules[kMaxModules];
    BootCpu      g_cpus[kMaxCpus];

    MemoryKind TranslateKind(uint64_t limineType) {
        switch (limineType) {
            case LIMINE_MEMMAP_USABLE:                 return MemoryKind::Usable;
            case LIMINE_MEMMAP_RESERVED:               return MemoryKind::Reserved;
            case LIMINE_MEMMAP_ACPI_RECLAIMABLE:       return MemoryKind::AcpiReclaimable;
            case LIMINE_MEMMAP_ACPI_NVS:               return MemoryKind::AcpiNvs;
            case LIMINE_MEMMAP_BAD_MEMORY:             return MemoryKind::BadMemory;
            case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE: return MemoryKind::BootloaderReclaimable;
            // Renamed KERNEL_AND_MODULES -> EXECUTABLE_AND_MODULES at Limine
            // API revision 3; accept whichever this build exposes.
#ifdef LIMINE_MEMMAP_KERNEL_AND_MODULES
            case LIMINE_MEMMAP_KERNEL_AND_MODULES:     return MemoryKind::KernelAndModules;
#else
            case LIMINE_MEMMAP_EXECUTABLE_AND_MODULES: return MemoryKind::KernelAndModules;
#endif
            case LIMINE_MEMMAP_FRAMEBUFFER:            return MemoryKind::Framebuffer;
            default:                                   return MemoryKind::Unknown;
        }
    }

    // Trampoline bridging Limine's AP entry convention (called with the
    // native limine_mp_info*) to the contract's entry convention (called
    // with the BootCpu*).  startCpu() stashes the BootCpu* in Limine's
    // extra_argument so we can recover it here.
    void LimineApTrampoline(limine_mp_info* info) {
        BootCpu* cpu = reinterpret_cast<BootCpu*>(info->extra_argument);
        cpu->entry(cpu);
    }

    bool LimineStartCpu(BootCpu* cpu, void (*entry)(BootCpu*)) {
        auto* info = reinterpret_cast<limine_mp_info*>(cpu->native);
        if (info == nullptr) return false;

        cpu->entry = entry;
        info->extra_argument = reinterpret_cast<uint64_t>(cpu);

        // Publish the entry point last, with release semantics, so the
        // parked AP observes a fully-initialised BootCpu/extra_argument.
        __atomic_store_n(&info->goto_address,
                         reinterpret_cast<limine_goto_address>(LimineApTrampoline),
                         __ATOMIC_SEQ_CST);
        return true;
    }
}

namespace montauk::boot {

    bool Acquire(BootInfo& out) {
        // Refuse to run under a Limine revision we do not understand.
        if (!LIMINE_BASE_REVISION_SUPPORTED) {
            return false;
        }

        out.contractVersion = ContractVersion;
        out.features        = 0;
        out.loaderName      = "Limine";

        // ---- Required: HHDM ----
        if (hhdm_request.response == nullptr) {
            return false;  // cannot address physical memory without this
        }
        out.hhdmBase = hhdm_request.response->offset;

        // ---- Required: physical memory map ----
        // If absent we leave the map empty; the kernel reports the failure
        // with a visible Panic once the console is up (a silent halt here
        // would give the user no diagnostic).
        if (memmap_request.response != nullptr
         && memmap_request.response->entry_count > 0) {
            auto* resp = memmap_request.response;
            uint64_t count = resp->entry_count;
            if (count > kMaxRegions) count = kMaxRegions;
            for (uint64_t i = 0; i < count; i++) {
                limine_memmap_entry* e = resp->entries[i];
                g_regions[i].base   = e->base;
                g_regions[i].length = e->length;
                g_regions[i].kind   = TranslateKind(e->type);
            }
            out.memoryMap.regions = g_regions;
            out.memoryMap.count   = count;
        }

        // ---- Optional: framebuffer ----
        if (framebuffer_request.response != nullptr
         && framebuffer_request.response->framebuffer_count >= 1) {
            limine_framebuffer* fb = framebuffer_request.response->framebuffers[0];
            out.framebuffer.address        = reinterpret_cast<uint64_t>(fb->address);
            out.framebuffer.width          = fb->width;
            out.framebuffer.height         = fb->height;
            out.framebuffer.pitch          = fb->pitch;
            out.framebuffer.bpp            = fb->bpp;
            out.framebuffer.redMaskSize    = fb->red_mask_size;
            out.framebuffer.redMaskShift   = fb->red_mask_shift;
            out.framebuffer.greenMaskSize  = fb->green_mask_size;
            out.framebuffer.greenMaskShift = fb->green_mask_shift;
            out.framebuffer.blueMaskSize   = fb->blue_mask_size;
            out.framebuffer.blueMaskShift  = fb->blue_mask_shift;
            out.features |= FeatureFramebuffer;
        }

        // ---- Optional: ACPI RSDP (physical address) ----
        if (rsdp_request.response != nullptr) {
            out.rsdpPhysical = rsdp_request.response->address;
            out.features |= FeatureRsdp;
        }

        // ---- Optional: boot modules / ramdisk ----
        if (module_request.response != nullptr
         && module_request.response->module_count > 0) {
            auto* resp = module_request.response;
            uint64_t count = resp->module_count;
            if (count > kMaxModules) count = kMaxModules;
            for (uint64_t i = 0; i < count; i++) {
                limine_file* f = resp->modules[i];
                g_modules[i].address = f->address;
                g_modules[i].size    = f->size;
                // Limine API revision >= 3 names the tag field `string`.
                g_modules[i].name    = (f->string != nullptr) ? f->string : "";
            }
            out.modules.modules = g_modules;
            out.modules.count   = count;
            out.features |= FeatureModules;
        }

        // ---- Optional: UEFI system table (physical address) ----
        if (system_table_request.response != nullptr
         && system_table_request.response->address != 0) {
            out.efi.systemTablePhysical = system_table_request.response->address;
            out.features |= FeatureEfiSystemTable;
        }

        // ---- Optional: UEFI memory map (for runtime-services mapping) ----
        if (efi_memmap_request.response != nullptr
         && efi_memmap_request.response->memmap != nullptr) {
            auto* resp = efi_memmap_request.response;
            out.efi.memoryMap         = resp->memmap;
            out.efi.memoryMapSize     = resp->memmap_size;
            out.efi.descriptorSize    = resp->desc_size;
            out.efi.descriptorVersion = static_cast<uint32_t>(resp->desc_version);
            out.features |= FeatureEfiMemoryMap;
        }

        // ---- Optional: SMP / application processors ----
        if (mp_request.response != nullptr
         && mp_request.response->cpu_count > 1) {
            auto* resp = mp_request.response;
            uint64_t count = resp->cpu_count;
            if (count > kMaxCpus) count = kMaxCpus;
            for (uint64_t i = 0; i < count; i++) {
                limine_mp_info* info = resp->cpus[i];
                g_cpus[i].lapicId       = info->lapic_id;
                g_cpus[i].extraArgument = 0;
                g_cpus[i].entry         = nullptr;
                g_cpus[i].native        = info;
            }
            out.smp.cpuCount   = count;
            out.smp.bspLapicId = resp->bsp_lapic_id;
            out.smp.cpus       = g_cpus;
            out.smp.startCpu   = &LimineStartCpu;
            out.features |= FeatureSmp;
        } else {
            out.smp.cpuCount   = 1;
            out.smp.cpus       = nullptr;
            out.smp.startCpu   = nullptr;
        }

        return true;
    }

}
