# 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.9)
		set (
			FAILURE_MESSAGE
			"ANTLR version 4.9 or later required (found version “${ANTLR_VERSION}”)"
			PARENT_SCOPE)
		return ()
	endif ("${ANTLR_VERSION}" VERSION_LESS 4.9)

	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
		ERROR_QUIET)
	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)
