use core::f32;
use std::sync::{
    Arc,
    atomic::{AtomicU32, Ordering},
};

use pumpkin_data::entity::EntityType;
use pumpkin_util::math::vector3::Vector3;

use crate::{entity::EntityBaseFuture, server::Server, world::World};

use super::{Entity, EntityBase, NBTStorage, living::LivingEntity, player::Player};

pub struct ExperienceOrbEntity {
    entity: Entity,
    amount: u32,
    orb_age: AtomicU32,
}

impl ExperienceOrbEntity {
    pub fn new(entity: Entity, amount: u32) -> Self {
        entity.yaw.store(rand::random::<f32>() * 360.0);
        Self {
            entity,
            amount,
            orb_age: AtomicU32::new(0),
        }
    }

    pub async fn spawn(world: &Arc<World>, position: Vector3<f64>, amount: u32) {
        let mut amount = amount;
        while amount > 0 {
            let i = Self::round_to_orb_size(amount);
            amount -= i;
            let entity = Entity::new(world.clone(), position, &EntityType::EXPERIENCE_ORB);
            let orb = Arc::new(Self::new(entity, i));
            world.spawn_entity(orb).await;
        }
    }

    const fn round_to_orb_size(value: u32) -> u32 {
        if value >= 2477 {
            2477
        } else if value >= 1237 {
            1237
        } else if value >= 617 {
            617
        } else if value >= 307 {
            307
        } else if value >= 149 {
            149
        } else if value >= 73 {
            73
        } else if value >= 37 {
            37
        } else if value >= 17 {
            17
        } else if value >= 7 {
            7
        } else if value >= 3 {
            3
        } else {
            1
        }
    }
}

impl NBTStorage for ExperienceOrbEntity {}

impl EntityBase for ExperienceOrbEntity {
    fn tick<'a>(
        &'a self,
        caller: &'a Arc<dyn EntityBase>,
        server: &'a Server,
    ) -> EntityBaseFuture<'a, ()> {
        Box::pin(async move {
            let entity = &self.entity;
            entity.tick(caller, server).await;
            let bounding_box = entity.bounding_box.load();

            let original_velo = entity.velocity.load();

            let mut velo = original_velo;

            let no_clip = !self
                .entity
                .world
                .load()
                .is_space_empty(bounding_box.expand(-1.0e-7, -1.0e-7, -1.0e-7));
            // TODO: isSubmergedIn
            if !no_clip {
                velo.y -= self.get_gravity();
            }

            entity.velocity.store(velo);

            entity.move_entity(caller, velo).await;

            entity.tick_block_collisions(caller, server).await;

            let age = self.orb_age.fetch_add(1, Ordering::Relaxed);
            if age >= 6000 {
                self.entity.remove().await;
            }
        })
    }

    fn get_entity(&self) -> &Entity {
        &self.entity
    }

    fn on_player_collision<'a>(&'a self, player: &'a Arc<Player>) -> EntityBaseFuture<'a, ()> {
        Box::pin(async move {
            if player.living_entity.health.load() > 0.0 {
                let mut delay = player.experience_pick_up_delay.lock().await;
                if *delay == 0 {
                    *delay = 2;
                    player.living_entity.pickup(&self.entity, 1);
                    let remaining = player.apply_mending_from_xp(self.amount as i32).await;
                    if remaining > 0 {
                        player.add_experience_points(remaining).await;
                    }
                    // TODO: pickingCount for merging
                    self.entity.remove().await;
                }
            }
        })
    }

    fn get_living_entity(&self) -> Option<&LivingEntity> {
        None
    }

    fn as_nbt_storage(&self) -> &dyn NBTStorage {
        self
    }

    fn get_gravity(&self) -> f64 {
        0.03
    }

    fn cast_any(&self) -> &dyn std::any::Any {
        self
    }
}
