use crate::{
    core::{
        algebra::{Point3, Vector2},
        color::Color,
        math::Rect,
        pool::Handle,
        visitor::prelude::*,
    },
    resource::texture::Texture,
    scene2d::{
        base::{Base, BaseBuilder},
        graph::Graph,
        node::Node,
    },
};
use std::ops::{Deref, DerefMut};

#[derive(Visit)]
pub struct Sprite {
    base: Base,
    texture: Option<Texture>,
    color: Color,
    size: f32,
}

impl Default for Sprite {
    fn default() -> Self {
        Self {
            base: Default::default(),
            texture: None,
            color: Default::default(),
            size: 16.0,
        }
    }
}

impl Deref for Sprite {
    type Target = Base;

    fn deref(&self) -> &Self::Target {
        &self.base
    }
}

impl DerefMut for Sprite {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.base
    }
}

impl Sprite {
    pub fn texture(&self) -> Option<&Texture> {
        self.texture.as_ref()
    }

    pub fn set_texture(&mut self, texture: Option<Texture>) {
        self.texture = texture;
    }

    pub fn color(&self) -> Color {
        self.color
    }

    pub fn set_color(&mut self, color: Color) {
        self.color = color;
    }

    pub fn size(&self) -> f32 {
        self.size
    }

    pub fn set_size(&mut self, size: f32) {
        self.size = size;
    }

    pub fn local_bounds(&self) -> Rect<f32> {
        Rect {
            position: self.local_transform().position(),
            size: Vector2::new(self.size, self.size),
        }
    }

    pub fn global_bounds(&self) -> Rect<f32> {
        let mut bounds = Rect::default();
        let local_top_left = self.local_transform().position();
        let local_bottom_right = local_top_left + Vector2::new(self.size, self.size);
        let global_top_left = self
            .global_transform()
            .transform_point(&Point3::new(local_top_left.x, local_top_left.y, 0.0))
            .coords;
        let global_bottom_right = self
            .global_transform()
            .transform_point(&Point3::new(
                local_bottom_right.x,
                local_bottom_right.y,
                0.0,
            ))
            .coords;
        bounds.push(global_top_left.xy());
        bounds.push(global_bottom_right.xy());
        bounds
    }

    pub fn raw_copy(&self) -> Self {
        Self {
            base: self.base.raw_copy(),
            texture: self.texture.clone(),
            color: self.color,
            size: self.size,
        }
    }
}

pub struct SpriteBuilder {
    base_builder: BaseBuilder,
    texture: Option<Texture>,
    color: Color,
    size: f32,
}

impl SpriteBuilder {
    pub fn new(base_builder: BaseBuilder) -> Self {
        Self {
            base_builder,
            texture: None,
            color: Color::WHITE,
            size: 16.0,
        }
    }

    pub fn with_texture(mut self, texture: Texture) -> Self {
        self.texture = Some(texture);
        self
    }

    pub fn with_color(mut self, color: Color) -> Self {
        self.color = color;
        self
    }

    pub fn with_size(mut self, size: f32) -> Self {
        self.size = size;
        self
    }

    pub fn build(self, graph: &mut Graph) -> Handle<Node> {
        graph.add_node(Node::Sprite(Sprite {
            base: self.base_builder.build_base(),
            texture: self.texture,
            color: self.color,
            size: self.size,
        }))
    }
}