#-------------------------------------------------------------------------------
# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors#
#
# SPDX-License-Identifier: BSD-3-Clause
#
#-------------------------------------------------------------------------------

include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)

find_package(Git)

# This function helps to handle options with an empty string values.
# There is a feature/bug in CMake that result in problem with the empty string arguments.
# See https://gitlab.kitware.com/cmake/cmake/-/issues/16341 for details
#
# Arguments:
#   [in]  KEY              - option name
#   [out]  KEY_VAR         - name of variable that is set to ${KEY} on exit if value is not
#                            an empty string otherwise to the empty string.
#   [out]  VALUE_VAR       - name of variable that is set to option value for ${KEY}.
#   [in/out]  ARG_LIST_VAR - name of variable that holds list of key/value pairs - arguments.
#                            Function looks for key/value pair specified by ${KEY} variable in
#                            this list. Function removes key/value pair specified by ${KEY} on
#                            exit.
#
# Example #1:
#   # We have following key/options:
#   #  GIT_SUBMODULES  ""
#   #  BOO  "abc"
#   #  HEY  "hi"
#   set(ARGS    GIT_SUBMODULES "" BOO "abc" HEY "hi")
#   # Extract key/value for option "GIT_SUBMODULES"
#   extract_key_value(GIT_SUBMODULES GIT_SUBMODULES_VAR GIT_SUBMODULES_VALUE_VAR ARGS)
#   # ${GIT_SUBMODULES_VAR} is equal to ""
#   # ${GIT_SUBMODULES_VALUE_VAR} is equal to ""
#
# Example #2:
#   # We have following key/options:
#   #  GIT_SUBMODULES  "name"
#   #  BOO  "abc"
#   #  HEY  "hi"
#   set(ARGS    GIT_SUBMODULES "name" BOO "abc" HEY "hi")
#   # Extract key/value for option "GIT_SUBMODULES"
#   extract_key_value(GIT_SUBMODULES GIT_SUBMODULES_VAR GIT_SUBMODULES_VALUE_VAR ARGS)
#   # ${GIT_SUBMODULES_VAR} is equal to "GIT_SUBMODULES"
#   # ${GIT_SUBMODULES_VALUE_VAR} is equal to "name"
function(extract_key_value KEY KEY_VAR VALUE_VAR ARG_LIST_VAR)
    list(FIND ${ARG_LIST_VAR} ${KEY} KEY_INDEX)
    if(${KEY_INDEX} GREATER_EQUAL 0)
        # Variable has been set, remove KEY
        list(REMOVE_AT ${ARG_LIST_VAR} ${KEY_INDEX})

        # Validate that there is an option value in the list of arguments
        list(LENGTH ${ARG_LIST_VAR} ARG_LIST_LENGTH)
        if(${KEY_INDEX} GREATER_EQUAL ${ARG_LIST_LENGTH})
            message(FATAL_ERROR "Missing option value for ${KEY}")
        endif()

        # Get value
        list(GET ${ARG_LIST_VAR} ${KEY_INDEX} VALUE)

        # Remove value in the list
        list(REMOVE_AT ${ARG_LIST_VAR} ${KEY_INDEX})

        # Update argument list
        set(${ARG_LIST_VAR} ${${ARG_LIST_VAR}} PARENT_SCOPE)

        # Set KEY_VAR & VALUE_VAR
        set(${KEY_VAR} ${KEY} PARENT_SCOPE)
        set(${VALUE_VAR} ${VALUE} PARENT_SCOPE)
    else()
        # Variable is not defined, set KEY_VAR & VALUE_VAR to empty strings
        set(${KEY_VAR} "" PARENT_SCOPE)
        set(${VALUE_VAR} "" PARENT_SCOPE)
    endif()
endfunction()


# This function allows to fetch library from a remote repository or use a local
# library copy.
#
# You can specify location of directory with patches. Patches are applied in
# alphabetical order.
#
# Arguments:
# [in]     LIB_NAME <name> - library name
# [in/out] LIB_SOURCE_PATH_VAR <var> - name of variable which holds path to library source
#           or "DOWNLOAD" if sources should be fetched from the remote repository. This
#           variable is updated in case if library is downloaded. It will point
#           to the path where FetchContent_Populate will locate local library copy.
# [out]    LIB_BINARY_PATH_VAR <var> - optional name of variable which is updated to
#           directory intended for use as a corresponding build directory if
#           library is fetched from the remote repository.
# [in]     LIB_BASE_DIR <path>  - is used to set FETCHCONTENT_BASE_DIR.
# [in]     LIB_PATCH_DIR <path> - optional path to local folder which contains patches
#           that should be applied.
# [in]     LIB_FORCE_PATCH - optional argument to force applying patches when the path
#            is a local folder instead of fetching from the remote repository.
# [in]     GIT_REPOSITORY, GIT_TAG, ... - see https://cmake.org/cmake/help/latest/module/ExternalProject.html
#           for more details
#
# This function set CMP0097 to NEW if CMAKE_VERSION is greater or equal than 3.18.0.
# Because of https://gitlab.kitware.com/cmake/cmake/-/issues/20579 CMP0097 is
# non-functional until cmake 3.18.0.
# See https://cmake.org/cmake/help/latest/policy/CMP0097.html for more info.
function(fetch_remote_library)
    # Parse arguments
    set(options "")
    set(oneValueArgs LIB_NAME LIB_SOURCE_PATH_VAR LIB_BINARY_PATH_VAR LIB_BASE_DIR LIB_PATCH_DIR LIB_FORCE_PATCH)
    set(multiValueArgs FETCH_CONTENT_ARGS)
    cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${oneValueArgs}" "${multiValueArgs}")

    if(ARG_LIB_BASE_DIR)
        set(FETCHCONTENT_BASE_DIR "${ARG_LIB_BASE_DIR}")
    endif()

    if ("${${ARG_LIB_SOURCE_PATH_VAR}}" STREQUAL "DOWNLOAD")
        # Process arguments which can be an empty string
        # There is a feature/bug in CMake that result in problem with empty string arguments
        # See https://gitlab.kitware.com/cmake/cmake/-/issues/16341 for details
        extract_key_value(GIT_SUBMODULES GIT_SUBMODULES GIT_SUBMODULES_VALUE ARG_FETCH_CONTENT_ARGS)

        # Validate that there is no empty arguments to FetchContent_Declare
        LIST(FIND ARG_FETCH_CONTENT_ARGS "" EMPTY_VALUE_INDEX)
        if(${EMPTY_VALUE_INDEX} GREATER_EQUAL 0)
            # There is an unsupported empty string argument, FATAL ERROR!
            math(EXPR EMPTY_KEY_INDEX "${EMPTY_VALUE_INDEX} - 1")
            list(GET ARG_FETCH_CONTENT_ARGS ${EMPTY_KEY_INDEX} EMPTY_KEY)
            # TODO: Use extract_key_value if you have argument with empty value (see GIT_SUBMODULES above)
            message(FATAL_ERROR "fetch_remote_library: Unexpected empty string value for ${EMPTY_KEY}. "
                                "Please, validate arguments or update fetch_remote_library to support empty value for ${EMPTY_KEY}!!!")
        endif()
    else()
        string(TOUPPER ${ARG_LIB_NAME} ARG_LIB_NAME_UPCASE)
        set(FETCHCONTENT_SOURCE_DIR_${ARG_LIB_NAME_UPCASE} ${${ARG_LIB_SOURCE_PATH_VAR}})

        # User-supplied source path: do not let MakeAvailable add_subdirectory it,
        # because it may rely on a parent project's CMake extensions (e.g. Zephyr's).
        list(APPEND ARG_FETCH_CONTENT_ARGS
         SOURCE_SUBDIR _fetch_remote_library_no_add_subdirectory)
    endif()

    # list all available patches in absolute names
    file(GLOB PATCH_FILES ${ARG_LIB_PATCH_DIR}/*.patch LIST_DIRECTORIES true)

    if(PATCH_FILES)
        set(PATCH_COMMAND PATCH_COMMAND ${GIT_EXECUTABLE} am --ignore-date ${PATCH_FILES})
    endif()

    # Content fetching
    FetchContent_Declare(${ARG_LIB_NAME}
        ${ARG_FETCH_CONTENT_ARGS}
        ${GIT_SUBMODULES}     ${GIT_SUBMODULES_VALUE}
        ${PATCH_COMMAND}
    )
    FetchContent_MakeAvailable(${ARG_LIB_NAME})
    set(${ARG_LIB_SOURCE_PATH_VAR} ${${ARG_LIB_NAME}_SOURCE_DIR} CACHE PATH "Library has been downloaded from ${REPO_URL_VAR}, tag ${TAG_VAR}" FORCE)
endfunction()
