
################################################################################
##                                                                            ##
##  This file is part of MCPL (see https://mctools.github.io/mcpl/)           ##
##                                                                            ##
##  Copyright 2015-2025 MCPL developers.                                      ##
##                                                                            ##
##  Licensed under the Apache License, Version 2.0 (the "License");           ##
##  you may not use this file except in compliance with the License.          ##
##  You may obtain a copy of the License at                                   ##
##                                                                            ##
##      http://www.apache.org/licenses/LICENSE-2.0                            ##
##                                                                            ##
##  Unless required by applicable law or agreed to in writing, software       ##
##  distributed under the License is distributed on an "AS IS" BASIS,         ##
##  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  ##
##  See the License for the specific language governing permissions and       ##
##  limitations under the License.                                            ##
##                                                                            ##
################################################################################

cmake_minimum_required(VERSION 3.16...3.31)

#Detect skbuild mode (std/monolithic) if appropriate:

if ( DEFINED SKBUILD_PROJECT_NAME )
  set( MCPL_NOTOUCH_CMAKE_BUILD_TYPE "ON" )
endif()

# Respect value of CMAKE_BUILD_TYPE if already defined, otherwise fall back to
# Release. In any case, expose CMAKE_BUILD_TYPE as an explicit cache variable
# (gives drop-down list in gui). This must come before the call to
# project(..). We do not do this in case the generator is multi-cfg, and we also
# provide the hidden MCPL_NOTOUCH_CMAKE_BUILD_TYPE option to not do it.
#

if( NOT MCPL_NOTOUCH_CMAKE_BUILD_TYPE )
  get_property( gen_is_multicfg GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG )
  if ( NOT gen_is_multicfg )
    if( DEFINED CMAKE_BUILD_TYPE )
      set( _def_cbt ${CMAKE_BUILD_TYPE} )
    else()
      set( _def_cbt Release )
    endif()
    set( CMAKE_BUILD_TYPE ${_def_cbt} CACHE STRING
      "Choose the type of build, options are: Debug Release RelWithDebInfo and MinSizeRel." )
    set_property( CACHE CMAKE_BUILD_TYPE
      PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel None )
  endif()
endif()

#Setup project:

project( MCPL VERSION 2.0.0
  LANGUAGES C
  DESCRIPTION "Monte Carlo Particle Lists"
  HOMEPAGE_URL "https://github.com/mctools/mcpl"
)

if( NOT MCPL_NOTOUCH_CMAKE_BUILD_TYPE )
  if ( NOT gen_is_multicfg )
    if ( NOT CMAKE_BUILD_TYPE )
      #This can happen if parent project called the project(..) function before
      #doing the song and dance we did above.
      set(CMAKE_BUILD_TYPE Release)
    endif()
  endif()
endif()

# Set module path
set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake/modules")

#Define options:
include(mcpl_options)

function( _mcpl_option_force varname value )
  message( STATUS "Forcing ${varname}=\"${value}\"" )
  set( "${varname}" "${value}" PARENT_SCOPE )
endfunction()

if ( DEFINED SKBUILD_PROJECT_NAME )
  message( STATUS "scikit-build mode detected. Overriding some settings." )
  set( mcplcore_pymodname "_mcpl_core" )
  _mcpl_option_force( CMAKE_INSTALL_LIBDIR "${mcplcore_pymodname}/data/lib" )
  _mcpl_option_force( CMAKE_INSTALL_INCLUDEDIR "${mcplcore_pymodname}/data/include" )
  _mcpl_option_force( CMAKE_INSTALL_BINDIR "${mcplcore_pymodname}/data/bin" )
  set ( mcpl_skbld_autogendir "${PROJECT_SOURCE_DIR}/skbld_autogen" )
  #Cleanup skbld_autogendir unless we are building FROM an sdist (or creating
  #one):
  if ( EXISTS "${mcpl_skbld_autogendir}/is_sdist.txt"
      AND NOT "${SKBUILD_STATE}" STREQUAL "sdist" )
    message(STATUS "Running from sdist - will not clean autogenerated files")
  else()
    if ( IS_DIRECTORY "${mcpl_skbld_autogendir}" )
      message( STATUS "Cleaning out ${mcpl_skbld_autogendir}" )
      file( REMOVE_RECURSE "${mcpl_skbld_autogendir}" )
    endif()
    file( MAKE_DIRECTORY "${mcpl_skbld_autogendir}" )
    if ( "${SKBUILD_STATE}" STREQUAL "sdist" )
      file(TOUCH "${mcpl_skbld_autogendir}/is_sdist.txt")
    endif()
  endif()
endif()

if ( MCPL_ENABLE_CPACK )
  set( CPACK_PACKAGE_CONTACT "MCPL developers" )
  set( CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/mctools/mcpl" )
  set( CPACK_NSIS_PACKAGE_NAME "${PROJECT_NAME} ${PROJECT_VERSION}" )
  set( CPACK_NSIS_DISPLAY_NAME "${PROJECT_NAME} ${PROJECT_VERSION}" )
  include(CPack)
endif()


if ( MCPL_ENABLE_TESTING AND MCPL_ENABLE_CORE_TESTING )
  message( FATAL_ERROR
    "Enabling both MCPL_ENABLE_TESTING"
    " and MCPL_ENABLE_CORE_TESTING is not supported" )
endif()

if ( MCPL_ENABLE_CORE_TESTING )
  enable_testing()
  include(mcpl_coretests)
endif()

#Installation directories (try to follow standard conventions):
include(GNUInstallDirs)
set(MCPL_BINDIR "${CMAKE_INSTALL_BINDIR}")#e.g. <prefix>/bin>
set(MCPL_LIBDIR "${CMAKE_INSTALL_LIBDIR}")#e.g. <prefix>/lib>

function( mcplfind_relinstpath resultvar srcdir tgtdir )
  if ( NOT srcdir OR NOT IS_ABSOLUTE "${srcdir}" )
    set( srcdir "${CMAKE_INSTALL_PREFIX}/${srcdir}" )
  endif()
  if ( NOT tgtdir OR NOT IS_ABSOLUTE "${tgtdir}" )
    set( tgtdir "${CMAKE_INSTALL_PREFIX}/${tgtdir}" )
  endif()
  file(RELATIVE_PATH res "${srcdir}" "${tgtdir}")
  set( "${resultvar}" "${res}" PARENT_SCOPE )
endfunction()

set(mcpl_skbuild_shlib_in_wheel_scripts "OFF")
if ( WIN32 )
  #dll's must be in %PATH% so we put them in bin (same as seems to be the way
  #most packages do it on conda-forge).
  set(MCPL_SHLIBDIR "${MCPL_BINDIR}")
  if ( DEFINED SKBUILD_PROJECT_NAME )
    #For wheels we will place the mcpl.dll into the scripts folder, since
    #that seems to be the only way to get the dll onto the PATH.
    set(MCPL_SHLIBDIR "${SKBUILD_SCRIPTS_DIR}")
    set(mcpl_skbuild_shlib_in_wheel_scripts "ON")
  endif()
else()
  #sane standard world
  set(MCPL_SHLIBDIR "${MCPL_LIBDIR}")
endif()

set(MCPL_INCDIR "${CMAKE_INSTALL_INCLUDEDIR}")#e.g. <prefix>/include>
if ( NOT MCPL_CMAKEDIR )
  set(MCPL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")#e.g. <prefix>/lib/cmake/MCPL>
endif()

#Get a few relative paths, mostly for expansion in various installed files (we
#use PROJECT_BINARY_DIR as prefix here, but it should not matter which as long
#as it is an absolute path):

mcplfind_relinstpath( MCPL_relpath_BINDIR2LIBDIR "${MCPL_BINDIR}" "${MCPL_LIBDIR}" )
mcplfind_relinstpath( MCPL_relpath_BINDIR2SHLIBDIR "${MCPL_BINDIR}"  "${MCPL_SHLIBDIR}" )
mcplfind_relinstpath( MCPL_relpath_BINDIR2CMAKEDIR "${MCPL_BINDIR}"  "${MCPL_CMAKEDIR}" )
mcplfind_relinstpath( MCPL_relpath_BINDIR2INCDIR "${MCPL_BINDIR}"  "${MCPL_INCDIR}" )
mcplfind_relinstpath( MCPL_relpath_BINDIR2CMAKEDIR "${MCPL_BINDIR}"  "${MCPL_CMAKEDIR}" )
mcplfind_relinstpath( MCPL_relpath_BINDIR2INCDIR "${MCPL_BINDIR}"  "${MCPL_INCDIR}" )
mcplfind_relinstpath( MCPL_relpath_CMAKEDIR2ROOT "${MCPL_CMAKEDIR}" "" )
mcplfind_relinstpath( MCPL_relpath_CMAKEDIR2BINDIR "${MCPL_BINDIR}" "" )
mcplfind_relinstpath( MCPL_relpath_CMAKEDIR2LIBDIR "${MCPL_LIBDIR}" "" )
mcplfind_relinstpath( MCPL_relpath_CMAKEDIR2INCDIR "${MCPL_INCDIR}" "" )

include( mctools_utils )

set( STRICT_CSTD OFF )

if ( MCPL_BUILD_STRICT )

  #We also want to test the C-example with strict C standard (90, 99, 11). For
  #simplicity we simply pick a C standard value based on the provided c++
  #standard, in a way which allows us to cover the various C standards.
  if ( MCPL_BUILD_STRICT STREQUAL "99" OR MCPL_BUILD_STRICT STREQUAL "ON" )
    set( STRICT_CSTD 99 )
  elseif ( MCPL_BUILD_STRICT STREQUAL "11" )
    set( STRICT_CSTD 11 )
  elseif ( MCPL_BUILD_STRICT STREQUAL "17" )
    if( "${CMAKE_VERSION}" VERSION_LESS "3.21" )
      message(FATAL_ERROR "MCPL_BUILD_STRICT=17 requires cmake 3.21")
    endif()
    set( STRICT_CSTD 17 )
  elseif ( MCPL_BUILD_STRICT STREQUAL "23" )
    if( "${CMAKE_VERSION}" VERSION_LESS "3.21" )
      message(FATAL_ERROR "MCPL_BUILD_STRICT=17 requires cmake 3.21")
    endif()
    set( STRICT_CSTD 23 )
  else()
    #should have been caught earlier:
    message( FATAL_ERROR "Unexpected value of MCPL_BUILD_STRICT" )
  endif()
endif()


function(set_target_common_props targetname)
  #NOTE: Only apply strict flags to at the target level if zlib is not built in:
  if (  "${MCPL_ENABLE_ZLIB}" STREQUAL "USEPREINSTALLED" )
    mctools_apply_strict_comp_properties( ${targetname} )
    if ( STRICT_CSTD )
      set_target_properties (
        ${targetname} PROPERTIES
        C_STANDARD ${STRICT_CSTD}
        C_STANDARD_REQUIRED ON C_EXTENSIONS OFF
      )
    endif()
  endif()
endfunction()

#MCPL library and header files, including optional built-in modules if enabled:
set( mcpl_src_files "src/mcpl.c" "src/mcpl_fileutils.c" "src/mcpl_fileutils.h" )
set_source_files_properties(
  ${mcpl_src_files} PROPERTIES C_VISIBILITY_PRESET "hidden"
)

add_library( mcpl SHARED ${mcpl_src_files} )

if ( MCPL_BUILD_STRICT )
  #Always apply any strict flags directly at the source file level as well,
  #since the target itself might not always get the strict flags (since we don't
  #want zlib sources to get them):
  mctools_determine_strict_comp_flags( tmp )
  if ( tmp )
    set_property(
      SOURCE ${mcpl_src_files}
      APPEND PROPERTY COMPILE_OPTIONS "${tmp}"
    )
  endif()
endif()

set_target_properties( mcpl PROPERTIES C_VISIBILITY_PRESET "hidden" )

add_executable( mcpltool "app_tool/mcpltool_app.c" ${mcpl_src_files} )
target_compile_definitions( mcpltool PRIVATE mcpl_LIB_IS_STATIC )

include( "mcpl_headercreate" )
mcpl_create_header( mcplheader_include_path )

set_target_common_props( mcpl )
set_target_common_props( mcpltool )
target_compile_features( mcpl PUBLIC c_std_99 )
target_compile_features( mcpltool PRIVATE c_std_99 )
mctools_detect_extra_cflags( mcpl_extra_private_compile_options )
target_compile_options( mcpl PRIVATE ${mcpl_extra_private_compile_options} )
target_compile_options( mcpltool PRIVATE ${mcpl_extra_private_compile_options} )
target_include_directories(
  mcpl
  PRIVATE "${PROJECT_SOURCE_DIR}/src"
  PUBLIC
  $<BUILD_INTERFACE:${mcplheader_include_path}>
  $<INSTALL_INTERFACE:${MCPL_INCDIR}>
)
target_include_directories(
  mcpltool PRIVATE
  "${PROJECT_SOURCE_DIR}/src"
  "${mcplheader_include_path}"
)

#Zlib:

##ZLib support:
include( mcpl_zlib )
add_zlib_dependency( mcpl )
add_zlib_dependency( mcpltool )

mctools_detect_math_libs( "MCPL_MATH_LIBRARIES" )

target_link_libraries(mcpl PRIVATE ${MCPL_MATH_LIBRARIES} )
target_link_libraries(mcpltool PRIVATE ${MCPL_MATH_LIBRARIES} )

install(
  TARGETS mcpl
  EXPORT MCPLTargets
  RUNTIME DESTINATION ${MCPL_SHLIBDIR}
  ARCHIVE DESTINATION ${MCPL_LIBDIR}
  LIBRARY DESTINATION ${MCPL_LIBDIR}
)

install(
  TARGETS mcpltool
  RUNTIME DESTINATION ${MCPL_BINDIR}
)

install(
  FILES "${mcplheader_include_path}/mcpl.h"
  DESTINATION "${MCPL_INCDIR}"
)

#Package configuration files for downstream cmake projects:
install( EXPORT MCPLTargets FILE "MCPLTargets.cmake" NAMESPACE MCPL:: DESTINATION ${MCPL_CMAKEDIR} )
#always alias namespaces locally:
add_library(MCPL::mcpl ALIAS mcpl)
#And in this case also make both casings available:
add_library(MCPL::MCPL ALIAS mcpl)#always alias namespaces locally

if ( mcpl_skbuild_shlib_in_wheel_scripts )
  #In python wheels on windows we put mcpl.dll in the scripts directory,
  #which moves around upon installation. To make the imported MCPL::MCPL
  #target work, we need to fix the library location at the end of the generated
  #MCPLTargets-*.cmake. We put those fixups in a function in
  #MCPLConfig.cmake, and simply add a function call here:
  install(CODE "
file(
  APPEND
  \"${CMAKE_INSTALL_PREFIX}/${MCPL_CMAKEDIR}/MCPLTargets-$<LOWER_CASE:$<CONFIG>>.cmake\"
  \"\n_mcpl_fixup_mcpltargets()\n\"
)
")
endif()

#NB: Important we do any configure_file AFTER all variables used have been set!
include(CMakePackageConfigHelpers)
#NB: We choose COMPATIBILITY AnyNewerVersion, since we mostly aim for API (but
#not ABI) compatiblity even when bumping major versions. Otherwise client code
#would break whenever we update major versions:
write_basic_package_version_file(
  "${PROJECT_BINARY_DIR}/MCPLConfigVersion.cmake"
  VERSION ${MCPL_VERSION} COMPATIBILITY AnyNewerVersion
)

#First generated a configured version of the template:
configure_file(
  "${PROJECT_SOURCE_DIR}/cmake/MCPLConfig.cmake.in"
  "${PROJECT_BINARY_DIR}/MCPLConfig.cmake.pregen.in" @ONLY
)

#At build-time, generate the actual files needed for inclusion, expanding any
#generator-expressions:
file(
  GENERATE
  OUTPUT "${PROJECT_BINARY_DIR}/MCPLConfig.cmake"
  INPUT "${PROJECT_BINARY_DIR}/MCPLConfig.cmake.pregen.in"
)

install(
  FILES "${PROJECT_BINARY_DIR}/MCPLConfigVersion.cmake"
  "${PROJECT_BINARY_DIR}/MCPLConfig.cmake"
  DESTINATION ${MCPL_CMAKEDIR}
)

set( mcplcfg_expects_shlibdir_override OFF )
if ( DEFINED SKBUILD_PROJECT_NAME )
  #Only if installed via scikit-build will we generate an appropriate
  #_mcpl_core Python module for installation. We will also copy over some
  #metadata files which resides outside <reporoot>/mcpl_core for inclusion
  #in the sdist. We always regenerate this, even when running from sdist.
  set( pymoddir "${mcpl_skbld_autogendir}/${mcplcore_pymodname}" )
  if ( IS_DIRECTORY ${pymoddir} )
    file ( REMOVE_RECURSE ${pymoddir} )
  endif()
  file( MAKE_DIRECTORY "${pymoddir}" )
  file( WRITE "${pymoddir}/__init__.py" "__version__='${MCPL_VERSION}'\n" )
  #First generated a configured version of the template:
  if ( mcpl_skbuild_shlib_in_wheel_scripts )
    #dll is over in the wheel scripts dir, at an unknown relative location.
    set( mcpl_pymod_shlib_in_scripts "1" )#non-empty string
    set( mcpl_pymod_shlib_dirname "" )
    set( mcplcfg_expects_shlibdir_override ON )
  else()
    set( mcpl_pymod_shlib_in_scripts "" )#empty string
    get_filename_component(
      "mcpl_pymod_shlib_dirname" "${MCPL_SHLIBDIR}" NAME
    )
  endif()

  configure_file(
    "${PROJECT_SOURCE_DIR}/cmake/template_pymod.py.in"
    "${PROJECT_BINARY_DIR}/template_pymod.py.pregen.in"
    @ONLY
  )
  #At build-time, generate the actual files needed for inclusion, expanding any
  #generator-expressions:
  file( GENERATE
    OUTPUT "${pymoddir}/info.py"
    INPUT "${PROJECT_BINARY_DIR}/template_pymod.py.pregen.in"
  )
endif()

if ( MCPL_ENABLE_CFGAPP )
  include( "mcpl_cfgapp" )
  create_mcpl_config_app( ${mcplcfg_expects_shlibdir_override} )
endif()

if ( NOT MCPL_QUIET )
  foreach( optname ${_MCPL_all_opts} )
    message( STATUS "MCPL-cfg: ${optname}=${${optname}}" )
  endforeach()
endif()
