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

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

set(CMAKE_C_STANDARD 11)

# Includes, except CPack which must always be after settings
include(CheckCCompilerFlag)
include(InstallRequiredSystemLibraries)
include(CheckIncludeFiles)
include(CheckSymbolExists)
include(CheckFunctionExists)
include(CheckStructHasMember)
include(CheckCSourceCompiles)
# 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)
  include(FindOpenMP)
endif()


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

# Ifs because otherwise M_PI etc were not found.
# Option -s reduces executable size but is no good if debugging.
# OpenMP may not work with -O2 or less.
# For binaries intended to run on your system only you should choose -march=native, 
# which will select what is available on your processor.
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=gnu11")
else()
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -mfpmath=sse -Wall -Wno-unknown-pragmas -D_POSIX_C_SOURCE=200809L -std=c11")
endif()

# Set compiler and linker flags for OpenMP except in macOS and for 32-bit executables
if(NOT APPLE)
  find_package(OpenMP)
endif()
if(OPENMP_FOUND)
  message(STATUS "setting OpenMP flags")
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
# Copy OpenMP DLL to accompany the compiled binaries:
# Correct DLL still not always found, therefore OpenMP may not be supported on Windows
# although the code below is working.
  if(MINGW OR MSYS)
    find_library(LIBOPENMP NAMES "libgomp_64-1.dll" PATHS ${CMAKE_FIND_ROOT_PATH})
    if(LIBOPENMP)
      file(COPY ${LIBOPENMP} DESTINATION ${PROJECT_BINARY_DIR}/bin/)
    endif()
  endif()
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()


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

check_include_files(stdint.h HAVE_STDINT_H)
check_include_files(stddef.h HAVE_STDDEF_H)
check_include_files(math.h HAVE_MATH_H)
check_include_files(stddef.h HAVE_LOCALE_H)
check_include_files(direct.h HAVE_DIRECT_H)
check_include_files(omp.h HAVE_OMP_H)

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

# Set relative installation path
set(LIBPATH lib)


#
# Check whether required functions are available in current platform
#

check_function_exists (exp HAVE_EXP)
if(NOT HAVE_EXP)
  message(FATAL_ERROR "ERROR: required exp function not found")
endif(NOT HAVE_EXP)

check_function_exists (isnan HAVE_ISNAN)
if(NOT HAVE_ISNAN)
  message(FATAL_ERROR "ERROR: required isnan function not found")
endif(NOT HAVE_ISNAN)

check_function_exists (mkdir HAVE_MKDIR)
if(NOT HAVE_MKDIR)
  message(FATAL_ERROR "ERROR: required mkdir function not found")
endif(NOT HAVE_MKDIR)
check_function_exists ("_mkdir" HAVE__MKDIR)
if(HAVE__MKDIR)
  message(STATUS "_mkdir function is found")
endif(HAVE__MKDIR)

check_function_exists (opendir HAVE_OPENDIR)
if(NOT HAVE_OPENDIR)
  message(FATAL_ERROR "ERROR: required opendir function not found")
endif(NOT HAVE_OPENDIR)

check_function_exists (readdir HAVE_READDIR)
if(NOT HAVE_READDIR)
  message(FATAL_ERROR "ERROR: required readdir function not found")
endif(NOT HAVE_READDIR)

check_function_exists (strcasecmp HAVE_STRCASECMP)
if(NOT HAVE_STRCASECMP)
  message(FATAL_ERROR "ERROR: required strcasecmp function not found")
endif(NOT HAVE_STRCASECMP)

check_function_exists (atexit HAVE_ATEXIT)
if(NOT HAVE_ATEXIT)
  message(FATAL_ERROR "ERROR: required atexit function not found")
endif(NOT HAVE_ATEXIT)

# Seed for pseudo-random number generation depend on which of these exist
if(NOT APPLE)
  # This standard C11 function exists in macOS but only for internal use
  check_function_exists ("timespec_get" HAVE_TIMESPEC_GET)
endif()
check_function_exists ("clock_gettime" HAVE_CLOCK_GETTIME)
check_function_exists ("gettimeofday" HAVE_GETTIMEOFDAY)
check_function_exists ("getpid" HAVE_GETPID)
check_function_exists ("rand_r" HAVE_RAND_R)
check_function_exists ("drand48" HAVE_DRAND48)

# Own functions will be used if these are missing
check_function_exists ("strcasestr" HAVE_STRCASESTR)
check_function_exists ("strdup" HAVE_STRDUP)
check_function_exists ("strndup" HAVE_STRNDUP)
check_function_exists ("strnlen" HAVE_STRNLEN)
check_function_exists ("strlcat" HAVE_STRLCAT)
check_function_exists ("strlcpy" HAVE_STRLCPY)
check_function_exists ("__builtin_strndup" HAVE_BUILTIN_STRNDUP)
check_function_exists ("get_current_dir_name" HAVE_GET_CURRENT_DIR_NAME)
check_function_exists ("timegm" HAVE_TIMEGM)
check_function_exists ("gmtime_r" HAVE_GMTIME_R)
check_function_exists ("gmtime_s" HAVE_GMTIME_S)
check_function_exists ("localtime_r" HAVE_LOCALTIME_R)
check_function_exists ("strptime" HAVE_STRPTIME)



# 
# Check for certain structure extensions
#
# GNU and BSD has extensions in struct tm
check_struct_has_member (
  "struct tm" tm_gmtoff "sys/time.h;time.h" HAVE_TM_GMTOFF
)
check_struct_has_member(
  "struct tm" tm_sec "sys/types.h;sys/time.h;time.h" HAVE_TIME_WITH_SYS_TIME
)
check_struct_has_member (
  "struct stat" st_birthtime "sys/types.h;sys/stat.h" HAVE_ST_BIRTHTIME
)

#
# 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()


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

# 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/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 "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_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}")
#message(STATUS "CPACK_SYSTEM_NAME=${CPACK_SYSTEM_NAME}")
message(STATUS "CMAKE_GENERATOR=${CMAKE_GENERATOR}")
#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 "CMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}")
#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}")
