#![allow(dead_code)]
use crate::clip_relations::{ClipId, ClipRelation, ClipRelationGraph, RelationType};
use std::collections::HashSet;
#[derive(Debug, Default)]
pub struct BidirectionalRelationGraph {
inner: ClipRelationGraph,
inserted: HashSet<(ClipId, ClipId, String)>,
}
impl BidirectionalRelationGraph {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, relation: ClipRelation) {
let key = canonical_key(relation.from_id, relation.to_id, &relation.relation_type);
if self.inserted.contains(&key) {
return;
}
self.inserted.insert(key);
let forward = ClipRelation::new(
relation.from_id,
relation.to_id,
relation.relation_type.clone(),
);
let reverse = ClipRelation::new(relation.to_id, relation.from_id, relation.relation_type);
self.inner.add(forward);
self.inner.add(reverse);
}
#[must_use]
pub fn find_related(&self, clip_id: ClipId) -> Vec<ClipId> {
self.inner.find_related(clip_id)
}
#[must_use]
pub fn find_related_by_type(&self, clip_id: ClipId, rel_type: &RelationType) -> Vec<ClipId> {
self.inner.find_related_by_type(clip_id, rel_type)
}
#[must_use]
pub fn transitively_related(&self, clip_id: ClipId) -> Vec<ClipId> {
self.inner.transitively_related(clip_id)
}
#[must_use]
pub fn has_relation(&self, a: ClipId, b: ClipId) -> bool {
self.inner.has_direct_relation(a, b) || self.inner.has_direct_relation(b, a)
}
#[must_use]
pub fn canonical_edge_count(&self) -> usize {
self.inserted.len()
}
#[must_use]
pub fn directed_edge_count(&self) -> usize {
self.inner.edge_count()
}
}
fn canonical_key(a: ClipId, b: ClipId, rel: &RelationType) -> (ClipId, ClipId, String) {
let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
(lo, hi, rel.label().to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_creates_both_directions() {
let mut g = BidirectionalRelationGraph::new();
g.add(ClipRelation::new(1, 2, RelationType::Duplicate));
assert!(g.has_relation(1, 2));
assert!(g.has_relation(2, 1));
}
#[test]
fn test_find_related_from_both_endpoints() {
let mut g = BidirectionalRelationGraph::new();
g.add(ClipRelation::new(10, 20, RelationType::Alternate));
let from_10 = g.find_related(10);
let from_20 = g.find_related(20);
assert!(from_10.contains(&20));
assert!(from_20.contains(&10));
}
#[test]
fn test_duplicate_add_is_idempotent() {
let mut g = BidirectionalRelationGraph::new();
g.add(ClipRelation::new(1, 2, RelationType::Subclip));
g.add(ClipRelation::new(1, 2, RelationType::Subclip)); g.add(ClipRelation::new(2, 1, RelationType::Subclip));
assert_eq!(g.canonical_edge_count(), 1);
assert_eq!(g.directed_edge_count(), 2);
}
#[test]
fn test_find_related_by_type_from_both_sides() {
let mut g = BidirectionalRelationGraph::new();
g.add(ClipRelation::new(5, 6, RelationType::Version));
let versions_from_5 = g.find_related_by_type(5, &RelationType::Version);
let versions_from_6 = g.find_related_by_type(6, &RelationType::Version);
assert!(versions_from_5.contains(&6));
assert!(versions_from_6.contains(&5));
}
#[test]
fn test_transitively_related_bidirectional_chain() {
let mut g = BidirectionalRelationGraph::new();
g.add(ClipRelation::new(1, 2, RelationType::Version));
g.add(ClipRelation::new(2, 3, RelationType::Version));
let mut tr = g.transitively_related(1);
tr.sort_unstable();
assert_eq!(tr, vec![2, 3]);
}
#[test]
fn test_canonical_edge_count_multiple_types() {
let mut g = BidirectionalRelationGraph::new();
g.add(ClipRelation::new(1, 2, RelationType::Duplicate));
g.add(ClipRelation::new(1, 2, RelationType::Alternate));
assert_eq!(g.canonical_edge_count(), 2);
assert_eq!(g.directed_edge_count(), 4);
}
#[test]
fn test_empty_graph_has_no_relations() {
let g = BidirectionalRelationGraph::new();
assert!(!g.has_relation(1, 2));
assert!(g.find_related(1).is_empty());
}
#[test]
fn test_multiple_clips_connected_to_one() {
let mut g = BidirectionalRelationGraph::new();
g.add(ClipRelation::new(1, 2, RelationType::Subclip));
g.add(ClipRelation::new(1, 3, RelationType::Subclip));
g.add(ClipRelation::new(1, 4, RelationType::Subclip));
let related = g.find_related(1);
assert_eq!(related.len(), 3);
assert!(g.find_related(2).contains(&1));
assert!(g.find_related(3).contains(&1));
assert!(g.find_related(4).contains(&1));
}
}