use clap::{Parser, ValueEnum};
use std::{
    collections::HashSet,
    path::{Path, PathBuf},
};

mod filtered;
mod owned;

fn parse(filename: impl AsRef<Path>) -> Graph {
    let content = std::fs::read_to_string(filename).unwrap();

    let mut line_iter = content
        .split('\n')
        .map(|l| l.trim())
        .filter(|l| !l.is_empty());

    let first_line = line_iter.next().unwrap();

    let (nodes_str, edges_str) = first_line.split_once(' ').unwrap();

    let nodes = nodes_str.parse::<u16>().unwrap();
    let edges = edges_str.parse::<u16>().unwrap();

    let mut g = Graph::new(nodes);

    let mut c = 0;
    for l in line_iter {
        let (from_str, to_str) = l.split_once(' ').unwrap();
        let from = from_str.parse::<u16>().unwrap();
        let to = to_str.parse::<u16>().unwrap();

        g.add_edge(from - 1, to - 1);
        c += 1;
    }
    assert_eq!(edges, c);

    g
}

/// Datastructure is 0 indexed
#[derive(Debug)]
struct Graph {
    edge_list: Vec<Vec<u16>>,
}

impl Graph {
    fn new(num_vertices: u16) -> Self {
        Self {
            edge_list: vec![Vec::new(); num_vertices as usize],
        }
    }

    fn num_vertices(&self) -> u16 {
        self.edge_list.len() as u16
    }

    fn add_edge(&mut self, from: u16, to: u16) {
        self.edge_list[(from) as usize].push(to);
        self.edge_list[(to) as usize].push(from);
    }

    fn get_edges(&self, vertex: u16) -> impl Iterator<Item = u16> + '_ {
        self.edge_list[vertex as usize].iter().copied()
    }

    fn iter_edges_filtered<'a>(
        &'a self,
        v: &'a HashSet<u16>,
    ) -> impl Iterator<Item = (u16, u16)> + 'a {
        self.edge_list
            .iter()
            .enumerate()
            .filter(|(i, _)| !v.contains(&(*i as u16)))
            .flat_map(move |(i, s)| {
                let from = i as u16;
                s.iter().filter_map(move |&to| {
                    if !v.contains(&to) && from < to {
                        Some((from, to))
                    } else {
                        None
                    }
                })
            })
    }

    fn get_edge_filtered(&self, v: &HashSet<u16>) -> Option<(u16, u16)> {
        self.iter_edges_filtered(v).next()
    }

    fn iter_vertices_filtered_with_degree_grater<'a>(
        &'a self,
        v: &'a HashSet<u16>,
        min_degree: u16,
    ) -> impl Iterator<Item = u16> + 'a {
        self.edge_list.iter().enumerate().filter_map(move |(i, s)| {
            let i = i as u16;
            if !v.contains(&i) && s.iter().filter(|&o| !v.contains(o)).count() as u16 >= min_degree
            {
                Some(i)
            } else {
                None
            }
        })
    }

    fn get_vertex_filtered_with_degree_greater(
        &self,
        v: &HashSet<u16>,
        min_degree: u16,
    ) -> Option<u16> {
        self.iter_vertices_filtered_with_degree_grater(v, min_degree)
            .next()
    }
}

fn vertex_cover(graph: &Graph, k: u16, method: Method) -> Option<Vec<u16>> {
    match method {
        Method::Edge => {
            let r = filtered::binary_edge(graph, k, HashSet::new());

            r.map(|mut r| r.drain().collect())
        }
        Method::Vertex => {
            let r = filtered::binary_vertex(graph, k, HashSet::new());
            r.map(|mut r| r.drain().collect())
        }
        Method::OwnedVertex => {
            let mut g = owned::Graph::new(graph);

            owned::binary_vertex(&mut g, k)
        }
        Method::Reduction => {
            let mut g = owned::Graph::new(graph);

            owned::reduction(&mut g, k)
        }
        Method::Reduction2 => {
            let mut g = owned::Graph::new(graph);

            owned::reduction2(&mut g, k)
        }
    }
}

#[derive(Debug, Clone, Copy, ValueEnum)]
enum Method {
    Edge,
    Vertex,
    OwnedVertex,
    Reduction,
    Reduction2,
}

#[derive(Debug, Parser)]
struct Args {
    filename: PathBuf,
    #[clap(default_value = "reduction2")]
    method: Method,
    k: Option<u16>,
    #[clap(short, long)]
    reverse: bool,
}

fn main() {
    let total_start = std::time::Instant::now();
    let args = Args::parse();

    let graph = parse(args.filename);

    let result = if let Some(k) = args.k {
        let start = std::time::Instant::now();
        let r = vertex_cover(&graph, k, args.method);
        eprintln!("{}: {:?}", k, start.elapsed());
        r
    } else if args.reverse {
        let mut k = graph.num_vertices();
        let mut last_solution = None;
        loop {
            let start = std::time::Instant::now();
            let r = vertex_cover(&graph, k, args.method);
            eprintln!("{}: {:?}", k, start.elapsed());
            if let Some(r) = r {
                k = r.len() as u16 - 1;
                last_solution = Some(r);
            } else {
                break last_solution;
            };
        }
    } else {
        let mut k = 0;
        loop {
            let start = std::time::Instant::now();
            let r = vertex_cover(&graph, k, args.method);
            eprintln!("{}: {:?}", k, start.elapsed());
            if let Some(r) = r {
                break Some(r);
            }
            k += 1;
        }
    };

    if let Some(mut solution) = result {
        solution.sort();
        println!("{}", solution.len());

        for n in solution {
            println!("{}", n + 1);
        }
    }
    eprintln!("Total time: {:?}", total_start.elapsed());
}
