//! Kernel commandline parameters handling

use alloc::string::String;
use libk_util::OneTimeInit;
use yggdrasil_abi::error::Error;

use crate::fs::sysfs::{
    self,
    attribute::{StringAttribute, StringAttributeOps},
};

#[cfg(any(target_arch = "x86_64", target_arch = "x86", rust_analyzer))]
mod x86;
#[cfg(any(target_arch = "x86_64", target_arch = "x86", rust_analyzer))]
pub use x86::X86Options;

#[cfg(any(target_arch = "x86_64", rust_analyzer))]
mod x86_64;
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
pub use x86_64::X86_64Options;

mod general;
pub use general::DebugOptions;
#[cfg(any(rust_analyzer, target_arch = "aarch64", target_arch = "riscv64"))]
pub use general::DeviceTreeOptions;

trait SetOption {
    fn set_key_value(&mut self, key: &str, value: &str) -> bool;
    fn set_flag(&mut self, value: &str) -> bool;
}

/// Kernel commandline options
#[derive(Debug, Default)]
pub struct Options {
    pub debug: DebugOptions,
    #[cfg(any(rust_analyzer, target_arch = "aarch64", target_arch = "riscv64"))]
    pub device_tree: DeviceTreeOptions,
    #[cfg(any(target_arch = "x86_64", target_arch = "x86", rust_analyzer))]
    pub x86: X86Options,
    #[cfg(any(target_arch = "x86_64", rust_analyzer))]
    pub x86_64: X86_64Options,
}

impl SetOption for Options {
    fn set_flag(&mut self, value: &str) -> bool {
        match value {
            _ if let Some(value) = value.strip_prefix("debug.") => self.debug.set_flag(value),
            #[cfg(any(rust_analyzer, target_arch = "aarch64", target_arch = "riscv64"))]
            _ if let Some(value) = value.strip_prefix("dt.") => self.device_tree.set_flag(value),
            #[cfg(any(target_arch = "x86_64", target_arch = "x86", rust_analyzer))]
            _ if let Some(value) = value.strip_prefix("x86.") => self.x86.set_flag(value),
            #[cfg(any(target_arch = "x86_64", rust_analyzer))]
            _ if let Some(value) = value.strip_prefix("x86_64.") => self.x86_64.set_flag(value),
            _ => false,
        }
    }

    fn set_key_value(&mut self, key: &str, value: &str) -> bool {
        match key {
            _ if let Some(key) = key.strip_prefix("debug.") => self.debug.set_key_value(key, value),
            #[cfg(any(rust_analyzer, target_arch = "aarch64", target_arch = "riscv64"))]
            _ if let Some(key) = key.strip_prefix("dt.") => {
                self.device_tree.set_key_value(key, value)
            }
            #[cfg(any(target_arch = "x86_64", target_arch = "x86", rust_analyzer))]
            _ if let Some(key) = key.strip_prefix("x86.") => self.x86.set_key_value(key, value),
            #[cfg(any(target_arch = "x86_64", rust_analyzer))]
            _ if let Some(key) = key.strip_prefix("x86_64.") => {
                self.x86_64.set_key_value(key, value)
            }
            _ => false,
        }
    }
}

static OPTIONS: OneTimeInit<Options> = OneTimeInit::new();
static OPTIONS_STRING: OneTimeInit<String> = OneTimeInit::new();

pub fn get() -> &'static Options {
    OPTIONS.get()
}

fn parse_argument(options: &mut Options, arg: &str) -> bool {
    let arg = arg.trim();

    if let Some((key, value)) = arg.split_once('=') {
        options.set_key_value(key.trim_end(), value.trim_start())
    } else {
        options.set_flag(arg)
    }
}

/// Sets up kernel options from an argument list
pub fn parse_boot_argument_list<'a, I: Iterator<Item = &'a str>>(args: I) {
    let mut options = Default::default();
    let mut string = String::new();

    args.for_each(|arg| {
        if parse_argument(&mut options, arg) {
            string.push_str(arg);
            string.push('\n');
        }
    });

    OPTIONS.init(options);
    OPTIONS_STRING.init(string);

    // Add to sysfs
    if let Some(kernel) = sysfs::kernel() {
        struct Cmdline;

        impl StringAttributeOps for Cmdline {
            const NAME: &'static str = "cmdline";
            const NEWLINE: bool = false;

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

        kernel.add_attribute(StringAttribute::from(Cmdline)).ok();
    }
}

/// Sets up kernel options from an argument string, where options are separated by whitespace
pub fn parse_boot_arguments(args: &str) {
    parse_boot_argument_list(args.trim().split(' '));
}
