# This functions checks if the dependencies for the Yan LR plugin are available.
#
# If they are, the function sets the variable `FOUND_DEPENDENCIES` to `TRUE`. The function then also sets:
#
# - `ANTLR_EXECUTABLE` to the name of the ANTLR executable,
# - `ANTLR4CPP_LIBRARIES` to the paths of the libraries provided by ANTLR’s C++ runtime, and
# - `ANTLR4CPP_INCLUDE_DIRS` to the paths of the included directories of ANTLR’s C++ runtime
#
# . If the function was unsuccessful it sets `FOUND_DEPENDENCIES` to `FALSE` and stores the reason for the failure in the variable
# `FAILURE_MESSAGE`.
function (check_dependencies)

	set (FOUND_DEPENDENCIES FALSE PARENT_SCOPE)
	unset (ANTLR_EXECUTABLE)
	unset (FAILURE_MESSAGE)

	execute_process (COMMAND antlr4 RESULT_VARIABLE ANTLR4_NOT_AVAILABLE OUTPUT_VARIABLE ANTLR4_OUTPUT)
	execute_process (COMMAND antlr RESULT_VARIABLE ANTLR_NOT_AVAILABLE OUTPUT_VARIABLE ANTLR_OUTPUT)
	if (ANTLR4_NOT_AVAILABLE AND ANTLR_NOT_AVAILABLE)
		set (FAILURE_MESSAGE "ANTLR 4 executable (antlr4, antlr) not found" PARENT_SCOPE)
		return ()
	else (ANTLR4_NOT_AVAILABLE AND ANTLR_NOT_AVAILABLE)
		if (ANTLR4_NOT_AVAILABLE)
			set (ANTLR_EXECUTABLE antlr PARENT_SCOPE)
			set (ANTLR_VERSION ${ANTLR_OUTPUT})
		else (ANTLR4_NOT_AVAILABLE)
			set (ANTLR_EXECUTABLE antlr4 PARENT_SCOPE)
			set (ANTLR_VERSION ${ANTLR4_OUTPUT})
		endif (ANTLR4_NOT_AVAILABLE)
	endif (ANTLR4_NOT_AVAILABLE AND ANTLR_NOT_AVAILABLE)

	string (REGEX
		REPLACE ".*Version ([0-9]+\\.[0-9]+\\.[0-9]+).*"
			"\\1"
			ANTLR_VERSION
			${ANTLR_VERSION})
	if ("${ANTLR_VERSION}" VERSION_LESS 4.6)
		set (FAILURE_MESSAGE "ANTLR version 4.6 or later required (found version “${ANTLR_VERSION}”)" PARENT_SCOPE)
		return ()
	endif ("${ANTLR_VERSION}" VERSION_LESS 4.6)

	find_package (ANTLR4CPP QUIET)
	if (NOT ANTLR4CPP_FOUND)
		set (FAILURE_MESSAGE "ANTLR 4 CPP runtime (antlr4-cpp-runtime) not found" PARENT_SCOPE)
		return ()
	endif (NOT ANTLR4CPP_FOUND)
	set (ANTLR4CPP_LIBRARIES ${ANTLR4CPP_LIBRARIES} PARENT_SCOPE)
	set (ANTLR4CPP_INCLUDE_DIRS ${ANTLR4CPP_INCLUDE_DIRS} PARENT_SCOPE)

	# AdressSanitizer enabled builds of the plugin report runtime errors about member calls, which do not point to an object of type
	# `_Sp_counted_base` inside the system header file `shared_ptr_base.h`. In Clang builds of the plugin we ignore this error in our
	# [blacklist](tests/sanitizer.blacklist). Unfortunately GCC does not support a blacklist, so we remove the plugin in this case.
	set (DISABLE_PLUGIN_ASAN
	     ${ENABLE_ASAN}
	     AND
	     "${CMAKE_CXX_COMPILER_ID}"
	     MATCHES
	     "GNU"
	     AND
	     ${CMAKE_CXX_COMPILER_VERSION}
	     VERSION_LESS
	     9)
	if (${DISABLE_PLUGIN_ASAN})
		set (FAILURE_MESSAGE "ASan enabled GCC builds of the plugin report member calls on addresses, "
				     "which do not point to an object of type `_Sp_counted_base`" PARENT_SCOPE)
		return ()
	endif (${DISABLE_PLUGIN_ASAN})

	set (FOUND_DEPENDENCIES TRUE PARENT_SCOPE)
endfunction (check_dependencies)

# This functions generates the source files of the YAML parser using the given ANTLR executable (`ANTLR_EXECUTABLE`). The function also
# invokes the script `RenameSymbols.cmake` to replace the symbol names used by ANTLR in error messages by a more human readable form.
#
# The function exports the list
#
# - `GENERATED_SOURCE_FILES`, which contains the list of source files generated by ANTLR and `RenameSymbols.cmake`
#
# .
function (generate_code ANTLR_EXECUTABLE)
	set (GRAMMAR_NAME YAML)
	set (GRAMMAR_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${GRAMMAR_NAME}.g4)
	set (TOKEN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${GRAMMAR_NAME}.tokens)
	set (GENERATED_SOURCE_FILES_NAMES BaseListener Listener)
	set (PARSER_SOURCE_FILE ${CMAKE_CURRENT_BINARY_DIR}/${GRAMMAR_NAME}.cpp)
	set (PARSER_MODIFIED_SOURCE_FILE ${CMAKE_CURRENT_BINARY_DIR}/${GRAMMAR_NAME}ImprovedSymbolNames.cpp)

	foreach (file ${GENERATED_SOURCE_FILES_NAMES} "")
		foreach (extension "cpp" "h")
			set (filepath ${CMAKE_CURRENT_BINARY_DIR}/${GRAMMAR_NAME}${file}.${extension})
			set_source_files_properties (${filepath}
						     PROPERTIES
						     GENERATED
						     TRUE)
			if (CMAKE_COMPILER_IS_GNUCXX)
				set_source_files_properties (${filepath}
							     PROPERTIES
							     COMPILE_FLAGS
							     "-Wno-shadow")
			endif (CMAKE_COMPILER_IS_GNUCXX)
			if (NOT ${filepath} STREQUAL ${PARSER_SOURCE_FILE})
				list (APPEND GENERATED_SOURCE_FILES_EXPORT)
			endif (NOT ${filepath} STREQUAL ${PARSER_SOURCE_FILE})
			list (APPEND GENERATED_SOURCE_FILES ${filepath})
		endforeach (extension "cpp" "h")
	endforeach (file ${GENERATED_SOURCE_FILES_NAMES})

	add_custom_command (OUTPUT ${GENERATED_SOURCE_FILES}
			    COMMAND ${ANTLR_EXECUTABLE}
				    -Werror
				    -Dlanguage=Cpp
				    -o
				    ${CMAKE_CURRENT_BINARY_DIR}
				    -package
				    yanlr
				    ${GRAMMAR_FILE}
			    DEPENDS ${GRAMMAR_FILE} ${TOKEN_FILE}
			    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

	set_source_files_properties (${PARSER_MODIFIED_SOURCE_FILE}
				     PROPERTIES
				     GENERATED
				     TRUE)
	if (CMAKE_COMPILER_IS_GNUCXX)
		set_source_files_properties (${PARSER_MODIFIED_SOURCE_FILE}
					     PROPERTIES
					     COMPILE_FLAGS
					     "-Wno-shadow")
	endif (CMAKE_COMPILER_IS_GNUCXX)
	add_custom_command (OUTPUT ${PARSER_MODIFIED_SOURCE_FILE}
			    COMMAND ${CMAKE_COMMAND}
				    ARGS
				    -D
				    PARSER_SOURCE_FILE=${PARSER_SOURCE_FILE}
				    -D
				    PARSER_MODIFIED_SOURCE_FILE=${PARSER_MODIFIED_SOURCE_FILE}
				    -P
				    ${CMAKE_CURRENT_SOURCE_DIR}/RenameSymbols.cmake
			    DEPENDS ${PARSER_SOURCE_FILE} RenameSymbols.cmake
			    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

	set (GENERATED_SOURCE_FILES ${GENERATED_SOURCE_FILES_EXPORT} ${PARSER_MODIFIED_SOURCE_FILE} PARENT_SCOPE)

endfunction (generate_code)

if (DEPENDENCY_PHASE)
	check_dependencies ()
	if (NOT FOUND_DEPENDENCIES)
		remove_plugin (yanlr ${FAILURE_MESSAGE})
	else (NOT FOUND_DEPENDENCIES)
		generate_code (${ANTLR_EXECUTABLE})
		set (SOURCE_FILES
		     "${GENERATED_SOURCE_FILES}"
		     listener.hpp
		     listener.cpp
		     error_listener.hpp
		     error_listener.cpp
		     yaml_lexer.hpp
		     yaml_lexer.cpp
		     yanlr.hpp
		     yanlr.cpp)
	endif (NOT FOUND_DEPENDENCIES)
endif (DEPENDENCY_PHASE)

# The generated parser code seems to contain a double free that causes the unit test to crash with a segfault on **some** systems that use
# `glibc`. If AddressSanitizer is enabled everything seems to work fine.
set (TEST_ARGUMENTS ADD_TEST CPP_TEST)
if ("${CMAKE_SYSTEM_NAME}" STREQUAL Linux AND NOT ${ENABLE_ASAN})

	# We only disable the test, if we detect a GNU C Library based system.
	execute_process (COMMAND ldd --version RESULT_VARIABLE ANTLR4_NOT_AVAILABLE OUTPUT_VARIABLE LDD_OUTPUT)
	if ("${LDD_OUTPUT}" MATCHES "GLIBC|GNU libc")
		set (TEST_ARGUMENTS "")
	endif ("${LDD_OUTPUT}" MATCHES "GLIBC|GNU libc")

endif ("${CMAKE_SYSTEM_NAME}" STREQUAL Linux AND NOT ${ENABLE_ASAN})

add_plugin (yanlr CPP ${TEST_ARGUMENTS}
	    SOURCES ${SOURCE_FILES}
	    INCLUDE_SYSTEM_DIRECTORIES ${ANTLR4CPP_INCLUDE_DIRS}
	    LINK_LIBRARIES ${ANTLR4CPP_LIBRARIES}
	    LINK_ELEKTRA elektra-ease
	    INSTALL_TEST_DATA TEST_README
	    # Unfortunately it looks like ANTLR’s code [causes a container-overflow](https://github.com/antlr/antlr4/issues/2332).
	    TEST_ENVIRONMENT "ASAN_OPTIONS=detect_container_overflow=0"
	    TEST_REQUIRED_PLUGINS directoryvalue yamlsmith)
