#
# SPDX-FileCopyrightText: 2020-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
# Author: Renato Araujo Oliveira Filho <renato.araujo@kdab.com>
#
# SPDX-License-Identifier: BSD-3-Clause
#

if(NOT ${PROJECT_NAME}_PYTHON_BINDINGS_INSTALL_PREFIX)
    # cmake-lint: disable=C0103
    set(${PROJECT_NAME}_PYTHON_BINDINGS_INSTALL_PREFIX
        ${CMAKE_INSTALL_PREFIX}
        CACHE FILEPATH "Custom path to install python bindings."
    )
endif()

message(STATUS "PYTHON INSTALL PREFIX ${${PROJECT_NAME}_PYTHON_BINDINGS_INSTALL_PREFIX}")

if(WIN32)
    set(PATH_SEP "\;")
else()
    set(PATH_SEP ":")
endif()
#Qt6 requires C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#remove noisy compiler warnings (as the generated code is not necessarily super-warning-free)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-all -Wno-extra")
endif()

# On macOS, check if Qt is a framework build. This affects how include paths should be handled.
get_target_property(QtCore_is_framework Qt6::Core FRAMEWORK)
if(QtCore_is_framework)
    # Get the path to the framework dir.
    list(GET Qt6Core_INCLUDE_DIRS 0 QT_INCLUDE_DIR)
    get_filename_component(QT_FRAMEWORK_INCLUDE_DIR "${QT_INCLUDE_DIR}/../" ABSOLUTE)

    # QT_INCLUDE_DIR points to the QtCore.framework directory, so we need to adjust this to point
    # to the actual include directory, which has include files for non-framework parts of Qt.
    get_filename_component(QT_INCLUDE_DIR "${QT_INCLUDE_DIR}/../../include" ABSOLUTE)
endif()

# Flags that we will pass to shiboken-generator
# --generator-set=shiboken:  tells the generator that we want to use shiboken to generate code,
#                            a doc generator is also available
# --enable-parent-ctor-heuristic: Enable heuristics to detect parent relationship on constructors,
#                           this try to guess parent ownership based on the arguments of the constructors
# --enable-pyside-extensionsL: This will generate code for Qt based classes, adding extra attributes,
#                           like signal, slot;
# --enable-return-value-heuristic: Similar as --enable-parent-ctor-heuristic this use some logic to guess
#                           parent child relationship based on the returned argument
# --use-isnull-as-nb_nonzero: If a class have an isNull() const method, it will be used to compute
#                           the value of boolean casts.
#                           Example, QImage::isNull() will be used when on python side you do `if (myQImage)`
set(GENERATOR_EXTRA_FLAGS
    --generator-set=shiboken
    --enable-parent-ctor-heuristic
    --enable-pyside-extensions
    --enable-return-value-heuristic
    --use-isnull-as-nb_nonzero
    -std=c++${CMAKE_CXX_STANDARD}
)

# 2017-04-24 The protected hack can unfortunately not be disabled, because
# Clang does produce linker errors when we disable the hack.
# But the ugly workaround in Python is replaced by a shiboken change.
if(WIN32 OR DEFINED AVOID_PROTECTED_HACK)
    set(GENERATOR_EXTRA_FLAGS ${GENERATOR_EXTRA_FLAGS} --avoid-protected-hack)
    add_definitions(-DAVOID_PROTECTED_HACK)
endif()

# Replace all semicolons in a string with this platform's path separator
macro(make_path varname)
    # accepts any number of path variables
    string(REPLACE ";" "${PATH_SEP}" ${varname} "${ARGN}")
endmacro()

# Creates a PySide module target based on the arguments
# This will:
# 1 - Create a Cmake custom-target that call shiboken-generator passign the correct arguments
# 2 - Create a Cmake library target called "Py${libraryName}" the output name of this target
#     will be changed to match PySide template
# Args:
#   libraryName - The name of the output module
#   typesystemPaths - A list of paths where shiboken should look for typesystem files
#   includePaths - Include paths necessary to parse your class. *This is not the same as build*
#   outputSource - The files that will be generated by shiboken
#   targetIncludeDirs - This will be passed to target_include_directories
#   targetLinkLibraries - This will be passed to targetLinkLibraries
#   globalInclude - A header-file that contains all classes that will be generated
#   typesystemXML - The target binding typesystem (that should be the full path)
#   dependsArg - This var will be passed to add_custom_command(DEPENDS) so a new generation will be
#             trigger if one of these files changes
#   moduleOutputDir - Where the library file should be stored
macro(
    create_python_bindings
    libraryName
    typesystemPaths
    includePaths
    outputSource
    targetIncludeDirs
    targetLinkLibraries
    globalInclude
    typesystemXML
    dependsArg
    moduleOutputDir
)

    # Transform the path separators into something shiboken understands.
    make_path(shiboken_include_dirs ${includePaths})
    make_path(shiboken_typesystem_dirs ${typesystemPaths})
    get_property(
        raw_python_dir_include_dirs
        DIRECTORY
        PROPERTY INCLUDE_DIRECTORIES
    )
    make_path(python_dir_include_dirs ${raw_python_dir_include_dirs})
    set(shiboken_include_dirs "${shiboken_include_dirs}${PATH_SEP}${python_dir_include_dirs}")

    set(shiboken_framework_include_dirs_option "")
    if(CMAKE_HOST_APPLE)
        set(shiboken_framework_include_dirs "${QT_FRAMEWORK_INCLUDE_DIR}")
        make_path(shiboken_framework_include_dirs ${shiboken_framework_include_dirs})
        set(shiboken_framework_include_dirs_option "--framework-include-paths=${shiboken_framework_include_dirs}")
    endif()
    set_property(SOURCE ${outputSource} PROPERTY SKIP_AUTOGEN ON)
    add_custom_command(
        OUTPUT ${outputSource}
        COMMAND
            $<TARGET_PROPERTY:Shiboken6::shiboken,LOCATION> ${GENERATOR_EXTRA_FLAGS} ${globalInclude}
            --include-paths=${shiboken_include_dirs} --typesystem-paths=${shiboken_typesystem_dirs}
            ${shiboken_framework_include_dirs_option} --output-directory=${CMAKE_CURRENT_BINARY_DIR} ${typesystemXML}
        DEPENDS ${typesystemXML} ${dependsArg}
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        COMMENT "Running generator for ${libraryName} binding..."
    )

    set(TARGET_NAME "Py${libraryName}")
    set(MODULE_NAME "${libraryName}")
    add_library(${TARGET_NAME} MODULE ${outputSource})

    set_target_properties(
        ${TARGET_NAME}
        PROPERTIES PREFIX ""
                   OUTPUT_NAME ${MODULE_NAME}
                   LIBRARY_OUTPUT_DIRECTORY ${moduleOutputDir}
    )
    if(APPLE)
        set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@loader_path")
    elseif(NOT WIN32) #ie. linux
        set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN")
    endif()

    if(WIN32)
        set_target_properties(${TARGET_NAME} PROPERTIES SUFFIX ".pyd")
    endif()

    target_include_directories(${TARGET_NAME} PUBLIC ${targetIncludeDirs})

    target_link_libraries(${TARGET_NAME} ${targetLinkLibraries} PySide6::pyside6 Shiboken6::libshiboken)
    target_compile_definitions(${TARGET_NAME} PRIVATE Py_LIMITED_API=0x03050000)
    if(APPLE)
        set_property(
            TARGET ${TARGET_NAME}
            APPEND
            PROPERTY LINK_FLAGS "-undefined dynamic_lookup"
        )
    endif()
    install(TARGETS ${TARGET_NAME} LIBRARY DESTINATION ${${PROJECT_NAME}_PYTHON_BINDINGS_INSTALL_PREFIX})
endmacro()
