use std::collections::HashMap;

use crate::components::dissolve::DissolveComponent;
use crate::components::follow::FollowComponent;
use crate::components::jump::JumpComponent;
use crate::components::lights::spot::SpotlightComponent;
use crate::components::{
    CameraComponent, InputComponent, MeshComponent, MovementComponent, PhysicsComponent,
    RotateComponent,
};
use crate::entity::{EntityHandle, EntityManager};
use crate::state::StateMachine;

pub use crate::utility::transform::Transform;

pub struct Storage<T>
{
    pub components: HashMap<EntityHandle, T>,
}

impl<T> Storage<T>
{
    pub fn new() -> Self
    {
        Self {
            components: HashMap::new(),
        }
    }

    pub fn insert(&mut self, entity: EntityHandle, component: T)
    {
        self.components.insert(entity, component);
    }

    pub fn get(&self, entity: EntityHandle) -> Option<&T>
    {
        self.components.get(&entity)
    }

    pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut T>
    {
        self.components.get_mut(&entity)
    }

    pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
    where
        F: FnOnce(&T) -> R,
    {
        self.components.get(&entity).map(f)
    }

    pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
    where
        F: FnOnce(&mut T) -> R,
    {
        self.components.get_mut(&entity).map(f)
    }

    pub fn remove(&mut self, entity: EntityHandle)
    {
        self.components.remove(&entity);
    }

    pub fn all(&self) -> Vec<EntityHandle>
    {
        self.components.keys().copied().collect()
    }
}

pub struct World
{
    pub entities: EntityManager,
    pub transforms: Storage<Transform>,
    pub meshes: Storage<MeshComponent>,
    pub physics: Storage<PhysicsComponent>,
    pub movements: Storage<MovementComponent>,
    pub jumps: Storage<JumpComponent>,
    pub inputs: Storage<InputComponent>,
    pub player_tags: Storage<()>,
    pub state_machines: Storage<StateMachine>,
    pub cameras: Storage<CameraComponent>,
    pub spotlights: Storage<SpotlightComponent>,
    pub tree_tags: Storage<()>,
    pub dissolves: Storage<DissolveComponent>,
    pub follows: Storage<FollowComponent>,
    pub rotates: Storage<RotateComponent>,
}

impl World
{
    pub fn new() -> Self
    {
        Self {
            entities: EntityManager::new(),
            transforms: Storage::new(),
            meshes: Storage::new(),
            physics: Storage::new(),
            movements: Storage::new(),
            jumps: Storage::new(),
            inputs: Storage::new(),
            player_tags: Storage::new(),
            state_machines: Storage::new(),
            cameras: Storage::new(),
            spotlights: Storage::new(),
            tree_tags: Storage::new(),
            dissolves: Storage::new(),
            follows: Storage::new(),
            rotates: Storage::new(),
        }
    }

    pub fn spawn(&mut self) -> EntityHandle
    {
        self.entities.spawn()
    }

    pub fn despawn(&mut self, entity: EntityHandle)
    {
        self.transforms.remove(entity);
        self.meshes.remove(entity);
        self.physics.remove(entity);
        self.movements.remove(entity);
        self.jumps.remove(entity);
        self.inputs.remove(entity);
        self.player_tags.remove(entity);
        self.state_machines.remove(entity);
        self.cameras.remove(entity);
        self.spotlights.remove(entity);
        self.tree_tags.remove(entity);
        self.dissolves.remove(entity);
        self.follows.remove(entity);
        self.rotates.remove(entity);
        self.entities.despawn(entity);
    }

    pub fn active_camera(&self) -> Option<(EntityHandle, &CameraComponent)>
    {
        self.cameras
            .components
            .iter()
            .find(|(_, cam)| cam.is_active)
            .map(|(e, c)| (*e, c))
    }

    pub fn active_camera_position(&self) -> glam::Vec3
    {
        self.active_camera()
            .and_then(|(entity, _)| self.transforms.get(entity))
            .map(|t| t.position)
            .unwrap_or(glam::Vec3::ZERO)
    }

    pub fn player_position(&self) -> glam::Vec3
    {
        self.player_tags
            .all()
            .first()
            .and_then(|e| self.transforms.get(*e))
            .map(|t| t.position)
            .unwrap_or(glam::Vec3::ZERO)
    }
}
