#-----------------------------------------------------------------------------
#
# Copyright (C) 2022-2023 Bjarne von Horn <vh@igh.de>
#               2022-2025 Florian Pose <fp@igh.de>
#
# This file is part of the QtPdCom library.
#
# The QtPdCom library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# The QtPdCom library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
# License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with the QtPdCom Library. If not, see <http://www.gnu.org/licenses/>.
#
#-----------------------------------------------------------------------------

cmake_minimum_required(VERSION 3.10)

project(QtPdCom1 VERSION 1.5.0) # remember to update NEWS file!
set(SOVERSION 3)

include(CMakeDependentOption)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)

# Configure build

option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
option(BUILD_EXAMPLE "Also build example" OFF)
option(USE_SASL "Use LoginManager with SASL" ON)
option(USE_SYMBOL_VERSIONING "Use ELF symbol versioning" OFF)
cmake_dependent_option(
    CHECK_SYMBOL_VERSIONING
    "Verify symbol versioning after build"
    ON
    "USE_SYMBOL_VERSIONING"
    OFF
)
option(USE_QT6 "Use Qt6 instead of Qt5" OFF)
option(PUT_QT_MAJOR_INTO_LIBNAME "Put Qt Major Version into Library Name" OFF)
option(GENERATE_QHP "Build Qt Assistant help files" OFF)

if(USE_QT6)
    set(Qt Qt6)
    function(add_translation _qm_files)
        qt6_add_translation("${_qm_files}" ${ARGN})
    endfunction()

    find_package(Qt6 REQUIRED COMPONENTS Qml)

    if (NOT QTPDCOM_QML_INSTALL_PATH)
        if (NOT QT6_INSTALL_QML)
            set(QTPDCOM_QML_INSTALL_PATH "${CMAKE_INSTALL_FULL_LIBDIR}/qt6/qml")
        else()
            set(QTPDCOM_QML_INSTALL_PATH "${CMAKE_INSTALL_PREFIX}/${QT6_INSTALL_QML}")
        endif()
    endif()

else()
    set(Qt Qt5)
    function(add_translation _qm_files)
        qt5_add_translation("${_qm_files}" ${ARGN})
    endfunction()
endif()

if (PUT_QT_MAJOR_INTO_LIBNAME)
    set(LIBNAME "${Qt}PdCom1")
else()
    set(LIBNAME "QtPdCom1")
endif()

set(CMAKE_AUTOMOC ON)

# Load required packages

set(PUBLIC_QT_MODULES Core Gui Network)
set(REQUIRED_QT_MODULES LinguistTools Xml)

if (GENERATE_QHP)
    if (USE_QT6)
        list(APPEND REQUIRED_QT_MODULES Tools)
    else()
        list(APPEND REQUIRED_QT_MODULES Help)
    endif()

    set(DOXYGEN_GENERATE_QHP "YES")
else()
    set(DOXYGEN_GENERATE_QHP "NO")
endif()


if (USE_SASL)
    find_package(PdCom5 REQUIRED COMPONENTS sasl)
else()
    find_package(PdCom5 REQUIRED)
endif()

find_package(${Qt} REQUIRED
    COMPONENTS ${PUBLIC_QT_MODULES} ${REQUIRED_QT_MODULES}
)

if (USE_SYMBOL_VERSIONING)
    find_package(PythonInterp 3 REQUIRED)
endif()

# prepare doxygen file

if (GENERATE_QHP)
    get_target_property(DOXYGEN_QHG_LOCATION ${Qt}::qhelpgenerator IMPORTED_LOCATION)
endif()
configure_file(Doxyfile.in "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile" @ONLY)


# handle translation files

set(TS_FILES
    QtPdCom_de.ts
    QtPdCom_nl.ts
)

set(QTPDCOM_HAS_LOGIN_MANAGER ${USE_SASL})
configure_file(QtPdCom_ts.qrc "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY)
add_translation(QM_FILES ${TS_FILES})

# add source files and headers

set(PUBLIC_HEADERS
    ${PROJECT_NAME}/BroadcastModel.h
    ${PROJECT_NAME}/ClientStatisticsModel.h
    ${PROJECT_NAME}/Export.h
    ${PROJECT_NAME}/FutureWatchers.h
    ${PROJECT_NAME}/FutureWatchersDetails.h
    ${PROJECT_NAME}/Message.h
    ${PROJECT_NAME}/MessageModel.h
    ${PROJECT_NAME}/MessageModelFilter.h
    ${PROJECT_NAME}/MessageModelUnion.h
    ${PROJECT_NAME}/Process.h
    ${PROJECT_NAME}/ScalarSubscriber.h
    ${PROJECT_NAME}/ScalarVariable.h
    ${PROJECT_NAME}/TableColumn.h
    ${PROJECT_NAME}/TableModel.h
    ${PROJECT_NAME}/Translator.h
    ${PROJECT_NAME}/Transmission.h
    ${PROJECT_NAME}/ValueRing.h
    ${PROJECT_NAME}/VariableList.h
)

set(SOURCES
    src/BroadcastModel.cpp
    src/ClientStatisticsModel.cpp
    src/Message.cpp
    src/MessageImpl.cpp
    src/MessageItem.cpp
    src/MessageManager.cpp
    src/MessageModel.cpp
    src/MessageModelFilter.cpp
    src/MessageModelImpl.cpp
    src/MessageModelUnion.cpp
    src/Process.cpp
    src/ScalarSubscriber.cpp
    src/TableColumn.cpp
    src/TableModel.cpp
    src/Translator.cpp
    src/Transmission.cpp
)

if (USE_SASL)
    list(APPEND PUBLIC_HEADERS "${PROJECT_NAME}/LoginManager.h")
    list(APPEND SOURCES "src/LoginManager.cpp")
endif()

set(ALL_SOURCES
    ${PUBLIC_HEADERS}
    ${SOURCES}

    ${QM_FILES}
    ${CMAKE_CURRENT_BINARY_DIR}/QtPdCom_ts.qrc
)

if (USE_QT6 AND BUILD_SHARED_LIBS)
    qt6_add_library(${LIBNAME} SHARED ${ALL_SOURCES})
elseif (USE_QT6)
    qt6_add_library(${LIBNAME} STATIC ${ALL_SOURCES})
else()
    add_library(${LIBNAME} ${ALL_SOURCES})
endif()

configure_file(QtPdCom1.h.in "${CMAKE_CURRENT_BINARY_DIR}/QtPdCom1.h")

# set include path

target_include_directories(${LIBNAME} PUBLIC
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}>"
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>"
    "$<INSTALL_INTERFACE:include>"
)

# link to required and optional libraries

target_link_libraries(${LIBNAME} PUBLIC
    EtherLab::pdcom5
    ${Qt}::Core
    ${Qt}::Gui
    ${Qt}::Network
  PRIVATE
    ${Qt}::Xml
)

if (PUT_QT_MAJOR_INTO_LIBNAME)
    # Export.h needs _EXPORTS macro without Qt Major version
    # to expand QTPDCOM_PUBLIC
    target_compile_definitions(${LIBNAME} PRIVATE
        "${PROJECT_NAME}_EXPORTS"
    )
endif()

if(WIN32)
    # for gethostname in Process.cpp
    target_link_libraries(${LIBNAME} PRIVATE -lwsock32)
endif()

# use sasl?

if (USE_SASL)
    target_link_libraries(${LIBNAME} PRIVATE
        EtherLab::pdcom5-sasl
    )
endif()

# bake in version information

if (VERSION_HASH)
    # Hash was given as option, so write it directly
    # useful for OBS packaging
    file (WRITE "${CMAKE_CURRENT_BINARY_DIR}/git_revision_hash.h"
        "#define GIT_REV \"${VERSION_HASH}\"
")
else()
    # recompute hash on every `make` invocation
    # Note: CMake variable definitions (-D) must be before the -P option!
    add_custom_target (GitRevision
        BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/git_revision_hash.h"
        COMMAND ${CMAKE_COMMAND}
            -DSOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}"
            -DHASH_MACRO_NAME="GIT_REV"
            -DTARGET_FILE="${CMAKE_CURRENT_BINARY_DIR}/git_revision_hash.h"
            -P ${CMAKE_CURRENT_SOURCE_DIR}/git_revision.cmake
    )
    add_dependencies(${LIBNAME} GitRevision)
endif()

# configure symbol versioning

if (USE_SYMBOL_VERSIONING)
    math(EXPR VERSCRIPT_ARCH "8 * ${CMAKE_SIZEOF_VOID_P}")
    add_custom_command(OUTPUT QtPdCom.map
        COMMAND "${PYTHON_EXECUTABLE}"
        ARGS "${CMAKE_CURRENT_SOURCE_DIR}/symbol-versioning/symbol_version_tool.py"
                -i "${CMAKE_CURRENT_SOURCE_DIR}/QtPdCom.map.yaml"
                -o "${CMAKE_CURRENT_BINARY_DIR}/QtPdCom.map"
                --arch "generic${VERSCRIPT_ARCH}"
                GenerateLinkerVersionScript
        MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/QtPdCom.map.yaml"
        DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/symbol-versioning/symbol_version_tool.py"
    )
    # to force relinking upon changing the map file,
    # we add an OBJECT_DEPENDS
    set(PROCESS_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/QtPdCom.map")
    set_source_files_properties(src/Process.cpp PROPERTIES OBJECT_DEPENDS "${PROCESS_DEPENDS}")
    # inform linker about version script
    string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,-version-script=${CMAKE_CURRENT_BINARY_DIR}/QtPdCom.map")

    if (CHECK_SYMBOL_VERSIONING)
        add_custom_target(CheckVersionScript ALL
            "${PYTHON_EXECUTABLE}"
            "${CMAKE_CURRENT_SOURCE_DIR}/symbol-versioning/symbol_version_tool.py"
            -i "${CMAKE_CURRENT_SOURCE_DIR}/QtPdCom.map.yaml"
            --arch "generic${VERSCRIPT_ARCH}"
            --library $<TARGET_FILE:${LIBNAME}>
            CheckGeneratedLibraries
            COMMENT "Checking whether all symbols are versioned correctly..."
        )
    endif()
endif()

# enable resource compiler etc.

set_target_properties(${LIBNAME} PROPERTIES
    AUTOUIC ON
    AUTOMOC ON
    AUTORCC ON
    PUBLIC_HEADER "${PUBLIC_HEADERS}"
    CXX_VISIBILITY_PRESET "hidden"
    VISIBILITY_INLINES_HIDDEN 1
    VERSION "${SOVERSION}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}"
    SOVERSION ${SOVERSION}
)

target_compile_features(${LIBNAME} PUBLIC cxx_std_11)

target_compile_options(${LIBNAME} PRIVATE -Wall -Wextra)

set(TARGETS_TO_INCLUDE
    ${LIBNAME}
)

# build metatypes file with all properties and Q_INVOKABLE
# this file is used in QmlPdWidgets to export
# Process, LoginManager and MessageModel.

if(USE_QT6)
    if (BUILD_SHARED_LIBS)
        qt6_extract_metatypes(${LIBNAME} OUTPUT_FILES METATYPE_FILE)

        get_filename_component(METATYPE_FILE_NAME "${METATYPE_FILE}" NAME)

        set(METATYPES_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/qt6/metatypes")

        target_sources(${LIBNAME} INTERFACE
            "$<INSTALL_INTERFACE:$<$<BOOL:$<TARGET_PROPERTY:QT_CONSUMES_METATYPES>>:${METATYPES_DIR}/${METATYPE_FILE_NAME}>>"
            "$<BUILD_INTERFACE:$<$<BOOL:$<TARGET_PROPERTY:QT_CONSUMES_METATYPES>>:${METATYPE_FILE}>>"
        )
    endif()

    if (BUILD_SHARED_LIBS)
        set(QML_PLUGIN_MODE SHARED)
    else()
        set(QML_PLUGIN_MODE STATIC)
    endif()

    set(QTPDCOM_QML_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/qml_out/de/igh/qtpdcom")

    qt_add_qml_module(${LIBNAME}plugin
        PLUGIN_TARGET ${LIBNAME}plugin
        ${QML_PLUGIN_MODE}
        URI de.igh.qtpdcom
        VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
        NO_PLUGIN_OPTIONAL
        NO_GENERATE_PLUGIN_SOURCE
        RESOURCE_PREFIX "/de.igh.qtpdcom${PROJECT_VERSION_MAJOR}/imports"
        OUTPUT_DIRECTORY "${QTPDCOM_QML_OUTPUT_DIR}"
        OUTPUT_TARGETS QTPDCOM_QML_PLUGIN_TARGETS
        SOURCES
            src/PdConnection.h
            src/PdVariable.h
            src/Plugin.h
            src/Qml_classes.h

            src/PdConnection.cpp
            src/PdVariable.cpp
            src/Plugin.cpp
            src/Qml_classes.cpp
    )

    target_include_directories(${LIBNAME}plugin PRIVATE
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/src"
    )

    # add fake interface library to export QML install dir
    # for both build and install directory
    add_library(${LIBNAME}_qmldir INTERFACE)
    set_target_properties(${LIBNAME}_qmldir PROPERTIES
        INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/qml_out>$<INSTALL_INTERFACE:${QTPDCOM_QML_INSTALL_PATH}>"
    )

    target_link_libraries(${LIBNAME}plugin PUBLIC ${LIBNAME} Qt6::Qml)

    list(APPEND TARGETS_TO_INCLUDE ${QTPDCOM_QML_PLUGIN_TARGETS} "${LIBNAME}plugin" "${LIBNAME}_qmldir")
endif()

# install library and headers

install(TARGETS ${LIBNAME}
    EXPORT ${LIBNAME}Targets
    ARCHIVE DESTINATION       "${CMAKE_INSTALL_LIBDIR}"
    LIBRARY DESTINATION       "${CMAKE_INSTALL_LIBDIR}"
    RUNTIME DESTINATION       "${CMAKE_INSTALL_BINDIR}"
    PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}"
)

install(FILES "${CMAKE_CURRENT_BINARY_DIR}/QtPdCom1.h"
        DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)

set(ConfigPackageLocation "${CMAKE_INSTALL_LIBDIR}/cmake/${LIBNAME}")

if (USE_QT6)
    if (BUILD_SHARED_LIBS)
        install(FILES "${METATYPE_FILE}"
                DESTINATION "${METATYPES_DIR}"
        )
    endif()

    install(DIRECTORY "${QTPDCOM_QML_OUTPUT_DIR}"
        DESTINATION "${QTPDCOM_QML_INSTALL_PATH}/de/igh"
        FILES_MATCHING
            PATTERN "qmldir"
            PATTERN "*.otf"
            PATTERN "*.qml"
            PATTERN "*.js"
            PATTERN "*.qmltypes"
    )

    configure_file("Config-Qml.cmake.in"
        "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}-Qml-config.cmake"
        @ONLY
    )

    write_basic_package_version_file(
        "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}-Qml-config-version.cmake"
        COMPATIBILITY SameMajorVersion
    )

    install(FILES
        "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}-Qml-config.cmake"
        "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}-Qml-config-version.cmake"
        DESTINATION ${ConfigPackageLocation}
    )

    foreach(PD_TARGET ${QTPDCOM_QML_PLUGIN_TARGETS} "${LIBNAME}plugin" "${LIBNAME}_qmldir")
        install(TARGETS ${PD_TARGET}
            EXPORT ${PD_TARGET}Targets
            ARCHIVE DESTINATION       "${CMAKE_INSTALL_LIBDIR}"
            LIBRARY DESTINATION       "${QTPDCOM_QML_INSTALL_PATH}/de/igh/qtpdcom"
            RUNTIME DESTINATION       "${QTPDCOM_QML_INSTALL_PATH}/de/igh/qtpdcom"
        )

        export(EXPORT ${PD_TARGET}Targets
            FILE "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${PD_TARGET}Targets.cmake"
            NAMESPACE EtherLab::
        )

        install(EXPORT ${PD_TARGET}Targets
            NAMESPACE EtherLab::
            FILE "${PD_TARGET}Targets.cmake"
            DESTINATION "${ConfigPackageLocation}"
        )

    endforeach()

endif()

# export target for use in other projects

configure_package_config_file(Config.cmake.in
        "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}Config.cmake"
    INSTALL_DESTINATION ${ConfigPackageLocation}
)
write_basic_package_version_file(
        "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}ConfigVersion.cmake"
    COMPATIBILITY SameMajorVersion
)

install(EXPORT ${LIBNAME}Targets
    NAMESPACE EtherLab::
    FILE "${LIBNAME}Targets.cmake"
    DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${LIBNAME}"
)
install(FILES
        "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}Config.cmake"
        "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}ConfigVersion.cmake"
    DESTINATION ${ConfigPackageLocation}
)
export(EXPORT ${LIBNAME}Targets
    FILE "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}Targets.cmake"
    NAMESPACE EtherLab::
)

# build example

if(BUILD_EXAMPLE)
    add_library(EtherLab::${LIBNAME} ALIAS ${LIBNAME})
    add_subdirectory(example)
endif()

# replacement for lupdate-pro
# just run `make lupdate` in your build dir after running cmake
add_custom_target(lupdate
    ${Qt}::lupdate -IQtPdCom1 ${SOURCES} ${PUBLIC_HEADERS} -ts ${TS_FILES}
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
