#include "Log.h"
#include "arch/CPU.h"
#include "arch/Timer.h"
#include "binfmt/BinaryFormat.h"
#include "boot/Init.h"
#include "config.h"
#include "fs/InitRD.h"
#include "fs/devices/DeviceRegistry.h"
#include "fs/tmpfs/FileSystem.h"
#include "memory/MemoryManager.h"
#include "thread/Scheduler.h"
#include <luna/Units.h>

#ifdef ARCH_X86_64
#include "arch/x86_64/disk/ATA.h"
#endif

extern void set_host_name(StringView);

void reap_thread()
{
    while (true)
    {
        CPU::disable_interrupts();
        auto dying_threads = Scheduler::check_for_dying_threads();
        CPU::enable_interrupts();

        dying_threads.consume([](Thread* thread) { Scheduler::reap_thread(thread); });

        kernel_wait_for_event();
    }
}

void oom_thread()
{
    while (true)
    {
        kernel_wait_for_event();
        // OOM! Do everything we can to recover memory.
        StorageCache::clear_caches();
    }
}

[[noreturn]] void init()
{
    {
        kinfoln("Starting Moon %s %s", MOON_VERSION, MOON_RELEASE);

        // Default hostname if nobody from userspace changes it
        set_host_name("moon"_sv);

        kinfoln("Current platform: %s", CPU::platform_string().chars());
        kinfoln("Current processor: %s", CPU::identify().value_or("(unknown)"_sv).chars());

        auto root = mark_critical(TmpFS::FileSystem::create(), "Failed to create initial ramfs");
        mark_critical(VFS::mount_root(root), "Failed to mount the initial ramfs as the root filesystem");
        mark_critical(InitRD::populate_vfs(), "Failed to load files from the initial ramdisk");
        mark_critical(DeviceRegistry::init(), "Failed to register initial devices");

        mark_critical(BinaryFormat::init(), "Failed to register initial binary formats");

        auto init =
            mark_critical(VFS::resolve_path("/bin/preinit", Credentials {}), "Can't find init in the initial ramfs!");
        auto init_thread = mark_critical(Scheduler::new_userspace_thread(init, "/bin/preinit"),
                                         "Failed to create PID 1 process for init");

        auto reap = mark_critical(Scheduler::new_kernel_thread(reap_thread, "[reap]"),
                                  "Failed to create the process reaper kernel thread");
        Scheduler::set_reap_thread(reap);

        auto oom = mark_critical(Scheduler::new_kernel_thread(oom_thread, "[oom]"),
                                 "Failed to create the out-of-memory kernel thread");
        Scheduler::set_oom_thread(oom);

#ifdef ARCH_X86_64
        ATA::Controller::scan();
#endif

        // Disable console logging before transferring control to userspace.
        setup_log(log_debug_enabled(), log_serial_enabled(), false);

        init_thread->wake_up();
    }

    kernel_exit();
}

extern "C" [[noreturn]] void _start()
{
    Init::check_magic();
    Init::early_init();

    Timer::init();

    Thread::init();
    Scheduler::init();

    Scheduler::new_kernel_thread(init, "[kinit]");

    CPU::platform_finish_init();

    CPU::enable_interrupts();

    CPU::idle_loop();
}