//! Basic ELF parsing functionality using the `elf` crate.
//!
//! This may be extended in the future to support loading programs, however we
//! currently use this for getting the sizes of sections in our kernel ELF at
//! runtime.
//!
//! This is used for implementing stacktraces in std::unwind.
//!
//! # TODO
//!
//! * Add support for loading binary programs (this should probably be written
//!   in a different module)

use alloc::format;
use elf::{
    ElfBytes, ParseError,
    endian::LittleEndian,
    parse::{ParseAt, ParsingTable},
    section::{SectionHeader, SectionHeaderTable},
    string_table::StringTable,
    symbol::SymbolTable,
};
use limine::request::KernelFileRequest;

use crate::prelude::*;

#[cfg(target_arch = "x86_64")]
/// The length of the ELF header in bytes.
pub const ELF_HEADER_LEN: usize = 64;

/// Information about our own ELF file to make ELF parsing easier, such as the
/// length of the file and a pointer to the contents.
#[used]
pub static KERNEL_FILE_REQUEST: KernelFileRequest = KernelFileRequest::new();

/// A list of errors that may occur when parsing ELF files.
#[derive(Debug)]
pub enum ElfError {
    /// Returned if a section did not exist in [ElfReader::get_section_size].
    SectionNotExists,
    /// Parse errors returned by the `elf` crate.
    OtherParseError(elf::ParseError),
}

impl From<elf::ParseError> for ElfError {
    fn from(err: elf::ParseError) -> Self {
        Self::OtherParseError(err)
    }
}

pub struct ElfReader {
    /// Structure returned by the `elf` crate having parsed the ELF header.
    elf: ElfBytes<'static, LittleEndian>,
}

impl ElfReader {
    /// Parses the ELF file for the kernel, this uses data from Limine's Kernel
    /// File Request to get a slice over the whole executable file.
    ///
    /// # Safety
    ///
    /// Assumes a properly formed ELF file, and that Limine returns a correct
    /// pointer to the start of the file as well as a valid length in bytes.
    ///
    /// Both of these should be satisfied, but this function is marked unsafe
    /// just in case, because we are derefererencing arbitrary pointers.
    pub unsafe fn new() -> Result<Self, ElfError> {
        let response = KERNEL_FILE_REQUEST
            .get_response()
            .expect("Didn't get the kernel file from Limine. That's odd.");

        // We fetch these from Limine and use them to parse our own ELF file.
        let file = response.file();
        let file_start_ptr = file.addr();
        let file_size = file.size() as usize;

        // Safety: This slice should contain the whole bytes of the ELF file.
        let elf_hdr_slice =
            unsafe { core::slice::from_raw_parts(file_start_ptr, file_size) };

        let elf: ElfBytes<'static, LittleEndian> =
            elf::ElfBytes::minimal_parse(elf_hdr_slice)?;

        Ok(Self { elf })
    }

    pub fn get_symbol_table(
        &self,
    ) -> Result<
        Option<(SymbolTable<'static, LittleEndian>, StringTable<'static>)>,
        ElfError,
    > {
        Ok(self.elf.symbol_table()?)
    }

    /// Gets the section size of `section_name` in bytes.
    pub fn get_section_size(
        &self,
        section_name: &'static str,
    ) -> Result<u64, ElfError> {
        Ok(self.get_section_header(section_name)?.sh_size)
    }

    /// Gets the start address of the section `section_name` in memory.
    pub fn get_section_addr(
        &self,
        section_name: &'static str,
    ) -> Result<*const u8, ElfError> {
        Ok(self.get_section_header(section_name)?.sh_addr as *const u8)
    }

    /// Gets the section header of `section_name`.
    pub fn get_section_header(
        &self,
        section_name: &'static str,
    ) -> Result<SectionHeader, ElfError> {
        let section_hdr = self
            .elf
            .section_header_by_name(section_name)
            .map_err(|_e| ElfError::SectionNotExists)?;

        section_hdr.ok_or(ElfError::SectionNotExists)
    }
}
