use std::fmt::{Debug, Display};

use petgraph::{prelude::NodeIndex, prelude::*};

use crate::Node;

use super::nodes::standard_input::StandardInput;

#[derive(Debug)]
pub struct FilterGraph {
    pub dag: DiGraph<Node, ()>,
    source_graphic_idx: NodeIndex,
    source_alpha_idx: NodeIndex,
    background_image_idx: NodeIndex,
    background_alpha_idx: NodeIndex,
    fill_paint_idx: NodeIndex,
    stroke_paint_idx: NodeIndex,
}

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

#[derive(Debug, Clone, Copy)]
pub enum NodeInput {
    Standard(StandardInput),
    Idx(NodeIndex),
}

impl Display for NodeInput {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            NodeInput::Standard(s) => Debug::fmt(s, f),
            NodeInput::Idx(idx) => write!(f, "r{}", idx.index()),
        }
    }
}

impl From<StandardInput> for NodeInput {
    fn from(value: StandardInput) -> Self {
        Self::Standard(value)
    }
}

impl From<NodeIndex> for NodeInput {
    fn from(value: NodeIndex) -> Self {
        Self::Idx(value)
    }
}

impl FilterGraph {
    pub fn new() -> Self {
        let mut dag = DiGraph::new();

        let source_graphic_idx = dag.add_node(Node::StdInput(StandardInput::SourceGraphic));
        let source_alpha_idx = dag.add_node(Node::StdInput(StandardInput::SourceAlpha));
        let background_image_idx = dag.add_node(Node::StdInput(StandardInput::BackgroundImage));
        let background_alpha_idx = dag.add_node(Node::StdInput(StandardInput::BackgroundAlpha));
        let fill_paint_idx = dag.add_node(Node::StdInput(StandardInput::FillPaint));
        let stroke_paint_idx = dag.add_node(Node::StdInput(StandardInput::StrokePaint));

        Self {
            dag,
            source_graphic_idx,
            source_alpha_idx,
            background_image_idx,
            background_alpha_idx,
            fill_paint_idx,
            stroke_paint_idx,
        }
    }

    pub fn add_node(&mut self, node: Node) -> NodeIndex {
        self.dag.add_node(node)
    }

    fn resolve_input(&self, input: NodeInput) -> NodeIndex {
        match input {
            NodeInput::Standard(StandardInput::SourceGraphic) => self.source_graphic_idx,
            NodeInput::Standard(StandardInput::SourceAlpha) => self.source_alpha_idx,
            NodeInput::Standard(StandardInput::BackgroundImage) => self.background_image_idx,
            NodeInput::Standard(StandardInput::BackgroundAlpha) => self.background_alpha_idx,
            NodeInput::Standard(StandardInput::FillPaint) => self.fill_paint_idx,
            NodeInput::Standard(StandardInput::StrokePaint) => self.stroke_paint_idx,
            NodeInput::Idx(i) => i,
        }
    }

    #[allow(
        clippy::unwrap_used,
        reason = "we only operate on values we know exist, so unwrapping is safe"
    )]
    pub fn inputs(&self, node_idx: NodeIndex) -> Vec<NodeInput> {
        let mut inputs = self
            .dag
            .neighbors_directed(node_idx, Direction::Incoming)
            .map(|input_idx| (self.dag.find_edge(input_idx, node_idx).unwrap(), input_idx))
            .collect::<Vec<_>>();

        inputs.sort_by(|(a, _), (b, _)| a.cmp(b));

        inputs
            .into_iter()
            .map(
                |(_, input_idx)| match self.dag.node_weight(input_idx).unwrap() {
                    Node::StdInput(s) => NodeInput::Standard(*s),
                    Node::Primitive { .. } => NodeInput::Idx(input_idx),
                },
            )
            .collect()
    }

    pub fn outputs(&self, node_idx: NodeIndex) -> Vec<NodeIndex> {
        self.dag
            .neighbors_directed(node_idx, Direction::Outgoing)
            .collect()
    }

    pub fn source_graphic(&self) -> NodeIndex {
        self.source_graphic_idx
    }
    pub fn source_alpha(&self) -> NodeIndex {
        self.source_alpha_idx
    }
    pub fn background_image(&self) -> NodeIndex {
        self.background_image_idx
    }
    pub fn background_alpha(&self) -> NodeIndex {
        self.background_alpha_idx
    }
    pub fn fill_paint(&self) -> NodeIndex {
        self.fill_paint_idx
    }
    pub fn stroke_paint(&self) -> NodeIndex {
        self.stroke_paint_idx
    }
}

pub mod abstracted_inputs;

pub mod edge;
