mod enums;
mod structs;
mod vk_handles;

use enums::*;
use structs::*;
use vk_handles::*;
use vulkan_rs::prelude::*;

use std::{ffi::c_void, fs::File, mem, os::raw::c_char, ptr};

static mut ACQUIRE_NEXT_IMAGE: PFN_vkAcquireNextImageKHR =
    unsafe { mem::transmute(vkVoidFunction as *const c_void) };

use crate::{get_config, logging, write_log, LOG_ENABLED, LOG_FILE, OVERLAY};

#[no_mangle]
#[allow(non_snake_case)]
pub(crate) extern "C" fn vkNegotiateLoaderLayerInterfaceVersion(
    pVersionStruct: *mut VkNegotiateLayerInterface,
) -> VkResult {
    let log_enabled = match std::env::var("RFACTOR_HUD_LOG") {
        Ok(var) => {
            let i: u32 = var.parse().unwrap_or(0);

            i == 1
        }
        Err(_) => false,
    };

    unsafe { LOG_ENABLED = log_enabled };

    let home = std::env::var("HOME").unwrap();

    if logging() {
        unsafe {
            LOG_FILE = format!("{}/rf2_vk_hud.log", home);
        }

        if let Err(_) = File::create(unsafe { &LOG_FILE }) {}

        write_log!(" ==================================================================");
        write_log!(" ======================= New Negotiation ==========================");
        write_log!(" ==================================================================");
    }

    unsafe {
        OVERLAY.set_config(get_config(&home));
    }

    unsafe {
        *pVersionStruct = VkNegotiateLayerInterface {
            sType: enums::VkNegotiateLayerStructType::LAYER_NEGOTIATE_INTERFACE_STRUCT,
            pNext: ptr::null_mut(),
            loaderLayerInterfaceVersion: 2,
            pfnGetInstanceProcAddr: Some(get_instance_proc_addr),
            pfnGetDeviceProcAddr: Some(get_device_proc_addr),
            pfnGetPhysicalDeviceProcAddr: None,
        }
    };

    set_vk_handles(match VkTypedefHandles::new() {
        Ok(handles) => handles,
        Err(err) => {
            write_log!(format!("failed to load typedef handles {:?}", err));
            return VK_ERROR_INITIALIZATION_FAILED;
        }
    });

    VK_SUCCESS
}

#[no_mangle]
extern "system" fn get_device_proc_addr(
    _device: VkDevice,
    function_name: *const c_char,
) -> PFN_vkVoidFunction {
    let func_string = match VkString::try_from(function_name) {
        Ok(func) => func,
        Err(_) => {
            write_log!("Err: failed creating string");
            return Functions::Null.convert();
        }
    };

    let s = func_string.as_str();

    if let Some(func) = Functions::get_vk_func(s) {
        return func.convert();
    }

    if let Some(func) = vk_handles().handle(s) {
        return func;
    }

    Functions::Null.convert()
}

#[no_mangle]
extern "system" fn get_instance_proc_addr(
    _instance: VkInstance,
    function_name: *const c_char,
) -> PFN_vkVoidFunction {
    let func_string = match VkString::try_from(function_name) {
        Ok(func) => func,
        Err(_) => {
            write_log!("Err: failed creating string");
            return Functions::Null.convert();
        }
    };

    let s = func_string.as_str();

    if let Some(func) = Functions::get_vk_func(s) {
        return func.convert();
    }

    if let Some(func) = vk_handles().handle(s) {
        return func;
    }

    Functions::Null.convert()
}

pub(crate) extern "system" fn create_instance(
    create_info: *const VkInstanceCreateInfo,
    allocator: *const VkAllocationCallbacks,
    instance: *mut VkInstance,
) -> VkResult {
    write_log!(" ================== vulkan layer create instance ==================");

    let chain_info = match VkLayerInstanceCreateInfo::get_chain_info(
        unsafe { &*create_info },
        VK_LAYER_LINK_INFO,
    ) {
        Some(info) => info,
        None => {
            write_log!("instance chain info not found.");
            return VK_ERROR_LAYER_NOT_PRESENT;
        }
    };

    let proc_addr = chain_info.layer_info().next_instance_proc_addr;
    let create_instance: PFN_vkCreateInstance = unsafe {
        mem::transmute(proc_addr(
            VkInstance::NULL_HANDLE,
            VkString::new("vkCreateInstance").as_ptr(),
        ))
    };

    chain_info.advance_layer_info();
    let result = create_instance(create_info, allocator, instance);
    if result != VK_SUCCESS {
        return result;
    };

    vk_handles_mut().load_instance_functions(unsafe { *instance }, proc_addr);

    write_log!("-> successfully created instance.");

    let ext_names = unsafe { (*create_info).extension_names() };

    write_log!(format!("{:?}", ext_names));

    // DXVK workaround, it creates the instance twice with different properties
    if ext_names.contains(&VkString::new("VK_KHR_surface")) {
        unsafe {
            let ins = match Instance::preinitialized(
                *instance,
                proc_addr,
                &ext_names,
                (*(*create_info).pApplicationInfo).apiVersion,
            ) {
                Ok(ins) => ins,
                Err(err) => {
                    write_log!(format!("-> local instance creation failed: {:?}", err));
                    return VK_ERROR_INITIALIZATION_FAILED;
                }
            };

            OVERLAY.set_instance(ins);
        }

        write_log!("-> created local instance handle");
    }

    VK_SUCCESS
}

pub(crate) extern "system" fn destroy_instance(
    instance: VkInstance,
    allocator: *const VkAllocationCallbacks,
) {
    write_log!(" ================== vulkan layer destroy instance ==================");

    unsafe {
        if let Some(vk_fn) = vk_handles().handle("vkDestroyInstance") {
            let destroy_instance: PFN_vkDestroyInstance = mem::transmute(vk_fn);

            destroy_instance(instance, allocator);
        }
    }
}

pub(crate) extern "system" fn create_device(
    physical_device: VkPhysicalDevice,
    create_info: *const VkDeviceCreateInfo<'_>,
    allocator: *const VkAllocationCallbacks,
    device: *mut VkDevice,
) -> VkResult {
    write_log!(" ================== vulkan layer create device ==================");

    let chain_info =
        match VkLayerDeviceCreateInfo::get_chain_info(unsafe { &*create_info }, VK_LAYER_LINK_INFO)
        {
            Some(info) => info,
            None => {
                write_log!("device chain info not found.");
                return VK_ERROR_LAYER_NOT_PRESENT;
            }
        };

    let proc_addr = chain_info.layer_info().next_device_proc_addr;

    chain_info.advance_layer_info();

    let result = unsafe {
        match vk_handles().handle("vkCreateDevice") {
            Some(vk_fn) => {
                let create_device: PFN_vkCreateDevice = mem::transmute(vk_fn);
                create_device(physical_device, create_info, allocator, device)
            }
            None => {
                write_log!("failed creating device");
                return VK_ERROR_INITIALIZATION_FAILED;
            }
        }
    };

    if result != VK_SUCCESS {
        return result;
    }

    vk_handles_mut().load_device_functions(unsafe { *device }, proc_addr);
    unsafe {
        ACQUIRE_NEXT_IMAGE = match vk_handles().handle("vkAcquireNextImageKHR") {
            Some(acquire_next_image) => mem::transmute(acquire_next_image),
            None => {
                write_log!("failed querying vkAcquireNextImageKHR");
                return VK_ERROR_INITIALIZATION_FAILED;
            }
        };
    };

    let pdev = match PhysicalDevice::from_raw(unsafe { OVERLAY.instance() }, physical_device) {
        Ok(pdev) => pdev,
        Err(err) => {
            write_log!(format!("failed creating physical device: {:?}", err));
            return VK_ERROR_INITIALIZATION_FAILED;
        }
    };

    let queue_info = match Queue::create_non_presentable_request_info(&pdev, VK_QUEUE_GRAPHICS_BIT)
    {
        Ok(qi) => qi,
        Err(err) => {
            write_log!(format!("failed getting queue info: {:?}", err));
            return VK_ERROR_INITIALIZATION_FAILED;
        }
    };

    unsafe {
        let create_queue_info = std::slice::from_raw_parts(
            (*create_info).pQueueCreateInfos,
            (*create_info).queueCreateInfoCount as usize,
        );

        for info in create_queue_info {
            write_log!(format!(
                "pCreateDeviceInfo; queue fam: {}, queue count: {}",
                info.queueFamilyIndex, info.queueCount
            ));
        }

        write_log!(format!(
            "Queue: queue fam: {}, queue count: {}",
            queue_info.queue_create_info.queueFamilyIndex, queue_info.queue_create_info.queueCount
        ));
    }

    let ext_names = unsafe { (*create_info).extension_names() };

    write_log!(format!("{:?}", ext_names));

    let device = match Device::preinitialized(unsafe { *device }, proc_addr, pdev, &ext_names) {
        Ok(dev) => dev,
        Err(err) => {
            write_log!(format!("-> local device creation failed: {:?}", err));
            return VK_ERROR_INITIALIZATION_FAILED;
        }
    };

    write_log!("created device");

    let queue = device.get_queue(queue_info.queue_family_index, queue_info.queue_index);

    write_log!("got queue from device");

    unsafe {
        OVERLAY.set_device(device);
        OVERLAY.set_queue(queue);
    }

    VK_SUCCESS
}

pub(crate) extern "system" fn destroy_device(
    device: VkDevice,
    allocator: *const VkAllocationCallbacks,
) {
    write_log!(" ================== vulkan layer destroy device ==================");

    unsafe {
        if let Some(vk_fn) = vk_handles().handle("vkDestroyDevice") {
            let destroy_device: PFN_vkDestroyDevice = mem::transmute(vk_fn);

            destroy_device(device, allocator);
        }
    }
}

pub(crate) extern "system" fn create_swapchain(
    _device: VkDevice,
    create_info: *const VkSwapchainCreateInfoKHR,
    _allocator: *const VkAllocationCallbacks,
    p_swapchain: *mut VkSwapchainKHR,
) -> VkResult {
    write_log!(" ================== vulkan layer create swapchain ==================");

    let create_swapchain: PFN_vkCreateSwapchainKHR =
        match vk_handles().handle("vkCreateSwapchainKHR") {
            Some(create_swapchain) => unsafe { mem::transmute(create_swapchain) },
            None => {
                write_log!("failed querying vkCreateSwapchainKHR");
                return VK_ERROR_INITIALIZATION_FAILED;
            }
        };

    create_swapchain(_device, create_info, _allocator, p_swapchain);

    write_log!(format!("-> created swapchain vk handle {:?}", unsafe {
        *p_swapchain
    }));

    let swapchain =
        match unsafe { Swapchain::from_raw(OVERLAY.device(), &*create_info, *p_swapchain) } {
            Ok(swapchain) => swapchain,
            Err(err) => {
                write_log!(format!("create swapchain failed: {:?}", err));
                return VK_ERROR_INITIALIZATION_FAILED;
            }
        };

    write_log!("-> created Arc<Swapchain>");

    if let Err(err) = unsafe { OVERLAY.create_rendering(swapchain) } {
        write_log!(format!("create overlay rendering struct failed: {:?}", err));
        return VK_ERROR_INITIALIZATION_FAILED;
    }

    write_log!("-> created renderer");

    VK_SUCCESS
}

pub(crate) extern "system" fn acquire_next_image(
    device: VkDevice,
    swapchain: VkSwapchainKHR,
    timeout: u64,
    semaphore: VkSemaphore,
    fence: VkFence,
    image_index: *mut u32,
) -> VkResult {
    unsafe {
        match OVERLAY.swapchain(swapchain) {
            Some(sc) => {
                let device = OVERLAY.device();

                let res = device.acquire_next_image(
                    sc.vk_handle(),
                    timeout,
                    Some(semaphore),
                    Some(fence),
                );

                write_log!(format!(
                    "acquire (swapchain: {:?}) res: {:?}",
                    sc.vk_handle(),
                    res
                ));

                match res {
                    Ok(res) => match res {
                        OutOfDate::Ok(index) => {
                            sc.set_image_index(index);
                            VK_SUCCESS
                        }
                        OutOfDate::OutOfDate => VK_ERROR_OUT_OF_DATE_KHR,
                        OutOfDate::TimeOut => VK_TIMEOUT,
                    },
                    Err(err) => {
                        write_log!(format!("failed acquiring next image {:?}", err));
                        VK_ERROR_DEVICE_LOST
                    }
                }
            }
            None => {
                write_log!("acquired other swapchain image");
                ACQUIRE_NEXT_IMAGE(device, swapchain, timeout, semaphore, fence, image_index)
            }
        }
    }
}

pub(crate) extern "system" fn present_queue(
    queue: VkQueue,
    present_info: *const VkPresentInfoKHR,
) -> VkResult {
    if logging() {
        write_log!(" ================== vulkan layer queue present ==================");
        write_log!(format!("iq: {:?}, cq: {:?}", queue, unsafe {
            OVERLAY.queue().lock().unwrap().vk_handle()
        }));
        unsafe {
            let swapchains = std::slice::from_raw_parts(
                (*present_info).pSwapchains,
                (*present_info).swapchainCount as usize,
            );

            write_log!(format!("present {} swapchain(s)", swapchains.len()));

            for swapchain in swapchains {
                write_log!(format!("present swapchain: {:?}", swapchain));

                if let Some(swch) = OVERLAY.swapchain(*swapchain) {
                    write_log!(format!(
                        " -> internal swapchain found! ({:?})",
                        swch.vk_handle()
                    ));
                }
            }
        }
    }

    match unsafe { OVERLAY.render() } {
        Ok(_) => (),
        Err(err) => {
            write_log!(format!("overlay rendering failed: {:?}", err));
            return VK_ERROR_DEVICE_LOST;
        }
    };

    let pfn: PFN_vkQueuePresentKHR = match vk_handles().handle("vkQueuePresentKHR") {
        Some(pfn) => unsafe { mem::transmute(pfn) },
        None => {
            write_log!("failed querying vkQueuePresentKHR");
            return VK_ERROR_DEVICE_LOST;
        }
    };

    pfn(queue, present_info)
}