// rasm-core/src/codegen/aot/elf.rs
//
//! # ELF64 Writer
//!
//! Собирает минимальный исполняемый ELF64-файл из байтового буфера машинного кода.
//!
//! ## Структура файла, который мы генерируем
//!
//! ```text
//! Offset  Size   Что
//! ──────  ─────  ──────────────────────────────────────────
//! 0x00    64     ELF Header
//! 0x40    56     Program Header #0 (PT_LOAD, .text: rx)
//! 0x78    56     Program Header #1 (PT_LOAD, .data: rw)  <- если есть данные
//! 0xB0    N      .text секция (машинный код)
//! 0xB0+N  M      .data секция (данные директив)
//! ```
//!
//! Это **статически слинкованный** исполняемый файл — никакой динамической линковки.
//! Точка входа определяется по метке `_start` (или той, что передана в `entry_label`).
//!
//! ## Адреса загрузки
//!
//! По умолчанию код загружается по виртуальному адресу `0x400000` (стандарт для x86_64 Linux).
//! Это соответствует тому, что делает `ld` по умолчанию.
use std::io::{self, Write};

/// Базовый виртуальный адрес загрузки кода. Стандарт для x86_64 Linux.
const LOAD_BASE: u64 = 0x40_0000;

/// Смещение, с которого начинается .text в файле (после ELF header + program headers).
/// 64 (ELF hdr) + 56*2 (два program headers) = 176 = 0xB0
const TEXT_FILE_OFFSET: u64 = 0xB0;

/// # ELF Writer
///
/// Принимает байты `.text` и `.data` секций и записывает готовый ELF64 в любой `Write`.
pub struct ElfWriter {
    /// Машинный код (.text секция).
    pub text: Vec<u8>,

    /// Данные (.data секция). Может быть пустой.
    pub data: Vec<u8>,

    /// Байтовое смещение точки входа внутри `.text`.
    pub entry_offset: usize,
}

impl ElfWriter {
    pub fn new(text: Vec<u8>, data: Vec<u8>, entry_offset: usize) -> Self {
        Self {
            text,
            data,
            entry_offset,
        }
    }

    /// Записывает готовый ELF64 в переданный writer (файл, Vec<u8>, и т.д.).
    pub fn write<W: Write>(&self, out: &mut W) -> io::Result<()> {
        let text_vaddr = LOAD_BASE + TEXT_FILE_OFFSET;
        let entry_vaddr = text_vaddr + self.entry_offset as u64;

        let data_file_offset = TEXT_FILE_OFFSET + self.text.len() as u64;
        let data_vaddr = align_up(LOAD_BASE + data_file_offset, 0x1000);

        let has_data = !self.data.is_empty();

        out.write_all(&self.elf_header(entry_vaddr, has_data))?;

        out.write_all(&self.phdr_text(text_vaddr))?;

        if has_data {
            out.write_all(&self.phdr_data(data_file_offset, data_vaddr))?;
        } else {
            // Место под второй phdr всё равно зарезервировано в заголовке (phnum=2),
            // поэтому если данных нет, выводим нулевой phdr — это валидный ELF.
            out.write_all(&[0u8; 56])?;
        }

        out.write_all(&self.text)?;
        if has_data {
            out.write_all(&self.data)?;
        }

        Ok(())
    }

    // =========================================================================
    // ELF Header (64 байта)
    // =========================================================================

    fn elf_header(&self, entry_vaddr: u64, _has_data: bool) -> [u8; 64] {
        let mut h = [0u8; 64];

        // e_ident: magic, class, data, version, OS/ABI
        h[0..4].copy_from_slice(b"\x7fELF");
        h[4] = 2; // ELFCLASS64
        h[5] = 1; // ELFDATA2LSB (little-endian)
        h[6] = 1; // EV_CURRENT
        h[7] = 0; // ELFOSABI_NONE (System V)
        // h[8..16] — padding, оставляем нулями

        // e_type = ET_EXEC (2)
        h[16..18].copy_from_slice(&2u16.to_le_bytes());
        // e_machine = EM_X86_64 (62)
        h[18..20].copy_from_slice(&62u16.to_le_bytes());
        // e_version = 1
        h[20..24].copy_from_slice(&1u32.to_le_bytes());
        // e_entry
        h[24..32].copy_from_slice(&entry_vaddr.to_le_bytes());
        // e_phoff = 64 (program headers сразу после ELF header)
        h[32..40].copy_from_slice(&64u64.to_le_bytes());
        // e_shoff = 0 (секционных заголовков нет — минимальный ELF)
        // e_flags = 0
        // e_ehsize = 64
        h[52..54].copy_from_slice(&64u16.to_le_bytes());
        // e_phentsize = 56
        h[54..56].copy_from_slice(&56u16.to_le_bytes());
        // e_phnum = 2 (text + data)
        h[56..58].copy_from_slice(&2u16.to_le_bytes());
        // e_shentsize = 64
        h[58..60].copy_from_slice(&64u16.to_le_bytes());
        // e_shnum = 0
        // e_shstrndx = 0

        h
    }

    // =========================================================================
    // Program Header (56 байт каждый)
    // =========================================================================

    /// PT_LOAD для .text: виртуальный адрес = text_vaddr, права = r-x
    fn phdr_text(&self, text_vaddr: u64) -> [u8; 56] {
        let filesz = self.text.len() as u64;
        self.phdr(
            1,                // p_type = PT_LOAD
            5,                // p_flags = PF_R | PF_X
            TEXT_FILE_OFFSET, // p_offset
            text_vaddr,       // p_vaddr
            text_vaddr,       // p_paddr
            filesz,           // p_filesz
            filesz,           // p_memsz
            0x1000,           // p_align
        )
    }

    /// PT_LOAD для .data: права = rw-
    fn phdr_data(&self, file_offset: u64, vaddr: u64) -> [u8; 56] {
        let filesz = self.data.len() as u64;
        self.phdr(
            1, // PT_LOAD
            6, // PF_R | PF_W
            file_offset,
            vaddr,
            vaddr,
            filesz,
            filesz,
            0x1000,
        )
    }

    #[allow(clippy::too_many_arguments)]
    fn phdr(
        &self,
        p_type: u32,
        p_flags: u32,
        p_offset: u64,
        p_vaddr: u64,
        p_paddr: u64,
        p_filesz: u64,
        p_memsz: u64,
        p_align: u64,
    ) -> [u8; 56] {
        let mut p = [0u8; 56];
        p[0..4].copy_from_slice(&p_type.to_le_bytes());
        p[4..8].copy_from_slice(&p_flags.to_le_bytes());
        p[8..16].copy_from_slice(&p_offset.to_le_bytes());
        p[16..24].copy_from_slice(&p_vaddr.to_le_bytes());
        p[24..32].copy_from_slice(&p_paddr.to_le_bytes());
        p[32..40].copy_from_slice(&p_filesz.to_le_bytes());
        p[40..48].copy_from_slice(&p_memsz.to_le_bytes());
        p[48..56].copy_from_slice(&p_align.to_le_bytes());
        p
    }
}

/// Выравнивание вверх: возвращает наименьшее число ≥ `val`, кратное `align`.
fn align_up(val: u64, align: u64) -> u64 {
    (val + align - 1) & !(align - 1)
}
