pub mod types;
pub mod dump;

use tool_helper::Error;
use types::{MemoryMap, Section, Symbol};

pub fn scan<F: FnMut() -> Option<String>>(mut next_line: F) -> Result<MemoryMap, Error> {
    const SECTION_IDENTIFIER:&'static str      = "Sections:";
    const SYMBOL_TABLE_IDENTIFIER:&'static str = "SYMBOL TABLE:";

    let mut memory_map = MemoryMap::default();

    while let Some(mut line) = next_line() {
        loop {
            // loop so we pick-up changes of line
            if line == SECTION_IDENTIFIER {
                line = scan_sections(&mut next_line, &mut memory_map.sections)?;
                continue;
            }

            if line == SYMBOL_TABLE_IDENTIFIER {
                // Process Symbol table here
                line = scan_symbols(&mut next_line, &mut memory_map)?;
                continue;
            }
            break;
        }
    }

    sort_memory_map(&mut memory_map);
    Ok(memory_map)
}

fn sort_memory_map(memory_map: &mut MemoryMap) {
    fn sort_symbol_vec(symbol_vec: &mut Vec<Symbol>) {
        symbol_vec.sort_by(|a, b| {
            a.adr.cmp(&b.adr)
        });
    }

    fn sort_section_vec(section_vec: &mut Vec<Section>) {
        section_vec.sort_by(|a, b| {
            a.adr.cmp(&b.adr)
        });
    }

    sort_section_vec(&mut memory_map.sections);
    sort_symbol_vec(&mut memory_map.global);
    for section in &mut memory_map.sections {
        sort_symbol_vec(&mut section.symbols);
    }
}

fn scan_symbols<F: FnMut() -> Option<String>>(next_line: &mut F, memory_map: &mut MemoryMap) -> Result<String, Error> {
    fn find_section<'a>(name: &str, memory_map: &'a mut MemoryMap) -> Option<&'a mut Section> {
        for section in &mut memory_map.sections {
            if section.name == name {
                return Some(section);
            }
        }

        None
    }

    fn skip_symbol(name: &str, section_name: &str) -> bool {
        name == section_name || name.ends_with(".cpp") || name.ends_with(".c") || name.ends_with(".cxx")
    }

    while let Some(line) = next_line() {
        fn read_symbol(line: String) -> Result<Option<(Symbol, String)>, Error> {
            let split_count    = line.split_whitespace().count();
            let mut split_line = line.split_whitespace();
            let adr            = split_line_radix(&mut split_line, 16, "Address of Symbol")?;
            let _type          = split_line.next().ok_or(Error::from_str("Failed obtaining type of symbol"))?;
            if split_count > 5 {
                let _flag = split_line.next().ok_or(Error::from_str("Failed obtaining flag of symbol"))?;
            }
            let section        = split_line.next().ok_or(Error::from_str("Failed obtaining section of symbol"))?;
            let size           = split_line_radix(&mut split_line, 16, "Size of Symbol")? as usize;
            let name           = split_line.next().ok_or(Error::from_str("Failed obtaining name of symbol"))?;
            
            if skip_symbol(name, section) {
                // Not of interest
                Ok(None)
            }

            else {
                Ok(Some((Symbol::new(name.to_string(), adr, size), section.to_owned())))
            }
        }
        
        if line.chars().all(char::is_whitespace) {
            return Ok(line);
        }

        if let Ok(Some((symbol, section))) = read_symbol(line) {
            if let Some(section) = find_section(section.as_str(), memory_map) {
                if section.contains_adr(symbol.adr) {
                    section.symbols.push(symbol);
                    // Do not add to global cause they have a section
                    continue;
                }
            }

            memory_map.global.push(symbol);
        }
    }

    Err(Error::from_str("Failed obtaining all Symbols"))
}

fn scan_sections<F: FnMut() -> Option<String>>(next_line: &mut F, sections: &mut Vec<Section>) -> Result<String, Error> {
    while let Some(_) = next_line() {
        // We read every other line
        let line           = next_line().ok_or(Error::from_str("Failed skipping to next section"))?;
        let mut split_line = line.split_whitespace();
        if let Ok(_) = split_line_radix(&mut split_line, 10, "") {
            let name = split_line.next().ok_or(Error::from_str("Failed reading Section Name"))?;
            let size = split_line_radix(&mut split_line, 16, "Section Size")?;
            let adr  = split_line_radix(&mut split_line, 16, "Section Address")?;

            sections.push(Section::new(name.to_string(), adr, size as usize));
        }

        else {
            return Ok(line);
        }
    }

    Err(Error::from_str("Failed obtaining complete section"))
}

fn split_line_radix<'a>(split_line: &mut std::str::SplitWhitespace<'a>, radix: u32, value_name: &str) -> Result<u64, Error> {
    let text = split_line.next().ok_or(Error::from_text(format!("Failed reading: {}", value_name)))?;
    match u64::from_str_radix(text, radix) {
        Ok(value)  => Ok(value),
        Err(error) => Err(Error::from_text(format!("Converting value ({}) for {} failed with: {}", text, value_name, error)))
    }
}