use std::borrow::Cow;

use csscolorparser::Color;
use quick_xml::{events::attributes::Attribute, name::QName};

use self::{
    primitives::{
        blend::{Blend, BlendMode},
        color_matrix::{ColorMatrix, ColorMatrixType},
        component_transfer::{ComponentTransfer, TransferFn},
        composite::{Composite, CompositeOperator},
        displacement_map::{Channel, DisplacementMap},
        flood::Flood,
        gaussian_blur::GaussianBlur,
        offset::Offset,
        turbulence::{NoiseType, StitchTiles, Turbulence},
        FePrimitive,
    },
    standard_input::StandardInput,
};

use super::length::{Coordinate, Length};

pub mod primitives;
pub mod standard_input;

#[derive(Debug)]
pub enum Node {
    StdInput(StandardInput),
    Primitive {
        primitive: FePrimitive,
        common_attrs: CommonAttrs,
    },
}

impl Default for Node {
    fn default() -> Self {
        Self::StdInput(StandardInput::SourceGraphic)
    }
}

#[derive(Default, Debug, Clone, Copy)]
pub struct CommonAttrs {
    pub x: Coordinate,
    pub y: Coordinate,
    pub width: Length,
    pub height: Length,
}

impl From<CommonAttrs> for Vec<Attribute<'_>> {
    fn from(val: CommonAttrs) -> Self {
        gen_attrs![
            !val.x.is_zero() => b"x": val.x,
            !val.y.is_zero() => b"y": val.y,
            !val.width.is_zero() => b"width": val.width,
            !val.height.is_zero() => b"height": val.height
        ]
    }
}

impl Node {
    pub fn simple(el: FePrimitive) -> Node {
        Node::Primitive {
            primitive: el,
            common_attrs: CommonAttrs::default(),
        }
    }

    pub fn primitive(&self) -> Option<(&FePrimitive, &CommonAttrs)> {
        if let Node::Primitive {
            primitive,
            common_attrs,
        } = self
        {
            Some((primitive, common_attrs))
        } else {
            None
        }
    }

    pub fn input_count(&self) -> u8 {
        match self {
            Node::Primitive {
                primitive:
                    FePrimitive::ColorMatrix(_)
                    | FePrimitive::ComponentTransfer(_)
                    | FePrimitive::ConvolveMatrix(_)
                    | FePrimitive::DiffuseLighting(_)
                    | FePrimitive::GaussianBlur(_)
                    | FePrimitive::Morphology(_)
                    | FePrimitive::Offset(_)
                    | FePrimitive::SpecularLighting(_)
                    | FePrimitive::Tile(_),
                ..
            } => 1,

            Node::Primitive {
                primitive:
                    FePrimitive::Composite(_) | FePrimitive::Blend(_) | FePrimitive::DisplacementMap(_),
                ..
            } => 2,

            Node::StdInput(_)
            | Node::Primitive {
                primitive:
                    FePrimitive::Flood(_) | FePrimitive::Image(_) | FePrimitive::Turbulence(_),
                ..
            } => 0,
            Node::Primitive {
                primitive: FePrimitive::Merge(_),
                ..
            } => todo!(),
        }
    }

    pub fn blend(mode: BlendMode) -> Self {
        Self::simple(FePrimitive::Blend(Blend::new(mode)))
    }

    pub fn color_matrix(cm_type: ColorMatrixType) -> Self {
        Self::simple(FePrimitive::ColorMatrix(ColorMatrix::new(cm_type)))
    }

    pub fn composite(op: CompositeOperator) -> Self {
        Self::simple(FePrimitive::Composite(Composite::new(op)))
    }

    pub fn composite_arithmetic(k1: f32, k2: f32, k3: f32, k4: f32) -> Self {
        Self::composite(CompositeOperator::Arithmetic { k1, k2, k3, k4 })
    }

    pub fn gaussian_blur(v: u16) -> Self {
        Self::simple(FePrimitive::GaussianBlur(GaussianBlur::single(v)))
    }

    pub fn gaussian_blur_xy(x: u16, y: u16) -> Self {
        Self::simple(FePrimitive::GaussianBlur(GaussianBlur::with_xy(x, y)))
    }

    pub fn offset(dx: f32, dy: f32) -> Self {
        Self::simple(FePrimitive::Offset(Offset::new(dx, dy)))
    }

    pub fn component_transfer_rgba(
        r: TransferFn,
        g: TransferFn,
        b: TransferFn,
        a: TransferFn,
    ) -> Self {
        Self::simple(FePrimitive::ComponentTransfer(ComponentTransfer {
            func_r: r,
            func_g: g,
            func_b: b,
            func_a: a,
        }))
    }

    pub fn component_transfer_rgb(r: TransferFn, g: TransferFn, b: TransferFn) -> Self {
        Self::component_transfer_rgba(r, g, b, TransferFn::Identity)
    }

    pub fn component_transfer_r(func: TransferFn) -> Self {
        Self::component_transfer_rgba(
            func,
            TransferFn::Identity,
            TransferFn::Identity,
            TransferFn::Identity,
        )
    }

    pub fn component_transfer_g(func: TransferFn) -> Self {
        Self::component_transfer_rgba(
            TransferFn::Identity,
            func,
            TransferFn::Identity,
            TransferFn::Identity,
        )
    }

    pub fn component_transfer_b(func: TransferFn) -> Self {
        Self::component_transfer_rgba(
            TransferFn::Identity,
            TransferFn::Identity,
            func,
            TransferFn::Identity,
        )
    }

    pub fn component_transfer_a(func: TransferFn) -> Self {
        Self::component_transfer_rgba(
            TransferFn::Identity,
            TransferFn::Identity,
            TransferFn::Identity,
            func,
        )
    }

    pub fn component_transfer_single(func: TransferFn) -> Self {
        Self::component_transfer_rgb(func.clone(), func.clone(), func)
    }

    pub fn flood(flood_color: Color, flood_opacity: f32) -> Self {
        Self::simple(FePrimitive::Flood(Flood {
            flood_color,
            flood_opacity,
        }))
    }

    pub fn flood_opaque(flood_color: Color) -> Self {
        Self::flood(flood_color, 1.)
    }

    pub fn turbulence(
        base_freq_x: f32,
        base_freq_y: f32,
        num_octaves: u16,
        seed: u32,
        stitch_tiles: StitchTiles,
        noise_type: NoiseType,
    ) -> Self {
        Self::simple(FePrimitive::Turbulence(Turbulence {
            base_frequency: (base_freq_x, base_freq_y),
            num_octaves,
            seed,
            stitch_tiles,
            noise_type,
        }))
    }

    pub fn displacement_map(scale: f32, x_channel: Channel, y_channel: Channel) -> Self {
        Self::simple(FePrimitive::DisplacementMap(DisplacementMap {
            scale,
            x_channel_selector: x_channel,
            y_channel_selector: y_channel,
        }))
    }
}
