##*************************************************************************##
##  CUBE        http://www.scalasca.org/                                   ##
##*************************************************************************##
##  Copyright (c) 2023                                                     ##
##  Forschungszentrum Juelich GmbH, Juelich Supercomputing Centre          ##
##                                                                         ##
##  This software may be modified and distributed under the terms of       ##
##  a BSD-style license.  See the COPYING file in the package base         ##
##  directory for details.                                                 ##
##*************************************************************************##


###################################################################################################
# Checks whether the compiler supports thread-local storage.
# - sets THREAD_LOCAL_STORAGE_SPECIFIER
# - converted from m4/afs_thread_local_storage.m4
# - not yet complete, some autotools checks seem to be outdated, how to handle current mingw?
###################################################################################################
function(check_thread_local_storage_specifier)
    include(CheckCSourceCompiles)
    set(MAIN "\nint main() { return 0; }")
    # Check if __thread is supported
    check_c_source_compiles("__thread int x; ${MAIN}" HAVE_THREAD_LOCAL_STORAGE)
    if(HAVE_THREAD_LOCAL_STORAGE)
        set(THREAD_LOCAL_STORAGE_SPECIFIER "__thread" PARENT_SCOPE)
    else()
        # Check if _Thread_local is supported
        check_c_source_compiles("_Thread_local int x; ${MAIN}" HAVE_THREAD_LOCAL_STORAGE)
        if(HAVE_THREAD_LOCAL_STORAGE)
            set(THREAD_LOCAL_STORAGE_SPECIFIER "_Thread_local" PARENT_SCOPE)
        else()
            # Check if _Thread_local is supported with -std=c11
            set(CMAKE_REQUIRED_FLAGS "-std=c11")
            check_c_source_compiles("_Thread_local int x; ${MAIN}" HAVE_THREAD_LOCAL_STORAGE)
            if(HAVE_THREAD_LOCAL_STORAGE)
                set(THREAD_LOCAL_STORAGE_SPECIFIER "_Thread_local" PARENT_SCOPE)
            endif()

            set(CMAKE_REQUIRED_FLAGS "")
        endif()
    endif()
endfunction()

###################################################################################################
# check for gcc atomic builtins
# - sets HAVE_GCC_ATOMIC_BUILTINS or HAVE_GCC_ATOMIC_BUILTINS_NEEDS_CASTS
# - converted from m4/afs_gcc_atomic_builtins.m4
# - not yet complete, doesn't support systems without gcc atomic builtins
###################################################################################################
function(check_gcc_atomic_builtins)
    set(ATOMIC_LIBRARIES " " "-latomic") # check if libatomic is required
    foreach(LIB ${ATOMIC_LIBRARIES})
        set(CMAKE_REQUIRED_LIBRARIES "${LIB}")

        set(ATOMIC_TEST_SOURCE "
            int main() { return 0; }
            #include \"${CMAKE_SOURCE_DIR}/common/utils/src/atomic/UTILS_Atomic.inc.c\"
            ")
        check_c_source_compiles("${ATOMIC_TEST_SOURCE}" HAVE_GCC_ATOMIC_BUILTINS)

        if(HAVE_GCC_ATOMIC_BUILTINS)
            set(HAVE_GCC_ATOMIC_BUILTINS TRUE PARENT_SCOPE)
            break()
        else() # Check if atomic builtins need casts
            set(ATOMIC_TEST_SOURCE "
            #define HAVE_GCC_ATOMIC_BUILTINS_NEEDS_CASTS 1
            #include \"${CMAKE_SOURCE_DIR}/common/utils/src/atomic/UTILS_Atomic.inc.c\"
            int main() { return 0; }
            ")
            check_c_source_compiles("${ATOMIC_TEST_SOURCE}" HAVE_GCC_ATOMIC_BUILTINS_NEEDS_CASTS)

            if(HAVE_GCC_ATOMIC_BUILTINS_NEEDS_CASTS)
                set(HAVE_GCC_ATOMIC_BUILTINS_NEEDS_CASTS TRUE PARENT_SCOPE)
                break()
            endif()
        endif()
    endforeach()
endfunction()

###################################################################################################
# generate header files CubeErrorCodes.h and ${PROJECT_NAME}_error_decls.gen.h
###################################################################################################
string(TOLOWER "${CMAKE_PROJECT_NAME}" PROJECT_NAME)
string(TOUPPER "${CMAKE_PROJECT_NAME}" AFS_PACKAGE_NAME)

file(READ "${CMAKE_SOURCE_DIR}/share/${PROJECT_NAME}/${PROJECT_NAME}.errors" INPUT_CONTENT)
# Split into lines
string(REGEX REPLACE "\n" ";" INPUT_LINES "${INPUT_CONTENT}")

# create ${PROJECT_NAME}_error_decls.gen.h and PACKAGE_SPECIFIC_ERROR_CODES from the contents of ${PROJECT_NAME}.errors
set(PACKAGE_SPECIFIC_ERROR_CODES "")
foreach(line IN LISTS INPUT_LINES)
    # ${PROJECT_NAME}_error_decls.gen.h
    string(REGEX REPLACE "error_code ([A-Z_]+) \"(.*)\"" "/** \\2 */\n${AFS_PACKAGE_NAME}_ERROR_\\1," ENTRY "${line}")
    set(PACKAGE_SPECIFIC_ERROR_CODES "${PACKAGE_SPECIFIC_ERROR_CODES}\n${ENTRY}")
    # ${PROJECT_NAME}_error_decls.gen.h
    string(REGEX REPLACE "error_code ([A-Z_]+) \"(.*)\"" "UTILS_ERROR_DECL(\\1, \"\\2\")," ENTRY "${line}")
    set(ERROR_DECLS "${ERROR_DECLS}\n${ENTRY}")
endforeach()

# Remove the extra comma at the end
string(REGEX REPLACE ",$" "" PACKAGE_SPECIFIC_ERROR_CODES "${PACKAGE_SPECIFIC_ERROR_CODES}")

# configure ErrorCodes.tmpl.h with PACKAGE_SPECIFIC_ERROR_CODES and AFS_PACKAGE_NAME
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/exception/ErrorCodes.tmpl.h ${CMAKE_BINARY_DIR}/src/CubeErrorCodes.h @ONLY)

# Write the content to the output file
file(WRITE "${CMAKE_BINARY_DIR}/src/${PROJECT_NAME}_error_decls.gen.h" "${ERROR_DECLS}")

# header that is required for UTILS
file(CREATE_LINK "${CMAKE_SOURCE_DIR}/build-config/common/config-common.h" "${CMAKE_BINARY_DIR}/src/config-common.h" COPY_ON_ERROR SYMBOLIC)
file(CREATE_LINK "${CMAKE_SOURCE_DIR}/build-config/common/config-common.h" "${CMAKE_BINARY_DIR}/src/${PROJECT_NAME}-config-common.h" COPY_ON_ERROR SYMBOLIC)

###################################################################################################
# build utils as object library
###################################################################################################

set ( UTILS_SOURCES
     ./src/cstr/UTILS_CStr.c
     ./src/exception/UTILS_Debug.c
     ./src/exception/UTILS_Error.c
     ./src/io/UTILS_IO_GetExe.c
     ./src/io/UTILS_IO_Tools.c
     ./test/cutest/CuTest.c
)
#if( NOT HAVE_GCC_ATOMIC_BUILTINS) todo: support systems without gcc atomic builtins
#list(APPEND UTILS_SOURCES
#     ./src/mutex/UTILS_Mutex.c
#     ./src/mutex/UTILS_Mutex.inc.c
#)
#endif()

add_library(utils OBJECT
    ${UTILS_SOURCES}
)

target_compile_definitions(utils PRIVATE
    HAVE_CONFIG_H # required by UTILS_Atomic.h
)

target_include_directories(utils PUBLIC
    ${CMAKE_BINARY_DIR}/src
   ./include
   ./test/cutest
)

set_property(TARGET utils PROPERTY POSITION_INDEPENDENT_CODE ON)

###################################################################################################
# check requirements
###################################################################################################
check_thread_local_storage_specifier()
target_compile_definitions(utils PRIVATE THREAD_LOCAL_STORAGE_SPECIFIER=${THREAD_LOCAL_STORAGE_SPECIFIER})

check_gcc_atomic_builtins()
if(HAVE_GCC_ATOMIC_BUILTINS)
     target_compile_definitions(utils PRIVATE HAVE_GCC_ATOMIC_BUILTINS)
elseif(HAVE_GCC_ATOMIC_BUILTINS_NEEDS_CASTS)
     target_compile_definitions(utils PRIVATE HAVE_GCC_ATOMIC_BUILTINS HAVE_GCC_ATOMIC_BUILTINS_NEEDS_CASTS)
else()
     message(FATAL_ERROR "gcc atomic builtins are not available on this platform")
endif()
