use std::{
    any::TypeId,
    collections::HashMap,
    time::{Duration, Instant},
};

use anyhow::{Result, bail};
use indexmap::IndexMap;
use utilities::prelude::{remove_life_time, remove_life_time_mut};

use crate::{entity_object_manager::EntityObjectManager, *};

pub struct WorldBuilder {
    pub(crate) updates: Updates,
    pub events: Events,
    pub resources: Resources,
    systems: Vec<Box<dyn Fn(&mut World) -> Result<bool> + Send + Sync + 'static>>,
}

impl WorldBuilder {
    pub fn add_system<F>(&mut self, f: F)
    where
        F: Fn(&mut World) -> Result<bool> + Send + Sync + 'static,
    {
        self.systems.push(Box::new(f));
    }
}

impl WorldBuilder {
    pub fn build(self) -> World {
        World {
            updates: self.updates,
            events: self.events,
            resources: self.resources,
            entities: Default::default(),

            entities_to_remove: Default::default(),
            entities_to_add: Default::default(),
            entities_updates: Default::default(),

            entity_object_manager: Default::default(),

            start_time: Instant::now(),

            systems: self.systems,
        }
    }
}

enum ComponentChange {
    Added(TypeId, Box<dyn EntityComponent>),
    Removed(TypeId),
}

pub struct World {
    pub(crate) updates: Updates,
    pub events: Events,
    pub resources: Resources,
    pub(crate) entities: IndexMap<Entity, EntityObject>,

    entities_to_remove: Vec<Entity>,
    entities_to_add: Vec<EntityObject>,
    entities_updates: HashMap<Entity, Vec<ComponentChange>>,

    entity_object_manager: EntityObjectManager,

    start_time: Instant,

    systems: Vec<Box<dyn Fn(&mut World) -> Result<bool> + Send + Sync + 'static>>,
}

impl World {
    pub fn builder() -> WorldBuilder {
        WorldBuilder {
            updates: Default::default(),
            events: Default::default(),
            resources: Default::default(),
            systems: Default::default(),
        }
    }

    pub fn now(&self) -> Duration {
        self.start_time.elapsed()
    }

    pub fn new_entity(&mut self) -> EntityObject {
        self.entity_object_manager.create_entity()
    }

    pub fn entities(&self) -> impl Iterator<Item = &EntityObject> {
        self.entities.values()
    }

    pub fn entity(
        &self,
        entity: Entity,
    ) -> std::result::Result<&EntityObject, EntityNotFoundError> {
        self.entities
            .get(&entity)
            .ok_or_else(|| EntityNotFoundError::new(entity))
    }

    pub unsafe fn entity_unchecked<'a>(
        &self,
        entity: Entity,
    ) -> std::result::Result<&'a EntityObject, EntityNotFoundError> {
        self.entity(entity).map(|e| unsafe { remove_life_time(e) })
    }

    pub fn entity_mut(
        &mut self,
        entity: Entity,
    ) -> std::result::Result<&mut EntityObject, EntityNotFoundError> {
        self.entities
            .get_mut(&entity)
            .ok_or_else(|| EntityNotFoundError::new(entity))
    }

    pub unsafe fn entity_mut_unchecked<'a>(
        &mut self,
        entity: Entity,
    ) -> std::result::Result<&'a mut EntityObject, EntityNotFoundError> {
        self.entity_mut(entity)
            .map(|e| unsafe { remove_life_time_mut(e) })
    }

    pub fn entities_multi_mut(&mut self) -> EntityMultiMut<'_> {
        EntityMultiMut::new(&mut self.entities)
    }

    pub fn add_entity(&mut self, entity_object: EntityObject) -> Result<Entity> {
        let entity = entity_object.as_entity();
        self.entities_to_add.push(entity_object);

        Ok(entity)
    }

    pub fn insert_component<T: EntityComponent + ComponentDebug>(
        &mut self,
        entity: Entity,
        component: T,
    ) -> Result<()> {
        println!("insert component {}", T::debug_name());

        let change = ComponentChange::Added(TypeId::of::<T>(), Box::new(component));

        match self.entities_updates.get_mut(&entity) {
            Some(changes) => {
                changes.push(change);
            }
            None => {
                self.entities_updates.insert(entity, vec![change]);
            }
        }

        Ok(())
    }

    pub fn remove_component<T: EntityComponent + ComponentDebug>(
        &mut self,
        entity: Entity,
    ) -> Result<()> {
        println!("remove component {}", T::debug_name());

        let change = ComponentChange::Removed(TypeId::of::<T>());

        match self.entities_updates.get_mut(&entity) {
            Some(changes) => {
                changes.push(change);
            }
            None => {
                self.entities_updates.insert(entity, vec![change]);
            }
        }

        Ok(())
    }

    pub fn remove_entity(&mut self, entity: Entity) -> Result<Option<EntityObject>> {
        self.entities_to_remove.push(entity);

        Ok(None)
    }
}

// async application of changes
impl World {
    fn _add_entity(&mut self, mut entity: EntityObject) -> Result<Entity> {
        // call enable event for all components
        for (_, component) in entity.components.iter_mut() {
            component.enable(self)?;
        }

        entity.activation_state.apply_change();

        let e = entity.as_entity();

        self.updates.add_entity(&mut entity, &self.entities)?;
        assert!(self.entities.insert(e, entity).is_none());

        Ok(e)
    }

    fn _remove_entity(&mut self, entity: Entity) -> Result<Option<EntityObject>> {
        if let Some(mut entity_object) = self.entities.swap_remove(&entity) {
            for (_, component) in entity_object.components.iter_mut() {
                component.disable(self)?;
            }

            entity_object.activation_state.apply_change();
            self.updates.remove_entity(entity);

            return Ok(Some(entity_object));
        }

        Ok(None)
    }

    fn _insert_component<T: EntityComponent + ComponentDebug>(
        &mut self,
        entity: Entity,
        mut component: T,
    ) -> Result<()> {
        let entities = unsafe { remove_life_time_mut(&mut self.entities) };

        let entity_object = entities
            .get_mut(&entity)
            .ok_or_else(|| EntityNotFoundError::new(entity))?;

        self.updates.remove_entity(entity);

        entity_object.activation_state.apply_change();

        component.enable(self)?;
        if entity_object.insert_component(component).is_some() {
            bail!("component {} already present", T::debug_name());
        }

        entity_object.activation_state.apply_change();

        self.updates.add_entity(entity_object, &self.entities)?;

        Ok(())
    }

    fn _remove_component<T: EntityComponent + ComponentDebug>(
        &mut self,
        entity: Entity,
    ) -> Result<()> {
        let entities = unsafe { remove_life_time_mut(&mut self.entities) };

        let entity_object = entities
            .get_mut(&entity)
            .ok_or_else(|| EntityNotFoundError::new(entity))?;

        self.updates.remove_entity(entity);

        entity_object.activation_state.apply_change();

        if let Some(mut component) = entity_object.remove_component_by_id(TypeId::of::<T>()) {
            component.disable(self)?;
        }

        entity_object.activation_state.apply_change();

        self.updates.add_entity(entity_object, &self.entities)?;

        Ok(())
    }

    fn commit_entity_changes(&mut self) -> Result<()> {
        if !self.entities_to_remove.is_empty() {
            println!("entities to remove {}", self.entities_to_remove.len());
        }

        for entity in core::mem::take(&mut self.entities_to_remove) {
            self._remove_entity(entity)?;
        }

        if !self.entities_to_add.is_empty() {
            println!("entities to add {}", self.entities_to_add.len());
        }

        for entity_object in core::mem::take(&mut self.entities_to_add) {
            self._add_entity(entity_object)?;
        }

        if !self.entities_updates.is_empty() {
            println!("entities to update {}", self.entities_updates.len());
        }

        for (entity, changes) in core::mem::take(&mut self.entities_updates) {
            self.updates.remove_entity(entity);

            if let Some(entity_object) = unsafe { self.entity_mut_unchecked(entity).ok() } {
                entity_object.activation_state.apply_change();

                for change in changes {
                    match change {
                        ComponentChange::Added(type_id, mut component) => {
                            component.enable(self)?;
                            let name = component.name().to_string();

                            if entity_object
                                .insert_component_by_id(type_id, component)
                                .is_some()
                            {
                                bail!("component {name} already present");
                            }
                        }
                        ComponentChange::Removed(type_id) => {
                            if let Some(mut component) =
                                entity_object.remove_component_by_id(type_id)
                            {
                                component.disable(self)?;
                            }
                        }
                    }
                }

                entity_object.activation_state.apply_change();

                self.updates.add_entity(entity_object, &self.entities)?;
            }
        }

        Ok(())
    }
}

impl World {
    pub fn run(&mut self) -> Result<()> {
        let systems = std::mem::take(&mut self.systems);

        loop {
            // we need to take all events because of borrowing rules
            let mut events = self.events.take_events();
            events.fire_events(self)?;

            {
                // actually safe:
                //      - updates can't be altered on a running world
                //      - entity changes are processed afterwards
                let w = unsafe { remove_life_time_mut(self) };

                self.updates.update(w)?;
            }

            self.commit_entity_changes()?;

            for system in systems.iter() {
                if !system(self)? {
                    return Ok(());
                }
            }
        }
    }
}