//! 生成对应架构的内核链接脚本

use std::{env, fmt::Debug, fs, path::PathBuf, result};

use moa_target_define::{KERNEL_AARCH64, KERNEL_RISCV64, Target};
use moa_vmem::{BOOT_STACK_SIZE, KERNEL_ADDR_START, PAGE_SIZE};

/// 链接脚本构建器
#[derive(Default)]
pub struct MkLinker {
    offset: usize,
}

impl MkLinker {
    /// 设置内核加载物理地址偏移
    ///
    /// BSP 在 build.rs 中调用，告知 bootloader 放置内核的物理地址偏移。
    pub fn set_start_offset(&mut self, offset: usize) {
        self.offset = offset;
    }

    /// 构建链接脚本
    ///
    /// # Errors
    /// 对应架构不支持或文件系统错误时返回错误
    pub fn genlinker(self) -> Result<()> {
        let target = env::var("TARGET").map_err(|_| MlError::EnvError)?;
        let (def, has_idmap) = Self::arch(&target)?;
        let script = Self::render(def, has_idmap, KERNEL_ADDR_START + self.offset);

        let out = PathBuf::from(env::var_os("OUT_DIR").ok_or(MlError::EnvError)?);
        fs::write(out.join("linker.lds"), script).map_err(|_| MlError::FsError)?;

        println!("cargo:rustc-link-search={}", out.display());
        println!("cargo:rustc-link-arg=-Tlinker.lds");
        Ok(())
    }

    /// 解析 target，返回对应 `Target` 及是否需要恒等映射页表。
    ///
    /// `has_idmap` 是内核链接策略（仅 aarch64 内核保留恒等映射页表），
    /// 不属于 target 事实，故在此判定。
    fn arch(target: &str) -> Result<(&'static Target, bool)> {
        if target == KERNEL_AARCH64.triple {
            Ok((&KERNEL_AARCH64, true))
        } else if target == KERNEL_RISCV64.triple {
            Ok((&KERNEL_RISCV64, false))
        } else {
            Err(MlError::UnImpl)
        }
    }

    fn render(target: &Target, has_idmap: bool, kimage_vaddr: usize) -> String {
        let output_arch = target.output_arch;
        let discard_attr = target.discard_attr;
        let page_size = PAGE_SIZE;
        let boot_stack = BOOT_STACK_SIZE;
        // aarch64 在 init_kernel_dir 之前额外保留一页恒等映射页表。
        let idmap = if has_idmap {
            format!("init_idmap_dir = .;\n    . += {page_size:#x};")
        } else {
            String::new()
        };
        format!(
            r"
OUTPUT_ARCH({output_arch})
ENTRY(_head)

SECTIONS
{{
    /DISCARD/ : {{
        *(.eh_frame .eh_frame_hdr)
        *(.comment)
        *(.note .note.*)
        *(.interp .dynamic)
        *(.dynsym .dynstr .hash .gnu.hash)
        *(.gnu.version*)
        *(.rela*)
        *(.got .got.*)
        *(.got.plt)
        *(.plt .plt.* .iplt)
        *(.igot .igot.plt)
        *(.fixup)
        *(.gnu.warning)
        *({discard_attr})
    }}

    . = {kimage_vaddr:#x};

    .head.text : {{
        _head = .;
        _stext_idmap = .;
        KEEP(*(.head.text.entry))
        KEEP(*(.head.text .head.text.*))
        _etext_idmap = .;
    }}

    . = ALIGN(8);
    .text : {{
        _stext = .;
        *(.trap.text)
        *(.text .text.*)
        _etext = .;
    }}

    . = ALIGN({page_size:#x});
    .rodata : {{
        _srodata = .;
        *(.rodata .rodata.*)
        _erodata = .;
    }}

    . = ALIGN(8);
    .ex_fixup : {{
        _sfixup = .;
        KEEP(*(.ex_fixup))
        _efixup = .;
    }}

    . = ALIGN({page_size:#x});
    .data : {{
        _sdata = .;
        *(.data .data.*)
        _edata = .;
    }}

    . = ALIGN({page_size:#x});
    .init.text : {{
        _sinit = .;
        KEEP(*(.init.text .init.text.*))
    }}

    . = ALIGN(8);
    .init.data : {{
        KEEP(*(.init.data .init.data.*))
    }}

    . = ALIGN(8);
    .init.rodata : {{
        KEEP(*(.init.rodata .init.rodata.*))
    }}

    . = ALIGN({page_size:#x});
    {idmap}
    init_kernel_dir = .;
    . += {page_size:#x};
    init_pg_end = .;

    boot_stack_base = .;
    . += {boot_stack:#x};
    boot_stack_top = .;

    . = ALIGN({page_size:#x});
    _einit = .;

    .bss : {{
        _sbss = .;
        *(.bss .bss.*)
        . = ALIGN(64);
        _ebss = .;
    }}

    _end = .;
}}
"
        )
    }
}

/// 构建链接脚本结果
pub type Result<T> = result::Result<T, MlError>;

/// 构建链接脚本错误
pub enum MlError {
    /// 未实现
    UnImpl,
    /// 本机文件系统错误
    FsError,
    /// 环境变量错误
    EnvError,
}

impl Debug for MlError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let error = match self {
            Self::UnImpl => "架构未实现",
            Self::FsError => "文件系统错误",
            Self::EnvError => "环境变量错误",
        };
        write!(f, "{error}")
    }
}
