use std::convert::TryFrom;

use crate::error::Error;
use crate::parts::{Lazy, Relocatable, Symbol, Text};
use xmas_elf::header::Type as ElfType;
use xmas_elf::sections::*;
use xmas_elf::ElfFile;

impl<'a> Lazy<&'a str, ElfFile<'a>> for Symbol<'a> {
    fn value(&self, src: &ElfFile<'a>) -> Result<&'a str, Error> {
        src.get_string(self.index)
            .map_err(|e| Error::StringError(e))
    }

    fn resolved(&self) -> bool {
        self.str_ref.is_some()
    }
}

impl<'a> TryFrom<SectionData<'a>> for Text<'a> {
    type Error = Error;

    fn try_from(data: SectionData<'a>) -> Result<Self, Error> {
        match data {
            SectionData::Undefined(bytes) => Ok(Text { bytes }),
            _ => Err(Error::InvalidSectionData), // TODO: more context needed
        }
    }
}

impl<'a> Relocatable<'a> {
    pub fn from_elf(origin: &'a str, elf: &'a ElfFile) -> Result<Self, Error> {
        is_relocatable(elf)?;
        let mut result = Relocatable::new(origin);

        for header in elf.section_iter() {
            // .text sections > 0 only
            if is_text_section(elf, &header)? {
                let data = header
                    .get_data(&elf)
                    .map_err(|e| Error::MissingSectionData(e))?;

                result.texts.push(Text::try_from(data)?);
            }
        }

        Ok(result)
    }
}

fn is_text_section(elf: &ElfFile, header: &SectionHeader) -> Result<bool, Error> {
    let name = header.get_name(&elf).unwrap_or("null");
    let h_type = header
        .get_type()
        .map_err(|e| Error::MissingSectionHeader(e))?;

    Ok(h_type == ShType::ProgBits
        && header.flags() == (SHF_EXECINSTR | SHF_ALLOC)
        && name.starts_with(".text")
        && header.size() > 0)
}

fn is_relocatable(elf: &ElfFile) -> Result<(), Error> {
    let raw_type = elf.header.pt2.type_();
    let elf_type: ElfType = raw_type.as_type();
    if elf_type != ElfType::Relocatable {
        return Err(Error::InvalidObjectType(raw_type.0.into()));
    }

    Ok(())
}
