include(FindClangTidy)
include(GetArchitecture)

# Sets `target` to contain the CMake target name for a dynamic plugin.
macro (zeek_get_dynamic_plugin_target target ns name)
    set(${target} "${ns}_${name}")
endmacro ()

# Implements the dynamically linked version of zeek_add_plugin.
function (zeek_add_dynamic_plugin ns name)
    # Sanity check: need ZEEK_PLUGIN_SCRIPTS_PATH.
    if (NOT EXISTS "${ZEEK_PLUGIN_SCRIPTS_PATH}")
        message(
            FATAL_ERROR
                "Cannot build dynamic plugins: ZEEK_PLUGIN_SCRIPTS_PATH is undefined or invalid")
    endif ()

    # Helper variables.
    zeek_get_dynamic_plugin_target(target_name ${ns} ${name})
    set(full_name "${ns}::${name}")
    set(canon_name "${ns}_${name}")
    set(base_dir "${CMAKE_CURRENT_BINARY_DIR}")
    set(lib_dir "${base_dir}/lib")
    set(bif_dir "${lib_dir}/bif")
    set(readme "${base_dir}/README")
    set(scripts_bin "${base_dir}/scripts")
    set(scripts_src "${CMAKE_CURRENT_SOURCE_DIR}/scripts")

    # Create the target if no begin function has been used.
    if (NOT TARGET ${target_name})
        add_library(${target_name} MODULE)

        target_compile_features(${target_name} PRIVATE cxx_std_17)
        set_target_properties(${target_name} PROPERTIES CXX_EXTENSIONS OFF)
    endif ()

    # Place library file into the 'lib' directory, drop default-generated file
    # prefix and override the default file name to include the architecture.
    set_target_properties(
        ${target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${lib_dir}" PREFIX ""
                                  LIBRARY_OUTPUT_NAME "${ns}-${name}.${HOST_ARCHITECTURE}")

    # Parse arguments (note: DIST_FILES and SCRIPT_FILES are ignored in static builds).
    set(fn_varargs
        INCLUDE_DIRS
        DEPENDENCIES
        SOURCES
        BIFS
        DIST_FILES
        SCRIPT_FILES
        PAC)
    cmake_parse_arguments(FN_ARGS "" "" "${fn_varargs}" ${ARGN})

    # Take care of compiling BIFs.
    if (FN_ARGS_BIFS)
        # Generate the targets and add the .cc files.
        foreach (bif ${FN_ARGS_BIFS})
            bif_target(${bif} "plugin" ${full_name} ${canon_name} OFF)
            target_sources(${target_name} PRIVATE ${BIF_OUTPUT_CC})
        endforeach ()
        # Generate __load__.zeek when building the plugin outside of Zeek.
        if (ZEEK_PLUGIN_INTERNAL_BUILD)
            set(loader_target ${target_name}_bif_loader)
            bro_bif_create_loader(${loader_target} ${FN_ARGS_BIFS})
            add_dependencies(${target_name} ${loader_target})
        else ()
            set(load_script "${bif_dir}/__load__.zeek")
            file(WRITE ${load_script} "# Warning, this is an autogenerated file!\n")
            foreach (bif ${FN_ARGS_BIFS})
                get_filename_component(file_name ${bif} NAME)
                file(APPEND ${load_script} "@load ./${file_name}.zeek\n")
            endforeach ()
        endif ()
    endif ()

    # Take care of PAC files.
    zeek_next_pac_block(at_end pacInputs pacRemainder ${ARGN})
    while (NOT at_end)
        binpac_target(${pacInputs})
        target_sources(${target_name} PRIVATE ${BINPAC_OUTPUT_CC})
        zeek_next_pac_block(at_end pacInputs pacRemainder ${pacRemainder})
    endwhile ()

    # Add user-defined extra include directories.
    if (FN_ARGS_INCLUDE_DIRS)
        target_include_directories(${target_name} PRIVATE ${FN_ARGS_INCLUDE_DIRS})
    endif ()

    # Add user-defined extra dependencies.
    if (FN_ARGS_DEPENDENCIES)
        target_link_libraries(${target_name} PUBLIC ${FN_ARGS_DEPENDENCIES})
    endif ()

    # Add the sources for the plugin.
    if (FN_ARGS_SOURCES)
        target_sources(${target_name} PRIVATE ${FN_ARGS_SOURCES})
    endif ()

    # Add extra dependencies when compiling with MSVC.
    if (MSVC)
        target_link_libraries(${target_name} ws2_32)
    endif ()

    # Pass compiler flags, paths and dependencies to the target.
    target_link_libraries(${target_name} PRIVATE Zeek::DynamicPluginBase)
    target_include_directories(${target_name} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

    # Per convention, plugins have their headers and sources under src/ and
    # legacy/external plugins expect this to auto-magically be available as
    # include path.
    if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src)
        target_include_directories(${target_name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
    endif ()

    # Link scripts into the build directory (if present).
    if (IS_DIRECTORY "${scripts_src}")
        set(symlink_target ${target_name}_symlink)
        add_custom_target(${symlink_target} COMMAND "${CMAKE_COMMAND}" -E create_symlink
                                                    "${scripts_src}" "${scripts_bin}")
        add_dependencies(${target_name} ${symlink_target})
    endif ()

    # Add BinPAC_INCLUDE_DIR for picking up paths from FindBinPAC.cmake.
    if (BinPAC_INCLUDE_DIR)
        target_include_directories(${target_name} PRIVATE ${BinPAC_INCLUDE_DIR})
    endif ()

    # Write the 'magic' __zeek_plugin__ file. We can do that once during CMake
    # invocation since it won't change.
    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/__zeek_plugin__" "${full_name}\n")

    # Stop here unless building 3rd party plugins.
    if (ZEEK_PLUGIN_INTERNAL_BUILD)
        return()
    endif ()

    # Plugins may set BRO_PLUGIN_INSTALL_ROOT to override the default
    # installation directory. Otherwise, we use ZEEK_PLUGIN_DIR from Zeek.
    if (BRO_PLUGIN_INSTALL_ROOT)
        set(install_dir "${BRO_PLUGIN_INSTALL_ROOT}")
    else ()
        set(install_dir ${ZEEK_PLUGIN_DIR})
    endif ()

    # Create the binary install package.
    set(dist_tarball_dir ${CMAKE_CURRENT_BINARY_DIR}/dist/${canon_name})
    set(dist_tarball_name ${canon_name}.tgz)
    set(dist_tarball_path ${CMAKE_CURRENT_BINARY_DIR}/${dist_tarball_name})
    message(STATUS "Install prefix for plugin ${canon_name}: ${install_dir}")
    message(STATUS "Tarball path for plugin ${canon_name}: ${dist_tarball_path}")
    add_custom_command(OUTPUT ${dist_tarball_path} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
                       COMMENT "Building binary plugin package: ${dist_tarball_path}")

    # Handle dist files: Pre-place user provided dist files into the
    # directory that zeek-plugin-create-package.sh will tar up. Previously,
    # the zeek-plugin-create-package.sh script had done this itself.
    #
    # This computes the relative path of individual DIST_FILES to the
    # PROJECT_SOURCE_DIR (last project() invocation) and uses the
    # resulting path in the tarball.
    foreach (df ${FN_ARGS_DIST_FILES})
        get_filename_component(df_src "${df}" ABSOLUTE)

        if (NOT EXISTS "${df_src}")
            # This was silently ignored previously.
            message(WARNING "The file ${df} (${df_src}) given to DIST_FILES does not exist")
            continue()
        endif ()

        file(RELATIVE_PATH df_dist ${PROJECT_SOURCE_DIR} ${df_src})

        add_custom_command(
            OUTPUT ${dist_tarball_path}
            COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${df}
                    ${dist_tarball_dir}/${df_dist}
            DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${df}
            APPEND)
    endforeach ()

    add_custom_command(
        OUTPUT ${dist_tarball_path}
        COMMAND ${ZEEK_PLUGIN_SCRIPTS_PATH}/zeek-plugin-create-package.sh ${canon_name}
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
        DEPENDS ${target_name} ${FN_ARGS_SCRIPT_FILES}
        APPEND)

    add_custom_target(${target_name}_tarball ALL DEPENDS ${dist_tarball_path})

    # Tell CMake to install our tarball. Note: This usually runs from our
    # plugin-support skeleton.
    install(
        CODE "execute_process(
        COMMAND ${ZEEK_PLUGIN_SCRIPTS_PATH}/zeek-plugin-install-package.sh ${canon_name} \$ENV{DESTDIR}/${install_dir}
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
        COMMAND_ECHO STDOUT
    )")
endfunction ()
