#*******************************************************************************
#**  This file is part of Pecunia.                                           ***
#**                                                                          ***
#**  Copyright (C) 2017, 2018, 2019, 2020, 2021, 2023, 2024, 2025            ***
#**  John Schneiderman <Licencing _AT_ Schneiderman _DOT_ me>                ***
#**                                                                          ***
#**  This program is free software: you can redistribute it and/or modify it ***
#**  under the terms of the GNU Lesser General Public License as published   ***
#**  by the Free Software Foundation, either version 3 of the License, or    ***
#**  (at your option) any later version.                                     ***
#**                                                                          ***
#**  This program is distributed in the hope that it will be useful, but     ***
#**  WITHOUT ANY WARRANTY; without even the implied warranty of              ***
#**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                    ***
#**  See the GNU Lesser General Public License for more details.             ***
#**                                                                          ***
#**  You should have received a copy of the GNU Lesser General Public License***
#**  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
#*******************************************************************************
cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
project(Pecunia
	VERSION 0.10.0
	DESCRIPTION "Library using the ISO-4217 currency standard & a fixed monetary unit size"
	HOMEPAGE_URL "http://pecunia.schneiderman.me"
	LANGUAGES CXX
)

include (src/SemVer.cmake)
set(
	${PROJECT_NAME}_PRE_RELEASE
	""
	CACHE
	STRING
	"Sets the pre-release name of the semantic version."
)
mark_as_advanced(${PROJECT_NAME}_PRE_RELEASE)
set(
	${PROJECT_NAME}_REVISION
	""
	CACHE
	STRING
	"Sets the pre-release revision of the semantic version."
)
mark_as_advanced(${PROJECT_NAME}_REVISION)
set(
	${PROJECT_NAME}_CHANGESET
	""
	CACHE
	STRING
	"Sets the changeset hash for the metadata of the semantic version."
)
mark_as_advanced(${PROJECT_NAME}_CHANGESET)

#{ Version Repository Information

if (NOT ${PROJECT_NAME}_CHANGESET)
	find_program(HGCOMMAND
		NAMES hg
		HINTS /Applications/TortoiseHg.app/Contents/MacOS
		REQUIRED
	)
	execute_process(
		COMMAND ${HGCOMMAND} id -i
		WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
		OUTPUT_VARIABLE HG_SHORT_HASH
		OUTPUT_STRIP_TRAILING_WHITESPACE
		ERROR_QUIET
		RESULT_VARIABLE HG_RESULT
	)

	if (HG_RESULT EQUAL 0)
		string(SUBSTRING ${HG_SHORT_HASH} 0 8 ${PROJECT_NAME}_CHANGESET)
	else ()
		message(FATAL_ERROR "Failed to get the current change-set, error code ${HG_RESULT}")
	endif ()
endif ()

#}

set_semantic_version("${${PROJECT_NAME}_PRE_RELEASE}" "${${PROJECT_NAME}_REVISION}" "${${PROJECT_NAME}_CHANGESET}")

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_DEBUG_POSTFIX "d")

if (EXISTS "${CMAKE_BINARY_DIR}/conanbuildinfo.cmake")
	message(STATUS "Enabling Conan.io support.")
	include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
	conan_basic_setup()
endif ()

set(
	${PROJECT_NAME}_SUB_MINOR_DIGITS
	2u
	CACHE
	STRING
	"Sets the extra number of digits to have after the minor unit."
)
set(
	${PROJECT_NAME}_MINOR_UNIT_TYPE
	std::uint16_t
	CACHE
	STRING
	"Sets the type used for the minor unit."
)
set(
	${PROJECT_NAME}_UNIT_STORAGE_TYPE
	std::int64_t
	CACHE
	STRING
	"Sets the type used for the money amount storage unit."
)
set(
	${PROJECT_NAME}_FLOATING_POINT_TYPE
	double
	CACHE
	STRING
	"Sets the type used for floating point operations."
)
set(
	${PROJECT_NAME}_FLOATING_POINT_PRECISION
	2u
	CACHE
	STRING
	"Sets the extra number of digit precision during floating point operations."
)
mark_as_advanced(${PROJECT_NAME}_FLOATING_POINT_PRECISION)
set(
	${PROJECT_NAME}_DEFAULT_ROUNDING_DIGITS
	3u
	CACHE
	STRING
	"Sets the number of digits during rounding operations to use as a default."
)
mark_as_advanced(${PROJECT_NAME}_DEFAULT_ROUNDING_DIGITS)
option(${PROJECT_NAME}_ENABLE_CODE_COVERAGE_TARGET
	"Enables/disables the creation of a code coverage target."
	OFF
)
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
set(
	${PROJECT_NAME}_ERROR_STACK_FRAME_SIZE
	256u
	CACHE
	STRING
	"Sets the maximum number of stack frames to collect in an exception thrown."
)
mark_as_advanced(${PROJECT_NAME}_ERROR_STACK_FRAME_SIZE)
option(${PROJECT_NAME}_ENABLE_CODE_COVERAGE_TARGET
	"Enables/disables the creation of a code coverage target."
	OFF
)
set(
	${PROJECT_NAME}_PARALLEL_SIZE
	"30"
	CACHE
	STRING
	"The number of parallel processes to use."
)
mark_as_advanced(${PROJECT_NAME}_PARALLEL_SIZE)

if (APPLE)
	set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
	set(CMAKE_OSX_DEPLOYMENT_TARGET "12.7" CACHE STRING "" FORCE)
	set(
		${PROJECT_NAME}_LOCALE
		"en_US.UTF-8"
		CACHE
		STRING
		"Sets the default locale used when setting up the library."
	)
elseif (UNIX)
	set(
		${PROJECT_NAME}_LOCALE
		"C.UTF-8"
		CACHE
		STRING
		"Sets the default locale used when setting up the library."
	)
elseif (WIN32)
	set(
		${PROJECT_NAME}_LOCALE
		"en_US.UTF-8"
		CACHE
		STRING
		"Sets the default locale used when setting up the library."
	)
else ()
	message(FATAL_ERROR "The default locale for the OS is not known.")
endif ()
mark_as_advanced(${PROJECT_NAME}_LOCALE)
set(${PROJECT_NAME}_COPYRIGHT_YEARS "2017 - 2025")

if (${PROJECT_NAME}_ENABLE_CODE_COVERAGE_TARGET AND NOT BUILD_TESTING)
	message(FATAL_ERROR "Code coverage target requires unit tests to be enabled.")
endif()

# Allow for packagers to supply the name of the library directory.
if (DEFINED LIB_INSTALL_DIR)
	message(STATUS "Using supplied library directory installation path.")
	set (${PROJECT_NAME}_LIBRARY_INSTALL_DIR ${LIB_INSTALL_DIR})
else ()
	if (EXISTS "${CMAKE_INSTALL_PREFIX}/lib32/" AND CMAKE_SIZEOF_VOID_P EQUAL 4)
		set (${PROJECT_NAME}_LIBRARY_INSTALL_DIR lib32)
	elseif (EXISTS "${CMAKE_INSTALL_PREFIX}/lib64/" AND CMAKE_SIZEOF_VOID_P EQUAL 8)
		set (${PROJECT_NAME}_LIBRARY_INSTALL_DIR lib64)
	else ()
		set (${PROJECT_NAME}_LIBRARY_INSTALL_DIR lib)
	endif ()
endif ()

if (${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
	message(DEBUG "Adding additional warning checks for GCC.")
	add_compile_options("-Wall")
	add_compile_options("-Wcast-qual")
	add_compile_options("-Wconversion")
	add_compile_options("-Wdangling-else")
	add_compile_options("-Wdeprecated")
	add_compile_options("-Wdeprecated-declarations")
	add_compile_options("-Wduplicated-branches")
	add_compile_options("-Wduplicated-cond")
	add_compile_options("-Wcast-align")
	add_compile_options("-Wnull-dereference")
	add_compile_options("-Wdouble-promotion")
	add_compile_options("-Werror")
	add_compile_options("-Wextra")
	add_compile_options("-Wextra-semi")
	add_compile_options("-Wfloat-equal")
	add_compile_options("-Winit-self")
	add_compile_options("-Wlogical-op")
	add_compile_options("-Wmissing-declarations")
	add_compile_options("-Wpedantic")
	add_compile_options("-Wshadow")
	add_compile_options("-Wsign-conversion")
	add_compile_options("-Wsuggest-override")
	add_compile_options("-Wundef")
	add_compile_options("-Wuninitialized")
	add_compile_options("-Wunused-macros")
	add_compile_options("-Winline")
	add_compile_options("-fconcepts-diagnostics-depth=5")
	add_link_options("-rdynamic")

	if ("${CMAKE_BUILD_TYPE}" STREQUAL Debug)
		# Justification: complaining about 3rd party libraries
		set(ENV{LSAN_OPTIONS} "suppressions=${CMAKE_SOURCE_DIR}/src/lsan.supp")
		add_compile_options("-fsanitize=address,leak,undefined")
		add_link_options("-fsanitize=address,leak,undefined")
		add_compile_options("-fsanitize-address-use-after-scope")
		add_link_options("-fsanitize-address-use-after-scope")
		add_compile_options("-fprofile-arcs")
		add_compile_options("-ftest-coverage")
		add_link_options("--coverage")
		add_link_options("-lgcov")
	else ()
		add_link_options("-flto")
	endif ()
	# Justification: caused by the interaction between forward declarations and definitions of
	# enum classes.
	add_compile_options("-Wno-attributes")
elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL Clang)
	message(DEBUG "Adding additional warning checks for Clang.")
	add_compile_options(-Wall)
	add_compile_options(-Wextra)
	add_compile_options(-Werror)
        # Justification: this is done as standard practise in the code base.
	add_compile_options(-Wno-unqualified-std-cast-call)
	add_link_options("-rdynamic")

	if ("${CMAKE_BUILD_TYPE}" STREQUAL Debug)
		set(ENV{LSAN_OPTIONS} "suppressions=${CMAKE_SOURCE_DIR}/src/lsan.supp")
		add_compile_options("-fsanitize=address,leak,undefined")
		add_link_options("-fsanitize=address,leak,undefined")
		add_compile_options("-fsanitize-address-use-after-scope")
		add_link_options("-fsanitize-address-use-after-scope")
	else ()
		add_link_options("-flto")
	endif ()
elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
	message(DEBUG "Adding additional warning checks for MSVC.")
	add_compile_options("/permissive-")
	add_compile_options("/Zc:__cplusplus")
	add_compile_options("/Zc:checkGwOdr")
	add_compile_options("/Zc:enumTypes")
	add_compile_options("/Zc:externConstexpr")
	add_compile_options("/Zc:templateScope")
	add_compile_options("/Zc:throwingNew")
	add_compile_options("/fp:strict")
	add_compile_options("/external:anglebrackets")
	add_compile_options("/external:W0")
	add_compile_options("/W4")
	add_compile_options("/utf-8")
	add_compile_options("/WX")
	add_link_options($<$<NOT:$<CONFIG:Debug>>:/INCREMENTAL:NO> $<$<NOT:$<CONFIG:Debug>>:/LTCG>)
	# Justification: The code is meant to be compiled as a single collection. When this changes,
	# make the templates member variables unique pointers.
	add_compile_options("/wd4251")
	# Justification: Using pragmas from other compilers.
	add_compile_options("/wd4068")
endif ()

include(CTest)
enable_testing()

add_subdirectory(src)

if (${PROJECT_NAME}_ENABLE_CODE_COVERAGE_TARGET)
	find_program(LCOV_FILE_PATH lcov)

	if (NOT LCOV_FILE_PATH)
		message(FATAL_ERROR "Failed to locate 'lcov' not found!")
	endif ()
	find_program(GENHTML_FILE_PATH genhtml)

	if (NOT GENHTML_FILE_PATH)
		message(FATAL_ERROR "Failed to locate 'genhtml' not found!")
	endif ()
	find_program(GCOVR_FILE_PATH gcovr)

	if (NOT GCOVR_FILE_PATH)
		message(FATAL_ERROR "Failed to locate 'gcovr' not found!")
	endif ()
	add_custom_target(Run-Code-Coverage
			COMMAND "${LCOV_FILE_PATH}"
				--quiet
				--parallel "${${PROJECT_NAME}_PARALLEL_SIZE}"
				--directory "${PROJECT_BINARY_DIR}"
				--base-directory "/"
				--rc geninfo_unexecuted_blocks=1
				--output-file cpp-code-coverage.info
				--capture
			COMMAND "${LCOV_FILE_PATH}"
				--quiet
				--parallel "${${PROJECT_NAME}_PARALLEL_SIZE}"
				--directory "${PROJECT_BINARY_DIR}"
				--base-directory "/"
				--output-file cpp-code-coverage.info
				--remove cpp-code-coverage.info '/usr/*'
				--remove cpp-code-coverage.info '*unit-tests'
			COMMAND "${GENHTML_FILE_PATH}"
				--quiet
				--parallel "${${PROJECT_NAME}_PARALLEL_SIZE}"
				--output-directory code-coverage
				--title "${PROJECT_NAME} All Unit tests"
				--legend
				cpp-code-coverage.info
			COMMAND "${GCOVR_FILE_PATH}"
				--cobertura
				--root "${PROJECT_SOURCE_DIR}"
				--exclude-unreachable-branches
				--print-summary
				-j "${${PROJECT_NAME}_PARALLEL_SIZE}"
				--output "${PROJECT_BINARY_DIR}/cpp-code-coverage.xml"
				"${PROJECT_BINARY_DIR}"
			COMMAND echo "Generated Report Path:"
			COMMAND echo "file://${PROJECT_BINARY_DIR}/code-coverage/index.html"
			BYPRODUCTS "${PROJECT_BINARY_DIR}/code-coverage/index.html"
				"${PROJECT_BINARY_DIR}/cpp-code-coverage.xml"
				"${PROJECT_BINARY_DIR}/cpp-code-coverage.info"
			WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
			COMMENT "Generating code coverage information..."
	)
endif ()

if (${PROJECT_NAME}_BUILD_DOCUMENTATION)
	find_package(Doxygen
		REQUIRED dot
		OPTIONAL_COMPONENTS mscgen dia
	)
	set(DOXYGEN_GENERATE_META_TAGS YES)
	set(DOXYGEN_GENERATE_HTML YES)
	set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc)
	set(DOXYGEN_EXTRACT_ALL YES)
	set(DOXYGEN_EXTRACT_PRIVATE NO)
	set(DOXYGEN_EXTRACT_STATIC YES)
	set(DOXYGEN_USE_MDFILE_AS_MAINPAGE doc/main.md)
	set(DOXYGEN_HAVE_DOT YES)
	set(DOXYGEN_DOT_IMAGE_FORMAT svg)
	set(DOXYGEN_INTERACTIVE_SVG YES)
	set(DOXYGEN_DOT_TRANSPARENT YES)
	set(DOXYGEN_COLLABORATION_GRAPH YES)
	set(DOXYGEN_CLASS_GRAPH YES)
	set(DOXYGEN_CALL_GRAPH YES)
	set(DOXYGEN_CALLER_GRAPH YES)
	# TODO: set(DOXYGEN_PROJECT_LOGO pkg/pecunia-logo.png)
	set(DOXYGEN_PROJECT_ICON pkg/pecunia.png)
	set(DOXYGEN_BUILTIN_STL_SUPPORT YES)
	set(DOXYGEN_EXTRACT_PRIVATE NO)
	set(DOXYGEN_SHOW_INCLUDE_FILES NO)
	set(DOXYGEN_SORT_BRIEF_DOCS YES)
	set(DOXYGEN_SORT_MEMBERS_CTORS_1ST YES)
	set(DOXYGEN_SORT_GROUP_NAMES YES)
	set(DOXYGEN_SHOW_USED_FILES NO)
	set(DOXYGEN_SHOW_FILES NO)
	set(DOXYGEN_WARN_NO_PARAMDOC YES)
	set(DOXYGEN_WARN_IF_UNDOC_ENUM_VA YES)
	# TODO: set(DOXYGEN_WARN_AS_ERROR YES)
	set(DOXYGEN_EXCLUDE_PATTERNS
		*/internal/*
		*/templates/*
		*/unit-tests/*
	)
	set(DOXYGEN_EXCLUDE_SYMBOLS
		detail::*
		*::detail::*
		internal::*
		*::internal::*
	)
	set(DOXYGEN_VERBATIM_HEADERS NO)
	set(DOXYGEN_GENERATE_TREEVIEW YES)
	set(DOXYGEN_SHOW_ENUM_VALUES YES)
	set(DOXYGEN_INCLUDE_PATH
		"${CMAKE_BINARY_DIR}/src/external/pecunia"
	)
	set(DOXYGEN_UML_LOOK YES)
	set(DOXYGEN_SEARCHENGINE YES)
	set(DOXYGEN_SERVER_BASED_SEARCH NO)
	set(DOXYGEN_NUM_PROC_THREADS ${${PROJECT_NAME}_PARALLEL_SIZE})
	set(DOXYGEN_MARKDOWN_SUPPORT YES)
	set(DOXYGEN_PROJECT_NUMBER ${PROJECT_SEMANTIC_VERSION})
	set(DOXYGEN_ENABLE_PREPROCESSING YES)
	set(DOXYGEN_EXPAND_ONLY_PREDEF NO)
	set(DOXYGEN_TEMPLATE_RELATIONS YES)
	set(DOXYGEN_EXTENSION_MAPPING
		h=h
		hpp=h
	)
	set(DOXYGEN_FILE_PATTERNS
		*.h
		*.hpp
		*.md
	)
	doxygen_add_docs(Generate-Documentation
		doc
		src
		README.md
		COMMENT "Generating documentation..."
	)
endif ()
