# Copyright (C) 2022 Roberto Rossini <roberros@uio.no>
#
# SPDX-License-Identifier: MIT

cmake_minimum_required(VERSION 3.25)
cmake_policy(VERSION 3.25...3.27)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/")

# Not ideal to use this global variable, but necessary to make sure that tooling and projects use the same version
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)

# strongly encouraged to enable this globally to avoid conflicts between -Wpedantic being enabled and -std=c++20 and
# -std=gnu++20 for example when compiling with PCH enabled
set(CMAKE_CXX_EXTENSIONS OFF)

set(ENABLE_DEVELOPER_MODE
    OFF
    CACHE BOOL "Enable 'developer mode'")

if(BUILD_SHARED_LIBS)
  message(
    FATAL_ERROR
      "Building project with dynamic linking is currently not supported! Please remove option -DBUILD_SHARED_LIBS=ON and re-run cmake."
  )
endif()

include(FetchContent)
# cmake-format: off
FetchContent_Declare(
        _project_options
        URL ${CMAKE_CURRENT_SOURCE_DIR}/external/project_options-v0.28.0.tar.xz
        URL_HASH SHA512=216d0970ace00e3176788a2b5abb7b92490c005111d26365e7807a5e86c6323e38b33e2e5b01f4ee43438a8f8e96cacf16cedc6f905a7e0ad58752905b260b38
)
# cmake-format: on

FetchContent_MakeAvailable(_project_options)
include(${_project_options_SOURCE_DIR}/Index.cmake)

include(cmake/Versioning.cmake)
project(
  MoDLE
  LANGUAGES CXX
  VERSION "${MODLE_PROJECT_VERSION_MAJOR}.${MODLE_PROJECT_VERSION_MINOR}.${MODLE_PROJECT_VERSION_PATCH}"
  HOMEPAGE_URL https://github.com/paulsengroup/modle
  DESCRIPTION "MoDLE: High-performance stochastic modeling of DNA loop extrusion interactions.")

get_property(BUILDING_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(BUILDING_MULTI_CONFIG)
  if(NOT CMAKE_BUILD_TYPE)
    # Make sure that all supported configuration types have their associated conan packages available. You can reduce
    # this list to only the configuration types you use, but only if one is not forced-set on the command line for VS
    message(TRACE "Setting up multi-config build types")
    set(CMAKE_CONFIGURATION_TYPES
        Debug Release RelWithDebInfo
        CACHE STRING "Enabled build types" FORCE)
  else()
    message(TRACE "User chose a specific build type, so we are using that")
    set(CMAKE_CONFIGURATION_TYPES
        ${CMAKE_BUILD_TYPE}
        CACHE STRING "Enabled build types" FORCE)
  endif()
endif()

include(${_project_options_SOURCE_DIR}/src/DynamicProjectOptions.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CompilerWarnings.cmake)

# dynamic_project_options sets recommended defaults and provides user and developer modes and full GUI support for
# choosing options at configure time

# for more flexibility, look into project_options() macro

# Any default can be overridden set(<feature_name>_DEFAULT <value>) - set default for both user and developer modes
# set(<feature_name>_DEVELOPER_DEFAULT <value>) - set default for developer mode set(<feature_name>_USER_DEFAULT
# <value>) - set default for user mode

# Initialize project_options variable related to this project This overwrites `project_options` and sets
# `project_warnings` uncomment the options to enable them:

set(ENABLE_CACHE_DEFAULT ON)
set(ENABLE_CLANG_TIDY_DEFAULT OFF)
set(ENABLE_COMPILE_COMMANDS_SYMLINK_DEFAULT OFF)
set(ENABLE_CONAN_DEFAULT OFF)
set(ENABLE_CPPCHECK_DEFAULT OFF)
set(ENABLE_DOXYGEN_USER OFF)
set(ENABLE_DOXYGEN_DEVELOPER ON)
set(ENABLE_GCC_ANALYZER_DEFAULT OFF)
set(ENABLE_INTERPROCEDURAL_OPTIMIZATION_DEFAULT ON)
set(ENABLE_NATIVE_OPTIMIZATION_DEFAULT OFF)
set(ENABLE_PCH_DEFAULT OFF)

dynamic_project_options(
  CPPCHECK_OPTIONS
  --enable=performance,portability,style,warning
  --inline-suppr
  # We cannot act on a bug/missing feature of cppcheck
  --suppress=internalAstError
  # if a file does not have an internalAstError, we get an unmatchedSuppression error
  --suppress=unmatchedSuppression
  --suppress=passedByValue
  --inconclusive
  MSVC_WARNINGS
  ${MSVC_WARNINGS}
  CLANG_WARNINGS
  ${CLANG_WARNINGS}
  GCC_WARNINGS
  ${GCC_WARNINGS}
  CUDA_WARNINGS
  ${CUDA_WARNINGS})

# This is used so that source files can include "modle/version/version.hpp" and "modle/version/git.hpp" which is
# automatically generated by CMake
include_directories(${CMAKE_CURRENT_BINARY_DIR}/src/version/include)

option(MODLE_WITH_BOOST_RANDOM "Use Boost's random library instead of that from the STL (recommended)" ON)
option(MODLE_OPTIMIZE_FOR_PROFILING
       "Compile project in RelWithDebInfo and with less aggressive optimizations to aid profiling" OFF)
option(
  MODLE_ENABLE_ASSERTIONS
  "Enable assertions and various other runtime checks (this is done regardless of the type passed to CMAKE_BUILD_TYPE)"
  OFF)
option(MODLE_DOWNLOAD_TEST_DATASET "Download datasets required by unit and integration tests" ON)
option(MODLE_ENABLE_TESTING "Build MoDLE's unit tests" ON)

target_compile_features(project_options INTERFACE cxx_std_${CMAKE_CXX_STANDARD})

include(cmake/TestStdlibFeatures.cmake)

target_compile_definitions(project_options
                           INTERFACE $<$<BOOL:${CHARCONV_VARIANT_AVAILABLE}>:MODLE_CHARCONV_VARIANT_AVAILABLE>)

find_package(Filesystem REQUIRED)
target_link_libraries(project_options INTERFACE std::filesystem)

# Reduce level of optimization to improve the code profiling experience
if(MODLE_OPTIMIZE_FOR_PROFILING)
  if(NOT
     "${uppercase_CMAKE_BUILD_TYPE}"
     STREQUAL
     "RelWithDebInfo")
    message(
      WARNING
        "Switching build type from ${CMAKE_BUILD_TYPE} to RelWithDebInfo because MODLE_OPTIMIZE_FOR_PROFILING was set to ON by the user"
    )
    set(CMAKE_BUILD_TYPE "RelWithDebInfo")
  endif()

  target_compile_options(project_options INTERFACE -O1 -fno-omit-frame-pointer)
endif()

# Source:
# https://github.com/llvm/llvm-project/blob/656ebd519e3fd52050e1c8abeacafcf94d1fa260/llvm/cmake/modules/HandleLLVMOptions.cmake#L54
if(MODLE_ENABLE_ASSERTIONS)
  if(NOT MSVC)
    add_definitions(-D_DEBUG)
  endif()
  # On non-Debug builds cmake automatically defines NDEBUG, so we explicitly undefine it:
  if(NOT
     uppercase_CMAKE_BUILD_TYPE
     STREQUAL
     "DEBUG")
    # NOTE: use `add_compile_options` rather than `add_definitions` since `add_definitions` does not support generator
    # expressions.
    target_compile_options(project_options INTERFACE $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:-UNDEBUG>)

    # Also remove /D NDEBUG to avoid MSVC warnings about conflicting defines.
    foreach(
      flags_var_to_scrub
      CMAKE_CXX_FLAGS_RELEASE
      CMAKE_CXX_FLAGS_RELWITHDEBINFO
      CMAKE_CXX_FLAGS_MINSIZEREL
      CMAKE_C_FLAGS_RELEASE
      CMAKE_C_FLAGS_RELWITHDEBINFO
      CMAKE_C_FLAGS_MINSIZEREL)
      string(
        REGEX
        REPLACE "(^| )[/-]D *NDEBUG($| )"
                " "
                "${flags_var_to_scrub}"
                "${${flags_var_to_scrub}}")
    endforeach()
  endif()
endif()

if(MODLE_WITH_BOOST_RANDOM)
  target_compile_definitions(project_options INTERFACE MODLE_WITH_BOOST_RANDOM=1)
endif()

# Tweak spdlog
target_compile_definitions(
  project_options
  INTERFACE SPDLOG_CLOCK_COARSE
            SPDLOG_DISABLE_DEFAULT_LOGGER
            SPDLOG_NO_ATOMIC_LEVELS
            SPDLOG_PREVENT_CHILD_FD)

# Tweak fmt
target_compile_definitions(project_options INTERFACE FMT_ENFORCE_COMPILE_STRING)

# Tweak boost
find_package(Boost CONFIG REQUIRED COMPONENTS filesystem)

if(Boost_VERSION_STRING VERSION_GREATER_EQUAL 1.77.0)
  target_compile_definitions(project_options INTERFACE BOOST_FILESYSTEM_VERSION=4)
elseif(Boost_VERSION_STRING VERSION_GREATER 1.44.0)
  target_compile_definitions(project_options INTERFACE BOOST_FILESYSTEM_VERSION=3)
endif()

if(MODLE_ENABLE_TESTING)
  enable_testing()
  message("-- Configuring unit tests")
  target_compile_definitions(project_options INTERFACE MODLE_ENABLE_TESTING)

  if(MODLE_DOWNLOAD_TEST_DATASET)
    message("-- Downloading test dataset...")
    include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchTestDataset.cmake)
  endif()

  add_subdirectory(test)
endif()

add_subdirectory(src)

include(cmake/FetchExternalDeps.cmake)

# include(cmake/Packaging.cmake)
