/*
    * main.cpp
    * Kernel entry point
    * Copyright (c) 2025 Daniel Hammer, Limine Contributors (via Limine C++ example)
*/

#include <cstdint>
#include <cstddef>
#include <Boot/Boot.hpp>
#include <Hal/GDT.hpp>
#include <Terminal/Terminal.hpp>
#include <Efi/UEFI.hpp>
#include <Common/Panic.hpp>
#include <Memory/Memmap.hpp>
#include <Memory/Heap.hpp>
#include <Memory/HHDM.hpp>
#include <Platform/Util.hpp>
#include <Hal/IDT.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Memory/Paging.hpp>
#include <ACPI/ACPI.hpp>
#include <ACPI/CpuIdle.hpp>
#include <ACPI/AcpiShutdown.hpp>
#include <ACPI/AcpiEvents.hpp>
#include <Hal/Apic/ApicInit.hpp>
#include <Pci/Pci.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <Drivers/PS2/PS2Controller.hpp>
#include <Drivers/PS2/Keyboard.hpp>
#include <Drivers/PS2/Mouse.hpp>
#include <Drivers/Init.hpp>
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
#include <Graphics/Framebuffer.hpp>
#include <Hal/MSR.hpp>
#include <Hal/Cpu.hpp>
#include <Hal/CpuPower.hpp>
#include <Fs/Boot.hpp>
#include <Sched/Scheduler.hpp>
#include <Ipc/Ipc.hpp>
#include <Api/Syscall.hpp>
#include <Hal/SmpBoot.hpp>
using namespace Kt;

namespace Memory {
    HeapAllocator* g_heap;
    PageFrameAllocator* g_pfa;
    uint64_t HHDMBase;
};

KernelOutStream kout{};
KernelErrorStream kerr{};

// Extern declarations for global constructors array.
extern void (*__init_array[])();
extern void (*__init_array_end[])();

extern "C" uint64_t KernelStartSymbol;
extern "C" uint64_t KernelEndSymbol;

extern "C" void kmain() {
    // Call global constructors.
    for (std::size_t i = 0; &__init_array[i] != __init_array_end; i++) {
        __init_array[i]();
    }

    // Acquire the boot environment through the Montauk Boot Contract.  The
    // active bootloader adapter (see Boot/Protocols/) translates its native
    // handoff into this bootloader-agnostic structure.  A false return means
    // we cannot even bring up a console (unsupported loader, no HHDM, or no
    // framebuffer) -- there is nothing to do but halt.
    if (!montauk::boot::Initialize()) {
        Hal::Halt();
    }
    const montauk::boot::BootInfo& boot = montauk::boot::Info();
    const montauk::boot::Framebuffer& framebuffer = boot.framebuffer;

    Kt::Initialize(
        (uint32_t*)framebuffer.address,
        framebuffer.width,
        framebuffer.height,
        framebuffer.pitch,
        framebuffer.redMaskSize,
        framebuffer.redMaskShift,
        framebuffer.greenMaskSize,
        framebuffer.greenMaskShift,
        framebuffer.blueMaskSize,
        framebuffer.blueMaskShift
    );


#if defined (__x86_64__)
    Hal::PrepareGDT();
    Hal::BridgeLoadGDT();

    Hal::EnableSSE();
#endif

    Memory::HHDMBase = boot.hhdmBase;

    if (boot.memoryMap.regions == nullptr || boot.memoryMap.count == 0) {
        Panic("System memory map missing!", nullptr);
    }

    Kt::KernelLogStream(OK, "Mem") << "Creating PageFrameAllocator";
    Memory::PageFrameAllocator pmm(Memory::Scan(boot.memoryMap));
    Memory::g_pfa = &pmm;

    Kt::KernelLogStream(OK, "Mem") << "Creating HeapAllocator";
    Memory::HeapAllocator heap{};
    Memory::g_heap = &heap;

    heap.Walk();


#if defined (__x86_64__)
    Hal::IDTInitialize();

    Memory::VMM::Paging g_paging{};
    Memory::VMM::g_paging = &g_paging;
    g_paging.Init((uint64_t)&KernelStartSymbol, ((uint64_t)&KernelEndSymbol - (uint64_t)&KernelStartSymbol), boot.memoryMap, framebuffer);

    // Reprogram PAT so entry 1 = Write-Combining (default is Write-Through).
    // Must be done after paging init and before any WC mappings.
    Hal::InitializePAT();
    Kt::KernelLogStream(OK, "Hal") << "PAT reprogrammed (entry 1 = WC)";

#endif

    // Initialize the framebuffer early so we can WC-map it before
    // the bulk of boot logging begins (ACPI, PCI, drivers, etc.)
    Graphics::Framebuffer::Initialize(framebuffer);

#if defined (__x86_64__)
    // Map framebuffer as Write-Combining immediately for faster screen writes.
    // All subsequent log output benefits from WC burst transfers.
    Graphics::Framebuffer::MapWriteCombining();
#endif

    Hal::ACPI g_acpi((Hal::ACPI::XSDP*)Memory::HHDM(boot.rsdpPhysical));

#if defined (__x86_64__)
    if (g_acpi.GetXSDT() != nullptr) {
        Hal::AcpiShutdown::Initialize(g_acpi.GetXSDT());
        Hal::CpuIdle::Initialize(g_acpi.GetXSDT());

        Hal::ApicInitialize(g_acpi.GetXSDT());

        // Set up BSP per-CPU data (GS base) before enabling interrupts.
        // ISR stubs use SWAPGS which requires GS base to point to CpuData.
        Smp::InitBsp();

        // Enable hardware P-state scaling and the thermal governor.
        // Needs GS base (per-CPU data) set up, and must run before the
        // APs boot so they inherit the shared policy in ApEntry.
        Hal::CpuPower::InitializeBsp();

        // Now safe to enable interrupts (SWAPGS-aware ISR stubs are installed)
        asm volatile("sti");

        // Initialize ACPI events (SCI, power button) after APIC is ready
        Hal::AcpiEvents::Initialize(g_acpi.GetXSDT());

        Pci::Initialize(g_acpi.GetXSDT());

        Drivers::ProbeEarly();
        Drivers::InitializeGraphics();

        Timekeeping::ApicTimerInitialize();

        Drivers::PS2::Initialize();
        Drivers::PS2::Keyboard::Initialize();
        Drivers::PS2::Mouse::Initialize();

        Drivers::ProbeNormal();
        Drivers::InitializeNetwork();
        Drivers::InitializeStorage();
        Drivers::InitializeAudio();
    }
#endif

    // UEFI runtime services are optional (absent on legacy-BIOS boots).
    if (boot.has(montauk::boot::FeatureEfiSystemTable)) {
        Efi::SystemTable* ST = (Efi::SystemTable*)Memory::HHDM(boot.efi.systemTablePhysical);
        Efi::Init(ST, boot.efi);
    }

    Fs::InitializeBootFilesystems(boot.modules);

    // A Bluetooth adapter present at boot enumerates during the xHCI port scan,
    // before the ramdisk is mounted.  Now that drive 0 is up, finish any
    // firmware-dependent bring-up that was deferred (loads ibt-*.sfi/.ddc).
    //
    // Deliberately synchronous, HERE, pre-Sched/pre-SMP/pre-MSI: deferring
    // this to the post-boot idle loop made the AX211 bootloader stop
    // answering after the first FC05 (2026-07-05, three attempts: CPU
    // reservation, restored pipe timing, event-pipe fixes -- identical
    // failure each time; suspected xHCI-MSI-context event processing, see
    // memory notes).  Boot pays the download cost until that is understood.
    Drivers::USB::Bluetooth::ServiceDeferredInit();

    Hal::LoadTSS();
    montauk::abi::InitializeSyscalls();

    Sched::Initialize();
    Ipc::Initialize();

    // Boot Application Processors (all subsystems ready, APs can schedule)
    Smp::BootAPs(boot.smp);

    // Flush any stale PS/2 mouse bytes that accumulated during boot
    // (edge-triggered IRQs can be lost while spinlocks disable interrupts)
    Drivers::PS2::Mouse::FlushState();

    Kt::SuppressKernelLog();
    Sched::Spawn("0:/os/init.elf");

    // Enable preemptive scheduling via the APIC timer
    Timekeeping::EnableSchedulerTick();

    // Main loop: idle until next interrupt.
    // Use MWAIT for deeper C-states if available, otherwise HLT.
    auto* bspCpu = Smp::GetCpuData(0);
    if (bspCpu && bspCpu->hasMwait) {
        static volatile uint64_t s_bspIdleMonitor = 0;
        for (;;) {
            Timekeeping::IdleOnce(true, &s_bspIdleMonitor);
        }
    } else {
        for (;;) {
            Timekeeping::IdleOnce(false);
        }
    }
}
