#
# SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
#
# SPDX-License-Identifier: BSD-3-Clause

# Qt 5 implementation of ECMQmlModule

cmake_policy(VERSION 3.16)

set(_ECM_QMLMODULE_STATIC_QMLONLY_H   "${CMAKE_CURRENT_LIST_DIR}/ECMQmlModule.h.in")
set(_ECM_QMLMODULE_STATIC_QMLONLY_CPP "${CMAKE_CURRENT_LIST_DIR}/ECMQmlModule.cpp.in")

set(_ECM_QMLMODULE_PROPERTY_URI "_ecm_qml_uri")
set(_ECM_QMLMODULE_PROPERTY_QMLDIR "_ecm_qmldir_file")
set(_ECM_QMLMODULE_PROPERTY_FILES "_ecm_qml_files")
set(_ECM_QMLMODULE_PROPERTY_QMLONLY "_ecm_qml_only")
set(_ECM_QMLMODULE_PROPERTY_VERSION "_ecm_qml_version")
set(_ECM_QMLMODULE_PROPERTY_PRIVATE "_ecm_qml_private")
set(_ECM_QMLMODULE_PROPERTY_PATH "_ecm_qml_path")
set(_ECM_QMLMODULE_PROPERTY_CLASSNAME "_ecm_qml_classname")
set(_ECM_QMLMODULE_PROPERTY_DEPENDS "_ecm_qml_depends")

macro(_ecm_qmlmodule_verify_qml_target ARG_TARGET)
    if (NOT TARGET ${ARG_TARGET})
        message(FATAL_ERROR "Target ${ARG_TARGET} does not exist")
    endif()
    get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
    if ("${_qml_uri}" STREQUAL "" OR "${_qml_uri}" STREQUAL "${_ECM_QMLMODULE_PROPERTY_URI}-NOTFOUND")
        message(FATAL_ERROR "Target ${ARG_TARGET} is not a QML plugin target")
    endif()
endmacro()

function(_ecm_qmlmodule_generate_qmldir ARG_TARGET)
    get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
    get_target_property(_qml_files ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_FILES})
    get_target_property(_qml_only ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLONLY})
    get_target_property(_qml_classname ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_CLASSNAME})

    set(_qmldir_template "# This file was automatically generated by ECMQmlModule and should not be modified")

    string(APPEND _qmldir_template "\nmodule ${_qml_uri}")

    if (NOT ${_qml_only})
        string(APPEND _qmldir_template "\nplugin ${ARG_TARGET}")

        if ("${_qml_classname}" STREQUAL "")
            string(APPEND _qmldir_template "\nclassname ${ARG_TARGET}")
        else()
            string(APPEND _qmldir_template "\nclassname ${_qml_classname}")
        endif()
    endif()

    get_target_property(_qml_depends ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_DEPENDS})
    if (NOT "${_qml_depends}" STREQUAL "")
        foreach(_depends ${_qml_depends})
            string(APPEND _qmldir_template "\ndepends ${_depends}")
        endforeach()
    endif()

    foreach(_file ${_qml_files})
        get_filename_component(_filename ${_file} NAME)
        get_filename_component(_classname ${_file} NAME_WE)
        get_property(_version SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_VERSION})
        get_property(_private SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PRIVATE})
        get_property(_singleton SOURCE ${_file} PROPERTY "QT_QML_SINGLETON_TYPE")
        get_property(_path SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PATH})
        cmake_path(SET _actual_path "${_path}")
        cmake_path(APPEND _actual_path "${_filename}")
        if (_singleton)
            string(APPEND _qmldir_template "\nsingleton ${_classname} ${_version} ${_actual_path}")
        else()
            if (NOT _private)
                string(APPEND _qmldir_template "\n${_classname} ${_version} ${_actual_path}")
            else()
                string(APPEND _qmldir_template "\ninternal ${_classname} ${_actual_path}")
            endif()
        endif()
    endforeach()
    get_target_property(_qml_version ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_VERSION})
    string(APPEND _qmldir_template "\n# KDE-qmldir-Version: ${_qml_version}")

    string(APPEND _qmldir_template "\n")

    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}_qmldir" "${_qmldir_template}")
    set_target_properties(${ARG_TARGET} PROPERTIES _ecm_qmldir_file "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}_qmldir")
endfunction()

function(_ecm_qmlmodule_generate_qrc ARG_TARGET)
    get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
    get_target_property(_qml_files ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_FILES})
    get_target_property(_qmldir_file ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLDIR})
    get_target_property(_qml_version ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_VERSION})

    _ecm_qmlmodule_uri_to_path(_qml_prefix "${_qml_uri}" "${_qml_version}")

    set(_qrc_template "<!-- This file was automatically generated by ECMQmlModule and should not be modified -->")

    string(APPEND _qrc_template "\n<RCC>\n<qresource prefix=\"/qt-project.org/imports/${_qml_prefix}\">")
    string(APPEND _qrc_template "\n<file alias=\"qmldir\">${_qmldir_file}</file>")

    foreach(_file ${_qml_files})
        get_filename_component(_filename ${_file} NAME)
        get_property(_path SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PATH})

        set(_file_path "${_filename}")
        if (NOT "${_path}" STREQUAL "")
            set(_file_path "${_path}/${_filename}")
        endif()

        string(APPEND _qrc_template "\n<file alias=\"${_file_path}\">${_file}</file>")
    endforeach()

    string(APPEND _qrc_template "\n</qresource>\n</RCC>\n")

    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}.qrc" "${_qrc_template}")
    qt_add_resources(_qrc_output "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}.qrc")

    target_sources(${ARG_TARGET} PRIVATE ${_qrc_output})
endfunction()

function(ecm_add_qml_module ARG_TARGET)
    cmake_parse_arguments(PARSE_ARGV 1 ARG "NO_PLUGIN" "URI;VERSION;CLASSNAME" "")

    if ("${ARG_TARGET}" STREQUAL "")
        message(FATAL_ERROR "ecm_add_qml_module called without a valid target name.")
    endif()

    if ("${ARG_URI}" STREQUAL "")
        message(FATAL_ERROR "ecm_add_qml_module called without a valid module URI.")
    endif()

    string(FIND "${ARG_URI}" " " "_space")
    if (${_space} GREATER_EQUAL 0)
        message(FATAL_ERROR "ecm_add_qml_module called without a valid module URI.")
    endif()

    if (${BUILD_SHARED_LIBS} AND ${ARG_NO_PLUGIN})
        # CMake doesn't like library targets without sources, so if we have no
        # C++ sources, use a plain target that we can add all the install stuff
        # to.
        add_custom_target(${ARG_TARGET} ALL)
    else()
        add_library(${ARG_TARGET})
    endif()

    if (NOT ARG_VERSION)
        set(ARG_VERSION "1.0")
    endif()

    set_target_properties(${ARG_TARGET} PROPERTIES
        ${_ECM_QMLMODULE_PROPERTY_URI} "${ARG_URI}"
        ${_ECM_QMLMODULE_PROPERTY_FILES} ""
        ${_ECM_QMLMODULE_PROPERTY_QMLONLY} "${ARG_NO_PLUGIN}"
        ${_ECM_QMLMODULE_PROPERTY_VERSION} "${ARG_VERSION}"
        ${_ECM_QMLMODULE_PROPERTY_CLASSNAME} "${ARG_CLASSNAME}"
        ${_ECM_QMLMODULE_PROPERTY_DEPENDS} ""
    )

    # QQmlImportDatabase::resolvePlugin doesn't accept lib prefixes under
    # Windows, causing to fail to import when using as a dynamic plugin.
    if (MINGW)
        set_target_properties(${ARG_TARGET} PROPERTIES PREFIX "")
    endif()

    # -Muri is required for static QML plugins to work properly, see
    # https://bugreports.qt.io/browse/QTBUG-82598
    set_target_properties(${ARG_TARGET} PROPERTIES AUTOMOC_MOC_OPTIONS -Muri=${ARG_URI})
endfunction()

function(ecm_add_qml_module_dependencies ARG_TARGET)
    cmake_parse_arguments(PARSE_ARGV 1 ARG "" "" "DEPENDS")

    _ecm_qmlmodule_verify_qml_target(${ARG_TARGET})

    if ("${ARG_DEPENDS}" STREQUAL "")
        message(FATAL_ERROR "ecm_add_qml_module_dependencies called without required argument DEPENDS")
    endif()

    set_target_properties(${ARG_TARGET} PROPERTIES ${_ECM_QMLMODULE_PROPERTY_DEPENDS} "${ARG_DEPENDS}")
endfunction()

function(ecm_target_qml_sources ARG_TARGET)
    cmake_parse_arguments(PARSE_ARGV 1 ARG "PRIVATE" "VERSION;PATH" "SOURCES")

    _ecm_qmlmodule_verify_qml_target(${ARG_TARGET})

    if ("${ARG_SOURCES}" STREQUAL "")
        message(FATAL_ERROR "ecm_target_qml_sources called without required argument SOURCES")
    endif()

    if ("${ARG_VERSION}" STREQUAL "")
        get_target_property(ARG_VERSION ${ARG_TARGET} "_ecm_qml_version")
    endif()
    # In case we have not specified a version in add_qml_module, we use 1.0 as a default for the individual QML files
    if (NOT ARG_VERSION)
        set(ARG_VERSION "1.0")
    endif()

    foreach(_file ${ARG_SOURCES})
        # Check if a given file exists, but only for files that are not
        # automatically generated.
        set(_path "${_file}")
        get_source_file_property(_generated ${_file} GENERATED)
        if (NOT _generated)
            if (IS_ABSOLUTE ${_path})
                # Assume absolute paths are generated, which may not always be
                # true but is fairly likely.
                set(_generated TRUE)
            else()
                string(FIND ${_file} ${CMAKE_BINARY_DIR} _in_binary_dir)
                if (${_in_binary_dir} GREATER_EQUAL 0)
                    # Assume anything in binary dir is generated.
                    set(_generated TRUE)
                else()
                    set(_path "${CMAKE_CURRENT_SOURCE_DIR}/${_file}")
                endif()
            endif()
        endif()

        if (NOT ${_generated} AND NOT EXISTS ${_path})
            message(FATAL_ERROR "ecm_target_qml_sources called with nonexistent file ${_file}")
        endif()

        set_property(SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_VERSION} "${ARG_VERSION}")
        if (ARG_PRIVATE)
            set_property(SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PRIVATE} TRUE)
        endif()
        set_property(SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PATH} "${ARG_PATH}")
        set_property(TARGET ${ARG_TARGET}
            APPEND PROPERTY ${_ECM_QMLMODULE_PROPERTY_FILES} ${_path}
        )
    endforeach()
endfunction()

function(ecm_finalize_qml_module ARG_TARGET)
    cmake_parse_arguments(PARSE_ARGV 1 ARG "" "DESTINATION" "")

    _ecm_qmlmodule_verify_qml_target(${ARG_TARGET})

    if (NOT ARG_DESTINATION)
        set(ARG_DESTINATION "${KDE_INSTALL_QMLDIR}")
    endif()
    if ("${ARG_DESTINATION}" STREQUAL "")
        message(FATAL_ERROR "ecm_finalize_qml_module called without argument DESTINATION and KDE_INSTALL_QMLDIR is not set")
    endif()

    get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
    get_target_property(_version ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_VERSION})

    _ecm_qmlmodule_generate_qmldir(${ARG_TARGET})
    _ecm_qmlmodule_uri_to_path(_plugin_path "${_qml_uri}" "${_version}")

    get_target_property(_qml_only ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLONLY})

    if (NOT BUILD_SHARED_LIBS)
        _ecm_qmlmodule_generate_qrc(${ARG_TARGET})
        set(CPP_RESOURCE_INIT "#include <qglobal.h> \n#include <QDebug> \n void initQmlResource${ARG_TARGET}() {Q_INIT_RESOURCE(${ARG_TARGET}); qWarning()<<Q_FUNC_INFO;};")
        file(WRITE ${ARG_TARGET}_init.cpp "${CPP_RESOURCE_INIT}")
        target_sources(${ARG_TARGET} PRIVATE ${ARG_TARGET}_init.cpp)
        target_compile_definitions(${ARG_TARGET} PRIVATE -DQT_PLUGIN_RESOURCE_INIT_FUNCTION=initQmlResource${ARG_TARGET} -DQT_STATICPLUGIN=1)

        if (${_qml_only})
            # If we do not have any C++ sources for the target, we need a way to
            # compile the generated QRC file. So generate a very plain C++ QML
            # plugin that we include in the target.
            configure_file(${_ECM_QMLMODULE_STATIC_QMLONLY_H} ${CMAKE_CURRENT_BINARY_DIR}/QmlModule.h @ONLY)
            configure_file(${_ECM_QMLMODULE_STATIC_QMLONLY_CPP} ${CMAKE_CURRENT_BINARY_DIR}/QmlModule.cpp @ONLY)

            target_sources(${ARG_TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/QmlModule.cpp)
            if (TARGET Qt::Qml)
                target_link_libraries(${ARG_TARGET} PRIVATE Qt::Qml)
            else()
                target_link_libraries(${ARG_TARGET} PRIVATE Qt5::Qml)
            endif()
        endif()

        install(TARGETS ${ARG_TARGET} DESTINATION ${ARG_DESTINATION}/${_plugin_path})

        return()
    endif()

    get_target_property(_runtime_output_dir ${ARG_TARGET} RUNTIME_OUTPUT_DIRECTORY)
    if (NOT ${_runtime_output_dir})
        if ("${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" STREQUAL "")
            set(_runtime_output_dir ${CMAKE_CURRENT_BINARY_DIR})
        else()
            set(_runtime_output_dir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
        endif()
    endif()

    add_custom_command(
        TARGET ${ARG_TARGET}
        POST_BUILD
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
        COMMAND ${CMAKE_COMMAND} -E make_directory ${_runtime_output_dir}/${_plugin_path}
        BYPRODUCTS ${_runtime_output_dir}/${_plugin_path}
    )

    get_target_property(_qmldir_file ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLDIR})
    install(FILES ${_qmldir_file} DESTINATION ${ARG_DESTINATION}/${_plugin_path} RENAME "qmldir")

    add_custom_command(
        TARGET ${ARG_TARGET}
        POST_BUILD
        WORKING_DIRECTORY ${_runtime_output_dir}
        COMMAND ${CMAKE_COMMAND} -E copy ${_qmldir_file} ${_plugin_path}/qmldir
    )

    if (NOT ${_qml_only})
        install(TARGETS ${ARG_TARGET} DESTINATION ${ARG_DESTINATION}/${_plugin_path})

        add_custom_command(
            TARGET ${ARG_TARGET}
            POST_BUILD
            WORKING_DIRECTORY ${_runtime_output_dir}
            COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${ARG_TARGET}> ${_plugin_path}
        )
    endif()

    get_target_property(_qml_files ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_FILES})
    foreach(_file ${_qml_files})
        get_filename_component(_filename ${_file} NAME)
        get_property(_path SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PATH})

        set(_file_path "${_plugin_path}/")
        if (NOT "${_path}" STREQUAL "")
            set(_file_path "${_plugin_path}/${_path}/")
        endif()

        install(FILES ${_file} DESTINATION ${ARG_DESTINATION}/${_file_path})

        add_custom_command(
            TARGET ${ARG_TARGET}
            POST_BUILD
            WORKING_DIRECTORY ${_runtime_output_dir}
            COMMAND ${CMAKE_COMMAND} -E make_directory ${_file_path}
            COMMAND ${CMAKE_COMMAND} -E copy ${_file} ${_file_path}
            BYPRODUCTS ${_runtime_output_dir}/${_file_path}/${_filename}
        )
    endforeach()
endfunction()
