##############################################################################
# Copyright 2020-2024 Leon Lynch
#
# This file is licensed under the terms of the LGPL v2.1 license.
# See LICENSE file.
##############################################################################

cmake_minimum_required(VERSION 3.16)

project(tr31
	VERSION 0.6.2
	DESCRIPTION "Key block library and tools for ANSI X9.143, ASC X9 TR-31 and ISO 20038"
	HOMEPAGE_URL "https://github.com/openemv/tr31"
	LANGUAGES C
)

# Determine whether this project is the top-level project
if(${CMAKE_VERSION} VERSION_LESS "3.21")
	get_directory_property(TR31_HAS_PARENT PARENT_DIRECTORY)
	if(NOT TR31_HAS_PARENT)
		set(TR31_IS_TOP_LEVEL True)
	endif()
else()
	# CMake >=3.21 provides <PROJECT-NAME>_IS_TOP_LEVEL
	set(TR31_IS_TOP_LEVEL ${tr31_IS_TOP_LEVEL})
endif()

# Configure compiler
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_EXTENSIONS OFF)
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
	add_compile_options(-Wall)
	add_compile_options($<$<CONFIG:Debug>:-ggdb>)
	add_compile_options($<$<CONFIG:RelWithDebInfo>:-ggdb>)
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "Clang")
	add_compile_options(-Wall)
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "AppleClang")
	add_compile_options(-Wall)
endif()

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")

# Determine version string (use git describe string if possible)
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.git)
	find_package(Git)

	if(GIT_FOUND)
		execute_process(
			COMMAND ${GIT_EXECUTABLE} describe --always --dirty
			WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
			RESULT_VARIABLE GIT_DESCRIBE_FAILED
			OUTPUT_VARIABLE GIT_DESCRIBE_STRING
			OUTPUT_STRIP_TRAILING_WHITESPACE
		)

		if(GIT_DESCRIBE_FAILED)
			message(WARNING "Failed to obtain git describe string (${GIT_DESCRIBE_FAILED})")
		else()
			message(STATUS "Using git describe string \"${GIT_DESCRIBE_STRING}\"")
			set(TR31_VERSION_STRING ${GIT_DESCRIBE_STRING})
		endif()
	endif()
endif()
if(NOT TR31_VERSION_STRING)
	# Otherwise use project version
	set(TR31_VERSION_STRING ${tr31_VERSION})
endif()

# Configure testing before adding subdirectories
if(TR31_IS_TOP_LEVEL)
	# Configure Valgrind before including CTest module
	find_program(VALGRIND_COMMAND valgrind)
	set(MEMORYCHECK_TYPE Valgrind)
	set(VALGRIND_COMMAND_OPTIONS "--leak-check=full --show-reachable=yes --track-origins=yes --num-callers=100 --show-error-list=yes")

	# Only top-level project should include CTest module
	include(CTest)
endif()

# Allow parent scope to provide crypto targets when not building shared libs
if(TR31_IS_TOP_LEVEL OR BUILD_SHARED_LIBS)
	add_subdirectory(crypto)
	add_subdirectory(crypto/test)
elseif (NOT TARGET crypto_tdes OR
	NOT TARGET crypto_aes OR
	NOT TARGET crypto_mem OR
	NOT TARGET crypto_rand)
	message(FATAL_ERROR "Parent project must provide crypto libraries for static builds")
endif()

add_subdirectory(src)
add_subdirectory(test)

include(GNUInstallDirs) # provides CMAKE_INSTALL_* variables and good defaults for install()

# install README and LICENSE files to runtime component
install(FILES
	"${CMAKE_CURRENT_SOURCE_DIR}/README.md"
	"${CMAKE_CURRENT_SOURCE_DIR}/LICENSE"
	TYPE DOC
	COMPONENT tr31_runtime
)

# generate and install basic CMake config files
include(CMakePackageConfigHelpers) # provides CMake config generator macros
# NOTE: crypto subdirectory provides CRYPTO_PACKAGE_DEPENDENCIES
foreach(pkg IN LISTS CRYPTO_PACKAGE_DEPENDENCIES)
	# build dependency string for use in CMake config file
	string(APPEND TR31_CONFIG_PACKAGE_DEPENDENCIES "find_dependency(${pkg})\n")
endforeach()
set(TR31_INSTALL_CMAKEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} CACHE STRING "Installation location for tr31 CMake config files")
message(STATUS "Using CMake config install location \"${TR31_INSTALL_CMAKEDIR}\"")
configure_package_config_file(cmake/tr31Config.cmake.in
	"${CMAKE_CURRENT_BINARY_DIR}/cmake/tr31Config.cmake"
	INSTALL_DESTINATION "${TR31_INSTALL_CMAKEDIR}"
)
write_basic_package_version_file(
	"${CMAKE_CURRENT_BINARY_DIR}/cmake/tr31ConfigVersion.cmake"
	COMPATIBILITY SameMinorVersion
)
install(FILES
	"${CMAKE_CURRENT_BINARY_DIR}/cmake/tr31Config.cmake"
	"${CMAKE_CURRENT_BINARY_DIR}/cmake/tr31ConfigVersion.cmake"
	DESTINATION "${TR31_INSTALL_CMAKEDIR}"
	COMPONENT tr31_development
)
install(EXPORT tr31Targets
	FILE tr31Targets.cmake
	DESTINATION "${TR31_INSTALL_CMAKEDIR}"
	NAMESPACE tr31::
	COMPONENT tr31_development
)
export(EXPORT tr31Targets
	FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/tr31Targets.cmake"
	NAMESPACE tr31::
)

# generate and install pkgconfig file
set(TR31_INSTALL_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE STRING "Installation location for tr31 pkgconfig files")
message(STATUS "Using pkgconfig install location \"${TR31_INSTALL_PKGCONFIG_DIR}\"")
set(TR31_PKGCONFIG_LIB_NAME ${CMAKE_PROJECT_NAME})
# NOTE: crypto subdirectory provides CRYPTO_PKGCONFIG_REQ_PRIV and CRYPTO_PKGCONFIG_LIBS_PRIV
set(TR31_PKGCONFIG_REQ_PRIV ${CRYPTO_PKGCONFIG_REQ_PRIV})
set(TR31_PKGCONFIG_LIBS_PRIV ${CRYPTO_PKGCONFIG_LIBS_PRIV})
configure_file(pkgconfig/libtr31.pc.in
	"${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/libtr31.pc"
	@ONLY
)
install(FILES
	"${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/libtr31.pc"
	DESTINATION "${TR31_INSTALL_PKGCONFIG_DIR}"
	COMPONENT tr31_development
)

# install bash-completion file
find_package(bash-completion CONFIG) # optional for providing bash-completion files
if(BASH_COMPLETION_FOUND)
	set(TR31_INSTALL_BASH_COMPLETION_DIR ${CMAKE_INSTALL_DATADIR}/bash-completion/completions CACHE STRING "Installation location for tr31 bash-completion files")
	message(STATUS "Using bash-completion install location \"${TR31_INSTALL_BASH_COMPLETION_DIR}\"")

	# install bash completion file
	install(FILES
		"${CMAKE_CURRENT_SOURCE_DIR}/bash-completion/tr31-tool"
		DESTINATION "${TR31_INSTALL_BASH_COMPLETION_DIR}"
		COMPONENT tr31_runtime
	)
endif()

# generate and install doxygen documentation
option(BUILD_DOCS "Build documentation")
find_package(Doxygen) # optional for generating doxygen files
if(Doxygen_FOUND)
	# use doxygen to generate HTML
	set(DOXYGEN_GENERATE_HTML YES)
	# ignore occurances of GCC __attribute__
	set(DOXYGEN_MACRO_EXPANSION YES)
	set(DOXYGEN_EXPAND_ONLY_PREDEF YES)
	set(DOXYGEN_PREDEFINED "__attribute__(x)=")

	# generate docs for public header
	if(BUILD_DOCS)
		doxygen_add_docs(docs
			tr31.h
			tr31_strings.h
			ALL # build by default
			WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src"
		)

		# install generated docs by default
		install(
			DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html
			TYPE DOC
			COMPONENT tr31_docs
		)
	else()
		doxygen_add_docs(docs
			tr31.h
			tr31_strings.h
			WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src"
		)
	endif()
endif()

# configure CPack to generate .tar.gz package
set(CPACK_GENERATOR "TGZ")
set(CPACK_PACKAGE_VERSION "${TR31_VERSION_STRING}")
set(CPACK_PACKAGE_VENDOR "OpenEMV")
set(CPACK_PACKAGE_CONTACT "lynch.leon@gmail.com")
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")

# configure generic component packaging attributes
set(CPACK_COMPONENT_TR31_DEVELOPMENT_DESCRIPTION "Development files for TR-31 library")
set(CPACK_COMPONENT_TR31_DOCS_DESCRIPTION "Documentation for TR-31 library")
set(CPACK_COMPONENT_TR31_DEVELOPMENT_DEPENDS tr31_runtime)

# NOTE: for monolithic packages, uncomment the line below or specify it when
# invoking CMake
#set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)

# generate Debian/Ubuntu packages if dpkg is available
find_package(dpkg) # optional for building Debian/Ubuntu packages
if(dpkg_FOUND)
	# configure CPack to generate .deb package
	list(APPEND CPACK_GENERATOR "DEB")
	# generate component packages
	# NOTE: this is overridden by CPACK_COMPONENTS_GROUPING above
	set(CPACK_DEB_COMPONENT_INSTALL ON)
	set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS ON)
	# use default debian package naming
	set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT")
	# assign package names to components
	set(CPACK_DEBIAN_TR31_RUNTIME_PACKAGE_NAME "tr31-runtime")
	set(CPACK_DEBIAN_TR31_DEVELOPMENT_PACKAGE_NAME "tr31-dev")
	set(CPACK_DEBIAN_TR31_DOCS_PACKAGE_NAME "tr31-doc")
	# assign package architecture to documentation component
	set(CPACK_DEBIAN_TR31_DOCS_PACKAGE_ARCHITECTURE "all")
	# assign package sections to components
	set(CPACK_DEBIAN_TR31_RUNTIME_PACKAGE_SECTION "utils")
	set(CPACK_DEBIAN_TR31_DEVELOPMENT_PACKAGE_SECTION "devel")
	set(CPACK_DEBIAN_TR31_DOCS_PACKAGE_SECTION "doc")
	# assign package suggestions
	set(CPACK_DEBIAN_TR31_DOCS_PACKAGE_SUGGESTS "tr31-dev")
	# generate package dependency list
	set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
endif()

# generate RedHat/Fedora packages if rpmbuild is available
find_package(rpmbuild) # optional for building RedHat/Fedora packages
if(rpmbuild_FOUND)
	list(APPEND CPACK_GENERATOR "RPM")
	# generate component packages
	# NOTE: this is overridden by CPACK_COMPONENTS_GROUPING above
	set(CPACK_RPM_COMPONENT_INSTALL ON)
	# use default rpm package naming
	set(CPACK_RPM_FILE_NAME "RPM-DEFAULT")
	set(CPACK_RPM_PACKAGE_RELEASE_DIST ON)
	# configure license and changelog
	set(CPACK_RPM_PACKAGE_LICENSE "LGPLv2+")
	set(CPACK_RPM_CHANGELOG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/rpm_changelog.txt")
	# assign package names to components
	set(CPACK_RPM_TR31_RUNTIME_PACKAGE_NAME "tr31-runtime")
	set(CPACK_RPM_TR31_DEVELOPMENT_PACKAGE_NAME "tr31-devel")
	set(CPACK_RPM_TR31_DOCS_PACKAGE_NAME "tr31-doc")
	# assign package architecture to documentation component
	set(CPACK_RPM_TR31_DOCS_PACKAGE_ARCHITECTURE "noarch")
	# assign package suggestions
	set(CPACK_RPM_TR31_DOCS_PACKAGE_SUGGESTS "tr31-devel")
	# NOTE: RPM generator automatically detects dependencies
endif()

include(CPack)