# ============================================================================
# Require minimal version of cmake
# ============================================================================
cmake_minimum_required(VERSION 3.11)
if(${CMAKE_VERSION} VERSION_GREATER "3.24")
  cmake_policy(VERSION 3.24)
endif()

# ============================================================================
# Mac OS specific: Since we cannot use AppleClang, try find and configure
# brew-installed clang automatically
# ============================================================================
if (APPLE)
  if (NOT DEFINED CMAKE_C_COMPILER OR NOT DEFINED CMAKE_CXX_COMPILER)
    message(WARNING
      "We've detected that you are using Mac OS but did not explicitly set the C and/or C++ compiler.\n"
      "We'll assume you have a brew-installed llvm and do our best to locate and use it.\n"
      "Visit https://aevol.fr/doc/install for more information.\n"
    )
    execute_process(
      COMMAND brew --prefix llvm
      OUTPUT_VARIABLE LLVM_PATH
      OUTPUT_STRIP_TRAILING_WHITESPACE
      ERROR_VARIABLE  LLVM_ERROR
    )
    if (NOT LLVM_ERROR STREQUAL "")
      message(FATAL_ERROR "${LLVM_ERROR}")
    endif ()
    if (NOT LLVM_PATH STREQUAL "")
      message("Found llvm: ${LLVM_PATH}")
    endif ()
    set(CMAKE_C_COMPILER "${LLVM_PATH}/bin/clang")
    set(CMAKE_CXX_COMPILER "${LLVM_PATH}/bin/clang++")
    set(CMAKE_EXE_LINKER_FLAGS "-L${LLVM_PATH}/lib/c++")
  endif ()
endif ()

# ============================================================================
# Set project name and languages
# ============================================================================
project(aevol VERSION 9.3.0 LANGUAGES C CXX)

# ============================================================================
# Use C++20
# ============================================================================
set(CMAKE_CXX_STANDARD 20)

# ============================================================================
# Check compiler compatibility
# ============================================================================
if (APPLE)
  # If compiler is AppleClang, issue message and exit
  if(CMAKE_C_COMPILER_ID STREQUAL "AppleClang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
    message(FATAL_ERROR "The configured compiler appears to be AppleClang, which is not capable of building Aevol.\n"
    "Have you forgotten to explicitly set the compiler?\n"
    "Try `cmake -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ ..`\n"
    "(note that you will probably have to run that command twice because of the way cmake handles cache)\n"
    "Visit https://aevol.fr/doc/install for more information.\n")
  endif ()
  # If compiler is GCC, version >=14 is required on Apple
  if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "14")
      message(FATAL_ERROR "The configured compiler appears to be gcc < 14, which is not capable of building Aevol.\n"
        "Please upgrade to version 14 or higher.\n"
        "Visit https://aevol.fr/doc/install for more information.\n")
    endif ()
  endif ()
endif ()

# If compiler is Clang, version >=19 is required
if(CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
  if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19")
    message(FATAL_ERROR "The configured compiler appears to be clang < 19, which is not capable of building Aevol.\n"
      "Please upgrade to version 19 or higher.\n"
      "Visit https://aevol.fr/doc/install for more information.\n")
  endif ()
endif ()
# If compiler is GCC, version >=13 is required
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "13")
    message(FATAL_ERROR "The configured compiler appears to be gcc < 13, which is not capable of building Aevol.\n"
      "Please upgrade to version 13 or higher.\n"
      "Visit https://aevol.fr/doc/install for more information.\n")
  endif ()
endif ()

# Check existence of std::from_chars for type double
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("
  #include <charconv>
  int main() {
      double d;
      char const c[] = \"0\";
      std::from_chars(c, c+1, d);
      return 0;
  }
" HAS_FROMCHARS_DOUBLE)
if(HAS_FROMCHARS_DOUBLE)
  add_compile_definitions(HAS_FROMCHARS_DOUBLE)
endif ()

# Set a default build type to 'Release' if none was specified
IF(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  MESSAGE(STATUS "Setting build type to 'Release' as none was specified.")
  SET(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
  # Set the possible values of build type for cmake-gui
  SET_PROPERTY(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
ENDIF()

set(AUTHORIZED_MTPERIOD 607 1279 2281 4253 11213 19937 44497 86243 132049 216091)

set(with-post-treatments ON CACHE BOOL "Whether to build post-treatments")

set(with-omp ON CACHE BOOL "Whether to enable OpenMP parallelization")

set(enable-profiling OFF CACHE BOOL "Whether to enable profiling")
set(with-tracing OFF CACHE BOOL "Whether to use tracing")
set(enable-perflog OFF CACHE BOOL "Whether to active performance log")
set(enable-normalized-fitness OFF CACHE BOOL "With this option, the NORMALIZED_FITNESS flag is defined, allowing a different fitness calculation")
set(enable-mtperiod 607 CACHE STRING "Period of the Mersen Twister. Autorized values are : 607 1279 2281 4253 11213 19937 44497 86243 132049 216091")
set(enable-trivialjumps OFF CACHE STRING "When this option is set, a trivial jump algorithm will be used instead of the polynomial-based method")
set(enable-omp-sort "" CACHE STRING "Which sorting algorithm to use for sorting mutant individual before processing them (by default, no sorting)")
set(dna-factory-alg "L2G" CACHE STRING "Which memory allocation algorithm to use for managing the DnaFactory pool of DNAs")

set(with-triangle OFF CACHE BOOL "Whether to enable triangle phenotypic target (else Gaussian)")
set(with-detectclone ON CACHE BOOL "Whether to enable clones and not recompute them")
set(with-floatconcentration OFF CACHE BOOL "Whether to enable the encoding of concentration has float (and not double)")
set(with-perf-traces OFF CACHE BOOL "Whether to activate performance traces of Aevol")
set(with-indiv-perf-traces OFF CACHE BOOL "Whether to activate performance traces (per individual) of Aevol")

set(search-type "OLD" CACHE STRING "Which SIMD pattern search version of Aevol")
set(with-opt-diff-search OFF CACHE BOOL "Activate the optimize version of differential search for motifs (RNA and Genes)")

set(with-progeny-stats OFF CACHE BOOL "Activate (or not) stats for progeny (should generate a lot of data)")

if ( ${with-indiv-perf-traces} )
        add_definitions(-DWITH_PERF_TRACES_PER_INDIV)
endif ()


if ( ${with-progeny-stats} )
	add_definitions(-DPROGENY_STATS)
endif ()

if ( ${with-omp} )
    find_package(OpenMP REQUIRED)

    if ( OPENMP_FOUND )
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
    endif ()
endif ()

if ( ${enable-profiling} )
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
endif ()

if ( ${with-tracing} )
	add_definitions(-D__TRACING__)
endif ()

if ( ${enable-perflog} )
    add_definitions(-D__PERF_LOG__)
endif ()

if ( ${with-perf-traces} )
    add_definitions(-DWITH_PERF_TRACES)
endif ()

if ( ${with-opt-diff-search} )
    add_definitions(-DWITH_OPTIMIZE_DIFF_SEARCH)
endif ()

if ( ${enable-normalized-fitness} )
    add_definitions(-DNORMALIZED_FITNESS)
endif ()

if ( DEFINED enable-mtperiod )
    list(FIND AUTHORIZED_MTPERIOD ${enable-mtperiod} _index)
    if ( ${_index} GREATER -1 )
        set(MTPERIOD ${enable-mtperiod})
    else ()
        message(FATAL_ERROR "period is not a valid Mersenne Twister period")
    endif ()
endif ()
message("Mersene Twister period is set to ${MTPERIOD}")
add_definitions(-DSFMT_MEXP=${MTPERIOD})

if ( ${enable-trivialjumps} )
    if ( ${enable-trivialjumps} MATCHES "[0-9]+" )
        add_definitions(-DTRIVIAL_METHOD_JUMP_SIZE=${enable-trivialjumps})
    else ()
        add_definitions(-DTRIVIAL_METHOD_JUMP_SIZE=1000)
    endif ()
endif ()

if ( ${with-triangle} )
    add_definitions(-DPHENOTYPIC_TARGET_TRIANGLE)
endif ()

if ( ${with-detectclone} )
    add_definitions(-D__DETECT_CLONE)
endif ()

if ( ${with-floatconcentration} )
    add_definitions(-D__FLOAT_CONCENTRATION)
endif ()

if ( NOT ${enable-omp-sort} STREQUAL "" )
    if ( ${enable-omp-sort} STREQUAL "LDNA" )
        add_definitions(-D__OMP_LIST_SORT=0)
    elseif( ${enable-omp-sort} STREQUAL "SHUFFLE" )
        add_definitions(-D__OMP_LIST_SORT=1)
    endif ()
endif ()

if ( ${dna-factory-alg} STREQUAL "FIRST" )
    add_definitions(-D__DNA_FACTORY_ALG=0)
elseif ( ${dna-factory-alg} STREQUAL "FIRSTFIT" )
    add_definitions(-D__DNA_FACTORY_ALG=1)
elseif ( ${dna-factory-alg} STREQUAL "L2G" )
    add_definitions(-D__DNA_FACTORY_ALG=2)
elseif ( ${dna-factory-alg} STREQUAL "ALLOCATE" )
    add_definitions(-D__DNA_FACTORY_ALG=3)
endif ()

# ===========================================================================
# Tell CMake to export compile commands for IDE integration
# ===========================================================================
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# ===========================================================================
# Fetch external dependencies
# ===========================================================================
include(FetchContent)
FetchContent_Declare(
    json
    URL      https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
    URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa
)
Message(STATUS "Getting external dependencies...")
FetchContent_MakeAvailable(json)
Message(STATUS "Getting external dependencies... Done")

# ============================================================================
# Look for Zlib
# ============================================================================
find_package(ZLIB REQUIRED)


# ============================================================================
# Get GNU standard installation directories (GNUInstallDirs module)
# ============================================================================
include(GNUInstallDirs)


# ============================================================================
# Tell CMake where to look for custom modules
# ============================================================================
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)


# ============================================================================
# Tell cmake where to put binary files.
# By GNU standards "executable programs that users can run" should go in
# bindir a.k.a ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}
# and "executable programs to be run by other programs rather than by users"
# in libexecdir a.k.a ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}
# ============================================================================
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})


# ============================================================================
# Set build type specific compilation flags
# ============================================================================
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -Wall -Wextra -Werror")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -Wall -Wextra -Werror")

set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -pipe")
if (CMAKE_C_COMPILER_ID MATCHES "GNU")
  set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fno-math-errno -funsafe-math-optimizations -fno-rounding-math -fno-signaling-nans -fcx-limited-range -fexcess-precision=fast")
endif ()
if (CMAKE_C_COMPILER_ID MATCHES "Clang")
  set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fapprox-func -fno-math-errno -fassociative-math -freciprocal-math -fno-signed-zeros -fno-trapping-math -fno-rounding-math -ffp-contract=fast")
endif ()

set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pipe")
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-math-errno -funsafe-math-optimizations -fno-rounding-math -fno-signaling-nans -fcx-limited-range -fexcess-precision=fast")
endif ()
if (CMAKE_C_COMPILER_ID MATCHES "Clang")
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fapprox-func -fno-math-errno -fassociative-math -freciprocal-math -fno-signed-zeros -fno-trapping-math -fno-rounding-math -ffp-contract=fast")
endif ()

# ============================================================================
# Define flavors of aevol
# ============================================================================
set(AEVOL_PROKARYOTE_RAW_FLAVORS 2b 4b)
set(AEVOL_EUKARYOTE_RAW_FLAVORS eukaryote_2b)
# Generate useful variables derived thereof (full list + "aevol_" prefixed lists)
set(AEVOL_RAW_FLAVORS ${AEVOL_PROKARYOTE_RAW_FLAVORS} ${AEVOL_EUKARYOTE_RAW_FLAVORS})
foreach (AEVOL_PROKARYOTE_RAW_FLAVOR IN LISTS AEVOL_PROKARYOTE_RAW_FLAVORS)
  set(AEVOL_PROKARYOTE_FLAVORS ${AEVOL_PROKARYOTE_FLAVORS} aevol_${AEVOL_PROKARYOTE_RAW_FLAVOR})
endforeach ()
foreach (AEVOL_EUKARYOTE_RAW_FLAVOR IN LISTS AEVOL_EUKARYOTE_RAW_FLAVORS)
  set(AEVOL_EUKARYOTE_FLAVORS ${AEVOL_EUKARYOTE_FLAVORS} aevol_${AEVOL_EUKARYOTE_RAW_FLAVOR})
endforeach ()
set(AEVOL_FLAVORS ${AEVOL_PROKARYOTE_FLAVORS} ${AEVOL_EUKARYOTE_FLAVORS})

# ============================================================================
# Tell cmake about subdirectories to look into
# ============================================================================
add_subdirectory(src)

# ============================================================================
# Include tests
# ============================================================================
enable_testing()
add_subdirectory(test EXCLUDE_FROM_ALL)


# ============================================================================
# Adds the 'dist' target (that will use CPack)
# ============================================================================
#add_custom_target(dist COMMAND ${CMAKE_BUILD_TOOL} package_source)


# ============================================================================
# Add the 'uninstall' target (uses a custom script)
# ============================================================================
configure_file(
        "${PROJECT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
        "${CMAKE_BINARY_DIR}/cmake_uninstall.cmake"
        IMMEDIATE @ONLY)

add_custom_target(uninstall
        COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/cmake_uninstall.cmake)
