include(FetchContent)
include(ProcessorCount)

set(DOWNLOAD_EXTRACT_TIMESTAMP TRUE)

set(TRANSFER_ENGINE_THIRDPARTY_SAFE_FLAGS
    "-fPIC -D_FORTIFY_SOURCE=2 -O2 -fstack-protector-strong -ffunction-sections -fdata-sections")

if (NOT TRANSFER_ENGINE_OPENSOURCE_DIR)
    if (DEFINED ENV{TRANSFER_ENGINE_OPENSOURCE_DIR})
        set(TRANSFER_ENGINE_OPENSOURCE_DIR $ENV{TRANSFER_ENGINE_OPENSOURCE_DIR})
    elseif(DEFINED ENV{DS_OPENSOURCE_DIR})
        set(TRANSFER_ENGINE_OPENSOURCE_DIR $ENV{DS_OPENSOURCE_DIR}/transfer_engine)
    else()
        string(SHA256 _TRANSFER_ENGINE_TEMP_PATH "${CMAKE_BINARY_DIR}/transfer_engine")
        set(TRANSFER_ENGINE_OPENSOURCE_DIR "/tmp/${_TRANSFER_ENGINE_TEMP_PATH}")
    endif()
endif()
get_filename_component(TRANSFER_ENGINE_OPENSOURCE_DIR "${TRANSFER_ENGINE_OPENSOURCE_DIR}" ABSOLUTE)

message(STATUS "transfer_engine third-party cache: ${TRANSFER_ENGINE_OPENSOURCE_DIR}")

find_program(TRANSFER_ENGINE_PATCH_EXECUTABLE patch)
find_program(TRANSFER_ENGINE_NINJA_EXECUTABLE ninja)

function(_te_exec_command)
    set(options)
    set(one_value_args WORKING_DIRECTORY)
    set(multi_value_args COMMAND)
    cmake_parse_arguments(ARG "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})

    execute_process(
        COMMAND ${ARG_COMMAND}
        WORKING_DIRECTORY ${ARG_WORKING_DIRECTORY}
        RESULT_VARIABLE _RET
    )
    if (NOT _RET EQUAL "0")
        message(FATAL_ERROR "Fail execute command: ${ARG_COMMAND}, error: ${_RET}")
    endif()
endfunction()

function(te_download_lib_pkg LIB_NAME URL SHA256)
    FetchContent_Declare(
        ${LIB_NAME}
        URL ${URL}
        URL_HASH SHA256=${SHA256}
    )
    FetchContent_GetProperties(${LIB_NAME})
    if (NOT ${LIB_NAME}_POPULATED)
        message(STATUS "Download ${LIB_NAME} from ${URL}")
        FetchContent_Populate(${LIB_NAME})
    endif()
    set(${LIB_NAME}_SOURCE_DIR "${${LIB_NAME}_SOURCE_DIR}" PARENT_SCOPE)
    set(${LIB_NAME}_BINARY_DIR "${${LIB_NAME}_BINARY_DIR}" PARENT_SCOPE)
endfunction()

function(TE_ADD_THIRDPARTY_LIB LIB_NAME)
    set(options)
    set(one_value_args URL SHA256 VERSION TOOLCHAIN CONF_PATH)
    set(multi_value_args CONF_OPTIONS PATCHES CXX_FLAGS C_FLAGS LINK_FLAGS EXTRA_MSGS)
    cmake_parse_arguments(ARG "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})

    string(TOLOWER "${LIB_NAME}" _LIB_NAME_LOWER)

    if (NOT ARG_TOOLCHAIN)
        set(ARG_TOOLCHAIN "cmake")
    endif()
    
    if (NOT TRANSFER_ENGINE_BUILD_THREAD_NUM)
      set(TRANSFER_ENGINE_BUILD_THREAD_NUM 8)
    endif()
    
    set(${LIB_NAME}_CONF_TXT "${_LIB_NAME_LOWER}_${ARG_SHA256}_${ARG_VERSION}_${ARG_TOOLCHAIN}")
    set(${LIB_NAME}_CONF_TXT "${${LIB_NAME}_CONF_TXT}_${ARG_CONF_OPTIONS}")
    set(${LIB_NAME}_CONF_TXT "${${LIB_NAME}_CONF_TXT}_${CMAKE_CXX_COMPILER_VERSION}_${CMAKE_C_COMPILER_VERSION}")
    set(${LIB_NAME}_CONF_TXT "${${LIB_NAME}_CONF_TXT}_${ARG_CXX_FLAGS}_${ARG_C_FLAGS}_${ARG_LINK_FLAGS}")
    foreach(_PATCH ${ARG_PATCHES})
        file(SHA256 "${_PATCH}" _PATCH_SHA256)
        set(${LIB_NAME}_CONF_TXT "${${LIB_NAME}_CONF_TXT}_${_PATCH_SHA256}")
    endforeach()
    foreach(_MSG ${ARG_EXTRA_MSGS})
        set(${LIB_NAME}_CONF_TXT "${${LIB_NAME}_CONF_TXT}_${_MSG}")
    endforeach()
    string(REPLACE ";" "_" ${LIB_NAME}_CONF_TXT "${${LIB_NAME}_CONF_TXT}")
    string(SHA256 _ROOT_SUFFIX "${${LIB_NAME}_CONF_TXT}")
    set(${LIB_NAME}_ROOT "${TRANSFER_ENGINE_OPENSOURCE_DIR}/${_LIB_NAME_LOWER}_${_ROOT_SUFFIX}")

    set(_VERIFY_FILE "${${LIB_NAME}_ROOT}/${LIB_NAME}_install.txt")
    if (EXISTS "${_VERIFY_FILE}")
        message(STATUS "${LIB_NAME} found in ${${LIB_NAME}_ROOT}")
        if (EXISTS "${${LIB_NAME}_ROOT}/lib64")
            set(${LIB_NAME}_LIB_PATH "${${LIB_NAME}_ROOT}/lib64")
        else()
            set(${LIB_NAME}_LIB_PATH "${${LIB_NAME}_ROOT}/lib")
        endif()
        set(${LIB_NAME}_ROOT "${${LIB_NAME}_ROOT}" PARENT_SCOPE)
        set(${LIB_NAME}_LIB_PATH "${${LIB_NAME}_LIB_PATH}" PARENT_SCOPE)
        return()
    endif()

    if (EXISTS "${${LIB_NAME}_ROOT}")
        file(REMOVE_RECURSE "${${LIB_NAME}_ROOT}")
    endif()

    te_download_lib_pkg(${_LIB_NAME_LOWER} "${ARG_URL}" "${ARG_SHA256}")

    foreach(_PATCH ${ARG_PATCHES})
        if (NOT TRANSFER_ENGINE_PATCH_EXECUTABLE)
            message(FATAL_ERROR "patch executable not found!")
        endif()
        execute_process(
            COMMAND ${TRANSFER_ENGINE_PATCH_EXECUTABLE} --verbose -p1 INPUT_FILE "${_PATCH}"
            WORKING_DIRECTORY "${${_LIB_NAME_LOWER}_SOURCE_DIR}"
            RESULT_VARIABLE _RET
        )
        if (NOT _RET EQUAL "0")
            message(FATAL_ERROR "Patch ${_PATCH} failed, error: ${_RET}")
        endif()
    endforeach()

    message(STATUS "Compiling ${LIB_NAME} in ${${_LIB_NAME_LOWER}_BINARY_DIR}")
    string(TOLOWER "${ARG_TOOLCHAIN}" _TOOLCHAIN_LOWER)

    if (_TOOLCHAIN_LOWER STREQUAL "cmake")
        if (ARG_CXX_FLAGS)
            list(APPEND ARG_CONF_OPTIONS "-DCMAKE_CXX_FLAGS=${ARG_CXX_FLAGS}")
        endif()
        if (ARG_C_FLAGS)
            list(APPEND ARG_CONF_OPTIONS "-DCMAKE_C_FLAGS=${ARG_C_FLAGS}")
        endif()
        if (ARG_LINK_FLAGS)
            list(APPEND ARG_CONF_OPTIONS "-DCMAKE_SHARED_LINKER_FLAGS=${ARG_LINK_FLAGS}")
        endif()
        list(APPEND ARG_CONF_OPTIONS "-DCMAKE_INSTALL_PREFIX=${${LIB_NAME}_ROOT}")
        list(APPEND ARG_CONF_OPTIONS "-DCMAKE_POSITION_INDEPENDENT_CODE=ON")
        if (CMAKE_GENERATOR)
            list(APPEND _GENERATOR_ARGS -G "${CMAKE_GENERATOR}")
        elseif(TRANSFER_ENGINE_NINJA_EXECUTABLE)
            list(APPEND _GENERATOR_ARGS -G Ninja)
        endif()

        _te_exec_command(
            COMMAND ${CMAKE_COMMAND} ${_GENERATOR_ARGS} ${ARG_CONF_OPTIONS} "${${_LIB_NAME_LOWER}_SOURCE_DIR}/${ARG_CONF_PATH}"
            WORKING_DIRECTORY "${${_LIB_NAME_LOWER}_BINARY_DIR}"
        )

        _te_exec_command(
            COMMAND ${CMAKE_COMMAND} --build . --target install -- -j${TRANSFER_ENGINE_BUILD_THREAD_NUM}
            WORKING_DIRECTORY "${${_LIB_NAME_LOWER}_BINARY_DIR}"
        )
    else()
        message(FATAL_ERROR "Unrecognized toolchain: ${ARG_TOOLCHAIN}")
    endif()

    file(WRITE "${_VERIFY_FILE}" "${${LIB_NAME}_CONF_TXT}")

    if (EXISTS "${${LIB_NAME}_ROOT}/lib64")
        set(${LIB_NAME}_LIB_PATH "${${LIB_NAME}_ROOT}/lib64")
    else()
        set(${LIB_NAME}_LIB_PATH "${${LIB_NAME}_ROOT}/lib")
    endif()
    set(${LIB_NAME}_ROOT "${${LIB_NAME}_ROOT}" PARENT_SCOPE)
    set(${LIB_NAME}_LIB_PATH "${${LIB_NAME}_LIB_PATH}" PARENT_SCOPE)
endfunction()