#!/usr/bin/python3 -i
#
# Copyright (c) 2015-2023 The Khronos Group Inc.
# Copyright (c) 2015-2023 Valve Corporation
# Copyright (c) 2015-2023 LunarG, Inc.
# Copyright (c) 2015-2023 Google Inc.
# Copyright (c) 2023-2023 RasterGrid Kft.
#
# 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.

import os
from generators.base_generator import BaseGenerator
from generators.generator_utils import PlatformGuardHelper

class ObjectTypesOutputGenerator(BaseGenerator):
    def __init__(self):
        BaseGenerator.__init__(self)

    def generate(self):
        # Helper for VkDebugReportObjectTypeEXT
        # Maps [ 'VkBuffer' : 'VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT' ]
        # Will be 'VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT' if no type
        debugReportObject = dict()

        # Search all fields of the Enum to see if has a DEBUG_REPORT_OBJECT
        for handle in self.vk.handles.values():
            debugObjects = ([enum.name for enum in self.vk.enums['VkDebugReportObjectTypeEXT'].fields if f'{handle.type[3:]}_EXT' in enum.name])
            object = 'VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT' if len(debugObjects) == 0 else debugObjects[0]
            debugReportObject[handle.name] = object

        out = []
        out.append(f'''// *** THIS FILE IS GENERATED - DO NOT EDIT ***
            // See {os.path.basename(__file__)} for modifications

            /***************************************************************************
            *
            * Copyright (c) 2015-2023 The Khronos Group Inc.
            * Copyright (c) 2015-2023 Valve Corporation
            * Copyright (c) 2015-2023 LunarG, Inc.
            * Copyright (c) 2015-2023 Google Inc.
            *
            * 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.
            ****************************************************************************/\n''')
        out.append('// NOLINTBEGIN') # Wrap for clang-tidy to ignore
        out.append('// clang-format off')
        out.append('''
#pragma once
#include "utils/cast_utils.h"
\n''')

        out.append('''
// Object Type enum for validation layer internal object handling
typedef enum VulkanObjectType {
    kVulkanObjectTypeUnknown = 0,\n''')
        for count, handle in enumerate(self.vk.handles.values(), start=1):
            out.append(f'    kVulkanObjectType{handle.name[2:]} = {count},\n')
        out.append(f'    kVulkanObjectTypeMax = {len(self.vk.handles) + 1}\n')
        out.append('} VulkanObjectType;\n')

        out.append('''
// Array of object name strings for OBJECT_TYPE enum conversion
static const char * const object_string[kVulkanObjectTypeMax] = {
    "VkNonDispatchableHandle",\n''')
        out.extend([f'    "{handle.name}",\n' for handle in self.vk.handles.values()])
        out.append('};\n')

        out.append('''
// Helper array to get Vulkan VK_EXT_debug_report object type enum from the internal layers version
const VkDebugReportObjectTypeEXT get_debug_report_enum[] = {
    VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, // kVulkanObjectTypeUnknown\n''')
        out.extend([f'    {debugReportObject[handle.name]},   // kVulkanObjectType{handle.name[2:]}\n' for handle in self.vk.handles.values()])
        out.append('};\n')

        out.append('''
// Helper function to get Official Vulkan VkObjectType enum from the internal layers version
static inline VkObjectType ConvertVulkanObjectToCoreObject(VulkanObjectType internal_type) {
    switch (internal_type) {\n''')
        out.extend([f'        case kVulkanObjectType{handle.name[2:]}: return {handle.type};\n' for handle in self.vk.handles.values()])
        out.append('''        default: return VK_OBJECT_TYPE_UNKNOWN;
    }
}\n''')

        out.append('''
// Helper function to get internal layers object ids from the official Vulkan VkObjectType enum
static inline VulkanObjectType ConvertCoreObjectToVulkanObject(VkObjectType vulkan_object_type) {
    switch (vulkan_object_type) {\n''')
        out.extend([f'        case {handle.type}: return kVulkanObjectType{handle.name[2:]};\n' for handle in self.vk.handles.values()])
        out.append('''        default: return kVulkanObjectTypeUnknown;
    }
}\n''')

        out.append('''
static inline VkDebugReportObjectTypeEXT ConvertCoreObjectToDebugReportObject(VkObjectType core_report_obj) {
    switch (core_report_obj) {
        case VK_OBJECT_TYPE_UNKNOWN: return VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT;\n''')
        for handle in self.vk.handles.values():
            object = debugReportObject[handle.name]
            if object != 'VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT':
                out.append(f'        case {handle.type}: return {object};\n')
        out.append('''        default: return VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT;
    }
}\n''')

        out.append('''
// Helper function to get Instance object types
static inline bool IsInstanceVkObjectType(VkObjectType type) {
    switch (type) {\n''')
        out.extend([f'        case {handle.type}:\n' for handle in self.vk.handles.values() if handle.instance])
        out.append('''            return true;''')
        out.append('''        default: return false;
    }
}\n''')

        out.append('''
// Traits objects from each type statically map from Vk<handleType> to the various enums
template <typename VkType> struct VkHandleInfo {};
template <VulkanObjectType id> struct VulkanObjectTypeInfo {};

// The following line must match the vulkan_core.h condition guarding VK_DEFINE_NON_DISPATCHABLE_HANDLE
#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || \
    defined(_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
#define TYPESAFE_NONDISPATCHABLE_HANDLES
#else
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkNonDispatchableHandle)

template <> struct VkHandleInfo<VkNonDispatchableHandle> {
    static const VulkanObjectType kVulkanObjectType = kVulkanObjectTypeUnknown;
    static const VkDebugReportObjectTypeEXT kDebugReportObjectType = VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT;
    static const VkObjectType kVkObjectType = VK_OBJECT_TYPE_UNKNOWN;
    static const char* Typename() {
        return "VkNonDispatchableHandle";
    }
};
template <> struct VulkanObjectTypeInfo<kVulkanObjectTypeUnknown> {
    typedef VkNonDispatchableHandle Type;
};

#endif //  VK_DEFINE_HANDLE logic duplication\n''')
        guard_helper = PlatformGuardHelper()
        for handle in [x for x in self.vk.handles.values() if x.dispatchable]:
            out.extend(guard_helper.add_guard(handle.protect))
            out.append(f'''
template <> struct VkHandleInfo<{handle.name}> {{
    static const VulkanObjectType kVulkanObjectType = kVulkanObjectType{handle.name[2:]};
    static const VkDebugReportObjectTypeEXT kDebugReportObjectType = {debugReportObject[handle.name]};
    static const VkObjectType kVkObjectType = {handle.type};
    static const char* Typename() {{
        return "{handle.name}";
    }}
}};
template <> struct VulkanObjectTypeInfo<kVulkanObjectType{handle.name[2:]}> {{
    typedef {handle.name} Type;
}};\n''')
        out.extend(guard_helper.add_guard(None))
        out.append('#ifdef TYPESAFE_NONDISPATCHABLE_HANDLES\n')

        for handle in [x for x in self.vk.handles.values() if not x.dispatchable]:
            out.extend(guard_helper.add_guard(handle.protect))
            out.append(f'''
template <> struct VkHandleInfo<{handle.name}> {{
    static const VulkanObjectType kVulkanObjectType = kVulkanObjectType{handle.name[2:]};
    static const VkDebugReportObjectTypeEXT kDebugReportObjectType = {debugReportObject[handle.name]};
    static const VkObjectType kVkObjectType = {handle.type};
    static const char* Typename() {{
        return "{handle.name}";
    }}
}};
template <> struct VulkanObjectTypeInfo<kVulkanObjectType{handle.name[2:]}> {{
    typedef {handle.name} Type;
}};\n''')
        out.extend(guard_helper.add_guard(None))
        out.append('#endif // TYPESAFE_NONDISPATCHABLE_HANDLES\n')

        out.append('''
struct VulkanTypedHandle {
    uint64_t handle;
    VulkanObjectType type;
    template <typename Handle>
    VulkanTypedHandle(Handle handle_, VulkanObjectType type_) :
        handle(CastToUint64(handle_)),
        type(type_) {
#ifdef TYPESAFE_NONDISPATCHABLE_HANDLES
        // For 32 bit it's not always safe to check for traits <-> type
        // as all non-dispatchable handles have the same type-id and thus traits,
        // but on 64 bit we can validate the passed type matches the passed handle
        assert(type == VkHandleInfo<Handle>::kVulkanObjectType);
#endif // TYPESAFE_NONDISPATCHABLE_HANDLES
    }
    template <typename Handle>
    Handle Cast() const {
#ifdef TYPESAFE_NONDISPATCHABLE_HANDLES
        assert(type == VkHandleInfo<Handle>::kVulkanObjectType);
#endif // TYPESAFE_NONDISPATCHABLE_HANDLES
        return CastFromUint64<Handle>(handle);
    }
    VulkanTypedHandle() :
        handle(CastToUint64(VK_NULL_HANDLE)),
        type(kVulkanObjectTypeUnknown) {}
    operator bool() const { return handle != 0; }
};\n''')
        out.append('// clang-format on')
        out.append('// NOLINTEND') # Wrap for clang-tidy to ignore
        self.write("".join(out))
