/*
    * Scheduler.hpp
    * Preemptive process scheduler with SMP support
    * Copyright (c) 2025-2026 Daniel Hammer
*/

#pragma once
#include <cstdint>
#include <Api/Syscall.hpp>

namespace Sched {

    static constexpr int MaxProcesses = 256;
    static constexpr uint64_t StackPages = 4;  // 16 KiB kernel stack per process
    static constexpr uint64_t StackSize = StackPages * 0x1000;
    static constexpr uint64_t UserStackPages = 8;  // 32 KiB user stack
    static constexpr uint64_t UserStackSize = UserStackPages * 0x1000;
    static constexpr uint64_t UserStackTop = 0x7FFFFFF000ULL;  // User stack top VA
    static constexpr uint64_t UserHeapBase = 0x40000000ULL;    // User heap start VA
    static constexpr uint32_t UserReadDirSlots = 64;           // rotating scratch pages for SYS_READDIR
    static constexpr uint64_t UserReadDirBase =
        UserHeapBase - (uint64_t)UserReadDirSlots * 0x1000ULL;
    static constexpr uint64_t ExitStubAddr = 0x3FF000ULL;     // User-space exit stub page
    static constexpr uint64_t TimeSliceMs = 10; // 10 ms time slice

    enum class ProcessState {
        Free,
        Ready,
        Running,
        Blocked,       // Waiting (e.g. waitpid) -- not schedulable
        Terminated
    };

    struct Process {
        int pid;
        ProcessState state;
        int waitingForPid;     // PID this process is blocked on (-1 if none)
        uint64_t sleepUntilTick; // Tick deadline for sleep/object wait timeout (0 = none)
        void* waitingOnObject; // IPC/scheduler object this process is blocked on (nullptr if none)
        char name[64];
        uint64_t savedRsp;
        uint64_t stackBase;       // Bottom of allocated kernel stack (lowest address)
        uint64_t entryPoint;
        uint64_t sliceRemaining;  // Milliseconds left in current time slice
        uint64_t cpuTimeMs;       // Accumulated scheduler runtime in milliseconds
        uint64_t pml4Phys;        // Physical address of per-process PML4
        uint64_t kernelStackTop;  // Top of kernel stack (for TSS RSP0 / SYSCALL)
        uint64_t userStackTop;    // User-space stack top
        uint64_t heapNext;        // Simple bump allocator for user heap
        uint32_t readdirCursor;   // Next SYS_READDIR scratch slot
        char args[256];           // Command-line arguments (set by parent via Spawn)
        char user[32];            // Owner user name (inherited from parent on spawn)
        char cwd[256];            // Absolute current working directory

        int runningOnCpu;         // CPU index running this process (-1 if not running)
        bool killPending = false; // Set by Sys_Kill when target is running on another CPU
        bool reapReady = false;   // Set once teardown is complete and BSP may free slot resources
        bool startPending = false; // Spawned but not yet made runnable

        // I/O redirection for GUI terminal
        bool redirected = false;
        int parentPid = -1;
        uint8_t* outBuf = nullptr;   // 4KB ring: child writes (print/putchar), parent reads
        volatile uint32_t outHead = 0;
        volatile uint32_t outTail = 0;
        uint8_t* inBuf = nullptr;    // 4KB ring: parent writes, child reads (getchar)
        volatile uint32_t inHead = 0;
        volatile uint32_t inTail = 0;
        Montauk::KeyEvent keyBuf[64]; // parent injects, child reads (getkey/iskeyavailable)
        volatile uint32_t keyHead = 0;
        volatile uint32_t keyTail = 0;
        static constexpr uint32_t IoBufSize = 4096;

        // GUI terminal dimensions (set by desktop, read by SYS_TERMSIZE)
        int termCols = 0;
        int termRows = 0;

        // IPC-backed redirected terminal channels
        int ioOutHandle = -1;
        int ioInHandle = -1;
        int ioKeyHandle = -1;
        int ioWaitsetHandle = -1;

        // FPU/SSE state (FXSAVE format, must be 16-byte aligned)
        uint8_t fpuState[512] __attribute__((aligned(16)));
    };

    void Initialize();
    int Spawn(const char* vfsPath, const char* args = nullptr, bool startReady = true);
    int StartProcess(int pid);
    void Schedule();

    // True when there is runnable work somewhere in the process table.
    bool HasReadyProcesses();

    // Called from the APIC timer handler with the elapsed time for that CPU's
    // tick interval. The BSP runs at 1 ms; APs may use a coarser interval.
    void Tick(uint32_t elapsedMs = 1);

    // Get the PID of the currently running process (-1 if idle)
    int GetCurrentPid();

    // Get a pointer to the currently running process (nullptr if idle)
    Process* GetCurrentProcessPtr();

    // Called by terminated processes to mark themselves done
    void ExitProcess();

    // Check if a process is still alive (Ready, Running, or Blocked)
    bool IsAlive(int pid);

    // Block the current process until the given PID exits.
    void BlockOnPid(int pid);

    // Block the current process for the given number of milliseconds.
    void BlockForSleep(uint64_t ms);

    // Block the current process until the given object is signaled or timed out.
    // timeoutMs == 0 means wait indefinitely.
    void BlockOnObject(void* object, uint64_t timeoutMs = 0);

    // Atomically check a condition under the scheduler lock and block only if
    // it still holds. This prevents lost wakeups for edge-triggered waiters.
    bool BlockOnObjectIf(void* object, uint64_t timeoutMs,
                         bool (*shouldBlock)(void*), void* context);

    // BSP-only scheduler housekeeping: wake expired sleepers and reclaim
    // terminated process resources.
    void RunBspMaintenance();

    // Return the earliest blocked sleep/object timeout deadline in ticks,
    // or 0 when no timed waits are pending.
    uint64_t GetNextDeadlineTick();

    // Wake any processes blocked on the given object.
    void WakeObjectWaiters(void* object);

    // Kill a process by PID. If the process is running on another CPU,
    // sets a kill-pending flag checked on the next timer tick.
    // Returns 0 on success, -1 on failure.
    int KillProcess(int pid);

    // Find a process by PID (returns nullptr if not found or not alive)
    Process* GetProcessByPid(int pid);

    // Spawn a crashpad process with the given crash file path as argument.
    // Called by the exception handler when a process faults.
    int SpawnCrashPad();

    // Get a pointer to slot i in the process table (for enumeration)
    Process* GetProcessSlot(int slot);

    // Per-process allocated page count (tracked by Heap syscalls, separate from Process struct)
    inline uint64_t g_allocatedPages[MaxProcesses] = {};

}
