// Memory profiling for different graph backends
// Run with: cargo run --release --bin memory_profile --no-default-features

use indexmap::IndexMap;
use ocg::graph::{GraphBackend, PropertyGraph, NetworKitRustBackend};
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};

// Memory tracking allocator
struct TrackingAllocator;

static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);

unsafe impl GlobalAlloc for TrackingAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let ret = System.alloc(layout);
        if !ret.is_null() {
            let new_total = ALLOCATED.fetch_add(layout.size(), Ordering::SeqCst) + layout.size();
            // Update peak if necessary
            PEAK.fetch_max(new_total, Ordering::SeqCst);
        }
        ret
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        System.dealloc(ptr, layout);
        ALLOCATED.fetch_sub(layout.size(), Ordering::SeqCst);
    }
}

#[global_allocator]
static GLOBAL: TrackingAllocator = TrackingAllocator;

fn reset_memory_tracking() {
    ALLOCATED.store(0, Ordering::SeqCst);
    PEAK.store(0, Ordering::SeqCst);
}

fn get_memory_usage() -> (usize, usize) {
    (
        ALLOCATED.load(Ordering::SeqCst),
        PEAK.load(Ordering::SeqCst),
    )
}

fn format_bytes(bytes: usize) -> String {
    const KB: usize = 1024;
    const MB: usize = KB * 1024;
    const GB: usize = MB * 1024;

    if bytes >= GB {
        format!("{:.2} GB", bytes as f64 / GB as f64)
    } else if bytes >= MB {
        format!("{:.2} MB", bytes as f64 / MB as f64)
    } else if bytes >= KB {
        format!("{:.2} KB", bytes as f64 / KB as f64)
    } else {
        format!("{} bytes", bytes)
    }
}

fn profile_property_graph(sizes: &[usize]) {
    println!("\nPropertyGraph (petgraph) Backend:");
    println!("{}", "=".repeat(60));
    println!("{:<12} {:<20} {:<20} {:<12}", "Graph Size", "Current Memory", "Peak Memory", "Bytes/Node");
    println!("{}", "-".repeat(60));

    for &size in sizes {
        reset_memory_tracking();

        let mut graph = PropertyGraph::new();

        // Create nodes
        for _ in 0..size {
            let props = IndexMap::new();
            graph.create_node(vec!["TestNode"], props);
        }

        // Create edges (ring topology)
        for i in 0..size {
            let next = (i + 1) % size;
            graph
                .create_relationship(i as u64, next as u64, "NEXT", IndexMap::new())
                .ok();
        }

        let (current, peak) = get_memory_usage();
        let bytes_per_node = if size > 0 { current / size } else { 0 };

        println!(
            "{:<12} {:<20} {:<20} {:<12}",
            format!("{} nodes", size),
            format_bytes(current),
            format_bytes(peak),
            bytes_per_node
        );
    }
}

fn profile_networkit_rust(sizes: &[usize]) {
    println!("\nNetworKitRust Backend:");
    println!("{}", "=".repeat(60));
    println!("{:<12} {:<20} {:<20} {:<12}", "Graph Size", "Current Memory", "Peak Memory", "Bytes/Node");
    println!("{}", "-".repeat(60));

    for &size in sizes {
        reset_memory_tracking();

        let mut graph = NetworKitRustBackend::new();

        // Create nodes
        for _ in 0..size {
            let props = IndexMap::new();
            graph.create_node(vec!["TestNode"], props);
        }

        // Create edges (ring topology)
        for i in 0..size {
            let next = (i + 1) % size;
            graph
                .create_relationship(i as u64, next as u64, "NEXT", IndexMap::new())
                .ok();
        }

        let (current, peak) = get_memory_usage();
        let bytes_per_node = if size > 0 { current / size } else { 0 };

        println!(
            "{:<12} {:<20} {:<20} {:<12}",
            format!("{} nodes", size),
            format_bytes(current),
            format_bytes(peak),
            bytes_per_node
        );
    }
}

fn main() {
    println!("Graph Backend Memory Profiling");
    println!("==============================\n");

    let sizes = vec![100, 1_000, 10_000];

    profile_property_graph(&sizes);
    profile_networkit_rust(&sizes);

    // Detailed breakdown for 10K
    println!("\n\nDetailed Memory Breakdown (10,000 nodes):");
    println!("{}", "=".repeat(60));

    reset_memory_tracking();
    let mut pg = PropertyGraph::new();

    // Only nodes
    for _ in 0..10_000 {
        let props = IndexMap::new();
        pg.create_node(vec!["TestNode"], props);
    }
    let nodes_only = get_memory_usage().0;

    // Add edges
    for i in 0..10_000 {
        let next = (i + 1) % 10_000;
        pg.create_relationship(i as u64, next as u64, "NEXT", IndexMap::new())
            .ok();
    }
    let with_edges = get_memory_usage().0;
    let edges_only = with_edges.saturating_sub(nodes_only);

    println!("PropertyGraph:");
    println!("  Nodes (10K):        {}", format_bytes(nodes_only));
    println!("  Edges (10K):        {}", format_bytes(edges_only));
    println!("  Total:              {}", format_bytes(with_edges));
    println!("  Bytes per node:     {}", if nodes_only > 0 { nodes_only / 10_000 } else { 0 });
    println!("  Bytes per edge:     {}", if edges_only > 0 { edges_only / 10_000 } else { 0 });

    // NetworKitRust comparison
    reset_memory_tracking();
    let mut nk = NetworKitRustBackend::new();

    for _ in 0..10_000 {
        let props = IndexMap::new();
        nk.create_node(vec!["TestNode"], props);
    }
    let nk_nodes_only = get_memory_usage().0;

    for i in 0..10_000 {
        let next = (i + 1) % 10_000;
        nk.create_relationship(i as u64, next as u64, "NEXT", IndexMap::new())
            .ok();
    }
    let nk_with_edges = get_memory_usage().0;
    let nk_edges_only = nk_with_edges.saturating_sub(nk_nodes_only);

    println!("\nNetworKitRust:");
    println!("  Nodes (10K):        {}", format_bytes(nk_nodes_only));
    println!("  Edges (10K):        {}", format_bytes(nk_edges_only));
    println!("  Total:              {}", format_bytes(nk_with_edges));
    println!("  Bytes per node:     {}", if nk_nodes_only > 0 { nk_nodes_only / 10_000 } else { 0 });
    println!("  Bytes per edge:     {}", if nk_edges_only > 0 { nk_edges_only / 10_000 } else { 0 });

    println!("\nMemory Efficiency Comparison:");
    let pg_total = with_edges as f64;
    let nk_total = nk_with_edges as f64;

    if pg_total > 0.0 && nk_total > 0.0 {
        let ratio = pg_total / nk_total;
        if ratio > 1.0 {
            println!(
                "  NetworKitRust uses {:.2}x less memory than PropertyGraph",
                ratio
            );
        } else {
            println!(
                "  PropertyGraph uses {:.2}x less memory than NetworKitRust",
                1.0 / ratio
            );
        }
    }
}
