use std::{
    cmp,
    collections::{BTreeSet, HashMap},
    fmt::Display,
    io::Read,
    ops::Not,
};

use indexmap::IndexMap;
use petgraph::{
    algo::toposort,
    graph::DiGraph,
    prelude::{EdgeIndex, NodeIndex},
};
use quick_xml::ElementWriter;

use crate::{
    types::{
        graph::{edge::Edge, FilterGraph, NodeInput},
        nodes::{primitives::WriteElement, CommonAttrs},
    },
    Node,
};

use self::error::CodegenError;

pub struct SvgDocument {
    filters: HashMap<String, FilterGraph>,
}

impl SvgDocument {
    pub fn new() -> Self {
        Self {
            filters: HashMap::new(),
        }
    }

    #[allow(clippy::unwrap_used, reason = "we literally just did the insertion")]
    pub fn create_filter(&mut self, id: impl ToString) -> &mut FilterGraph {
        let filter = FilterGraph::new();

        self.filters.insert(id.to_string(), filter);
        self.filters.get_mut(&id.to_string()).unwrap()
    }

    pub fn generate_svg_pretty(&self) -> String {
        let mut result = Vec::new();
        let doc_writer = quick_xml::Writer::new_with_indent(&mut result, b' ', 2);

        self.generate(doc_writer);

        String::from_utf8_lossy(&result).to_string()
    }

    pub fn generate_svg(&self) -> String {
        let mut result = Vec::new();
        let doc_writer = quick_xml::Writer::new(&mut result);

        self.generate(doc_writer);

        String::from_utf8_lossy(&result).to_string()
    }

    fn generate(&self, mut doc_writer: quick_xml::Writer<&mut Vec<u8>>) {
        doc_writer
            .create_element("svg")
            .write_inner_content(|writer| {
                self.filters
                    .iter()
                    .try_fold(writer, Self::gen_filter)
                    .map(|_| {})
            });
    }

    fn gen_filter<'w, 'r>(
        writer: &'w mut quick_xml::Writer<&'r mut Vec<u8>>,
        (id, graph): (&String, &FilterGraph),
    ) -> Result<&'w mut quick_xml::Writer<&'r mut Vec<u8>>, CodegenError> {
        writer
            .create_element("filter")
            .with_attribute(("id", id.as_str()))
            .write_inner_content(|writer| Self::graph_to_svg(writer, graph))
    }

    fn graph_to_svg(
        writer: &mut quick_xml::Writer<&mut Vec<u8>>,
        graph: &FilterGraph,
    ) -> Result<(), CodegenError> {
        let sorted = toposort(&graph.dag, None).expect("no cycles allowed in a DAG");
        sorted
            .into_iter()
            .filter_map(|node_idx| {
                graph
                    .dag
                    .node_weight(node_idx)
                    .and_then(|node| node.primitive())
                    .map(|(primitive, common_attrs)| (node_idx, primitive, common_attrs))
            })
            .try_fold(writer, |writer, (node_idx, primitive, common_attrs)| {
                primitive.element_writer(
                    writer,
                    *common_attrs,
                    graph
                        .inputs(node_idx)
                        .into_iter()
                        .map(|v| v.to_string())
                        .collect(),
                    graph
                        .outputs(node_idx)
                        .is_empty()
                        .not()
                        .then_some(format!("r{}", node_idx.index())),
                )
            })?;

        Ok(())
    }
}

/// convenience method to avoid fuckups during future changes
fn format_edge_idx(idx: EdgeIndex) -> String {
    format!("edge{}", idx.index())
}

fn format_node_idx(node_idx: NodeIndex) -> String {
    format!("r{}", node_idx.index())
}

mod error {
    use std::{error::Error, fmt::Display};

    #[derive(Debug)]
    pub enum CodegenError {
        QuickXmlError(quick_xml::Error),
    }

    impl From<quick_xml::Error> for CodegenError {
        fn from(value: quick_xml::Error) -> Self {
            Self::QuickXmlError(value)
        }
    }

    impl Display for CodegenError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                CodegenError::QuickXmlError(e) => e.fmt(f),
            }
        }
    }

    impl Error for CodegenError {}
}

impl Default for SvgDocument {
    fn default() -> Self {
        Self::new()
    }
}
