use std::fmt;
use symbolic_common::{Language, Name, NameMangling};
use super::{raw, SymCache};
impl<'data> SymCache<'data> {
    pub fn lookup(&self, addr: u64) -> SourceLocations<'data, '_> {
        let addr = match u32::try_from(addr) {
            Ok(addr) => addr,
            Err(_) => {
                return SourceLocations {
                    cache: self,
                    source_location_idx: u32::MAX,
                }
            }
        };
        let source_location_start = (self.source_locations.len() - self.ranges.len()) as u32;
        let mut source_location_idx = match self.ranges.binary_search_by_key(&addr, |r| r.0) {
            Ok(idx) => source_location_start + idx as u32,
            Err(idx) if idx == 0 => u32::MAX,
            Err(idx) => source_location_start + idx as u32 - 1,
        };
        if let Some(source_location) = self.source_locations.get(source_location_idx as usize) {
            if *source_location == raw::NO_SOURCE_LOCATION {
                source_location_idx = u32::MAX;
            }
        }
        SourceLocations {
            cache: self,
            source_location_idx,
        }
    }
    pub(crate) fn get_file(&self, file_idx: u32) -> Option<File<'data>> {
        let raw_file = self.files.get(file_idx as usize)?;
        Some(File {
            comp_dir: self.get_string(raw_file.comp_dir_offset),
            directory: self.get_string(raw_file.directory_offset),
            name: self.get_string(raw_file.name_offset).unwrap_or_default(),
        })
    }
    pub(crate) fn get_function(&self, function_idx: u32) -> Option<Function<'data>> {
        let raw_function = self.functions.get(function_idx as usize)?;
        Some(Function {
            name: self.get_string(raw_function.name_offset).unwrap_or("?"),
            entry_pc: raw_function.entry_pc,
            language: Language::from_u32(raw_function.lang),
        })
    }
    pub fn functions(&self) -> Functions<'data> {
        Functions {
            cache: self.clone(),
            function_idx: 0,
        }
    }
    pub fn files(&self) -> Files<'data> {
        Files {
            cache: self.clone(),
            file_idx: 0,
        }
    }
}
#[derive(Debug, Clone)]
pub struct File<'data> {
    comp_dir: Option<&'data str>,
    directory: Option<&'data str>,
    name: &'data str,
}
impl<'data> File<'data> {
    pub fn full_path(&self) -> String {
        let comp_dir = self.comp_dir.unwrap_or_default();
        let directory = self.directory.unwrap_or_default();
        let prefix = symbolic_common::join_path(comp_dir, directory);
        let full_path = symbolic_common::join_path(&prefix, self.name);
        let full_path = symbolic_common::clean_path(&full_path).into_owned();
        full_path
    }
}
#[derive(Clone, Debug)]
pub struct Function<'data> {
    name: &'data str,
    entry_pc: u32,
    language: Language,
}
impl<'data> Function<'data> {
    pub fn name(&self) -> &'data str {
        self.name
    }
    pub fn name_for_demangling(&self) -> Name<'data> {
        Name::new(self.name, NameMangling::Unknown, self.language)
    }
    pub fn entry_pc(&self) -> u32 {
        self.entry_pc
    }
    pub fn language(&self) -> Language {
        self.language
    }
}
impl<'data> Default for Function<'data> {
    fn default() -> Self {
        Self {
            name: "?",
            entry_pc: u32::MAX,
            language: Language::Unknown,
        }
    }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceLocation<'data, 'cache> {
    pub(crate) cache: &'cache SymCache<'data>,
    pub(crate) source_location: &'data raw::SourceLocation,
}
impl<'data, 'cache> SourceLocation<'data, 'cache> {
    pub fn line(&self) -> u32 {
        self.source_location.line
    }
    pub fn file(&self) -> Option<File<'data>> {
        self.cache.get_file(self.source_location.file_idx)
    }
    pub fn function(&self) -> Function<'data> {
        self.cache
            .get_function(self.source_location.function_idx)
            .unwrap_or_default()
    }
    }
#[derive(Debug, Clone)]
pub struct SourceLocations<'data, 'cache> {
    pub(crate) cache: &'cache SymCache<'data>,
    pub(crate) source_location_idx: u32,
}
impl<'data, 'cache> Iterator for SourceLocations<'data, 'cache> {
    type Item = SourceLocation<'data, 'cache>;
    fn next(&mut self) -> Option<Self::Item> {
        if self.source_location_idx == u32::MAX {
            return None;
        }
        self.cache
            .source_locations
            .get(self.source_location_idx as usize)
            .map(|source_location| {
                self.source_location_idx = source_location.inlined_into_idx;
                SourceLocation {
                    cache: self.cache,
                    source_location,
                }
            })
    }
}
#[derive(Debug, Clone)]
pub struct Functions<'data> {
    cache: SymCache<'data>,
    function_idx: u32,
}
impl<'data> Iterator for Functions<'data> {
    type Item = Function<'data>;
    fn next(&mut self) -> Option<Self::Item> {
        let mut function = self.cache.get_function(self.function_idx);
        while let Some(ref f) = function {
            if f.entry_pc == u32::MAX {
                self.function_idx += 1;
                function = self.cache.get_function(self.function_idx);
            } else {
                break;
            }
        }
        function.map(|f| {
            self.function_idx += 1;
            f
        })
    }
}
pub struct FunctionsDebug<'a>(pub &'a SymCache<'a>);
impl fmt::Debug for FunctionsDebug<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut vec: Vec<_> = self.0.functions().collect();
        vec.sort_by_key(|f| (f.entry_pc, f.name));
        for function in vec {
            writeln!(f, "{:>16x} {}", &function.entry_pc, &function.name)?;
        }
        Ok(())
    }
}
#[derive(Debug, Clone)]
pub struct Files<'data> {
    cache: SymCache<'data>,
    file_idx: u32,
}
impl<'data> Iterator for Files<'data> {
    type Item = File<'data>;
    fn next(&mut self) -> Option<Self::Item> {
        self.cache.get_file(self.file_idx).map(|f| {
            self.file_idx += 1;
            f
        })
    }
}
pub struct FilesDebug<'a>(pub &'a SymCache<'a>);
impl fmt::Debug for FilesDebug<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut vec: Vec<_> = self.0.files().map(|f| f.full_path()).collect();
        vec.sort();
        for file in vec {
            writeln!(f, "{file}")?;
        }
        Ok(())
    }
}