/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cstring>
#include <string>

#include "vk_layer_interface.h"
#include <android/log.h>
#include <vulkan/vulkan.h>

#define xstr(a) str(a)
#define str(a) #a

#define LAYER_FULL_NAME "VK_LAYER_ANDROID_nullLayer" xstr(LAYERNAME)
#define LOG_TAG LAYER_FULL_NAME

#define ALOGI(msg, ...) \
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, (msg), __VA_ARGS__)

#ifdef DEBUGUTILSPECVERSION
const VkExtensionProperties debug_utils_extension = {
    "VK_EXT_debug_utils",
    static_cast<uint32_t>(std::atoi(xstr(DEBUGUTILSPECVERSION))),
};
#endif

// Announce if anything loads this layer.  LAYERNAME is defined in Android.mk
class StaticLogMessage {
    public:
        StaticLogMessage(const char* msg) {
            ALOGI("%s", msg);
    }
};
StaticLogMessage
    g_initMessage("VK_LAYER_ANDROID_nullLayer" xstr(LAYERNAME) " loaded");

namespace {


// Minimal dispatch table for this simple layer
struct {
    PFN_vkGetDeviceProcAddr GetDeviceProcAddr;
    PFN_vkGetInstanceProcAddr GetInstanceProcAddr;
} g_VulkanDispatchTable;

template<class T>
VkResult getProperties(const uint32_t count, const T *properties, uint32_t *pCount,
                            T *pProperties) {
    uint32_t copySize;

    if (pProperties == NULL || properties == NULL) {
        *pCount = count;
        return VK_SUCCESS;
    }

    copySize = *pCount < count ? *pCount : count;
    memcpy(pProperties, properties, copySize * sizeof(T));
    *pCount = copySize;
    if (copySize < count) {
        return VK_INCOMPLETE;
    }

    return VK_SUCCESS;
}

static const VkLayerProperties LAYER_PROPERTIES = {
    LAYER_FULL_NAME,
    VK_MAKE_VERSION(1, 0, VK_HEADER_VERSION),
    1,
    "Layer: nullLayer" xstr(LAYERNAME),
};

VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceLayerProperties(uint32_t *pCount, VkLayerProperties *pProperties) {
    return getProperties<VkLayerProperties>(1, &LAYER_PROPERTIES, pCount, pProperties);
}

VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceLayerProperties(VkPhysicalDevice /* physicalDevice */, uint32_t *pCount,
                                                              VkLayerProperties *pProperties) {
    return getProperties<VkLayerProperties>(0, NULL, pCount, pProperties);
}

VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceExtensionProperties(const char* /* pLayerName */, uint32_t *pCount,
                                                                    VkExtensionProperties *pProperties) {
#ifdef DEBUGUTILSPECVERSION
  return getProperties<VkExtensionProperties>(1, &debug_utils_extension, pCount,
                                              pProperties);
#else
  return getProperties<VkExtensionProperties>(0, NULL, pCount, pProperties);
#endif
}

VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceExtensionProperties(VkPhysicalDevice /* physicalDevice */, const char* /* pLayerName */,
                                                                  uint32_t *pCount, VkExtensionProperties *pProperties) {
    return getProperties<VkExtensionProperties>(0, NULL, pCount, pProperties);
}

VKAPI_ATTR VkResult VKAPI_CALL nullCreateDevice(VkPhysicalDevice physicalDevice,
                                                const VkDeviceCreateInfo* pCreateInfo,
                                                const VkAllocationCallbacks* pAllocator,
                                                VkDevice* pDevice) {
    VkLayerDeviceCreateInfo *layerCreateInfo = (VkLayerDeviceCreateInfo*)pCreateInfo->pNext;
    const char *msg = "nullCreateDevice called in " LAYER_FULL_NAME;
    ALOGI("%s", msg);

    // Step through the pNext chain until we get to the link function
    while(layerCreateInfo && (layerCreateInfo->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO ||
                              layerCreateInfo->function != VK_LAYER_FUNCTION_LINK)) {
      layerCreateInfo = (VkLayerDeviceCreateInfo *)layerCreateInfo->pNext;
    }

    if(layerCreateInfo == NULL)
      return VK_ERROR_INITIALIZATION_FAILED;

    // Grab GDPA and GIPA for the next layer
    PFN_vkGetDeviceProcAddr gdpa = layerCreateInfo->u.pLayerInfo->pfnNextGetDeviceProcAddr;
    PFN_vkGetInstanceProcAddr gipa = layerCreateInfo->u.pLayerInfo->pfnNextGetInstanceProcAddr;

    // Track them in our dispatch table
    g_VulkanDispatchTable.GetDeviceProcAddr = gdpa;
    g_VulkanDispatchTable.GetInstanceProcAddr = gipa;

    // Advance the chain for next layer
    layerCreateInfo->u.pLayerInfo = layerCreateInfo->u.pLayerInfo->pNext;

    // Call the next layer
    PFN_vkCreateDevice createFunc = (PFN_vkCreateDevice)gipa(VK_NULL_HANDLE, "vkCreateDevice");
    VkResult ret = createFunc(physicalDevice, pCreateInfo, pAllocator, pDevice);

    return ret;
}

VKAPI_ATTR VkResult VKAPI_CALL nullCreateInstance(const VkInstanceCreateInfo* pCreateInfo,
                                                  const VkAllocationCallbacks* pAllocator,
                                                  VkInstance* pInstance) {
    VkLayerInstanceCreateInfo *layerCreateInfo = (VkLayerInstanceCreateInfo *)pCreateInfo->pNext;
    const char *msg = "nullCreateInstance called in " LAYER_FULL_NAME;
    ALOGI("%s", msg);

    // Step through the pNext chain until we get to the link function
    while(layerCreateInfo && (layerCreateInfo->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO ||
                              layerCreateInfo->function != VK_LAYER_FUNCTION_LINK)) {
      layerCreateInfo = (VkLayerInstanceCreateInfo *)layerCreateInfo->pNext;
    }

    if(layerCreateInfo == NULL)
      return VK_ERROR_INITIALIZATION_FAILED;

    // Grab GIPA for the next layer
    PFN_vkGetInstanceProcAddr gpa = layerCreateInfo->u.pLayerInfo->pfnNextGetInstanceProcAddr;

    // Track it in our dispatch table
    g_VulkanDispatchTable.GetInstanceProcAddr = gpa;

    // Advance the chain for next layer
    layerCreateInfo->u.pLayerInfo = layerCreateInfo->u.pLayerInfo->pNext;

    // Call the next layer
    PFN_vkCreateInstance createFunc = (PFN_vkCreateInstance)gpa(VK_NULL_HANDLE, "vkCreateInstance");
    VkResult ret = createFunc(pCreateInfo, pAllocator, pInstance);

    return ret;
}

VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice dev, const char* funcName) {

    const char* targetFunc = "vkCreateDevice";
    if (!strncmp(targetFunc, funcName, sizeof("vkCreateDevice"))) {
        return (PFN_vkVoidFunction)nullCreateDevice;
    }

    return (PFN_vkVoidFunction)g_VulkanDispatchTable.GetDeviceProcAddr(dev, funcName);
}

VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetInstanceProcAddr(VkInstance instance, const char* funcName) {

    const char* targetFunc = "vkCreateInstance";
    if (!strncmp(targetFunc, funcName, sizeof("vkCreateInstance"))) {
        return (PFN_vkVoidFunction)nullCreateInstance;
    }

    return (PFN_vkVoidFunction)g_VulkanDispatchTable.GetInstanceProcAddr(instance, funcName);
}

}  // namespace

// loader-layer interface v0, just wrappers since there is only a layer

__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(uint32_t *pCount,
                                                                  VkLayerProperties *pProperties) {
    return EnumerateInstanceLayerProperties(pCount, pProperties);
}

__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(VkPhysicalDevice physicalDevice, uint32_t *pCount,
                                                                VkLayerProperties *pProperties) {
    return EnumerateDeviceLayerProperties(physicalDevice, pCount, pProperties);
}

__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(const char *pLayerName, uint32_t *pCount,
                                                                      VkExtensionProperties *pProperties) {
    return EnumerateInstanceExtensionProperties(pLayerName, pCount, pProperties);
}

__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice,
                                                                    const char *pLayerName, uint32_t *pCount,
                                                                    VkExtensionProperties *pProperties) {
    return EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pCount, pProperties);
}

__attribute((visibility("default"))) VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice dev, const char *funcName) {
    return GetDeviceProcAddr(dev, funcName);
}

__attribute((visibility("default"))) VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char *funcName) {
    return GetInstanceProcAddr(instance, funcName);
}