#-----------------------------------------------------------------------------
#
# Copyright (C) 2022 Bjarne von Horn <vh@igh.de>
#
# This file is part of the QtPdWidgets library.
#
# The QtPdWidgets 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 QtPdWidgets 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 QtPdWidgets Library. If not, see
# <http://www.gnu.org/licenses/>.
#
#-----------------------------------------------------------------------------

cmake_minimum_required(VERSION 3.10)

project(QtPdWidgets2 VERSION 2.3.4) # Do not forget to update NEWS.md

set(SOVERSION 3)

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

# Configure build

option(DESIGNER_PLUGIN "Build as Qt Designer plugin" ON)
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
option(BUILD_EXAMPLE "Also build example" OFF)
option(USE_SYMBOL_VERSIONING "Use ELF 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)
cmake_dependent_option(
    CHECK_SYMBOL_VERSIONING
    "Verify symbol versioning after build"
    ON
    "USE_SYMBOL_VERSIONING"
    OFF
)

# Load required packages

set(REQUIRED_PUBLIC_QT_MODULES Gui Widgets)
set(REQUIRED_QT_MODULES ${REQUIRED_PUBLIC_QT_MODULES} LinguistTools Svg Xml Network)
if(DESIGNER_PLUGIN)
    list(APPEND REQUIRED_QT_MODULES Designer)
endif()
if(USE_QT6)
    set(Qt Qt6)
    list(APPEND REQUIRED_QT_MODULES Core5Compat)
else()
    set(Qt Qt5)
endif()

if (PUT_QT_MAJOR_INTO_LIBNAME)
    set(LIBNAME "${Qt}PdWidgets2")
    set(QtPdCom "${Qt}PdCom1")
else()
    set(LIBNAME "${PROJECT_NAME}")
    set(QtPdCom "QtPdCom1")
endif()
find_package(${Qt} REQUIRED
    COMPONENTS ${REQUIRED_QT_MODULES}
)
find_package(${QtPdCom} REQUIRED)

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

# handle translation files

set(TS_FILES
    QtPdWidgets_de.ts
    QtPdWidgets_nl.ts
)

configure_file(QtPdWidgets_ts.qrc "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY)

if(USE_QT6)
    qt6_add_resources(QRC_SOURCES
        QtPdWidgets.qrc
        ${CMAKE_CURRENT_BINARY_DIR}/QtPdWidgets_ts.qrc
    )

    qt6_add_translation(QM_FILES ${TS_FILES})
else()
    qt5_add_resources(QRC_SOURCES
        QtPdWidgets.qrc
        ${CMAKE_CURRENT_BINARY_DIR}/QtPdWidgets_ts.qrc
    )

    qt5_add_translation(QM_FILES ${TS_FILES})
endif()

# add source files and headers

set(PUBLIC_HEADERS
    ${PROJECT_NAME}/Translator.h
    ${PROJECT_NAME}/Bar.h
    ${PROJECT_NAME}/CheckBox.h
    ${PROJECT_NAME}/ClipImage.h
    ${PROJECT_NAME}/CursorEditWidget.h
    ${PROJECT_NAME}/Dial.h
    ${PROJECT_NAME}/Digital.h
    ${PROJECT_NAME}/DoubleSpinBox.h
    ${PROJECT_NAME}/Export.h
    ${PROJECT_NAME}/Graph.h
    ${PROJECT_NAME}/Image.h
    ${PROJECT_NAME}/Led.h
    ${PROJECT_NAME}/MultiLed.h
    ${PROJECT_NAME}/NoPdTouchEdit.h
    ${PROJECT_NAME}/ParameterSetWidget.h
    ${PROJECT_NAME}/PushButton.h
    ${PROJECT_NAME}/RadioButton.h
    ${PROJECT_NAME}/Rotor.h
    ${PROJECT_NAME}/SendBroadcastWidget.h
    ${PROJECT_NAME}/SpinBox.h
    ${PROJECT_NAME}/Svg.h
    ${PROJECT_NAME}/TableView.h
    ${PROJECT_NAME}/Tank.h
    ${PROJECT_NAME}/Text.h
    ${PROJECT_NAME}/Time.h
    ${PROJECT_NAME}/TouchEdit.h
    ${PROJECT_NAME}/TouchEditDialog.h
    ${PROJECT_NAME}/XYGraph.h
)

set(SOURCES
    src/Bar.cpp
    src/Bar_p.h
    src/BarSection.cpp
    src/BarSection.h
    src/BarStack.cpp
    src/BarStack.h
    src/CheckBox.cpp
    src/ClipImage.cpp
    src/CursorEditWidget.cpp
    src/Dial.cpp
    src/Digital.cpp
    src/DoubleSpinBox.cpp
    src/Graph.cpp
    src/Image.cpp
    src/Led.cpp
    src/MultiLed.cpp
    src/NoPdTouchEdit.cpp
    src/Parameter.cpp
    src/Parameter.h
    src/ParameterFileSystemModel.cpp
    src/ParameterFileSystemModel.h
    src/ParameterSetWidget.cpp
    src/PushButton.cpp
    src/RadioButton.cpp
    src/Rotor.cpp
    src/Scale.cpp
    src/Scale.h
    src/SendBroadcastWidget.cpp
    src/SpinBox.cpp
    src/Svg.cpp
    src/TableView.cpp
    src/TableViewImpl.h
    src/Tank.cpp
    src/Text.cpp
    src/Time.cpp
    src/TimeScale.cpp
    src/TimeScale.h
    src/TouchEdit.cpp
    src/TouchEditDialog.cpp
    src/Translator.cpp
    src/ValueRing.h
    src/Version.cpp
    src/Widget.h
    src/Widget.cpp
    src/XYGraph.cpp
)

if (DESIGNER_PLUGIN)
    add_library(${LIBNAME}Plugin
        src/Plugin.h
        src/WidgetCollection.h
        src/Plugin.cpp
        src/WidgetCollection.cpp
    )
endif()

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

add_library(${LIBNAME}
    ${PUBLIC_HEADERS}
    ${SOURCES}

    ${QM_FILES}
    ${QRC_SOURCES}
)

# bake in git revision

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

if (USE_SYMBOL_VERSIONING)
    if (NOT SYMBOL_VERSIONING_ARCH)
        if ("${CMAKE_SIZEOF_VOID_P}" EQUAL 8)
            set(SYMBOL_VERSIONING_ARCH "generic64")
        elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "^arm")
            set(SYMBOL_VERSIONING_ARCH "arm")
        else()
            set(SYMBOL_VERSIONING_ARCH "x86")
        endif()
        message(STATUS "Using Symbol Versioning arch ${SYMBOL_VERSIONING_ARCH}")
    endif()
    add_custom_command(OUTPUT QtPdWidgets.map
        COMMAND "${PYTHON_EXECUTABLE}"
        ARGS "${CMAKE_CURRENT_SOURCE_DIR}/symbol-versioning/symbol_version_tool.py"
                -i "${CMAKE_CURRENT_SOURCE_DIR}/${Qt}PdWidgets.map.yaml"
                -o "${CMAKE_CURRENT_BINARY_DIR}/QtPdWidgets.map"
                --arch "${SYMBOL_VERSIONING_ARCH}"
                GenerateLinkerVersionScript
        MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${Qt}PdWidgets.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(WIDGET_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/QtPdWidgets.map")
    set_source_files_properties(src/Widget.cpp PROPERTIES
        OBJECT_DEPENDS "${WIDGET_DEPENDS}"
    )
    # inform linker about version script
    set_target_properties(${LIBNAME} PROPERTIES
        LINK_FLAGS " -Wl,-version-script=${CMAKE_CURRENT_BINARY_DIR}/QtPdWidgets.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}/${Qt}PdWidgets.map.yaml"
            --arch "${SYMBOL_VERSIONING_ARCH}"
            --library $<TARGET_FILE:${LIBNAME}>
            CheckGeneratedLibraries
            COMMENT "Checking whether all symbols are versioned correctly..."
        )
    endif()
endif()

# 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::${QtPdCom}
    ${Qt}::Core
    ${Qt}::Gui
    ${Qt}::Widgets
PRIVATE
    ${Qt}::Svg
    ${Qt}::Xml
)

if(USE_QT6)
    target_link_libraries(${LIBNAME} PRIVATE Qt6::Core5Compat)
endif()

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


if(DESIGNER_PLUGIN)
    target_link_libraries(${LIBNAME}Plugin PRIVATE ${Qt}::Designer ${LIBNAME})
    set_target_properties(${LIBNAME}Plugin PROPERTIES
        AUTOMOC ON
    )
endif()

if(WIN32)
    # for gethostname in Process.cpp
    target_link_libraries(${LIBNAME} PRIVATE -lwsock32)
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)

# 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}/QtPdWidgets2.h"
    DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)

if (DESIGNER_PLUGIN)
    # Call qmake to query the plugin installation directory.
    get_target_property(QT_QMAKE_EXECUTABLE ${Qt}::qmake LOCATION)
    execute_process(COMMAND ${QT_QMAKE_EXECUTABLE} -query QT_INSTALL_PLUGINS
        OUTPUT_VARIABLE QT_INSTALL_PLUGINS OUTPUT_STRIP_TRAILING_WHITESPACE
    )

    # Install the library in QtDesigner plugin directory
    install(TARGETS ${LIBNAME}Plugin
        LIBRARY DESTINATION ${QT_INSTALL_PLUGINS}/designer
        RUNTIME DESTINATION ${QT_INSTALL_PLUGINS}/designer
    )
endif()

# export target for use in other projects

set(ConfigPackageLocation "${CMAKE_INSTALL_LIBDIR}/cmake/${LIBNAME}")
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::
)

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

configure_file(Doxyfile.in "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile" @ONLY)

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