//! Kernel main process implementation: filesystem initialization and userspace init start

use abi::error::Error;
use kernel_arch::{Architecture, ArchitectureImpl};
use libk::{
    device::display::console,
    fs::devfs,
    random,
    task::{binary::LoadOptions, process::Process, runtime, thread::Thread},
    vfs::{impls::fn_hardlink, IoContext, NodeRef, OwnedFilename},
};
use memfs::MemoryFilesystem;
use ygg_driver_pci::PciBusManager;

use crate::{
    fs::{FileBlockAllocator, INITRD_DATA},
    proc,
};

fn setup_root() -> Result<NodeRef, Error> {
    let initrd_data = INITRD_DATA.try_get().ok_or(Error::DoesNotExist)?;
    let fs = MemoryFilesystem::<FileBlockAllocator>::from_slice(initrd_data.data)?;
    fs.root()
}

/// Kernel's "main" process function.
///
/// # Note
///
/// This function is meant to be used as a kernel-space process after all the platform-specific
/// initialization has finished.
pub fn kinit() -> Result<(), Error> {
    assert!(!ArchitectureImpl::interrupt_mask());
    log::info!("In main");

    // Initialize late device tree devices
    #[cfg(any(rust_analyzer, target_arch = "riscv64", target_arch = "aarch64"))]
    {
        use device_tree::driver::InitSequence;
        use libk::event;

        device_tree::driver::lazy_init(
            |_| (),
            |node, error| {
                log::error!("{}: {error:?}", node.name().unwrap_or("<unknown>"));
            },
            InitSequence::Late,
        );
        device_tree::driver::init_irqs(
            |_| (),
            |node, error| {
                log::error!(
                    "{}: irq init error: {error:?}",
                    node.name().unwrap_or("<unknown>")
                );
            },
            InitSequence::Late,
        );

        runtime::spawn(event::gpio_event_handler_task()).ok();
        runtime::spawn(event::heartbeat_handler_task()).ok();
    }
    // Initialize PCI devices
    if let Err(error) = PciBusManager::setup_bus_devices(false) {
        log::error!("pci bus setup error: {error:?}");
    }
    assert!(!ArchitectureImpl::interrupt_mask());

    runtime::spawn(ygg_driver_usb::bus::bus_handler())?;
    runtime::spawn(console::flush_consoles_task()).ok();

    devfs::root()
        .add_child(
            OwnedFilename::new("tty").unwrap(),
            fn_hardlink(|| {
                let thread = Thread::current();
                let process = thread.process();

                process.session_terminal().ok_or(Error::InvalidOperation)
            }),
        )
        .ok();

    ygg_driver_net_loopback::init();

    ygg_driver_net_core::start_network_tasks()?;

    random::init();

    let root = setup_root().inspect_err(|error| {
        log::error!("Cannot setup root filesystem: {error:?}");
    })?;

    let mut ioctx = IoContext::new(root);

    // TODO move this to userspace so it doesn't block the init process, maybe lazy-load on first
    //      attempt to load a module?
    #[cfg(all(not(target_arch = "aarch64"), not(target_arch = "riscv64")))]
    {
        use libk::module::load_kernel_symbol_table;

        load_kernel_symbol_table(&mut ioctx, "/kernel.sym")?;
    }

    {
        let group_id = Process::create_group();
        let options = LoadOptions {
            group_id,
            parent: None,
            path: "/init",
            args: &["/init", "xxx"],
            envs: &[],
            single_step: false,
            disable_aslr: false,
        };
        let (user_init, user_init_main) = proc::load_binary(&mut ioctx, &options)?;

        let mut io = user_init.io.lock();
        io.set_ioctx(ioctx);
        drop(io);

        user_init_main.enqueue();
    }

    Ok(())
}
