use anyhow::Result;
use engine::prelude::*;

use cgmath::Vector2;

pub enum CollisionEventType {
    Block,
    Ignore,
}

pub type HitCallback =
    dyn FnMut(&mut SceneContents<'_>, Entity, Entity) -> Result<CollisionEventType> + Send + Sync;

// HitBox is basically a cylinder
pub struct HitBox {
    pub radius: f32,
    height: f32,

    hit_event: Option<Box<HitCallback>>,
}

impl HitBox {
    pub fn from_bb(bounding_box: &BoundingBox) -> Self {
        Self {
            radius: Self::calculate_radius(bounding_box),
            height: bounding_box.max[2] - bounding_box.min[2],

            hit_event: None,
        }
    }

    pub fn new(radius: f32, height: f32) -> Self {
        Self {
            radius,
            height,

            hit_event: None,
        }
    }

    pub fn forced(bounding_box: &BoundingBox, radius: f32, height: f32) -> Self {
        Self {
            radius: if radius == 0.0 {
                Self::calculate_radius(bounding_box)
            } else {
                radius
            },
            height: if height == 0.0 {
                bounding_box.max[2] - bounding_box.min[2]
            } else {
                height
            },

            hit_event: None,
        }
    }

    pub fn calculate_radius(bounding_box: &BoundingBox) -> f32 {
        // seek the furthest point
        let mut radius = 0.0;

        let mut tmp = bounding_box.min[0].abs();

        if tmp > radius {
            radius = tmp;
        }

        tmp = bounding_box.min[1].abs();

        if tmp > radius {
            radius = tmp;
        }

        tmp = bounding_box.max[0].abs();

        if tmp > radius {
            radius = tmp;
        }

        tmp = bounding_box.max[0].abs();

        if tmp > radius {
            radius = tmp;
        }

        radius
    }

    pub fn set_event<F>(&mut self, f: F)
    where
        F: FnMut(&mut SceneContents<'_>, Entity, Entity) -> Result<CollisionEventType>
            + Send
            + Sync
            + 'static,
    {
        self.hit_event = Some(Box::new(f));
    }

    pub fn set_radius(&mut self, radius: f32) {
        self.radius = radius;
    }

    pub fn set_height(&mut self, height: f32) {
        self.height = height;
    }

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

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

    pub fn check_collision(
        &self,
        my_pos: Vector2<f32>,
        other_radius: f32,
        other_pos: Vector2<f32>,
    ) -> bool {
        let d = other_pos - my_pos;
        let radii = other_radius + self.radius;

        ((d.x * d.x) + (d.y * d.y)) <= (radii * radii)
    }

    pub fn collision(
        &mut self,
        scene: &mut SceneContents<'_>,
        me: Entity,
        collider: Entity,
    ) -> Result<CollisionEventType> {
        match &mut self.hit_event {
            Some(hit_event) => hit_event(scene, me, collider),
            None => Ok(CollisionEventType::Ignore),
        }
    }
}

impl EntityComponent for HitBox {
    fn name(&self) -> &str {
        Self::debug_name()
    }
}

impl ComponentDebug for HitBox {
    fn debug_name() -> &'static str {
        "HitBox"
    }
}

impl Clone for HitBox {
    fn clone(&self) -> Self {
        Self {
            radius: self.radius.clone(),
            height: self.height.clone(),
            hit_event: None,
        }
    }
}