use sge_api::shapes_2d::{draw_dashed_line, draw_quad};

use super::*;

#[derive(Clone, Copy, Debug)]
pub struct BorderStyle {
    pub thickness: f32,
    pub color: Color,
    pub ty: BorderType,
}

#[derive(Clone, Copy, Debug)]
pub enum BorderType {
    Solid,
    Dashed(f32),
    Dotted,
    Rectangle,
}

impl BorderStyle {}

impl Default for BorderStyle {
    fn default() -> Self {
        Self::none()
    }
}

#[derive(Debug)]
pub enum Side {
    Top,
    Bottom,
    Left,
    Right,
}

impl BorderStyle {
    pub fn new(thickness: f32, color: Color) -> Self {
        Self {
            thickness,
            color,
            ty: BorderType::Solid,
        }
    }

    pub const fn none() -> Self {
        Self {
            thickness: 0.0,
            color: Color::TRANSPARENT,
            ty: BorderType::Solid,
        }
    }

    pub fn custom(thickness: f32, color: Color, ty: BorderType) -> Self {
        Self {
            thickness,
            color,
            ty,
        }
    }

    pub fn with_type(mut self, ty: BorderType) -> Self {
        self.ty = ty;
        self
    }

    pub fn draw(&self, a: Vec2, b: Vec2, side: Side) {
        match self.ty {
            BorderType::Solid => {
                let ((x_a, y_a), (x_b, y_b)) = match side {
                    Side::Top => ((1, 1), (-1, 1)),
                    Side::Left => ((1, 1), (1, -1)),
                    Side::Bottom => ((1, -1), (-1, -1)),
                    Side::Right => ((-1, 1), (-1, -1)),
                };

                let mul_a = Vec2::new(x_a as f32, y_a as f32);
                let mul_b = Vec2::new(x_b as f32, y_b as f32);

                let c = a + (self.thickness * mul_a);
                let d = b + (self.thickness * mul_b);

                draw_quad([a, c, d, b], self.color);
            }
            BorderType::Rectangle => {
                let (point_a, point_b) = match side {
                    Side::Top => (
                        a + Vec2::new(0.0, self.thickness),
                        b + Vec2::new(0.0, self.thickness),
                    ),
                    Side::Left => (
                        a + Vec2::new(self.thickness, 0.0),
                        b + Vec2::new(self.thickness, 0.0),
                    ),
                    Side::Bottom => (
                        a - Vec2::new(0.0, self.thickness),
                        b - Vec2::new(0.0, self.thickness),
                    ),
                    Side::Right => (
                        a - Vec2::new(self.thickness, 0.0),
                        b - Vec2::new(self.thickness, 0.0),
                    ),
                };

                draw_quad([a, point_a, point_b, b], self.color);
            }
            BorderType::Dashed(_) | BorderType::Dotted => {
                let half_thickness = self.thickness / 2.0;
                let (point_a, point_b) = match side {
                    Side::Top => (
                        a + Vec2::new(0.0, half_thickness),
                        b + Vec2::new(0.0, half_thickness),
                    ),
                    Side::Left => (
                        a + Vec2::new(half_thickness, 0.0),
                        b + Vec2::new(half_thickness, 0.0),
                    ),
                    Side::Bottom => (
                        a - Vec2::new(0.0, half_thickness),
                        b - Vec2::new(0.0, half_thickness),
                    ),
                    Side::Right => (
                        a - Vec2::new(half_thickness, 0.0),
                        b - Vec2::new(half_thickness, 0.0),
                    ),
                };

                let len = match self.ty {
                    BorderType::Dashed(len) => len,
                    BorderType::Dotted => self.thickness,
                    _ => unreachable!(),
                };

                draw_dashed_line(point_a, point_b, self.thickness, self.color, len);
            }
        }
    }

    pub const NONE: Self = Self::none();
}

#[derive(Debug)]
pub struct Border {
    top: BorderStyle,
    bottom: BorderStyle,
    left: BorderStyle,
    right: BorderStyle,

    child: Child,
}

impl Border {
    pub fn tblr(
        top: BorderStyle,
        bottom: BorderStyle,
        left: BorderStyle,
        right: BorderStyle,
        child: Child,
    ) -> UiRef {
        Self {
            top,
            bottom,
            left,
            right,
            child,
        }
        .to_ref()
    }

    pub fn top_bottom(top: BorderStyle, bottom: BorderStyle, child: Child) -> UiRef {
        Self {
            top,
            bottom,
            left: BorderStyle::NONE,
            right: BorderStyle::NONE,
            child,
        }
        .to_ref()
    }

    pub fn left_right(left: BorderStyle, right: BorderStyle, child: Child) -> UiRef {
        Self {
            top: BorderStyle::NONE,
            bottom: BorderStyle::NONE,
            left,
            right,
            child,
        }
        .to_ref()
    }

    pub fn top(style: BorderStyle, child: Child) -> UiRef {
        Self {
            top: style,
            bottom: BorderStyle::NONE,
            left: BorderStyle::NONE,
            right: BorderStyle::NONE,
            child,
        }
        .to_ref()
    }

    pub fn bottom(style: BorderStyle, child: Child) -> UiRef {
        Self {
            top: BorderStyle::NONE,
            bottom: style,
            left: BorderStyle::NONE,
            right: BorderStyle::NONE,
            child,
        }
        .to_ref()
    }

    pub fn left(style: BorderStyle, child: Child) -> UiRef {
        Self {
            top: BorderStyle::NONE,
            bottom: BorderStyle::NONE,
            left: style,
            right: BorderStyle::NONE,
            child,
        }
        .to_ref()
    }

    pub fn right(style: BorderStyle, child: Child) -> UiRef {
        Self {
            top: BorderStyle::NONE,
            bottom: BorderStyle::NONE,
            left: BorderStyle::NONE,
            right: style,
            child,
        }
        .to_ref()
    }

    pub fn all_style(style: BorderStyle, child: Child) -> UiRef {
        Self {
            top: style,
            bottom: style,
            left: style,
            right: style,
            child,
        }
        .to_ref()
    }

    pub fn all(thickness: f32, color: Color, child: Child) -> UiRef {
        let style = BorderStyle {
            thickness,
            color,
            ty: BorderType::Solid,
        };
        Self {
            top: style,
            bottom: style,
            left: style,
            right: style,
            child,
        }
        .to_ref()
    }

    fn extra_size(&self) -> Vec2 {
        Vec2::new(
            self.left.thickness + self.right.thickness,
            self.top.thickness + self.bottom.thickness,
        )
    }

    fn offset(&self) -> Vec2 {
        Vec2::new(self.left.thickness, self.top.thickness)
    }
}

impl UiNode for Border {
    fn preferred_dimensions(&self) -> Vec2 {
        self.child.node.preferred_dimensions() + self.extra_size()
    }

    fn size(&self, area: Area) -> Vec2 {
        (self.child.node.size(area) + self.extra_size()).min(area.size)
    }

    fn draw(&self, mut area: Area, state: &UiState) -> Vec2 {
        self.top.draw(area.top_left(), area.top_right(), Side::Top);
        self.left
            .draw(area.top_left(), area.bottom_left(), Side::Left);
        self.bottom
            .draw(area.bottom_left(), area.bottom_right(), Side::Bottom);
        self.right
            .draw(area.top_right(), area.bottom_right(), Side::Right);

        area.size -= self.extra_size();
        area.top_left += self.offset();

        let dimensions = self.child.node.draw(area, state);
        dimensions + self.extra_size()
    }
}

impl UiRef {
    pub fn border(self, color: Color, thickness: f32) -> Self {
        Border::all(thickness, color, self)
    }
}

#[derive(Debug)]
pub struct Outline {
    top: BorderStyle,
    bottom: BorderStyle,
    left: BorderStyle,
    right: BorderStyle,

    child: Child,
}

impl Outline {
    pub fn tblr(
        top: BorderStyle,
        bottom: BorderStyle,
        left: BorderStyle,
        right: BorderStyle,
        child: Child,
    ) -> UiRef {
        Self {
            top,
            bottom,
            left,
            right,
            child,
        }
        .to_ref()
    }

    pub fn top_bottom(top: BorderStyle, bottom: BorderStyle, child: Child) -> UiRef {
        Self {
            top,
            bottom,
            left: BorderStyle::NONE,
            right: BorderStyle::NONE,
            child,
        }
        .to_ref()
    }

    pub fn left_right(left: BorderStyle, right: BorderStyle, child: Child) -> UiRef {
        Self {
            top: BorderStyle::NONE,
            bottom: BorderStyle::NONE,
            left,
            right,
            child,
        }
        .to_ref()
    }

    pub fn top(style: BorderStyle, child: Child) -> UiRef {
        Self {
            top: style,
            bottom: BorderStyle::NONE,
            left: BorderStyle::NONE,
            right: BorderStyle::NONE,
            child,
        }
        .to_ref()
    }

    pub fn bottom(style: BorderStyle, child: Child) -> UiRef {
        Self {
            top: BorderStyle::NONE,
            bottom: style,
            left: BorderStyle::NONE,
            right: BorderStyle::NONE,
            child,
        }
        .to_ref()
    }

    pub fn left(style: BorderStyle, child: Child) -> UiRef {
        Self {
            top: BorderStyle::NONE,
            bottom: BorderStyle::NONE,
            left: style,
            right: BorderStyle::NONE,
            child,
        }
        .to_ref()
    }

    pub fn right(style: BorderStyle, child: Child) -> UiRef {
        Self {
            top: BorderStyle::NONE,
            bottom: BorderStyle::NONE,
            left: BorderStyle::NONE,
            right: style,
            child,
        }
        .to_ref()
    }

    pub fn all_style(style: BorderStyle, child: Child) -> UiRef {
        Self {
            top: style,
            bottom: style,
            left: style,
            right: style,
            child,
        }
        .to_ref()
    }

    pub fn all(thickness: f32, color: Color, child: Child) -> UiRef {
        let style = BorderStyle {
            thickness,
            color,
            ty: BorderType::Solid,
        };
        Self {
            top: style,
            bottom: style,
            left: style,
            right: style,
            child,
        }
        .to_ref()
    }
}

impl UiNode for Outline {
    fn preferred_dimensions(&self) -> Vec2 {
        self.child.node.preferred_dimensions()
    }

    fn size(&self, area: Area) -> Vec2 {
        self.child.node.size(area)
    }

    fn draw(&self, area: Area, state: &UiState) -> Vec2 {
        let dimensions = self.child.node.draw(area, state);

        self.top.draw(area.top_left(), area.top_right(), Side::Top);
        self.left
            .draw(area.top_left(), area.bottom_left(), Side::Left);
        self.bottom
            .draw(area.bottom_left(), area.bottom_right(), Side::Bottom);
        self.right
            .draw(area.top_right(), area.bottom_right(), Side::Right);

        dimensions
    }
}

impl UiRef {
    pub fn outline(self, color: Color, thickness: f32) -> Self {
        Outline::all(thickness, color, self)
    }
}
