use std::io::{BufRead, BufReader, BufWriter, LineWriter, Read, Write};

use itertools::Itertools;

use super::graph::{Graph, Node};

pub fn export_adjacency_list<W>(buffer: W, graph: &Graph) -> std::io::Result<()>
where
    W: Write,
{
    let mut writer = LineWriter::new(buffer);

    for label in 0..graph.len() {
        let mut nbr_iter = graph.get_neighbours(label).iter().copied();
        if let Some(first_nbr) = nbr_iter.next() {
            write!(writer, "{first_nbr}")?;
        }
        for nbr in nbr_iter {
            write!(writer, ",{nbr}")?;
        }
        writeln!(writer)?;
    }

    Ok(())
}

// Export the adjacency list with no duplicate neighbours
pub fn export_adjacency_list_simple<W>(buffer: W, graph: &Graph) -> std::io::Result<()>
where
    W: Write,
{
    let mut writer = LineWriter::new(buffer);

    for label in 0..graph.len() {
        let mut nbr_iter = graph.get_neighbours(label).iter().copied().unique();
        if let Some(first_nbr) = nbr_iter.next() {
            write!(writer, "{first_nbr}")?;
        }
        for nbr in nbr_iter {
            write!(writer, ",{nbr}")?;
        }
        writeln!(writer)?;
    }

    Ok(())
}

pub fn export_compact_adjacency_list<W>(buffer: W, graph: &Graph) -> std::io::Result<()>
where
    W: Write,
{
    let mut writer = BufWriter::new(buffer);

    let graph: super::compressed_graph::Graph = graph.into();

    // Write indices
    write!(writer, "{}", 0)?;
    for index in graph.get_raw_node_index() {
        write!(writer, ",{}", index)?;
    }
    writeln!(writer)?;

    // Write neighbour labels
    let mut neighbours_iter = graph.get_raw_neighbours().iter();
    if let Some(first_nbr) = neighbours_iter.next() {
        write!(writer, "{}", first_nbr)?;
    }
    for nbr in neighbours_iter {
        write!(writer, ",{}", nbr)?;
    }
    writeln!(writer)?;

    Ok(())
}

pub fn import_compact_adjacency_list<R>(buffer: R) -> std::io::Result<Graph>
where
    R: Read,
{
    let reader = BufReader::new(buffer);

    let mut lines = reader.lines();
    let node_index_line = lines.next().ok_or(std::io::Error::new(
        std::io::ErrorKind::InvalidData,
        "Missing node_index line",
    ))??;
    let node_index = node_index_line
        .split(',')
        .map(|index| {
            index
                .trim()
                .parse()
                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
        })
        .collect::<Result<_, _>>()?;
    let neighbours_line = lines.next().ok_or(std::io::Error::new(
        std::io::ErrorKind::InvalidData,
        "Missing neighbours line",
    ))??;
    let neighbours = neighbours_line
        .split(',')
        .map(|index| {
            index
                .trim()
                .parse()
                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
        })
        .collect::<Result<_, _>>()?;

    let graph = super::compressed_graph::Graph::from_raw_parts(node_index, neighbours).ok_or(
        std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "The compressed graph format was not correct",
        ),
    )?;
    Ok(graph.into())
}

/// Does own buffering
pub fn import_adjacency_list<R>(buffer: R) -> std::io::Result<Graph>
where
    R: Read,
{
    let reader = BufReader::new(buffer);
    let mut nodes = Vec::new();
    for line in reader.lines() {
        let line = line?;
        let mut nbrs = Vec::with_capacity(10);
        for nbr in line.split(',').map(|nbr_str| nbr_str.trim().parse()) {
            nbrs.push(nbr.expect("Should be able to parse any entry in adjacency list."));
        }
        nodes.push(Node::new(nbrs));
    }

    Ok(Graph::new(nodes))
}
