mod common;
pub mod graph;
mod utils;

use std::cmp::min;
use std::env;

use crate::common::{Distance, Node, NodeSet, Result};

// Only works with complete graphs (where each node leads to each other node)
fn visit_all_shortest(start_node: &Node) -> Vec<(Node, Distance)> {
    let mut visited = NodeSet::new();
    let mut path = Vec::new();
    let mut current_node: Node = start_node.clone();

    'search: loop {
        let mut edge: Option<(Node, Distance)> = None;

        {
            let node = current_node.borrow();
            for (out_node, out_weight) in &node.outgoing {
                if visited.contains(&out_node) {
                    continue;
                }

                edge = Some(match edge {
                    None => (out_node.clone(), *out_weight),
                    Some((n, w)) => {
                        if out_weight < &w {
                            (out_node.clone(), *out_weight)
                        } else {
                            (n, w)
                        }
                    }
                });
            }
        }

        match edge {
            None => break 'search,
            Some((next_node, weight)) => {
                visited.insert(current_node.clone());
                path.push((next_node.clone(), weight));
                current_node = next_node;
            }
        }
    }

    path
}

fn main() -> Result<()> {
    let path = env::args().nth(1).unwrap();
    let directions = common::parse_file(path)?;

    let mut shortest_distance = None;

    for node in &directions.nodes {
        let path = visit_all_shortest(node);

        if path.is_empty() {
            continue;
        }

        let distance: Distance = path.iter().map(|p| p.1).sum();
        shortest_distance = match shortest_distance {
            None => Some(distance),
            Some(d) => Some(min(distance, d)),
        };
    }

    println!("{}", shortest_distance.unwrap_or_default());

    Ok(())
}