cmake_minimum_required(VERSION 3.16)
cmake_policy(VERSION 3.16)

# Enable policy to run automoc on generated files.
if(POLICY CMP0071)
  cmake_policy(SET CMP0071 NEW)
endif()


# ================================ General configuration ======================================

project(PySide6QtAds)

# Find Python
find_package (Python3 COMPONENTS Interpreter)
if(NOT Python3_Interpreter_FOUND)
    message(FATAL_ERROR "Python3 not found")
endif()
message(STATUS "Using python: ${Python3_EXECUTABLE}")

# https://bugreports.qt.io/browse/QTBUG-89754
set(OpenGL_GL_PREFERENCE "LEGACY")

# Find the required Qt packages
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)

# Set CPP standard to C++11 minimum.
set(CMAKE_CXX_STANDARD 11)

# The C++ project library for which we will create bindings.
set(qtads_subdir "Qt-Advanced-Docking-System")
set(qtads_dir ${CMAKE_CURRENT_SOURCE_DIR}/${qtads_subdir})
add_subdirectory(${qtads_subdir} EXCLUDE_FROM_ALL)
set(project_library "qtadvanceddocking")

# The name of the generated bindings module (as imported in Python).
set(bindings_library "PySide6QtAds")

# The header file with all the types and functions for which bindings will be generated.
# Usually it simply includes other headers of the library you are creating bindings for.
set(wrapped_header ${CMAKE_CURRENT_SOURCE_DIR}/src/bindings.h)

# The typesystem xml file which defines the relationships between the C++ types / functions
# and the corresponding Python equivalents.
set(typesystem_file ${CMAKE_CURRENT_SOURCE_DIR}/src/bindings.xml)

# Specify which C++ files will be generated by shiboken. This includes the module wrapper
# and a '.cpp' file per C++ type. These are needed for generating the module shared
# library.
set(generated_sources    
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockareatabbar_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockareatitlebar_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockareawidget_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockcomponentsfactory_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockcontainerwidget_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockfocuscontroller_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockingstatereader_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockmanager_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockoverlay_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockoverlaycross_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdocksplitter_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockwidget_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockwidgettab_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_celidinglabel_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cfloatingdockcontainer_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cfloatingdragpreview_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_ciconprovider_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cspacerwidget_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_ctitlebarbutton_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_ifloatingwidget_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/pyside6qtads_module_wrapper.cpp
    )


# ================================== Shiboken detection ======================================


# Macro to get various pyside / python include / link flags and paths.
# Uses the not entirely supported utils/pyside_config.py file.
macro(pyside_config option output_var)
    if(${ARGC} GREATER 2)
        set(is_list ${ARGV2})
    else()
        set(is_list "")
    endif()

    execute_process(
      COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/pyside_config.py"
              ${option}
      OUTPUT_VARIABLE ${output_var}
      OUTPUT_STRIP_TRAILING_WHITESPACE)

    if ("${${output_var}}" STREQUAL "")
        message(FATAL_ERROR "Error: Calling ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/pyside_config.py ${option} returned no output.")
    endif()
    if(is_list)
        string (REPLACE " " ";" ${output_var} "${${output_var}}")
    endif()
endmacro()

# Query for the shiboken generator path, Python path, include paths and linker flags.
pyside_config(--shiboken-module-path shiboken_module_path)
pyside_config(--shiboken-generator-path shiboken_generator_path)
pyside_config(--pyside-path pyside_path)
pyside_config(--pyside-include-path pyside_include_dir 1)
pyside_config(--python-include-path python_include_dir)
pyside_config(--shiboken-generator-include-path shiboken_include_dir 1)
pyside_config(--shiboken-module-shared-libraries-cmake shiboken_shared_libraries 0)
pyside_config(--python-link-flags-cmake python_linking_data 0)
pyside_config(--pyside-shared-libraries-cmake pyside_shared_libraries 0)

set(shiboken_path "${shiboken_generator_path}/shiboken6${CMAKE_EXECUTABLE_SUFFIX}")
if(NOT EXISTS ${shiboken_path})
    message(FATAL_ERROR "Shiboken executable not found at path: ${shiboken_path}")
endif()


# ==================================== RPATH configuration ====================================

set(CMAKE_SKIP_BUILD_RPATH TRUE)

if(UNIX AND NOT APPLE)
    set(CMAKE_INSTALL_RPATH "$ORIGIN/lib:$ORIGIN/../PySide6/:$ORIGIN/../PySide6/Qt/lib:$ORIGIN/../shiboken6")
endif()

if(APPLE)
    set(CMAKE_INSTALL_RPATH "@loader_path/lib;@loader_path/../PySide6/;@loader_path/../PySide6/Qt/lib;@loader_path/../shiboken6")
    set(MACOSX_RPATH TRUE)
endif()

# ============================== Qt Includes for project_library ==============================

# Get the relevant Qt include dirs, to pass them on to shiboken.
set(QT_INCLUDE_DIR "")
get_target_property(QT_INCLUDE_DIR_LIST Qt6::Core INTERFACE_INCLUDE_DIRECTORIES)
foreach(_Q ${QT_INCLUDE_DIR_LIST})
    if(NOT "${_Q}" MATCHES "QtCore$")
        set(QT_INCLUDE_DIR "${_Q}")
    endif()
endforeach()
if(QT_INCLUDE_DIR STREQUAL "")
    message(FATAL_ERROR "Unable to obtain the Qt include directory")
endif()

set(QT_INCLUDES "")
list(APPEND QT_INCLUDES "-I${QT_INCLUDE_DIR}")

get_property(QT_CORE_INCLUDE_DIRS TARGET Qt6::Core PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
foreach(INCLUDE_DIR ${QT_CORE_INCLUDE_DIRS})
    list(APPEND QT_INCLUDES "-I${INCLUDE_DIR}")
endforeach()

get_property(QT_GUI_INCLUDE_DIRS TARGET Qt6::Gui PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
foreach(INCLUDE_DIR ${QT_GUI_INCLUDE_DIRS})
    list(APPEND QT_INCLUDES "-I${INCLUDE_DIR}")
endforeach()

get_property(QT_WIDGETS_INCLUDE_DIRS TARGET Qt6::Widgets PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
foreach(INCLUDE_DIR ${QT_WIDGETS_INCLUDE_DIRS})
    list(APPEND QT_INCLUDES "-I${INCLUDE_DIR}")
endforeach()

# Check if Qt is a framework build on macOS. 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 Qt framework dir.
    set(QT_FRAMEWORK_INCLUDE_DIR "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_LIBS}")
    message(STATUS "*** QT_FRAMEWORK_INCLUDE_DIR is ${QT_FRAMEWORK_INCLUDE_DIR}")
    list(APPEND QT_INCLUDES "--framework-include-paths=${QT_FRAMEWORK_INCLUDE_DIR}")
endif()

# We need to include the headers for the module bindings that we use
set(pyside_additional_includes "")
foreach(INCLUDE_DIR ${pyside_include_dir})
    list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtCore")
    list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtGui")
    list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtWidgets")
endforeach()


# ====================== Shiboken target for generating binding C++ files  ====================

set(implicit_includes)
foreach(_current ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
    set(implicit_includes ${implicit_includes} "-I${_current}")
endforeach()

# Set up the options to pass to shiboken.
set(shiboken_options --generator-set=shiboken --enable-parent-ctor-heuristic
    --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero
    --avoid-protected-hack
    ${QT_INCLUDES}
    ${implicit_includes}
    -I${qtads_dir}/src
    -T${CMAKE_CURRENT_SOURCE_DIR}
    -T${pyside_path}/typesystems
    --output-directory=${CMAKE_CURRENT_BINARY_DIR}
    )

set(generated_sources_dependencies ${wrapped_header} ${typesystem_file})

# Add custom target to run shiboken to generate the binding cpp files.
add_custom_command(OUTPUT ${generated_sources}
                    COMMAND ${shiboken_path}
                    ${shiboken_options} ${wrapped_header} ${typesystem_file}
                    DEPENDS ${generated_sources_dependencies}
                    IMPLICIT_DEPENDS CXX ${wrapped_header}
                    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
                    COMMENT "Running generator for ${typesystem_file}.")


# =============================== CMake target - bindings_library =============================

# Set the cpp files which will be used for the bindings library.
set(${bindings_library}_sources ${generated_sources})

# Define and build the bindings library.
add_library(${bindings_library} MODULE ${${bindings_library}_sources})

# Apply relevant include and link flags.
target_include_directories(${bindings_library} PRIVATE ${pyside_additional_includes})
target_include_directories(${bindings_library} PRIVATE ${pyside_include_dir})
target_include_directories(${bindings_library} PRIVATE ${python_include_dir})
target_include_directories(${bindings_library} PRIVATE ${shiboken_include_dir})
target_include_directories(${bindings_library} PRIVATE "${CMAKE_SOURCE_DIR}/src")

target_link_libraries(${bindings_library} PRIVATE Qt6::Widgets)
target_link_libraries(${bindings_library} PRIVATE ${project_library})
target_link_libraries(${bindings_library} PRIVATE ${pyside_shared_libraries})
target_link_libraries(${bindings_library} PRIVATE ${shiboken_shared_libraries})

target_compile_options(qtadvanceddocking PRIVATE -Og)
target_compile_options(${bindings_library} PRIVATE -Og)
target_compile_definitions(${bindings_library} PRIVATE "-DPy_LIMITED_API=0x03070000")

# Adjust the name of generated module.
set_property(TARGET ${bindings_library} PROPERTY PREFIX "")
set_property(TARGET ${bindings_library} PROPERTY OUTPUT_NAME
             "${bindings_library}${PYTHON_EXTENSION_SUFFIX}")
if(WIN32)
    set_property(TARGET ${bindings_library} PROPERTY SUFFIX ".pyd")
endif()

# Make sure the linker doesn't complain about not finding Python symbols on macOS.
if(APPLE)
  set_target_properties(${bindings_library} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
endif(APPLE)

if (WIN32)
    list(GET python_linking_data 0 python_libdir)
    target_link_directories(${bindings_library} PRIVATE ${python_libdir})
endif()


# ================================= Dubious deployment section ================================

install(TARGETS ${bindings_library}
        LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}
        RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}
        )

if(NOT BUILD_STATIC)
    install(TARGETS ${project_library}
            LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
            RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}
            )
endif()

install(FILES
    "${qtads_dir}/LICENSE"
    "${qtads_dir}/gnu-lgpl-v2.1.md"
    DESTINATION license/ads
    COMPONENT license
)
