cmake_minimum_required (VERSION 3.24)

project (Tapkee  LANGUAGES CXX)

# set paths
set (CMAKE_CXX_STANDARD 23)
set (TAPKEE_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include")
set (TAPKEE_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
set (TAPKEE_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test/unit")
set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/cmake")

set (CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib")
set (CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib")
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin")

# Eigen detection (supports Eigen 3.4+ and Eigen 5.x)
find_package(Eigen3 REQUIRED NO_MODULE)
if(Eigen3_VERSION VERSION_LESS "3.4")
    message(FATAL_ERROR "Tapkee requires Eigen 3.4 or later. Found: ${Eigen3_VERSION}")
endif()
set (EIGEN3_LIBRARY_TO_LINK "Eigen3::Eigen")
message(STATUS "Found Eigen version: ${Eigen3_VERSION}")

# fmt
find_package(fmt REQUIRED)
set (FMT_LIBRARY_TO_LINK "fmt::fmt-header-only")

# ARPACK detection
find_package(Arpack)
if (ARPACK_FOUND)
	link_directories("${ARPACK_PATH}")
endif()

# OpenMP detection
find_package(OpenMP)
if (OPENMP_FOUND)
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
	set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
endif()

# ViennaCL detection
find_package(OpenCL)
if (OPENCL_FOUND)
	find_package(ViennaCL)
	if (VIENNACL_FOUND)
		set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OPENCL_C_FLAGS}")
		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENCL_CXX_FLAGS}")
		set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OPENCL_EXE_LINKER_FLAGS}")
		include_directories("${VIENNACL_INCLUDE_DIRS}")
	endif()
endif()

include_directories("${TAPKEE_INCLUDE_DIR}")

# CLI executable
option(BUILD_CLI "Build CLI application" ON)
if (BUILD_CLI)
	find_package(cxxopts REQUIRED)
	add_executable(tapkee "${TAPKEE_SRC_DIR}/cli/main.cpp")
endif()

# Examples
option(BUILD_EXAMPLES "Whether to build examples or not" OFF)
if (BUILD_EXAMPLES)
	add_subdirectory(examples)
endif()

# library interface
file(GLOB_RECURSE headers_library "${TAPKEE_INCLUDE_DIR}/*.hpp")
add_library(tapkee_library INTERFACE)
target_include_directories(tapkee_library INTERFACE "${TAPKEE_INCLUDE_DIR}")

if (ARPACK_FOUND)
	if (BUILD_CLI)
		target_link_libraries(tapkee PUBLIC "${ARPACK_LIB}")
	endif()
	target_link_libraries(tapkee_library INTERFACE "${ARPACK_LIB}")
	add_definitions(-DTAPKEE_WITH_ARPACK)
endif()

if (VIENNACL_FOUND)
	if (BUILD_CLI)
		target_link_libraries(tapkee PUBLIC OpenCL)
	endif()
	target_link_libraries(tapkee_library INTERFACE OpenCL)
	add_definitions(-DTAPKEE_WITH_VIENNACL)
endif()

if (BUILD_CLI)
	target_link_libraries(tapkee PRIVATE "${FMT_LIBRARY_TO_LINK}")
	target_link_libraries(tapkee PRIVATE "${EIGEN3_LIBRARY_TO_LINK}")
endif()
target_link_libraries(tapkee_library INTERFACE "${FMT_LIBRARY_TO_LINK}")
target_link_libraries(tapkee_library INTERFACE "${EIGEN3_LIBRARY_TO_LINK}")

if (TAPKEE_CUSTOM_INSTALL_DIR)
	set (TAPKEE_INSTALL_DIR
		"${TAPKEE_CUSTOM_INSTALL_DIR}")
else()
	set (TAPKEE_INSTALL_DIR
		"${CMAKE_INSTALL_PREFIX}/include/tapkee")
endif()

install(
	DIRECTORY "${TAPKEE_INCLUDE_DIR}/tapkee"
	DESTINATION "${TAPKEE_INSTALL_DIR}"
)

install(
	DIRECTORY "${TAPKEE_INCLUDE_DIR}/stichwort"
	DESTINATION "${TAPKEE_INSTALL_DIR}"
)

# G++ specific flags
option(USE_GCOV "Use gcov to analyze test coverage" OFF)

if (CMAKE_COMPILER_IS_GNUCXX)
	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wno-long-long -Wshadow")
	if (USE_GCOV AND BUILD_CLI)
		set(CMAKE_BUILD_TYPE Debug)
		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
		set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage")

		add_custom_target(lcov DEPENDS tapkee)
		add_custom_command(TARGET lcov COMMAND mkdir -p coverage WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM)
		add_custom_command(TARGET lcov COMMAND lcov --directory . --zerocounters WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM)
		add_custom_command(TARGET lcov COMMAND make test WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM)
		add_custom_command(TARGET lcov COMMAND lcov --directory . --capture --output-file ./coverage/out.info WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM)
		add_custom_command(TARGET lcov COMMAND lcov --remove ./coverage/out.info /usr/local/include\\* /usr/include\\* unit\\* --output ./coverage/clean.info "${CMAKE_BINARY_DIR}" VERBATIM)
		add_custom_command(TARGET lcov COMMAND genhtml -o ./coverage ./coverage/clean.info --branch-coverage --demangle-cpp WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM)
	endif()
endif()

if (MSVC)
	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
endif()

# Part of nanobind's setting up the build system, configuring optimized build
# unless otherwise specified, to avoid slow binding code and large binaries.
# https://nanobind.readthedocs.io/en/latest/building.html#preliminaries
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
	set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
	set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()

if (CMAKE_BUILD_TYPE MATCHES Debug)
	add_definitions(-DTAPKEE_DEBUG)
endif()

option(GPL_FREE "Build without GPL-licensed components" OFF)

if (NOT GPL_FREE)
	add_definitions(-DTAPKEE_USE_LGPL_COVERTREE)
endif()

option(BUILD_TESTS "Build tests" OFF)

if (BUILD_TESTS)
	find_package(GTest REQUIRED)
	enable_testing()

	aux_source_directory("${TAPKEE_TESTS_DIR}" "TAPKEE_TESTS_SOURCES")
	foreach(i ${TAPKEE_TESTS_SOURCES})
		get_filename_component(exe ${i} NAME_WE)
		add_executable(test_${exe} ${i})
		target_link_libraries(test_${exe} PUBLIC GTest::gtest GTest::gtest_main)
		target_link_libraries(test_${exe} PRIVATE ${FMT_LIBRARY_TO_LINK})
		target_link_libraries(test_${exe} PRIVATE ${EIGEN3_LIBRARY_TO_LINK})
		if (ARPACK_FOUND)
			target_link_libraries(test_${exe} PUBLIC "${ARPACK_LIB}")
		endif()
		if (VIENNACL_FOUND)
			target_link_libraries(test_${exe} PUBLIC OpenCL)
		endif()
		add_test(
			NAME ${exe}
			WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
			COMMAND "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_${exe}"
			--gtest_color=yes)
	endforeach()
endif()

option(BUILD_NANOBIND "Build nanobind Python extension" OFF)

# https://nanobind.readthedocs.io/en/latest/building.html
if (BUILD_NANOBIND)
	message(STATUS "Detecting and configuring nanobind")
	find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
	# Detect the installed nanobind package and import it into CMake
	execute_process(
		COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
		OUTPUT_VARIABLE NB_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
		ERROR_VARIABLE NB_DIR)
	list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}")
	find_package(nanobind CONFIG REQUIRED)

	if (NOT ARPACK_FOUND)
		message(FATAL_ERROR "ARPACK is required for the Python extension. Install ARPACK and try again.")
	endif()

	# Build extension (the library that'll be imported from the Python interpreter)
	include_directories("${TAPKEE_SRC_DIR}")
	nanobind_add_module(pytapkee src/python/nanobind_extension.cpp)
	set_target_properties(pytapkee PROPERTIES OUTPUT_NAME tapkee)
	target_link_libraries(pytapkee PRIVATE "${EIGEN3_LIBRARY_TO_LINK}")
	target_link_libraries(pytapkee PRIVATE "${ARPACK_LIB}")
	target_link_libraries(pytapkee PRIVATE "${FMT_LIBRARY_TO_LINK}")
	install(TARGETS pytapkee LIBRARY DESTINATION . COMPONENT python)
	message(STATUS "Detecting and configuring nanobind - done")
endif()

export(
	TARGETS tapkee_library
	NAMESPACE tapkee::
	FILE ${PROJECT_BINARY_DIR}/tapkee.cmake
)
