use std::borrow::Cow;
use std::env;
use std::fs::File;
use std::path::{self, PathBuf};

use object::Object;

fn release_fixture_path() -> PathBuf {
    if let Ok(p) = env::var("ADDR2LINE_FIXTURE_PATH") {
        return p.into();
    }

    let mut path = PathBuf::new();
    if let Ok(dir) = env::var("CARGO_MANIFEST_DIR") {
        path.push(dir);
    }
    path.push("fixtures");
    path.push("addr2line-release");
    path
}

fn with_file<F: FnOnce(&object::File<'_>)>(target: &path::Path, f: F) {
    let file = File::open(target).unwrap();
    let map = unsafe { memmap2::Mmap::map(&file).unwrap() };
    let file = object::File::parse(&*map).unwrap();
    f(&file)
}

fn dwarf_load<'a>(object: &object::File<'a>) -> gimli::DwarfSections<Cow<'a, [u8]>> {
    let load_section = |id: gimli::SectionId| -> Result<Cow<'a, [u8]>, gimli::Error> {
        use object::ObjectSection;

        let data = object
            .section_by_name(id.name())
            .and_then(|section| section.data().ok())
            .unwrap_or(&[][..]);
        Ok(Cow::Borrowed(data))
    };
    gimli::DwarfSections::load(&load_section).unwrap()
}

fn dwarf_borrow<'a>(
    dwarf: &'a gimli::DwarfSections<Cow<'_, [u8]>>,
) -> gimli::Dwarf<gimli::EndianSlice<'a, gimli::LittleEndian>> {
    let borrow_section: &dyn for<'b> Fn(
        &'b Cow<'_, [u8]>,
    ) -> gimli::EndianSlice<'b, gimli::LittleEndian> =
        &|section| gimli::EndianSlice::new(section, gimli::LittleEndian);
    dwarf.borrow(&borrow_section)
}

#[test]
fn parse_base_rc() {
    let target = release_fixture_path();

    with_file(&target, |file| {
        addr2line::ObjectContext::new(file).unwrap();
    });
}

#[test]
fn parse_base_slice() {
    let target = release_fixture_path();

    with_file(&target, |file| {
        let dwarf = dwarf_load(file);
        let dwarf = dwarf_borrow(&dwarf);
        addr2line::Context::from_dwarf(dwarf).unwrap();
    });
}

#[test]
fn parse_lines_rc() {
    let target = release_fixture_path();

    with_file(&target, |file| {
        let context = addr2line::ObjectContext::new(file).unwrap();
        context.parse_lines().unwrap();
    });
}

#[test]
fn parse_lines_slice() {
    let target = release_fixture_path();

    with_file(&target, |file| {
        let dwarf = dwarf_load(file);
        let dwarf = dwarf_borrow(&dwarf);
        let context = addr2line::Context::from_dwarf(dwarf).unwrap();
        context.parse_lines().unwrap();
    });
}

#[test]
fn parse_functions_rc() {
    let target = release_fixture_path();

    with_file(&target, |file| {
        let context = addr2line::ObjectContext::new(file).unwrap();
        context.parse_functions().unwrap();
    });
}

#[test]
fn parse_functions_slice() {
    let target = release_fixture_path();

    with_file(&target, |file| {
        let dwarf = dwarf_load(file);
        let dwarf = dwarf_borrow(&dwarf);
        let context = addr2line::Context::from_dwarf(dwarf).unwrap();
        context.parse_functions().unwrap();
    });
}
