Analyze dependencies of cargo projects
use crate::graph::Node;
use crate::{AnnotationGraph, Measurement, Variable};

use forceatlas2::Layout;

fn layout<'graph>(
    annotations: &'graph AnnotationGraph,
) -> impl Iterator<Item = (&'graph Node<'graph>, forceatlas2::Node<f64, 2>)> {
    let edges = annotations
        .edges()
        // Get the graph index of both nodes connected to the edge (NodeIndex)
        .map(|(source, target)| (source.index(), target.index()))
        // Get the integer representation of that index (usize)
        .map(|(source, target)| (source.index(), target.index()))
        .map(|edge| (edge, 1.0))
        .collect();
    let sizes = annotations.edges().map(|(source, _target)| {
        annotations.variable(source, Variable::UnitDuration, Measurement::Exact)
    });

    let mut layout =
        Layout::from_graph_with_degree_mass(edges, sizes, forceatlas2::Settings::default());

    for _step in 0..1_000 {
        layout.iteration();
    }
    annotations.packages().zip(layout.nodes.into_iter())
}

pub fn data(annotations: &AnnotationGraph) -> charming::series::GraphData {
    let categories = vec![charming::series::GraphCategory {
        name: String::from("default"),
    }];
    let layout = layout(annotations);

    let nodes = layout
        .map(|(graph_node, layout_node)| {
            let unit_time =
                annotations.variable(graph_node, Variable::UnitDuration, Measurement::Exact);

            // TODO: figure out the difference between `value` and `symbol_size`
            charming::series::GraphNode {
                id: graph_node.id().repr.clone(),
                name: graph_node.name().to_string(),
                x: layout_node.pos.x(),
                y: layout_node.pos.y(),
                value: unit_time,
                category: 0,
                symbol_size: unit_time,
                label: None,
            }
        })
        .collect();

    let links = annotations
        .edges()
        .map(|(source, target)| charming::series::GraphLink {
            source: source.id().repr.to_string(),
            target: target.id().repr.to_string(),
            value: None,
        })
        .collect();

    charming::series::GraphData {
        nodes,
        links,
        categories,
    }
}