//! Bounded Egui ownership for the editor viewport render target.

use bevy::asset::AssetId;
use bevy::prelude::*;
use bevy_egui::{egui, EguiTextureHandle, EguiUserTextures};

use crate::render_target::ViewportRenderTarget;

#[derive(Resource, Default, Debug, Clone, Copy)]
struct ViewportTextureRegistration {
    image: Option<AssetId<Image>>,
}

/// Returns the Egui texture for the current viewport target while retaining exactly one strong
/// image registration. Replacements and an absent target relinquish the previous registration.
pub(crate) fn sync_viewport_target_texture(world: &mut World) -> Option<egui::TextureId> {
    if !world.contains_resource::<ViewportTextureRegistration>() {
        world.insert_resource(ViewportTextureRegistration::default());
    }

    let target = world
        .get_resource::<ViewportRenderTarget>()
        .and_then(|target| target.0.as_ref())
        .map(|target| target.image.clone());
    let next_image = target.as_ref().map(Handle::id);
    let previous_image = world.resource::<ViewportTextureRegistration>().image;

    if previous_image != next_image {
        if let Some(previous_image) = previous_image {
            world
                .resource_mut::<EguiUserTextures>()
                .remove_image(previous_image);
        }
        world.resource_mut::<ViewportTextureRegistration>().image = None;
    }

    let target = target?;
    let image_id = target.id();
    let texture_id = {
        let mut textures = world.resource_mut::<EguiUserTextures>();
        textures
            .image_id(image_id)
            .unwrap_or_else(|| textures.add_image(EguiTextureHandle::Strong(target)))
    };
    world.resource_mut::<ViewportTextureRegistration>().image = Some(image_id);
    Some(texture_id)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::render_target::ViewportTarget;

    fn world_with_target(image: Handle<Image>) -> World {
        let mut world = World::new();
        world.insert_resource(EguiUserTextures::default());
        world.insert_resource(ViewportRenderTarget(Some(ViewportTarget { image })));
        world
    }

    #[test]
    fn viewport_replacement_releases_the_previous_egui_registration() {
        let mut images = Assets::<Image>::default();
        let first = images.add(Image::default());
        let second = images.add(Image::default());
        let first_id = first.id();
        let second_id = second.id();
        let mut world = world_with_target(first);

        let first_texture = sync_viewport_target_texture(&mut world).expect("first registration");
        assert_eq!(
            world.resource::<EguiUserTextures>().image_id(first_id),
            Some(first_texture)
        );

        world.resource_mut::<ViewportRenderTarget>().0 = Some(ViewportTarget { image: second });
        sync_viewport_target_texture(&mut world).expect("replacement registration");
        assert!(world
            .resource::<EguiUserTextures>()
            .image_id(first_id)
            .is_none());
        assert!(world
            .resource::<EguiUserTextures>()
            .image_id(second_id)
            .is_some());

        world.resource_mut::<ViewportRenderTarget>().0 = None;
        assert!(sync_viewport_target_texture(&mut world).is_none());
        assert!(world
            .resource::<EguiUserTextures>()
            .image_id(second_id)
            .is_none());
    }

    #[test]
    fn stable_viewport_image_reuses_the_same_egui_texture() {
        let mut images = Assets::<Image>::default();
        let image = images.add(Image::default());
        let mut world = world_with_target(image);
        let first = sync_viewport_target_texture(&mut world).expect("first registration");
        let second = sync_viewport_target_texture(&mut world).expect("stable registration");
        assert_eq!(first, second);
    }
}
