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(LimeReport)

set(USE_QT6 ON)
if (NOT DEFINED ENABLE_ZINT)
  set(ENABLE_ZINT OFF)
endif()
#
set(LIMEREPORT_STATIC ON)

# 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}")


find_package(QT REQUIRED NAMES Qt6)
# Find the required Qt packages
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets Sql Network Xml Svg Qml PrintSupport)

# 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 "LimeReport")
set(qtads_dir ${CMAKE_CURRENT_SOURCE_DIR}/${qtads_subdir})
add_subdirectory(${qtads_subdir} EXCLUDE_FROM_ALL)
set(project_library limereport-qt${QT_VERSION_MAJOR})

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

# 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}/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}/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}/limereport_module_wrapper.cpp
  ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/limereport_enums_wrapper.cpp
  ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/limereport_reportengine_wrapper.cpp
  ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/limereport_icallbackdatasource_wrapper.cpp
  ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/limereport_idatasource_wrapper.cpp
  ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/limereport_idatasourceholder_wrapper.cpp
  ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/limereport_idatasourcemanager_wrapper.cpp
  ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/limereport_idbcredentialsprovider_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 ====================================

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

if(APPLE)
    set(CMAKE_BUILD_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_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()

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

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

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

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

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

get_property(QT_PRINTSUPPORT_INCLUDE_DIRS TARGET Qt6::PrintSupport PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
foreach(INCLUDE_DIR ${QT_PRINTSUPPORT_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")
    list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtQml")
    list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtNetwork")
    list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtPrintSupport")
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}/include
    -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(limereport-qt${QT_VERSION_MAJOR} 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()
