# Copyright 2019-2021 Collabora, Ltd.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
# SPDX-License-Identifier: BSL-1.0
#
# Original Author:
# 2019-2021 Ryan Pavlik <ryan.pavlik@collabora.com>

#.rst:
# FindOpenXR
# ----------
#
# Find various parts of OpenXR 1.0.
#
# COMPONENTS
# ^^^^^^^^^^
#
# This module respects several COMPONENTS: ``headers``, ``loader``, ``registry``, ``specscripts``, and
# ``sdkscripts``. If no components are specified, ``headers`` and ``loader`` are assumed.
#
# Targets
# ^^^^^^^
# If the corresponding files are available and components are specified, the following imported targets
# are created:
#
# ``OpenXR::openxr_loader``
#  Link against to get the loader and headers. (Deprecated alias is ``OpenXR::Loader``)
#
# ``OpenXR::headers``
#  Link against to get only the headers. (Deprecated alias is ``OpenXR::Headers``)
#
# ``OpenXR::headers_no_prototypes``
#  Link against to get only the headers, plus the define to disable the prototypes. (Deprecated alias is ``OpenXR::HeadersNoPrototypes``)
#
# Cache variables
# ^^^^^^^^^^^^^^^
#
# The following cache variables may also be set to assist/control the operation of this module:
#
# Related to the ``OpenXR-SDK`` repo <https://github.com/KhronosGroup/OpenXR-SDK> (this is the repo with pre-built headers),
# the ``OpenXR-SDK-Source`` repo <https://github.com/KhronosGroup/OpenXR-SDK-Source>, or the internal Khronos GitLab ``openxr`` repo:
#
# ``OPENXR_SDK_SRC_DIR``
#  Path to the root of the ``openxr``, ``OpenXR-SDK``, or ``OpenXR-SDK-Source`` repo source.
# ``OPENXR_SDK_BUILD_DIR``
#  Path to the root of the build directory corresponding to the ``openxr``, ``OpenXR-SDK``, or ``OpenXR-SDK-Source`` repo.
#

# Normalize paths
foreach(PATHVAR OPENXR_SDK_SRC_DIR OPENXR_SDK_BUILD_DIR)
    if(${${PATHVAR}})
        file(TO_CMAKE_PATH ${${PATHVAR}} ${PATHVAR})
    endif()
endforeach()

# Set up cache variables
set(OPENXR_SDK_SRC_DIR
    "${OPENXR_SDK_SRC_DIR}"
    CACHE
        PATH
        "The root of your OpenXR-SDK, OpenXR-SDK-Source, or GitLab openxr source directory - see https://github.com/KhronosGroup/OpenXR-SDK"
)
set(OPENXR_SDK_BUILD_DIR
    "${OPENXR_SDK_BUILD_DIR}"
    CACHE
        PATH
        "The root of your OpenXR-SDK, OpenXR-SDK-Source, or GitLab openxr build directory."
)

# Currently only explicitly supporting 1.0
set(OPENXR_MAJOR_VER 1)
set(OPENXR_MINOR_VER 0)
set(OPENXR_OUT_DIR ${OPENXR_MAJOR_VER}.${OPENXR_MINOR_VER})
if(WIN32)
    set(OPENXR_STATIC ON)
else()
    set(OPENXR_STATIC OFF)
endif()

find_package(PkgConfig QUIET)

if(PKG_CONFIG_FOUND)
    pkg_check_modules(PC_OPENXR QUIET openxr)
endif()

###
# Assemble lists of places to look
###
set(_oxr_include_search_dirs)
set(_oxr_loader_search_dirs)
set(_oxr_specscripts_search_dirs)
set(_oxr_sdkscripts_search_dirs)
set(_oxr_registry_search_dirs)

find_package(OpenXR CONFIG)
if(OpenXR_INCLUDE_DIR)
    list(APPEND _oxr_include_search_dirs ${OpenXR_INCLUDE_DIR})
endif()
# These are macros to avoid a new variable scope.

# Macro to extend search locations given a source dir.
macro(_oxr_handle_potential_root_src_dir _dir)
    list(APPEND _oxr_include_search_dirs "${_dir}/include"
                "${_dir}/specification/out/${OPENXR_OUT_DIR}")
    list(APPEND _oxr_registry_search_dirs "${_dir}/specification/registry/")
    list(APPEND _oxr_specscripts_search_dirs "${_dir}/specification/scripts/")
    list(APPEND _oxr_sdkscripts_search_dirs "${_dir}/src/scripts/")
endmacro()

# Macro to extend search locations given a build dir.
macro(_oxr_handle_potential_root_build_dir _dir)
    list(APPEND _oxr_include_search_dirs "${_dir}/include")
    list(APPEND _oxr_loader_search_dirs "${_dir}/src/loader")
endmacro()

if(PC_OPENXR_FOUND)
    list(APPEND _oxr_include_search_dirs ${PC_OPENXR_INCLUDE_DIRS})
    list(APPEND _oxr_loader_search_dirs ${PC_OPENXR_LIBRARY_DIRS})
endif()

set(_oxr_lib_path_suffixes)
if(WIN32)
    unset(_UWP_SUFFIX)
    if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
        set(_UWP_SUFFIX _uwp)
    endif()
    if(CMAKE_GENERATOR_PLATFORM_UPPER MATCHES "ARM.*")
        if(CMAKE_SIZEOF_VOID_P EQUAL 8)
            set(_PLATFORM ARM64)
        else()
            set(_PLATFORM ARM)
        endif()
    else()
        if(CMAKE_SIZEOF_VOID_P EQUAL 8)
            set(_PLATFORM x64)
        else()
            set(_PLATFORM Win32)
        endif()
    endif()
    list(APPEND _oxr_lib_path_suffixes ${_PLATFORM}${_UWP_SUFFIX}/lib)
    # old builds
    if(MSVC_VERSION GREATER 1919)
        list(APPEND _oxr_lib_path_suffixes msvs2019_static/lib)
    endif()
endif()

# User-supplied directories
if(OPENXR_SDK_SRC_DIR)
    _oxr_handle_potential_root_src_dir(${OPENXR_SDK_SRC_DIR})
endif()
if(OPENXR_SDK_BUILD_DIR)
    _oxr_handle_potential_root_build_dir(${OPENXR_SDK_BUILD_DIR})
endif()

# Guessed build dir based on src dir
if(OPENXR_SDK_SRC_DIR)
    _oxr_handle_potential_root_build_dir("${OPENXR_SDK_SRC_DIR}/build")
endif()

# Guesses of sibling directories by name - last resort.
foreach(_dir "${CMAKE_SOURCE_DIR}/../OpenXR-SDK"
        "${CMAKE_SOURCE_DIR}/../OpenXR-SDK-Source"
        "${CMAKE_SOURCE_DIR}/../openxr")
    _oxr_handle_potential_root_src_dir(${_dir})
    _oxr_handle_potential_root_build_dir(${_dir}/build)
endforeach()

###
# Search for includes
###

# This must also contain openxr/openxr_platform.h
find_path(
    OPENXR_OPENXR_INCLUDE_DIR
    NAMES openxr/openxr.h
    PATHS ${_oxr_include_search_dirs})

find_path(
    OPENXR_PLATFORM_DEFINES_INCLUDE_DIR
    NAMES openxr/openxr_platform_defines.h
    PATHS ${_oxr_include_search_dirs})

###
# Search for other parts
###
find_library(
    OPENXR_loader_LIBRARY
    NAMES libopenxr_loader openxr_loader
          openxr_loader-${OPENXR_MAJOR_VER}_${OPENXR_MINOR_VER}
    PATHS ${_oxr_loader_search_dirs}
    PATH_SUFFIXES ${_oxr_lib_path_suffixes})

find_path(
    OPENXR_SPECSCRIPTS_DIR
    NAMES reg.py
    PATHS ${_oxr_specscripts_search_dirs})

find_path(
    OPENXR_SDKSCRIPTS_DIR
    NAMES automatic_source_generator.py
    PATHS ${_oxr_sdkscripts_search_dirs})

find_file(
    OPENXR_REGISTRY
    NAMES xr.xml
    PATHS ${_oxr_registry_search_dirs})

###
# Fix up list of requested components
###
if(NOT OpenXR_FIND_COMPONENTS)
    # Default to headers and loader
    set(OpenXR_FIND_COMPONENTS headers loader)
endif()

if("${OpenXR_FIND_COMPONENTS}" MATCHES "scripts"
   AND NOT "${OpenXR_FIND_COMPONENTS}" MATCHES "registry")
    # scripts depend on registry (mostly).
    list(APPEND OpenXR_FIND_COMPONENTS registry)
endif()

if("${OpenXR_FIND_COMPONENTS}" MATCHES "loader"
   AND NOT "${OpenXR_FIND_COMPONENTS}" MATCHES "headers")
    # loader depends on headers.
    list(APPEND OpenXR_FIND_COMPONENTS headers)
endif()

if("${OpenXR_FIND_COMPONENTS}" MATCHES "sdkscripts"
   AND NOT "${OpenXR_FIND_COMPONENTS}" MATCHES "specscripts")
    # source scripts depend on spec scripts.
    list(APPEND OpenXR_FIND_COMPONENTS specscripts)
endif()

###
# Determine if the various requested components are found.
###
set(_oxr_component_required_vars)
foreach(_comp IN LISTS OpenXR_FIND_COMPONENTS)

    if(${_comp} STREQUAL "headers")
        if(TARGET OpenXR::headers)
            set(OpenXR_headers_FOUND TRUE)
            mark_as_advanced(OPENXR_OPENXR_INCLUDE_DIR
                             OPENXR_PLATFORM_DEFINES_INCLUDE_DIR)
        else()
            list(APPEND _oxr_component_required_vars OPENXR_OPENXR_INCLUDE_DIR
                        OPENXR_PLATFORM_DEFINES_INCLUDE_DIR)
            if(EXISTS "${OPENXR_OPENXR_INCLUDE_DIR}/openxr/openxr.h"
               AND EXISTS
                   "${OPENXR_OPENXR_INCLUDE_DIR}/openxr/openxr_platform.h"
               AND EXISTS
                   "${OPENXR_OPENXR_INCLUDE_DIR}/openxr/openxr_reflection.h"
               AND EXISTS
                   "${OPENXR_PLATFORM_DEFINES_INCLUDE_DIR}/openxr/openxr_platform_defines.h"
            )
                set(OpenXR_headers_FOUND TRUE)
                mark_as_advanced(OPENXR_OPENXR_INCLUDE_DIR
                                 OPENXR_PLATFORM_DEFINES_INCLUDE_DIR)
            else()
                set(OpenXR_headers_FOUND FALSE)
            endif()
        endif()

    elseif(${_comp} STREQUAL "loader")
        if(TARGET OpenXR::openxr_loader)
            set(OpenXR_loader_FOUND TRUE)
            mark_as_advanced(OPENXR_loader_LIBRARY)
            set(OPENXR_loader_LIBRARY OpenXR::openxr_loader)
        else()
            list(APPEND _oxr_component_required_vars OPENXR_loader_LIBRARY)
            if(EXISTS "${OPENXR_loader_LIBRARY}")
                set(OpenXR_loader_FOUND TRUE)
                mark_as_advanced(OPENXR_loader_LIBRARY)
            else()
                set(OpenXR_loader_FOUND FALSE)
            endif()
        endif()

    elseif(${_comp} STREQUAL "registry")
        list(APPEND _oxr_component_required_vars OPENXR_REGISTRY)
        if(EXISTS "${OPENXR_REGISTRY}")
            set(OpenXR_registry_FOUND TRUE)
            mark_as_advanced(OPENXR_REGISTRY)
        else()
            set(OpenXR_registry_FOUND FALSE)
        endif()

    elseif(${_comp} STREQUAL "specscripts")
        list(APPEND _oxr_component_required_vars OPENXR_SPECSCRIPTS_DIR)
        if(EXISTS "${OPENXR_SPECSCRIPTS_DIR}/generator.py"
           AND EXISTS "${OPENXR_SPECSCRIPTS_DIR}/reg.py"
           AND EXISTS "${OPENXR_SPECSCRIPTS_DIR}/genxr.py")
            set(OpenXR_specscripts_FOUND TRUE)
            mark_as_advanced(OPENXR_SPECSCRIPTS_DIR)
        else()
            set(OpenXR_specscripts_FOUND FALSE)
        endif()

    elseif(${_comp} STREQUAL "sdkscripts")
        list(APPEND _oxr_component_required_vars OPENXR_SDKSCRIPTS_DIR)
        if(EXISTS "${OPENXR_SDKSCRIPTS_DIR}/automatic_source_generator.py"
           AND EXISTS "${OPENXR_SDKSCRIPTS_DIR}/src_genxr.py")
            set(OpenXR_sdkscripts_FOUND TRUE)
            mark_as_advanced(OPENXR_SDKSCRIPTS_DIR)
        else()
            set(OpenXR_sdkscripts_FOUND FALSE)
        endif()

    else()
        message(WARNING "${_comp} is not a recognized OpenXR component")
        set(OpenXR_${_comp}_FOUND FALSE)
    endif()
endforeach()
unset(_comp)

###
# FPHSA call
###
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
    OpenXR
    REQUIRED_VARS
    ${_oxr_component_required_vars}
    HANDLE_COMPONENTS
    FAIL_MESSAGE
    "Could NOT find the requested OpenXR components, try setting OPENXR_SDK_SRC_DIR and/or OPENXR_SDK_BUILD_DIR"
)

###
# If found, set variables and create targets
###

# Component: headers
if(OpenXR_headers_FOUND AND NOT TARGET OpenXR::headers)
    set(OPENXR_INCLUDE_DIRS ${OPENXR_OPENXR_INCLUDE_DIR}
                            ${OPENXR_PLATFORM_DEFINES_INCLUDE_DIR})
    list(REMOVE_DUPLICATES OPENXR_INCLUDE_DIRS)

    # This target just provides the headers with prototypes.
    # You may have linker errors if you try using this
    # without linking to the loader
    add_library(OpenXR::headers INTERFACE IMPORTED)
    # This is not duplication: interface system dirs just marks as system.
    set_target_properties(
        OpenXR::headers
        PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
                   "${OPENXR_INCLUDE_DIRS}" INTERFACE_INCLUDE_DIRECTORIES
                   "${OPENXR_INCLUDE_DIRS}")
endif()

# Back-compat
if(TARGET OpenXR::headers AND NOT TARGET OpenXR::Headers)
    add_library(OpenXR::Headers INTERFACE IMPORTED)
    set_target_properties(OpenXR::Headers PROPERTIES INTERFACE_LINK_LIBRARIES
                                                     "OpenXR::headers")
endif()

# This target just provides the headers, without any prototypes.
# Finding and loading the loader at runtime is your problem.
if(TARGET OpenXR::headers AND NOT TARGET OpenXR::headers_no_prototypes)
    add_library(OpenXR::headers_no_prototypes INTERFACE IMPORTED)
    set_target_properties(
        OpenXR::headers_no_prototypes
        PROPERTIES INTERFACE_COMPILE_DEFINITIONS "XR_NO_PROTOTYPES"
                   INTERFACE_LINK_LIBRARIES "OpenXR::headers")
endif()
if(TARGET OpenXR::headers AND NOT TARGET OpenXR::HeadersNoPrototypes)
    add_library(OpenXR::HeadersNoPrototypes INTERFACE IMPORTED)
    set_target_properties(
        OpenXR::HeadersNoPrototypes
        PROPERTIES INTERFACE_COMPILE_DEFINITIONS "XR_NO_PROTOTYPES"
                   INTERFACE_LINK_LIBRARIES "OpenXR::headers")
endif()

# Component: loader
if(OpenXR_loader_FOUND
   AND OpenXR_headers_FOUND
   AND NOT TARGET OpenXR::openxr_loader)
    set(_oxr_loader_interface_libs OpenXR::headers)
    # include dl library for statically-linked loader
    get_filename_component(_oxr_loader_ext ${OPENXR_loader_LIBRARY} EXT)
    if(_oxr_loader_ext STREQUAL CMAKE_STATIC_LIBRARY_SUFFIX)
        set(_oxr_loader_lib_type STATIC)
        list(APPEND _oxr_loader_interface_libs ${CMAKE_DL_LIBS})
    else()
        set(_oxr_loader_lib_type SHARED)
    endif()

    add_library(OpenXR::openxr_loader UNKNOWN IMPORTED)
    set_target_properties(
        OpenXR::openxr_loader
        PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION
                   "${OPENXR_loader_LIBRARY}" INTERFACE_LINK_LIBRARIES
                   "${_oxr_loader_interface_libs}")

endif()

# Back-compat
if(TARGET OpenXR::openxr_loader AND NOT TARGET OpenXR::Loader)
    add_library(OpenXR::Loader INTERFACE IMPORTED)
    set_target_properties(OpenXR::Loader PROPERTIES INTERFACE_LINK_LIBRARIES
                                                    "OpenXR::openxr_loader")
endif()
