use crate::{
    app::{AppBuilder, AppLifeCycle},
    assets::{asset::AssetID, database::AssetsDatabase, protocols::prefab::PrefabAsset},
    hierarchy::{Name, NonPersistent, NonPersistentPrefabProxy, Parent, ParentPrefabProxy, Tag},
    state::StateToken,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use specs::{
    world::{Builder, EntitiesRes, LazyBuilder},
    Component, Entity, LazyUpdate, Read, ReadExpect, System, World, Write,
};
use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
};
pub use serde_yaml::{Number as PrefabNumber, Value as PrefabValue};
type ComponentFactory = Box<
    dyn for<'a> FnMut(
            LazyBuilder<'a>,
            PrefabValue,
            &HashMap<String, Entity>,
            StateToken,
        ) -> Result<LazyBuilder<'a>, PrefabError>
        + Send,
>;
#[derive(Debug)]
pub enum PrefabError {
    CouldNotSerialize(String),
    CouldNotDeserialize(String),
    Custom(String),
}
pub trait Prefab: Serialize + DeserializeOwned + Sized {
    fn from_prefab(data: PrefabValue) -> Result<Self, PrefabError> {
        match serde_yaml::from_value(data) {
            Ok(result) => {
                let mut result: Self = result;
                result.post_from_prefab();
                Ok(result)
            }
            Err(error) => Err(PrefabError::CouldNotSerialize(error.to_string())),
        }
    }
    fn from_prefab_with_extras(
        data: PrefabValue,
        _named_entities: &HashMap<String, Entity>,
        _state_token: StateToken,
    ) -> Result<Self, PrefabError> {
        Self::from_prefab(data)
    }
    fn to_prefab(&self) -> Result<PrefabValue, PrefabError> {
        match serde_yaml::to_value(self) {
            Ok(result) => Ok(result),
            Err(error) => Err(PrefabError::CouldNotDeserialize(error.to_string())),
        }
    }
    fn from_prefab_str(data: &str) -> Result<Self, PrefabError> {
        match serde_yaml::from_str(data) {
            Ok(result) => {
                let mut result: Self = result;
                result.post_from_prefab();
                Ok(result)
            }
            Err(error) => Err(PrefabError::CouldNotDeserialize(error.to_string())),
        }
    }
    fn to_prefab_string(&self) -> Result<String, PrefabError> {
        match serde_yaml::to_string(self) {
            Ok(result) => Ok(result),
            Err(error) => Err(PrefabError::CouldNotSerialize(error.to_string())),
        }
    }
    fn post_from_prefab(&mut self) {}
}
impl Prefab for PrefabValue {}
pub trait PrefabProxy<P>: Component
where
    P: Prefab,
{
    fn from_proxy_with_extras(
        proxy: P,
        named_entities: &HashMap<String, Entity>,
        state_token: StateToken,
    ) -> Result<Self, PrefabError>;
}
pub trait PrefabComponent: Prefab + Component + Send + Sync {}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct PrefabScene {
    #[serde(default)]
    pub autoload: bool,
    #[serde(default)]
    pub template_name: Option<String>,
    #[serde(default)]
    pub dependencies: Vec<String>,
    #[serde(default)]
    pub entities: Vec<PrefabSceneEntity>,
}
impl Prefab for PrefabScene {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PrefabSceneEntity {
    Data(PrefabSceneEntityData),
    Template(String),
}
impl Default for PrefabSceneEntity {
    fn default() -> Self {
        Self::Data(Default::default())
    }
}
impl Prefab for PrefabSceneEntity {}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct PrefabSceneEntityData {
    #[serde(default)]
    pub uid: Option<String>,
    #[serde(default)]
    pub components: HashMap<String, PrefabValue>,
}
impl Prefab for PrefabSceneEntityData {}
#[derive(Default)]
pub struct PrefabManager {
    component_factory: Arc<Mutex<HashMap<String, ComponentFactory>>>,
    templates: HashMap<String, PrefabScene>,
}
impl PrefabManager {
    pub fn register_component_factory<T>(&mut self, name: &str)
    where
        T: PrefabComponent,
    {
        if let Ok(mut component_factory) = self.component_factory.lock() {
            component_factory.insert(
                name.to_owned(),
                Box::new(move |builder, prefab, named_entities, state_token| {
                    let c = T::from_prefab_with_extras(prefab, named_entities, state_token)?;
                    Ok(builder.with(c))
                }),
            );
        }
    }
    pub fn register_component_factory_proxy<T, P>(&mut self, name: &str)
    where
        P: Prefab,
        T: PrefabProxy<P> + Component + Send + Sync,
    {
        if let Ok(mut component_factory) = self.component_factory.lock() {
            component_factory.insert(
                name.to_owned(),
                Box::new(move |builder, prefab, named_entities, state_token| {
                    let p = P::from_prefab(prefab)?;
                    let c = T::from_proxy_with_extras(p, named_entities, state_token)?;
                    Ok(builder.with(c))
                }),
            );
        }
    }
    pub fn unregister_component_factory(&mut self, name: &str) {
        if let Ok(mut component_factory) = self.component_factory.lock() {
            component_factory.remove(name);
        }
    }
    pub fn register_scene_template(&mut self, prefab: PrefabScene) -> Result<(), PrefabError> {
        if let Some(name) = &prefab.template_name {
            if self.templates.contains_key(name) {
                Err(PrefabError::Custom(format!(
                    "There is already registered template: {}",
                    name
                )))
            } else {
                self.templates.insert(name.to_owned(), prefab);
                Ok(())
            }
        } else {
            Err(PrefabError::Custom(
                "Template prefabs must have set template name".to_owned(),
            ))
        }
    }
    pub fn unregister_scene_template(&mut self, name: &str) {
        self.templates.remove(name);
    }
    pub fn find_template(&self, name: &str) -> Option<&PrefabScene> {
        self.templates.get(name)
    }
    pub fn instantiate_world(
        &mut self,
        name: &str,
        world: &World,
    ) -> Result<Vec<Entity>, PrefabError> {
        let entities = world.read_resource::<EntitiesRes>();
        let lazy_update = world.read_resource::<LazyUpdate>();
        let state_token = world.read_resource::<AppLifeCycle>().current_state_token();
        Ok(self
            .build_template(
                name,
                &entities,
                &lazy_update,
                state_token,
                &Default::default(),
            )?
            .0)
    }
    pub fn instantiate_direct(
        &mut self,
        name: &str,
        entities: &EntitiesRes,
        lazy_update: &LazyUpdate,
        state_token: StateToken,
    ) -> Result<Vec<Entity>, PrefabError> {
        Ok(self
            .build_template(
                name,
                entities,
                lazy_update,
                state_token,
                &Default::default(),
            )?
            .0)
    }
    pub fn instantiate_system_data<'s>(
        &mut self,
        name: &str,
        (entities, lazy_update, lifecycle): &(
            Read<'s, EntitiesRes>,
            Read<'s, LazyUpdate>,
            ReadExpect<'s, AppLifeCycle>,
        ),
    ) -> Result<Vec<Entity>, PrefabError> {
        self.instantiate_direct(
            name,
            &entities,
            &lazy_update,
            lifecycle.current_state_token(),
        )
    }
    pub fn load_scene_from_prefab_world(
        &mut self,
        prefab: &PrefabScene,
        world: &World,
    ) -> Result<Vec<Entity>, PrefabError> {
        let entities = world.read_resource::<EntitiesRes>();
        let lazy_update = world.read_resource::<LazyUpdate>();
        let state_token = world.read_resource::<AppLifeCycle>().current_state_token();
        self.load_scene_from_prefab_direct(prefab, &entities, &lazy_update, state_token)
    }
    pub fn load_scene_from_prefab_direct(
        &mut self,
        prefab: &PrefabScene,
        entities: &EntitiesRes,
        lazy_update: &LazyUpdate,
        state_token: StateToken,
    ) -> Result<Vec<Entity>, PrefabError> {
        Ok(self
            .load_scene_from_prefab_inner(
                prefab,
                entities,
                lazy_update,
                state_token,
                &Default::default(),
            )?
            .0)
    }
    pub fn load_scene_from_prefab_system_data<'s>(
        &mut self,
        prefab: &PrefabScene,
        (entities, lazy_update, lifecycle): &(
            Read<'s, EntitiesRes>,
            Read<'s, LazyUpdate>,
            ReadExpect<'s, AppLifeCycle>,
        ),
    ) -> Result<Vec<Entity>, PrefabError> {
        self.load_scene_from_prefab_direct(
            prefab,
            &entities,
            &lazy_update,
            lifecycle.current_state_token(),
        )
    }
    fn load_scene_from_prefab_inner(
        &mut self,
        prefab: &PrefabScene,
        entities: &EntitiesRes,
        lazy_update: &LazyUpdate,
        state_token: StateToken,
        named_entities: &HashMap<String, Entity>,
    ) -> Result<(Vec<Entity>, HashMap<String, Entity>), PrefabError> {
        let mut named_entities = named_entities.clone();
        let mut result_entities = vec![];
        for entity_meta in &prefab.entities {
            match entity_meta {
                PrefabSceneEntity::Data(data) => {
                    let entity = self.build_entity(
                        &data.components,
                        entities,
                        lazy_update,
                        state_token,
                        &named_entities,
                    )?;
                    if let Some(uid) = &data.uid {
                        named_entities.insert(uid.to_owned(), entity);
                    }
                    result_entities.push(entity);
                }
                PrefabSceneEntity::Template(name) => {
                    let (entities, uids) = self.build_template(
                        name,
                        entities,
                        lazy_update,
                        state_token,
                        &named_entities,
                    )?;
                    for (uid, entity) in uids {
                        named_entities.insert(uid.to_owned(), entity);
                    }
                    result_entities.extend(entities);
                }
            }
        }
        Ok((result_entities, named_entities))
    }
    fn build_entity(
        &mut self,
        components: &HashMap<String, PrefabValue>,
        entities: &EntitiesRes,
        lazy_update: &LazyUpdate,
        state_token: StateToken,
        named_entities: &HashMap<String, Entity>,
    ) -> Result<Entity, PrefabError> {
        if let Ok(mut component_factory) = self.component_factory.lock() {
            let mut entity_builder = lazy_update.create_entity(entities);
            for (key, component_meta) in components {
                if let Some(factory) = component_factory.get_mut(key) {
                    entity_builder = (factory)(
                        entity_builder,
                        component_meta.clone(),
                        named_entities,
                        state_token,
                    )?;
                } else {
                    return Err(PrefabError::CouldNotDeserialize(format!(
                        "Could not find component factory: {}",
                        key
                    )));
                }
            }
            Ok(entity_builder.build())
        } else {
            Err(PrefabError::Custom(
                "Could not acquire lock on component factory".to_owned(),
            ))
        }
    }
    fn build_template(
        &mut self,
        name: &str,
        entities: &EntitiesRes,
        lazy_update: &LazyUpdate,
        state_token: StateToken,
        named_entities: &HashMap<String, Entity>,
    ) -> Result<(Vec<Entity>, HashMap<String, Entity>), PrefabError> {
        if let Some(prefab) = self.templates.get(name).cloned() {
            self.load_scene_from_prefab_inner(
                &prefab,
                entities,
                lazy_update,
                state_token,
                named_entities,
            )
        } else {
            Err(PrefabError::Custom(format!(
                "There is no template registered: {}",
                name
            )))
        }
    }
}
#[derive(Default)]
pub struct PrefabSystem {
    templates_table: HashMap<AssetID, String>,
}
impl<'s> System<'s> for PrefabSystem {
    #[allow(clippy::type_complexity)]
    type SystemData = (
        Read<'s, EntitiesRes>,
        Read<'s, LazyUpdate>,
        ReadExpect<'s, AppLifeCycle>,
        ReadExpect<'s, AssetsDatabase>,
        Write<'s, PrefabManager>,
    );
    fn run(&mut self, (entities, lazy_update, lifecycle, assets, mut prefabs): Self::SystemData) {
        for id in assets.lately_loaded_protocol("prefab") {
            let id = *id;
            let asset = assets
                .asset_by_id(id)
                .expect("trying to use not loaded prefab asset");
            let asset = asset
                .get::<PrefabAsset>()
                .expect("trying to use non-prefab asset");
            if let Some(name) = &asset.get().template_name {
                if prefabs.register_scene_template(asset.get().clone()).is_ok() {
                    self.templates_table.insert(id, name.to_owned());
                }
            }
            if asset.get().autoload {
                drop(prefabs.load_scene_from_prefab_direct(
                    asset.get(),
                    &entities,
                    &lazy_update,
                    lifecycle.current_state_token(),
                ));
            }
        }
        for id in assets.lately_unloaded_protocol("prefab") {
            if let Some(name) = self.templates_table.remove(id) {
                prefabs.unregister_scene_template(&name);
            }
        }
    }
}
pub fn bundle_installer<'a, 'b, PMS>(
    builder: &mut AppBuilder<'a, 'b>,
    mut prefab_manager_setup: PMS,
) where
    PMS: FnMut(&mut PrefabManager),
{
    let mut manager = PrefabManager::default();
    manager.register_component_factory_proxy::<Parent, ParentPrefabProxy>("Parent");
    manager.register_component_factory::<Tag>("Tag");
    manager.register_component_factory::<Name>("Name");
    manager.register_component_factory_proxy::<NonPersistent, NonPersistentPrefabProxy>(
        "NonPersistent",
    );
    prefab_manager_setup(&mut manager);
    builder.install_resource(manager);
    builder.install_system(PrefabSystem::default(), "prefab", &[]);
}