//! `SkavOS`

#![deny(
    clippy::complexity,
    clippy::correctness,
    clippy::nursery,
    clippy::pedantic,
    clippy::perf,
    clippy::restriction,
    clippy::style
)]
#![allow(
    clippy::arithmetic_side_effects,
    clippy::as_conversions,
    clippy::blanket_clippy_restriction_lints,
    clippy::else_if_without_else,
    clippy::exhaustive_enums,
    clippy::exhaustive_structs,
    clippy::expect_used,
    clippy::implicit_return,
    clippy::integer_arithmetic,
    clippy::integer_division,
    clippy::match_same_arms,
    clippy::match_wildcard_for_single_variants,
    clippy::missing_trait_methods,
    clippy::mod_module_files,
    clippy::panic,
    clippy::panic_in_result_fn,
    clippy::pattern_type_mismatch,
    clippy::question_mark_used,
    clippy::separated_literal_suffix,
    clippy::shadow_reuse,
    clippy::shadow_unrelated,
    clippy::unreachable,
    clippy::unwrap_in_result,
    clippy::wildcard_in_or_patterns,
    const_item_mutation
)]
#![cfg_attr(
    test,
    allow(
        clippy::assertions_on_result_states,
        clippy::collection_is_never_read,
        clippy::enum_glob_use,
        clippy::indexing_slicing,
        clippy::non_ascii_literal,
        clippy::too_many_lines,
        clippy::unwrap_used,
        clippy::wildcard_imports
    )
)]
#![no_std]
#![no_main]
#![feature(abi_x86_interrupt)]
#![feature(alloc_error_handler)]
#![feature(const_extern_fn)]
#![feature(const_mut_refs)]
#![feature(const_trait_impl)]
#![feature(custom_test_frameworks)]
#![feature(error_in_core)]
#![feature(extend_one)]
#![feature(int_roundings)]
#![feature(iter_advance_by)]
#![feature(new_uninit)]
#![feature(no_coverage)]
#![feature(strict_provenance)]
#![feature(trait_upcasting)]
#![feature(vec_into_raw_parts)]
#![reexport_test_harness_main = "test_main"]
#![test_runner(test::runner)]

extern crate alloc;

mod encoding;
mod fs;
mod kernel;
mod macros;
mod mutex;
mod syslog;
mod user;

#[cfg(test)]
mod test;

use alloc::sync::Arc;
use alloc::vec::Vec;
use core::alloc::Layout;

use bootloader_api::config::{Mapping, Mappings};
use bootloader_api::{entry_point, BootInfo, BootloaderConfig};
use kernel::device::storage::Device;
use kernel::device::{STORAGE_CONTROLLERS, UEFI_UUID};
use kernel::task::executor::Executor;
use kernel::task::Task;
use log::{logger, set_logger, set_max_level, LevelFilter};
use spin::Mutex;
#[cfg(test)]
use test::exit;
use user::tty::print_key;
#[cfg(not(test))]
use x86_64::instructions;

use self::syslog::SYS_LOGGER;
use crate::kernel::interrupt::cmos::CMOS;

/// Configuration of the bootloader
///
/// The only change from the default configuration is the dynamic mapping to the physical memory
const BOOTLOADER_CONFIG: BootloaderConfig = {
    let mut config = BootloaderConfig::new_default();
    config.mappings = Mappings::new_default();
    config.mappings.page_table_recursive = Some(Mapping::Dynamic);
    config
};

/// Main function of the kernel
#[no_coverage]
#[allow(unreachable_code)]
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
    set_logger(&SYS_LOGGER).expect("Could not initialize the logger");
    set_max_level(LevelFilter::Trace);

    kernel::init(boot_info);

    let storage_devices = STORAGE_CONTROLLERS
        .get()
        .expect("Storage controllers have not been initialized yet")
        .iter()
        .flat_map(|controller| controller.lock().devices())
        .collect::<Vec<Arc<Mutex<dyn Device + Send>>>>()
        .into_iter()
        .filter(|device| device.lock().uuid() != UEFI_UUID)
        .collect::<Vec<Arc<Mutex<dyn Device + Send>>>>();

    if let Some(device) = storage_devices.first() {
        fs::init(&Arc::clone(device));
    }

    #[cfg(test)]
    {
        test_main();
        exit();
    };

    println!("Hello world!");

    let rtc = CMOS.rtc();
    println!("It is currently : {:0>2}:{:0>2}:{:0>2} (UTC+0)", rtc.hour, rtc.minute, rtc.second);

    let mut executor = Executor::new();
    executor.spawn(Task::new(print_key()));
    executor.run();
}

/// Panic prints on screen the reason
#[panic_handler]
#[no_coverage]
fn panic(info: &core::panic::PanicInfo) -> ! {
    logger().flush();

    #[cfg(not(test))]
    {
        println!("kernel panic: {:#?}", info);

        loop {
            instructions::hlt();
        }
    }

    #[cfg(test)]
    {
        use qemu_exit::QEMUExit;

        println!("[failed]\n");
        println!("Error: {}\n", info);

        qemu_exit::X86::new(0xf4, 13).exit_failure();
    }
}

/// Allocation failure handler
#[alloc_error_handler]
#[no_coverage]
fn alloc_error_handler(layout: Layout) -> ! {
    panic!("allocation error: {:?}", layout);
}

entry_point!(kernel_main, config = &BOOTLOADER_CONFIG);