use bevy::{ecs::system::SystemParam, prelude::*};
use super::nodes::MinimapNode;
#[derive(SystemParam)]
pub(super) struct DrawingParam<'w, 's> {
    query: Query<'w, 's, &'static UiImage, With<MinimapNode>>,
    images: ResMut<'w, Assets<Image>>,
}
impl<'w, 's> DrawingParam<'w, 's> {
    pub(super) fn drawing(&mut self) -> Drawing {
        let image = self.images.get_mut(&self.query.single().texture).unwrap();
        let size = UVec2::new(
            image.texture_descriptor.size.width,
            image.texture_descriptor.size.height,
        );
        Drawing::new(size, image.data.as_mut_slice())
    }
}
pub(super) struct Drawing<'a> {
    size: UVec2,
    data: &'a mut [u8],
}
impl<'a> Drawing<'a> {
    fn new(size: UVec2, data: &'a mut [u8]) -> Self {
        Self { size, data }
    }
    pub(super) fn fill(&mut self, color: Color) {
        let bytes = color.as_rgba_u32().to_le_bytes();
        for offset in (0..self.data.len()).step_by(4) {
            self.data[offset..(4 + offset)].copy_from_slice(&bytes);
        }
    }
    pub(super) fn line(&mut self, start: Vec2, end: Vec2, color: Color) {
        panic_bounds("start", start);
        panic_bounds("end", end);
        let start = self.rel_pos_to_px(start);
        let end = self.rel_pos_to_px(end);
        self.line_px(start, end, color);
    }
    pub(super) fn rect(&mut self, center: Vec2, size: Vec2, color: Color) {
        panic_bounds("center", center);
        if size.cmple(Vec2::ZERO).any() {
            panic!("Both dimensions of size must be positive, got: {size:?}");
        }
        let center = self.rel_pos_to_px(center);
        let half_size = 0.5 * size * self.size.as_vec2();
        let half_size_rounded = half_size.round();
        let half_size_int = half_size_rounded.as_ivec2();
        let error = half_size - half_size_rounded;
        let correction = (2. * error).round().as_ivec2();
        let top_left = center - half_size_int + correction.min(IVec2::ZERO);
        let bottom_right = center + half_size_int + correction.max(IVec2::ZERO);
        let bottom_right = bottom_right.max(top_left + IVec2::ONE);
        let top_left = top_left
            .max(IVec2::ZERO)
            .as_uvec2()
            .min(self.size - UVec2::ONE);
        let bottom_right = bottom_right
            .max(IVec2::ZERO)
            .as_uvec2()
            .min(self.size - UVec2::ONE);
        self.rect_px(top_left, bottom_right, color);
    }
    fn line_px(&mut self, start: IVec2, end: IVec2, color: Color) {
        let bytes = Self::color_to_bytes(color);
        let mut x = start.x;
        let mut y = start.y;
        let dx = (end.x - x).abs();
        let dy = -(end.y - y).abs();
        let mut error = dx + dy;
        let sx = if start.x < end.x { 1 } else { -1 };
        let sy = if start.y < end.y { 1 } else { -1 };
        loop {
            self.set_pixel_bytes(x as u32, y as u32, bytes);
            if x == end.x && y == end.y {
                break;
            }
            let e2 = 2 * error;
            if e2 >= dy {
                if x == end.x {
                    break;
                }
                error += dy;
                x += sx;
            }
            if e2 <= dx {
                if y == end.y {
                    break;
                }
                error += dx;
                y += sy;
            }
        }
    }
    fn rect_px(&mut self, top_left: UVec2, bottom_right: UVec2, color: Color) {
        let bytes = Self::color_to_bytes(color);
        for y in top_left.y..bottom_right.y {
            for x in top_left.x..bottom_right.x {
                self.set_pixel_bytes(x, y, bytes);
            }
        }
    }
    fn rel_pos_to_px(&self, point: Vec2) -> IVec2 {
        (point * (self.size.as_ivec2() - IVec2::ONE).as_vec2())
            .round()
            .as_ivec2()
    }
    #[inline]
    fn set_pixel_bytes(&mut self, x: u32, y: u32, bytes: [u8; 4]) {
        let offset = 4 * (y * self.size.x + x) as usize;
        self.data[offset..(4 + offset)].copy_from_slice(&bytes);
    }
    #[inline]
    fn color_to_bytes(color: Color) -> [u8; 4] {
        color.as_rgba_u32().to_le_bytes()
    }
}
fn panic_bounds(name: &str, point: Vec2) {
    if point.cmplt(Vec2::ZERO).any() || point.cmpgt(Vec2::ONE).any() {
        panic!("Coordinates of `{name}` are outside of image bounds.");
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_fill() {
        let size = UVec2::new(2, 3);
        let mut data = [0u8; 4 * 2 * 3];
        let mut drawing = Drawing::new(size, data.as_mut_slice());
        drawing.fill(Color::rgb(0.5, 0.2, 0.1));
        assert_eq!(
            data,
            [
                127, 51, 25, 255, 127, 51, 25, 255, 127, 51, 25, 255, 127, 51, 25, 255, 127, 51, 25, 255, 127, 51, 25, 255, ]
        )
    }
    #[test]
    fn test_rect() {
        let size = UVec2::splat(5);
        let mut data = [0u8; 4 * 5 * 5];
        let mut drawing = Drawing::new(size, data.as_mut_slice());
        drawing.rect(
            Vec2::new(0.8, 0.5), Vec2::new(0.4, 0.4), Color::rgb(0.1, 0.2, 0.1),
        );
        assert_eq!(
            data,
            [
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 51, 25, 255, 25, 51, 25, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 51, 25, 255, 25, 51, 25, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]
        )
    }
}