#![allow(unused)]

use super::configs::WindowConfig;
use super::osspecific::osspecific::OsSpecific;
use super::vulkancore::VulkanCore;

#[cfg(feature = "sound")]
use audio::SoundHandler;
use ecs::World;

use crate::prelude::*;
use anyhow::Result;

use presentation::{input::eventsystem::Event, prelude::*};

use std::collections::HashMap;
use std::env::set_var;
use std::mem::{self, swap};
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::rc::Rc;
use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::time::{Duration, Instant};

pub struct Context {
    core: VulkanCore,
    pub(crate) presentation: PresentationCore,
    render_core: Arc<RwLock<RenderCore>>,

    #[cfg(feature = "sound")]
    sound_handler: SoundHandler,

    os_specific: OsSpecific,

    fallback: Option<Box<dyn Fn(anyhow::Error) -> Result<()> + Send + Sync>>,
}

impl Context {
    pub fn new() -> ContextBuilder {
        ContextBuilder::default()
    }

    pub fn window_config(&self) -> WindowConfig<'_> {
        match self.presentation.backend() {
            PresentationBackend::Window(wsi) => WindowConfig::new(wsi),
            PresentationBackend::OpenXR(_xri) => {
                panic!("OpenXR backend has no window config")
            }
            PresentationBackend::OpenVR(_vri) => {
                panic!("OpenVR backend has no window config")
            }
        }
    }

    #[cfg(feature = "sound")]
    pub fn sound(&mut self) -> &mut SoundHandler {
        &mut self.sound_handler
    }

    pub fn next_frame<C, F>(&mut self, world: &mut World, mut f: F) -> Result<bool>
    where
        C: Send + Sync + 'static,
        F: FnMut(&mut World, &mut C, Event<'_>) -> Result<()> + Send + Sync + 'static,
    {
        let render_core = self.render_core.clone();
        let consumer = world.resources.get_mut_unchecked::<C>();
        let w = unsafe { remove_life_time_mut(world) };

        match self.presentation.poll_events(
            |event| f(w, consumer, event),
            |w, h| render_core.write().unwrap().resize(world, w, h),
        ) {
            Ok(res) => {
                if !res {
                    return Ok(false);
                }
            }
            Err(err) => {
                if let Some(fallback) = &self.fallback {
                    (fallback)(err)?;
                }
            }
        }

        if !self.render_core_mut().next_frame(world)? {
            return Ok(false);
        }

        Ok(true)
    }

    pub fn render_core(&self) -> impl Deref<Target = RenderCore> + '_ {
        self.render_core.read().unwrap()
    }

    pub fn render_core_mut(&self) -> impl DerefMut<Target = RenderCore> + '_ {
        self.render_core.write().unwrap()
    }

    pub fn set_fallback<F>(&mut self, fallback: F)
    where
        F: Fn(anyhow::Error) -> Result<()> + 'static + Send + Sync,
    {
        self.fallback = Some(Box::new(fallback));
    }

    pub fn close(&self) -> Result<()> {
        Ok(self.presentation.event_system().quit()?)
    }

    pub fn device(&self) -> &Arc<Device> {
        &self.core.device()
    }

    pub fn queue(&self) -> &Arc<Mutex<Queue>> {
        self.core.queue()
    }

    pub fn controllers(&self) -> impl Iterator<Item = &Controller> {
        self.presentation.event_system().controllers()
    }
}

impl std::fmt::Debug for Context {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Context {{ TODO }}")
    }
}

impl ContextInterface for Context {
    fn device(&self) -> &Arc<Device> {
        self.device()
    }

    fn queue(&self) -> &Arc<Mutex<Queue>> {
        self.queue()
    }

    fn format(&self) -> VkFormat {
        self.render_core().format()
    }

    fn image_layout(&self) -> VkImageLayout {
        self.render_core().image_layout()
    }

    fn image_count(&self) -> usize {
        self.render_core().image_count()
    }

    fn images(&self) -> TargetMode<Vec<Arc<Image>>> {
        self.render_core().images()
    }

    fn width(&self) -> u32 {
        self.render_core().width()
    }

    fn height(&self) -> u32 {
        self.render_core().height()
    }

    #[cfg(feature = "sound")]
    fn sound_handler(&mut self) -> &mut SoundHandler {
        self.sound()
    }
}

pub struct ContextBuilder {
    #[cfg(feature = "sound")]
    volume_info: HashMap<String, f32>,

    #[cfg(any(feature = "openvr", feature = "openxr"))]
    vr_mode: Option<VRMode>,

    #[cfg(feature = "openxr")]
    openxr_runtime_json: Option<String>,

    enable_backtrace: bool,

    // app info
    app_info: ApplicationInfo,

    // window information
    window_create_info: WindowCreateInfo,

    // os specifics
    os_specific_config: OsSpecificConfig,

    // vulkan core settings
    device_features: DeviceFeatures,
    sample_count: VkSampleCountFlags,
    enable_raytracing: bool,
    render_core_create_info: RenderCoreCreateInfo,

    // vulkan debug extension selection
    vulkan_debug_info: VulkanDebugInfo,

    // input settings
    enable_mouse: bool,
    enable_keyboard: bool,
    enable_controller: bool,
    controller_deadzones: ControllerDeadzones,
}

impl Default for ContextBuilder {
    fn default() -> Self {
        ContextBuilder {
            #[cfg(feature = "sound")]
            volume_info: HashMap::new(),

            #[cfg(any(feature = "openvr", feature = "openxr"))]
            vr_mode: None,

            #[cfg(feature = "openxr")]
            openxr_runtime_json: None,

            enable_backtrace: false,

            // app info
            app_info: ApplicationInfo {
                application_name: "not set".to_string(),
                application_version: 0,
                engine_name: "not set".to_string(),
                engine_version: 0,
            },

            // window information
            window_create_info: WindowCreateInfo {
                title: "Vulkan Application".to_string(),
                width: 800,
                height: 600,
                fullscreen: false,
                requested_display: None,
            },

            // os specifics
            os_specific_config: OsSpecificConfig::default(),

            // vulkan core settings
            device_features: DeviceFeatures::default(),
            sample_count: VK_SAMPLE_COUNT_1_BIT,
            enable_raytracing: false,
            render_core_create_info: RenderCoreCreateInfo {
                format: VK_FORMAT_R8G8B8A8_UNORM,
                usage: 0.into(),
                vsync: false,
            },

            // vulkan debug extension selection
            vulkan_debug_info: VulkanDebugInfo::default(),

            // input settings
            enable_mouse: false,
            enable_keyboard: false,
            enable_controller: false,
            controller_deadzones: ControllerDeadzones::default(),
        }
    }
}

impl ContextBuilder {
    #[cfg(feature = "sound")]
    pub fn set_volume_info(mut self, volume_info: HashMap<String, f32>) -> Self {
        self.volume_info = volume_info;

        self
    }

    #[cfg(any(feature = "openvr", feature = "openxr"))]
    pub fn set_vr_mode(mut self, vr_mode: VRMode) -> Self {
        self.vr_mode = Some(vr_mode);

        self
    }

    #[cfg(feature = "openxr")]
    pub fn set_openxr_json(mut self, openxr_json_path: &str) -> Self {
        self.openxr_runtime_json = Some(openxr_json_path.to_string());

        self
    }

    pub fn set_app_info(mut self, app_info: ApplicationInfo) -> Self {
        self.app_info = app_info;

        self
    }

    pub fn set_window_info(mut self, window_info: WindowCreateInfo) -> Self {
        self.window_create_info = window_info;

        self
    }

    pub fn set_os_specific_info(mut self, os_specific: OsSpecificConfig) -> Self {
        self.os_specific_config = os_specific;

        self
    }

    pub fn set_device_features(mut self, device_features: DeviceFeatures) -> Self {
        self.device_features = device_features;

        self
    }

    pub fn set_sample_count(mut self, sample_count: VkSampleCountFlags) -> Self {
        self.sample_count = sample_count;

        self
    }

    pub fn enable_ray_tracing(mut self) -> Self {
        self.enable_raytracing = true;

        self
    }

    pub fn set_render_core_info(
        mut self,
        format: VkFormat,
        usage: impl Into<VkImageUsageFlagBits>,
        vsync: bool,
    ) -> Self {
        self.render_core_create_info = RenderCoreCreateInfo {
            format,
            usage: usage.into(),
            vsync,
        };

        self
    }

    pub fn enable_backtrace(mut self) -> Self {
        self.enable_backtrace = true;

        self
    }

    pub fn set_vulkan_debug_info(mut self, vulkan_debug_info: VulkanDebugInfo) -> Self {
        self.vulkan_debug_info = vulkan_debug_info;

        self
    }

    pub fn enable_mouse(mut self) -> Self {
        self.enable_mouse = true;

        self
    }

    pub fn enable_keyboard(mut self) -> Self {
        self.enable_keyboard = true;

        self
    }

    pub fn enable_controller(mut self) -> Self {
        self.enable_controller = true;

        self
    }

    pub fn set_controller_deadzones(mut self, controller_deadzones: ControllerDeadzones) -> Self {
        self.controller_deadzones = controller_deadzones;

        self
    }

    pub fn build<S: TScene + 'static>(self) -> Result<Context> {
        if self.enable_backtrace {
            // set environment variable for Rust-debug-trace
            unsafe { set_var("RUST_BACKTRACE", "1") };
        }

        #[cfg(feature = "openxr")]
        self.use_openxr_json();

        let vr_mode = self.get_vr_mode();

        let mut presentation =
            PresentationCore::new(vr_mode, &self.window_create_info, self.app_info.clone())?;

        presentation
            .event_system_mut()
            .set_controller_axis_enable_deadzone(self.controller_deadzones.axis_enable_deadzone);
        presentation
            .event_system_mut()
            .set_controller_axis_disable_deadzone(self.controller_deadzones.axis_disable_deadzone);
        presentation
            .event_system_mut()
            .set_controller_trigger_enable_deadzone(
                self.controller_deadzones.trigger_enable_deadzone,
            );
        presentation
            .event_system_mut()
            .set_controller_trigger_disable_deadzone(
                self.controller_deadzones.trigger_disable_deadzone,
            );

        // vulkan core objects (VkInstance, VkDevice, ...)
        let core = VulkanCore::new(
            &presentation,
            &self.vulkan_debug_info,
            &self.app_info,
            self.device_features,
        )?;

        let os_specific = OsSpecific::new(&self.os_specific_config);

        let (render_core, _target_mode) = create_render_core::<S>(
            &presentation,
            core.device(),
            core.queue(),
            self.render_core_create_info,
        )?;

        if self.enable_mouse {
            presentation.event_system_mut().enable_mouse();
        }

        if self.enable_keyboard {
            presentation.event_system_mut().enable_keyboard();
        }

        if self.enable_controller {
            presentation.event_system_mut().enable_controller();
        }

        Ok(Context {
            core,
            presentation,
            render_core: Arc::new(RwLock::new(render_core)),

            #[cfg(feature = "sound")]
            sound_handler: self.create_sound_handler()?,

            os_specific,

            fallback: None,
        })
    }

    #[cfg(feature = "openxr")]
    fn use_openxr_json(&self) {
        if let Some(openxr_json) = &self.openxr_runtime_json {
            // set environment variable for OpenXR
            set_var("XR_RUNTIME_JSON", openxr_json);
        }
    }

    fn get_vr_mode(&self) -> Option<VRMode> {
        #[cfg(any(feature = "openvr", feature = "openxr"))]
        // if we requested a VR mode, check if it is available
        return self
            .vr_mode
            .map(|vr_mode| {
                let available_vr_modes = PresentationCore::enabled_vr_modes();

                // if requested VR mode is enabled, use it
                if available_vr_modes.contains(&vr_mode) {
                    Some(vr_mode)
                }
                // fallback to the first available
                else if !available_vr_modes.is_empty() {
                    let mode = available_vr_modes[0];

                    println!(
                        "Requested VRMode ({:?}) is not available, using {:?} instead.",
                        vr_mode, mode
                    );

                    Some(mode)
                }
                // use default desktop, as last resort
                else {
                    println!("No VRMode present, fallback to Window");

                    None
                }
            })
            .flatten();

        #[cfg(not(any(feature = "openvr", feature = "openxr")))]
        None
    }

    #[cfg(feature = "sound")]
    fn create_sound_handler(&self) -> Result<SoundHandler> {
        SoundHandler::new(self.volume_info.clone())
    }
}