/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate memmap2;
extern crate rustc_demangle;
extern crate thin_vec;
extern crate uuid;

#[cfg(feature = "parse_elf")]
extern crate object;

mod compact_symbol_table;

#[cfg(feature = "parse_elf")]
mod elf;

#[cfg(feature = "parse_elf")]
use memmap2::MmapOptions;
#[cfg(feature = "parse_elf")]
use std::fs::File;

use compact_symbol_table::CompactSymbolTable;
use rustc_demangle::try_demangle;
use std::ffi::CStr;
use std::mem;
use std::os::raw::c_char;
use std::ptr;

#[cfg(feature = "parse_elf")]
pub fn get_compact_symbol_table_from_file(
    debug_path: &str,
    breakpad_id: Option<&str>,
) -> Option<CompactSymbolTable> {
    let file = File::open(debug_path).ok()?;
    let buffer = unsafe { MmapOptions::new().map(&file).ok()? };
    elf::get_compact_symbol_table(&buffer, breakpad_id)
}

#[cfg(not(feature = "parse_elf"))]
pub fn get_compact_symbol_table_from_file(
    _debug_path: &str,
    _breakpad_id: Option<&str>,
) -> Option<CompactSymbolTable> {
    None
}

#[no_mangle]
pub unsafe extern "C" fn profiler_get_symbol_table(
    debug_path: *const c_char,
    breakpad_id: *const c_char,
    symbol_table: &mut CompactSymbolTable,
) -> bool {
    let debug_path = CStr::from_ptr(debug_path).to_string_lossy();
    let breakpad_id = if breakpad_id.is_null() {
        None
    } else {
        match CStr::from_ptr(breakpad_id).to_str() {
            Ok(s) => Some(s),
            Err(_) => return false,
        }
    };

    match get_compact_symbol_table_from_file(&debug_path, breakpad_id.map(|id| id.as_ref())) {
        Some(mut st) => {
            std::mem::swap(symbol_table, &mut st);
            true
        }
        None => false,
    }
}

#[no_mangle]
pub unsafe extern "C" fn profiler_demangle_rust(
    mangled: *const c_char,
    buffer: *mut c_char,
    buffer_len: usize,
) -> bool {
    assert!(!mangled.is_null());
    assert!(!buffer.is_null());

    if buffer_len == 0 {
        return false;
    }

    let buffer: *mut u8 = mem::transmute(buffer);
    let mangled = match CStr::from_ptr(mangled).to_str() {
        Ok(s) => s,
        Err(_) => return false,
    };

    match try_demangle(mangled) {
        Ok(demangled) => {
            let mut demangled = format!("{:#}", demangled);
            if !demangled.is_ascii() {
                return false;
            }
            demangled.truncate(buffer_len - 1);

            let bytes = demangled.as_bytes();
            ptr::copy(bytes.as_ptr(), buffer, bytes.len());
            ptr::write(buffer.offset(bytes.len() as isize), 0);
            true
        }
        Err(_) => false,
    }
}
