use std::cell::RefCell;
use std::error::Error;
use std::rc::Rc;

use crate::core::app::context::WindowContext;
use crate::core::render::primitives::vulkan_resource::{
    VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanDescriptorSetAllocator, VulkanDevice,
    VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator, VulkanTransferQueue,
};
use bevy_ecs::world::World;

use super::{AsScene, Scene};

pub struct SceneManager {
    scenes: Vec<Scene>,
    current_scene_index: Option<usize>,
    window_context: Option<Rc<RefCell<WindowContext>>>,
}

impl SceneManager {
    pub fn new() -> Self {
        Self {
            scenes: Vec::new(),
            current_scene_index: None,
            window_context: None,
        }
    }

    pub fn set_window_context(&mut self, window_context: Rc<RefCell<WindowContext>>) {
        self.window_context = Some(window_context);
    }

    fn create_world_with_resources(window_context: &WindowContext) -> World {
        let mut world = World::new();

        let vulkan_context = window_context.vulkan_context();
        let vulkano_context = vulkan_context.vulkano_context();

        let vulkan_instance = vulkano_context.instance();
        let vulkan_device = vulkano_context.device();
        let vulkan_graphics_queue = vulkano_context.graphics_queue();
        let vulkan_compute_queue = vulkano_context.compute_queue();
        let vulkan_transfer_queue = vulkano_context.transfer_queue();
        let vulkan_memory_allocator = vulkano_context.memory_allocator();
        let vulkan_command_buffer_allocator = vulkan_context.command_buffer_allocator();
        let vulkan_descriptor_set_allocator = vulkan_context.descriptor_set_allocator();

        world.insert_resource(VulkanInstance(vulkan_instance.clone()));
        world.insert_resource(VulkanDevice(vulkan_device.clone()));
        world.insert_resource(VulkanGraphicsQueue(vulkan_graphics_queue.clone()));
        world.insert_resource(VulkanComputeQueue(vulkan_compute_queue.clone()));
        world.insert_resource(VulkanTransferQueue(vulkan_transfer_queue.cloned()));
        world.insert_resource(VulkanMemoryAllocator(vulkan_memory_allocator.clone()));
        world.insert_resource(VulkanCommandBufferAllocator(
            vulkan_command_buffer_allocator.clone(),
        ));
        world.insert_resource(VulkanDescriptorSetAllocator(
            vulkan_descriptor_set_allocator.clone(),
        ));

        world
    }

    pub fn load_scene(&mut self, scene_impl: Box<dyn AsScene>, window_context: &WindowContext) {
        let world = Self::create_world_with_resources(window_context);
        let scene = Scene::new(scene_impl, world);
        self.scenes.push(scene);
        self.current_scene_index = Some(self.scenes.len() - 1);
    }

    pub fn replace_current_scene(
        &mut self,
        scene_impl: Box<dyn AsScene>,
        window_context: &WindowContext,
    ) {
        if let Some(index) = self.current_scene_index {
            if index < self.scenes.len() {
                self.scenes[index].unload();
                let world = Self::create_world_with_resources(window_context);
                self.scenes[index] = Scene::new(scene_impl, world);
            }
        } else {
            self.load_scene(scene_impl, window_context);
        }
    }

    pub fn current_scene(&self) -> Option<&Scene> {
        if let Some(index) = self.current_scene_index {
            self.scenes.get(index)
        } else {
            None
        }
    }

    pub fn current_scene_mut(&mut self) -> Option<&mut Scene> {
        if let Some(index) = self.current_scene_index {
            self.scenes.get_mut(index)
        } else {
            None
        }
    }

    pub fn load_scene_if_not_loaded(
        &mut self,
        window_context: &mut WindowContext,
    ) -> Result<(), Box<dyn Error>> {
        if let Some(scene) = self.current_scene_mut() {
            if !scene.loaded() {
                scene.load(window_context)?;
            }
        } else {
            tracing::warn!("No scene found in SceneManager!");
        }
        Ok(())
    }
}