use std::{sync::Mutex, time::Instant};

use rustc_hash::FxHashMap;
use tabled::{settings::Style, Table, Tabled};

lazy_static! {
    pub static ref PROFILER: Mutex<FxHashMap<&'static str, (u128, usize)>> = Mutex::default();
}

pub struct ProfSpan {
    name: &'static str,
    start: Instant
}

impl ProfSpan {
    pub fn new(name: &'static str) -> Self {
        ProfSpan { start: Instant::now(), name }
    }

    pub fn end(&self) {
        let elapsed = self.start.elapsed().as_nanos();

        let mut p_entry = PROFILER.lock().unwrap();
        let entry_data = p_entry.entry(self.name).or_insert((0, 0));

        entry_data.0 += elapsed;
        entry_data.1 += 1;
    }
}

#[macro_export]
macro_rules! profile {
    ($name: expr, $expr: expr) => {
        {
            let span = $crate::profiling::ProfSpan::new($name);

            let res = $expr;
    
            span.end();
    
            res    
        }
    };
}

#[derive(Tabled)]
struct ProfilerEntry {
    name: &'static str,
    total_time: u128,
    time: u128,
    count: usize,
    percentage: f32
}

pub fn clear_profiler() {
    PROFILER.lock().unwrap().clear();
}

pub fn print_profiler_results() {
    let mut entries = PROFILER.lock().unwrap().iter().map(|(a, b)| (*a, *b)).collect::<Vec<_>>();
    entries.sort_by_key(|(_, i)| *i);
    entries.reverse();

    let total_time = entries.iter().map(|(_, (i, _))| *i).sum::<u128>();

    let table = Table::new(
        entries.into_iter().map(|(name, (time, count))| ProfilerEntry {
            name,
            total_time: time / 1000000,
            count,
            time: time / count as u128,
            percentage: (time as f64 / total_time as f64) as f32,
        })
    ).with(Style::modern_rounded()).to_string();

    print!("{}", table);
}