//! osdev-x kernel crate
#![feature(
    if_let_guard,
    step_trait,
    decl_macro,
    optimize_attribute,
    const_trait_impl,
    maybe_uninit_slice,
    arbitrary_self_types,
    let_chains,
    linked_list_cursors,
    rustc_private,
    allocator_api,
    trait_alias,
    slice_ptr_get,
    slice_split_once,
    iter_collect_into,
    iter_next_chunk,
    exact_size_is_empty,
    never_type,
    format_args_nl,
    associated_type_defaults
)]
#![allow(
    clippy::new_without_default,
    clippy::fn_to_numeric_cast,
    clippy::match_ref_pats,
    clippy::match_single_binding,
    clippy::missing_transmute_annotations,
    clippy::modulo_one,
    async_fn_in_trait
)]
#![deny(missing_docs)]
#![no_std]
#![no_main]

use abi::{error::Error, io::FileMode};
use alloc::string::String;
use git_version::git_version;
use kernel_arch::{Architecture, ArchitectureImpl};
use libk::{
    arch::Cpu,
    debug,
    fs::{
        devfs,
        sysfs::{
            self,
            attribute::{StringAttribute, StringAttributeOps},
        },
    },
};
use libk_util::sync::SpinFence;

use crate::{
    arch::{Platform, PLATFORM},
    task::spawn_kernel_closure,
};

extern crate yggdrasil_abi as abi;

extern crate alloc;

#[cfg(not(rust_analyzer))]
extern crate compiler_builtins;

extern crate ygg_driver_ahci;
extern crate ygg_driver_net_rtl81xx;
extern crate ygg_driver_nvme;
extern crate ygg_driver_usb_xhci;
extern crate ygg_driver_virtio_blk;
extern crate ygg_driver_virtio_gpu;
extern crate ygg_driver_virtio_net;

cfg_if::cfg_if! {
    if #[cfg(target_arch = "x86_64")] {
        extern crate ygg_driver_net_igbe;
    } else if #[cfg(target_arch = "aarch64")] {
        extern crate ygg_driver_bsp_arm;
        extern crate ygg_driver_bsp_bcm283x;
    } else if #[cfg(target_arch = "riscv64")] {
        extern crate ygg_driver_bsp_riscv;
        extern crate ygg_driver_bsp_jh7110;

        extern crate ygg_driver_net_stmmac;
    }
}

pub mod arch;
pub mod device;
pub mod fs;
pub mod init;
pub mod mem;
pub mod panic;
pub mod proc;
pub mod syscall;
pub mod task;
pub mod util;

static CPU_INIT_FENCE: SpinFence = SpinFence::new();

/// Common kernel main function for application processors
pub fn kernel_secondary_main() -> ! {
    // Synchronize the CPUs to this point
    CPU_INIT_FENCE.signal();
    CPU_INIT_FENCE.wait_all(ArchitectureImpl::cpu_count());

    unsafe {
        task::enter();
    }
}

fn register_sysfs_attributes() {
    struct Version;
    struct Arch;

    impl StringAttributeOps for Version {
        const NAME: &'static str = "version";

        fn read(_state: &Self::Data) -> Result<String, Error> {
            Ok(git_version!().into())
        }
    }

    impl StringAttributeOps for Arch {
        const NAME: &'static str = "arch";

        fn read(_state: &Self::Data) -> Result<String, Error> {
            Ok(util::arch_str().into())
        }
    }

    let kernel = sysfs::kernel().unwrap();
    let kernel_node = kernel.node();

    kernel.add_attribute(StringAttribute::from(Version)).ok();
    kernel.add_attribute(StringAttribute::from(Arch)).ok();
    debug::add_kernel_log_file(kernel_node);
}

/// Common kernel main function. Must be called for BSP processor only.
///
/// # Prerequisites
///
/// Before the function can be called, the following preparations must be made:
///
/// * Virtual memory set up according to the architecture's memory map
/// * Physical memory
/// * Heap
/// * Basic debugging facilities
/// * Initrd
#[inline(never)]
pub fn kernel_main() -> ! {
    log::info!(
        "Yggdrasil v{} ({})",
        env!("CARGO_PKG_VERSION"),
        git_version!()
    );

    libk::panic::set_handler(panic::panic_handler);

    unsafe {
        PLATFORM.start_application_processors();
    }

    Cpu::init_ipi_queues(ArchitectureImpl::cpu_count());

    // Setup the sysfs
    register_sysfs_attributes();
    fs::add_pseudo_devices().unwrap();

    // Wait until all APs initialize
    CPU_INIT_FENCE.signal();
    CPU_INIT_FENCE.wait_all(ArchitectureImpl::cpu_count());

    // Add keyboard device
    if let Err(error) =
        devfs::add_named_char_device(ygg_driver_input::setup(), "kbd", FileMode::new(0o660))
    {
        log::error!("Couldn't add keyboard device: {error:?}");
    }

    task::init().expect("Failed to initialize the scheduler");

    spawn_kernel_closure("[kinit]", init::kinit).expect("Could not spawn [kinit]");

    log::info!("All cpus ready");

    unsafe {
        task::enter();
    }
}
