use std::{collections::HashMap, fmt::Write};
use gen_tui::layout::{LayoutEdge, LayoutNode, NodeRole};
use petgraph::{
Undirected,
stable_graph::StableGraph,
visit::{EdgeRef, IntoEdgeReferences},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DotExportMode {
Debug,
Simplified,
InputOnly,
}
pub fn export_graph_to_dot(
graph: &StableGraph<LayoutNode, LayoutEdge, Undirected, u32>,
filename: &str,
mode: DotExportMode,
) -> Result<(), std::io::Error> {
let dot_content = match mode {
DotExportMode::Debug => generate_dot_debug(graph),
DotExportMode::Simplified => generate_dot_simplified(graph),
DotExportMode::InputOnly => generate_dot_input_only(graph),
};
std::fs::write(filename, &dot_content)?;
println!("Graph exported to: {}", filename);
println!(
"View with: neato -Tpng {} -o {}.png",
filename,
filename.replace(".dot", "")
);
Ok(())
}
fn generate_dot_debug(graph: &StableGraph<LayoutNode, LayoutEdge, Undirected, u32>) -> String {
let mut dot = String::new();
writeln!(&mut dot, "graph {{").unwrap();
writeln!(&mut dot, " layout=neato;").unwrap();
writeln!(&mut dot, " overlap=false;").unwrap();
writeln!(&mut dot, " rankdir=LR;").unwrap();
let mut sorted_node_indices: Vec<_> = graph.node_indices().collect();
sorted_node_indices.sort_by(|&a, &b| {
let node_a = graph.node_weight(a).unwrap();
let node_b = graph.node_weight(b).unwrap();
(node_a.pos.x, node_a.pos.y).cmp(&(node_b.pos.x, node_b.pos.y))
});
for idx in sorted_node_indices {
if let Some(node) = graph.node_weight(idx) {
let node_id = idx.index();
let x = node.pos.x;
let y = node.pos.y;
let node_title = match &node.role {
NodeRole::Data(payload) => format!("D{}", payload.index()),
NodeRole::Routing => {
let glyph_index = compute_routing_glyph_index(graph, idx);
format!("R{:04b}", glyph_index)
}
NodeRole::Stitch(side) => match side {
gen_tui::partition::StitchSide::Left => "S_L".to_string(),
gen_tui::partition::StitchSide::Right => "S_R".to_string(),
},
};
let label = format!("{}\\n({},{})", node_title, x, y);
let (shape, color) = match &node.role {
NodeRole::Data(_) => ("box", "lightblue"),
NodeRole::Routing => ("circle", "lightgreen"),
NodeRole::Stitch(_) => ("diamond", "orange"),
};
writeln!(
&mut dot,
" n{} [label=\"{}\", pos=\"{},{}!\", shape=\"{}\", fillcolor=\"{}\", style=\"filled\", pin=true];",
node_id, label, x, y, shape, color
)
.unwrap();
}
}
let edge_colors = ["black", "red", "blue", "green", "purple", "orange", "brown"];
let mut bundle_to_color = HashMap::new();
let mut color_index = 0;
for edge_ref in graph.edge_references() {
let source_idx = edge_ref.source();
let target_idx = edge_ref.target();
let source = source_idx.index();
let target = target_idx.index();
let edge_data = edge_ref.weight();
if edge_data.bundle.is_empty() {
writeln!(
&mut dot,
" n{} -- n{} [label=\"EMPTY\", color=\"gray\", style=\"dashed\"];",
source, target
)
.unwrap();
} else {
let bundle_str = edge_data
.bundle
.iter()
.map(|(s, t)| format!("({},{})", s.index(), t.index()))
.collect::<Vec<_>>()
.join("\\n");
let bundle_key = format!("{:?}", edge_data.bundle);
if !bundle_to_color.contains_key(&bundle_key) {
bundle_to_color.insert(
bundle_key.clone(),
edge_colors[color_index % edge_colors.len()],
);
color_index += 1;
}
let color = bundle_to_color[&bundle_key];
let thickness = (edge_data.bundle.len()).clamp(1, 5);
writeln!(
&mut dot,
" n{} -- n{} [label=\"{}\", color=\"{}\", penwidth={}];",
source, target, bundle_str, color, thickness
)
.unwrap();
}
}
writeln!(&mut dot, "}}").unwrap();
dot
}
fn generate_dot_simplified(
_graph: &StableGraph<LayoutNode, LayoutEdge, Undirected, u32>,
) -> String {
unimplemented!("Simplified DOT export not yet implemented")
}
fn generate_dot_input_only(
_graph: &StableGraph<LayoutNode, LayoutEdge, Undirected, u32>,
) -> String {
unimplemented!("Input-only DOT export not yet implemented")
}
fn compute_routing_glyph_index(
graph: &StableGraph<LayoutNode, LayoutEdge, Undirected, u32>,
node_idx: petgraph::graph::NodeIndex,
) -> u8 {
let node = graph.node_weight(node_idx).unwrap();
let mut glyph_index = 0u8;
for edge in graph.edges(node_idx) {
let neighbor_idx = edge.target();
if let Some(neighbor) = graph.node_weight(neighbor_idx) {
let dx = neighbor.pos.x - node.pos.x;
let dy = neighbor.pos.y - node.pos.y;
if dx.abs() > dy.abs() {
if dx > 0 {
glyph_index |= 0b0100; } else {
glyph_index |= 0b0001; }
} else if dy > 0 {
glyph_index |= 0b0010; } else {
glyph_index |= 0b1000; }
}
}
glyph_index
}