# Specify the CMake version being used
cmake_minimum_required (VERSION 3.22.1 FATAL_ERROR)

# Name and language of the project
project(tpcclib LANGUAGES C VERSION 0.8.2)
set(CMAKE_BUILD_TYPE Release)
# Set copyright owner for all executables
set(tpcclib_COPYRIGHT "(c) 2026 by Turku PET Centre")
message(STATUS "")
message(STATUS "${PROJECT_NAME} ${tpcclib_COPYRIGHT}")

set(CMAKE_C_STANDARD 23)

# Show the system
message(STATUS "CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}")
message(STATUS "CMAKE_VERSION=${CMAKE_VERSION}")
message(STATUS "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}")
message(STATUS "CMAKE_HOST_SYSTEM_NAME: ${CMAKE_HOST_SYSTEM_NAME}")
message(STATUS "CMAKE_HOST_SYSTEM_PROCESSOR: ${CMAKE_HOST_SYSTEM_PROCESSOR}")
message(STATUS "CMAKE_SYSTEM=${CMAKE_SYSTEM}")
message(STATUS "CMAKE_GENERATOR=${CMAKE_GENERATOR}")
message(STATUS "CMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}")

# Includes, except CPack which must always be after settings
include(CheckCCompilerFlag)
include(InstallRequiredSystemLibraries)
include(CheckIncludeFile)
include(CheckSymbolExists)
include(CheckFunctionExists)
include(CheckStructHasMember)
include(CheckCSourceCompiles)

# Do not allow in-source builds:
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
  message(FATAL_ERROR "ERROR:" "In-source builds are not allowed.")
endif()


# We do not (yet) want to automatically install to systems default binary path
# but instead into current working path.
# This MAY only work if set before or after project() !
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}" CACHE PATH "Default install path" FORCE)

# Check that we are running on 64 bit system.
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
  message(STATUS "64 bits compiler detected")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
  message(STATUS "32 bits compiler detected")
  message(FATAL_ERROR "ERROR:" "64 bits platform required.")
else()
  message(FATAL_ERROR "ERROR:" "64 bits platform required.")
endif()

#add_definitions(-D_POSIX_C_SOURCE=200809L)


# Set compiler and linker flags for OpenMP except in macOS because OpenMP with AppleClang is not supported by Apple.
# In Windows, correct DLL is still not always found; in that case you may want to test for that, too
if(NOT APPLE)
  find_package(OpenMP)
endif()
if(OpenMP_FOUND)
#  message(STATUS "OpenMP_C_FLAGS=${OpenMP_C_FLAGS}")
#  message(STATUS "OpenMP_C_INCLUDE_DIRS=${OpenMP_C_INCLUDE_DIRS}")
#  message(STATUS "OpenMP_C_LIB_NAMES=${OpenMP_C_LIB_NAMES}")
#  message(STATUS "OpenMP_C_LIBRARIES=${OpenMP_C_LIBRARIES}")
endif()


# Ifs because otherwise M_PI etc were not found.
# Option -s reduces executable size but is no good if debugging since it removes all symbol table 
# and relocation information; it may also help if you get problems with virus scanners in Windows.
# OpenMP may not work with -O2 or less.
if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" )
#  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -mfpmath=sse -Wall -Wno-unknown-pragmas -Wno-format-truncation -s -D_POSIX_C_SOURCE=200809L -std=gnu2x")
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -mfpmath=sse -Wall -Wno-unknown-pragmas -Wno-format-truncation -s -std=gnu2x")
else()
#  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -mfpmath=sse -Wall -Wno-unknown-pragmas -s -D_POSIX_C_SOURCE=200809L -std=c23")
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -mfpmath=sse -Wall -Wno-unknown-pragmas -s -std=c23")
endif()
# Try to limit the number of dynamically linked libraries
if(NOT (MINGW OR MSYS))
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static")
else()
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static")
endif()

# For binaries intended to run on your system only you should choose -march=native, 
# which will select what is available on your processor.
#  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march-native")

#if("${CMAKE_SYSTEM}" STREQUAL "Windows" )
#  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -no-pthread -mconsole")
#endif()


# Without this __attribute__((packed)) may not be respected by old MinGW compiler
# but with newer gcc it may break it
#if(MINGW OR MSYS)
#  add_definitions(-mno-ms-bitfields)
#endif()

# Suppress all status messages associated with tests
set(CMAKE_REQUIRED_QUIET TRUE)


# Set compiler flags but keep also previous flags
check_c_compiler_flag(-Wextra HAVE_W_EXTRA)
if(HAVE_W_EXTRA)
  add_definitions(-Wextra)
endif(HAVE_W_EXTRA)

set(CMAKE_REQUIRED_LIBRARIES "m") 
#set(CMAKE_REQUIRED_FLAGS "-std=c23 -D_POSIX_C_SOURCE=200809L -Wall")
#set(CMAKE_REQUIRED_FLAGS "-std=c23 -Wall")

# Set relative installation path
set(LIBPATH lib)


#
# Check whether certain header files are available in current platform
#
# These can be used in Windows but not elsewhere 
if(MINGW OR MSYS)
  check_include_file(direct.h HAVE_DIRECT_H)
endif()

#
# Check whether certain system and C library dependent functions are available at compiling,
# because check_function_exists() and check_symbol_exists() do not reliably find them.
#

# Seed for pseudo-random number generation depend on which of these exist
try_compile(HAVE_TIMESPEC_GET SOURCES "${CMAKE_SOURCE_DIR}/try/try_timespec_get.c")
try_compile(HAVE_CLOCK_GETTIME SOURCES "${CMAKE_SOURCE_DIR}/try/try_clock_gettime.c")
try_compile(HAVE_GETTIMEOFDAY SOURCES "${CMAKE_SOURCE_DIR}/try/try_gettimeofday.c")
if(HAVE_TIMESPEC_GET)
#  message(STATUS "timespec_get() available")
endif()
if(HAVE_CLOCK_GETTIME)
#  message(STATUS "clock_gettime() available")
endif()
if(HAVE_GETTIMEOFDAY)
#  message(STATUS "gettimeofday() available")
endif()

# Which mkdir() do we have?
try_compile(HAVE_MKDIR1 SOURCES "${CMAKE_SOURCE_DIR}/try/try_mkdir1.c")
try_compile(HAVE_MKDIR2 SOURCES "${CMAKE_SOURCE_DIR}/try/try_mkdir2.c")
try_compile(HAVE__MKDIR SOURCES "${CMAKE_SOURCE_DIR}/try/try__mkdir.c")
if(HAVE_MKDIR1)
#  message(STATUS "mkdir(path) available")
endif()
if(HAVE_MKDIR2)
#  message(STATUS "mkdir(path, perm) available")
endif()
if(HAVE__MKDIR)
#  message(STATUS "_mkdir(path) available")
endif()

# String functions: own or alternative functions will be used if these are missing
check_symbol_exists ("strnlen" "string.h" HAVE_STRNLEN)
check_symbol_exists ("strnlen_s" "string.h" HAVE_STRNLEN_S)
check_symbol_exists ("strlcat" "string.h" HAVE_STRLCAT)
check_symbol_exists ("strlcpy" "string.h" HAVE_STRLCPY)
try_compile(HAVE_STRCASESTR SOURCES "${CMAKE_SOURCE_DIR}/try/try_strcasestr.c")
try_compile(HAVE_STRNDUP SOURCES "${CMAKE_SOURCE_DIR}/try/try_strndup.c")
if(HAVE_STRCASESTR)
#  message(STATUS "strcasestr() available")
endif()
if(HAVE_STRNDUP)
#  message(STATUS "strndup() available")
endif()

# Date and time functions: own or alternative functions will be used if these are missing
try_compile(HAVE_TIMEGM SOURCES "${CMAKE_SOURCE_DIR}/try/try_timegm.c")
try_compile(HAVE_GMTIME_R SOURCES "${CMAKE_SOURCE_DIR}/try/try_gmtime_r.c")
try_compile(HAVE_LOCALTIME_R SOURCES "${CMAKE_SOURCE_DIR}/try/try_localtime_r.c")
if(HAVE_TIMEGM)
#  message(STATUS "timegm() available")
endif()
if(HAVE_GMTIME_R)
#  message(STATUS "gmtime_r() available")
endif()
if(HAVE_LOCALTIME_R)
#  message(STATUS "localtime_r() available")
endif()

# 
# Check for certain structure extensions
#
check_struct_has_member (
  "struct tm" tm_gmtoff "sys/time.h;time.h" HAVE_TM_GMTOFF
)
if(HAVE_TM_GMTOFF)
#  message(STATUS "tm.tm_gmtoff available")
endif()


#
# Configure a header file to pass some of the CMake settings to the source code
# 
configure_file (
  "${PROJECT_SOURCE_DIR}/tpcclibConfig.h.in"
  "${PROJECT_BINARY_DIR}/tpcclibConfig.h"
  )
# add the binary tree to the search path for include files so that we will find tpcclibConfig.h
include_directories("${PROJECT_BINARY_DIR}")


#
# Add support for testing
#
enable_testing()
# Apps are tested with bash scripts, therefore bash is required for testing.
# 'REQUIRED* not set here, though, to allow building.
find_program(BASH_EXEC bash)
if(HAVE_TM_GMTOFF)
  message(STATUS "BASH_EXEC=${BASH_EXEC}")
endif()

#
# Proceed to source code subdirectories
#

# recurse into the library version 1 subdirectories
add_subdirectory (v1/libtpcmisc)
add_subdirectory (v1/libtpcsvg)
add_subdirectory (v1/libtpcmodel)
add_subdirectory (v1/libtpccurveio)
add_subdirectory (v1/libtpcimgio)
add_subdirectory (v1/libtpcimgp)
add_subdirectory (v1/libtpcmodext)
add_subdirectory (v1/libtpcrec)
add_subdirectory (v1/libtpcroi)
add_subdirectory (v1/libtpcidi)

# recurse into the executable subdirectories
add_subdirectory (v1/analyze)
add_subdirectory (v1/cmfits)
add_subdirectory (v1/ecat)
add_subdirectory (v1/hrrt)
add_subdirectory (v1/imgcmfits)
add_subdirectory (v1/imginform)
add_subdirectory (v1/imgtools)
add_subdirectory (v1/input)
add_subdirectory (v1/mask)
add_subdirectory (v1/mtga)
add_subdirectory (v1/nifti)
add_subdirectory (v1/restools)
add_subdirectory (v1/simulations)
add_subdirectory (v1/suv)
add_subdirectory (v1/tactools)
add_subdirectory (v1/tacfits)
add_subdirectory (v1/tools)
add_subdirectory (v1/upet)

#add_subdirectory (v1/devel)


# recurse into the library version 2 subdirectories
add_subdirectory (v2/libtpcextensions)
add_subdirectory (v2/libtpcisotope)
add_subdirectory (v2/libtpcift)
add_subdirectory (v2/libtpccsv)
add_subdirectory (v2/libtpctac)
add_subdirectory (v2/libtpcabss)
add_subdirectory (v2/libtpcmodels)
add_subdirectory (v2/libtpcfileutil)
add_subdirectory (v2/libtpcfcmc)
add_subdirectory (v2/libtpcfunc)
add_subdirectory (v2/libtpcpar)
add_subdirectory (v2/libtpcli)
add_subdirectory (v2/libtpcstatist)
add_subdirectory (v2/libtpcrand)
add_subdirectory (v2/libtpclinopt)
add_subdirectory (v2/libtpcnlopt)
add_subdirectory (v2/libtpcbootstrap)
add_subdirectory (v2/libtpctacmod)
add_subdirectory (v2/libtpccm)
add_subdirectory (v2/libtpcbfm)
add_subdirectory (v2/libtpcdcm)
add_subdirectory (v2/libtpcecat)
add_subdirectory (v2/libtpcnifti)
add_subdirectory (v2/libtpcmicropet)
add_subdirectory (v2/libtpcimage)
add_subdirectory (v2/libtpctacimg)

# recurse into the executable subdirectories
add_subdirectory (v2/abss)
add_subdirectory (v2/arg)
add_subdirectory (v2/bfm)
add_subdirectory (v2/csvutils)
add_subdirectory (v2/dcmutils)
add_subdirectory (v2/halflife)
add_subdirectory (v2/iftutils)
add_subdirectory (v2/imgcm)
add_subdirectory (v2/imgutils)
add_subdirectory (v2/input)
add_subdirectory (v2/llsq)
add_subdirectory (v2/misc)
add_subdirectory (v2/mtga)
add_subdirectory (v2/parutils)
add_subdirectory (v2/pk)
add_subdirectory (v2/sifutils)
add_subdirectory (v2/simimg)
add_subdirectory (v2/simulation)
add_subdirectory (v2/suv)
add_subdirectory (v2/taccm)
add_subdirectory (v2/tacfits)
add_subdirectory (v2/tacutils)

#add_subdirectory (v1/devel)


# "install" README and COPYING files to be included in the binary package
install(
  FILES 
  ${CMAKE_CURRENT_SOURCE_DIR}/README.md 
  ${CMAKE_CURRENT_SOURCE_DIR}/copying.md
  ${CMAKE_CURRENT_SOURCE_DIR}/license.md
  ${CMAKE_CURRENT_SOURCE_DIR}/changelog.md
  DESTINATION ${LIBPATH}
  # go wherever libraries go
  COMPONENT libraries 
)


# Add binary path to the CTest environment
set(PATH_STRING "$ENV{PATH};${CMAKE_BINARY_DIR}/bin")
STRING(REPLACE "\\;" ";" PATH_STRING "${PATH_STRING}")
STRING(REPLACE ";" "\\;" PATH_STRING "${PATH_STRING}")
set(CTEST_ENVIRONMENT "PATH=${PATH_STRING}")


#
# Build source code package and package for pre-built executables
#

if(${UNIX})
  set(CPACK_GENERATOR TGZ)
  set(CPACK_SOURCE_GENERATOR TGZ)
else(${UNIX})
  set(CPACK_GENERATOR ZIP)
  set(CPACK_SOURCE_GENERATOR ZIP)
endif(${UNIX})
if(NOT CPACK_SYSTEM_NAME) # otherwise we would have problems in osx
  set(CPACK_SYSTEM_NAME ${CMAKE_SYSTEM_NAME})
endif()
set(CPACK_ARCHIVE_COMPONENT_INSTALL TRUE)
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY FALSE)
set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY FALSE)
set(CPACK_MONOLITHIC_INSTALL FALSE)

set(CPACK_PACKAGE_VENDOR "Turku PET Centre, University of Turku")
set(CPACK_PACKAGE_CONTACT "vesa.oikonen@utu.fi")
# CPack may enforce file name extensions for certain package generators
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/README.md ${CMAKE_CURRENT_BINARY_DIR}/README.txt COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/copying.md ${CMAKE_CURRENT_BINARY_DIR}/COPYING.txt COPYONLY)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_BINARY_DIR}/COPYING.txt")
set(CPACK_COMPONENT_APPLICATIONS_DISPLAY_NAME "${PROJECT_NAME} applications")
set(CPACK_COMPONENT_APPLICATIONS_DESCRIPTION "Programs for the end-user")
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_BINARY_DIR}/README.txt")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "tpcclib contains command-line tools for processing and analysing PET data")

# Set path for packages
set(CPACK_OUTPUT_FILE_PREFIX ../packages)

# Set name for the source package
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${tpcclib_VERSION}-src")
# Do not package everything in the source folders
set(CPACK_SOURCE_IGNORE_FILES "/.git/;/doc/;/html/;\\\\.bak;\\\\.old")

# Set name for the binary package
if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64" )
  set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${tpcclib_VERSION}-${CPACK_SYSTEM_NAME}")
else()
  set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${tpcclib_VERSION}-${CPACK_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
endif()
#set(CPACK_PACKAGING_INSTALL_PREFIX "/")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}")

# Pack the executable applications only
set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_CURRENT_BINARY_DIR};${PROJECT_NAME};applications;/")
#set(CPACK_COMPONENTS_ALL applications) # Only applications, not libraries etc

# this must always be last!
include(CPack)


#
# List all CMake variables with values, in case of problems
#
# get_cmake_property( P VARIABLES )
#  foreach( VAR in ${P} )
#    message( STATUS " ${VAR}=${${VAR}}" )
#  endforeach()

#message(STATUS "UNIX=${UNIX}")
#message(STATUS "APPLE=${APPLE}")
#message(STATUS "WIN32=${WIN32}")
#message(STATUS "MSYS=${MSYS}")
#message(STATUS "MINGW=${MINGW}")
#message(STATUS "CYGWIN=${CYGWIN}")
#message(STATUS "CMAKE_COMPILER_IS_GNUCC=${CMAKE_COMPILER_IS_GNUCC}")
#message(STATUS "CMAKE_COMPILER_IS_GNUCXX=${CMAKE_COMPILER_IS_GNUCXX}")
#message(STATUS "OpenMP_C_FLAGS=${OpenMP_C_FLAGS}")
#message(STATUS "CMAKE_C_FLAGS=${CMAKE_C_FLAGS}")
#message(STATUS "CMAKE_REQUIRED_FLAGS=${CMAKE_REQUIRED_FLAGS}")
#message(STATUS "CMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS}")
