//! 生成对应架构的用户态链接脚本

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

use moa_target_define::{Target, USER_AARCH64, USER_RISCV64};
use moa_vmem::{PAGE_SIZE, USER_LOAD_ADDR};

/// 构建用户态链接脚本
///
/// # Errors
/// 不支持的架构返回错误
pub fn genlinker() -> Result<()> {
    let target = env::var("TARGET").map_err(|_| MlError::EnvError)?;
    let def = if target == USER_AARCH64.triple {
        &USER_AARCH64
    } else if target == USER_RISCV64.triple {
        &USER_RISCV64
    } else {
        return Err(MlError::UnImpl);
    };

    let script = render(def);
    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(())
}

fn render(target: &Target) -> String {
    let output_arch = target.output_arch;
    let discard_attr = target.discard_attr;
    let page_size = PAGE_SIZE;
    let load_addr = USER_LOAD_ADDR;
    format!(
        r"
OUTPUT_ARCH({output_arch})
ENTRY(_start)

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})
    }}

    . = {load_addr:#x};

    .text : {{
        _stext = .;
        KEEP(*(.text.entry))
        *(.text .text.*)
        _etext = .;
    }}

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

    . = ALIGN(8);
    .init_array : {{
        __init_array_start = .;
        KEEP(*(SORT(.init_array .init_array.*)))
        __init_array_end = .;
    }}

    . = ALIGN(8);
    .fini_array : {{
        __fini_array_start = .;
        KEEP(*(SORT(.fini_array .fini_array.*)))
        __fini_array_end = .;
    }}

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

    . = ALIGN(8);
    .tdata : {{
        __tdata_start = .;
        *(.tdata .tdata.*)
        __tdata_end = .;
    }}

    . = ALIGN(8);
    .tbss : {{
        __tbss_start = .;
        *(.tbss .tbss.*)
        __tbss_end = .;
    }}

    . = ALIGN({page_size:#x});
    .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}")
    }
}
