use crate::{
    core::{pool::Handle, visitor::prelude::*},
    scene2d::{
        graph::Graph,
        light::{BaseLight, BaseLightBuilder, Light},
        node::Node,
    },
};
use std::ops::{Deref, DerefMut};

#[derive(Visit)]
pub struct SpotLight {
    base_light: BaseLight,
    radius: f32,
    hotspot_angle: f32,
    falloff_angle_delta: f32,
}

impl SpotLight {
    pub fn radius(&self) -> f32 {
        self.radius
    }

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

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

    pub fn half_full_cone_angle_cos(&self) -> f32 {
        ((self.falloff_angle_delta + self.hotspot_angle) * 0.5).cos()
    }

    pub fn half_hotspot_cone_angle(&self) -> f32 {
        (self.hotspot_angle * 0.5).cos()
    }

    pub fn raw_copy(&self) -> Self {
        Self {
            base_light: self.base_light.raw_copy(),
            radius: self.radius,
            hotspot_angle: self.hotspot_angle,
            falloff_angle_delta: self.falloff_angle_delta,
        }
    }
}

impl Deref for SpotLight {
    type Target = BaseLight;

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

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

impl Default for SpotLight {
    fn default() -> Self {
        Self {
            base_light: Default::default(),
            radius: 10.0,
            hotspot_angle: 90.0f32.to_radians(),
            falloff_angle_delta: 10.0f32.to_radians(),
        }
    }
}

pub struct SpotLightBuilder {
    base_light_builder: BaseLightBuilder,
    radius: f32,
    hotspot: f32,
    delta: f32,
}

impl SpotLightBuilder {
    pub fn new(base_light_builder: BaseLightBuilder) -> Self {
        Self {
            base_light_builder,
            radius: 10.0,
            hotspot: 90.0f32.to_radians(),
            delta: 5.0f32.to_radians(),
        }
    }

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

    pub fn with_hotspot_angle(mut self, angle: f32) -> Self {
        self.hotspot = angle;
        self
    }

    pub fn with_angle_delta(mut self, delta: f32) -> Self {
        self.delta = delta.abs();
        self
    }

    pub fn build(self, graph: &mut Graph) -> Handle<Node> {
        graph.add_node(Node::Light(Light::Spot(SpotLight {
            base_light: self.base_light_builder.build(),
            radius: self.radius,
            hotspot_angle: self.hotspot,
            falloff_angle_delta: self.delta,
        })))
    }
}