###########################################################################
# top-level CMakeLists.txt for building BornAgain
############################################################################

### CMake configuration

cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
if (CMAKE_VERSION GREATER_EQUAL 3.30)
    cmake_policy(SET CMP0167 OLD)
    # Old policy uses FindBoost.cmake provided by CMake, which is deprecated.
    # New policy uses BoostConfig.cmake provided by Boost.  It returns targets
    # Boost::ProgramOptions etc instead of library paths.  We still have to find
    # out how replace the configure_file command in MakePythonWheel.cmake.
    # See https://jugit.fz-juelich.de/mlz/bornagain/-/issues/1074.
endif()
if (CMAKE_VERSION GREATER_EQUAL 3.31)
    cmake_policy(SET CMP0175 NEW)
endif()
if (CMAKE_VERSION GREATER_EQUAL 3.31)
    cmake_policy(SET CMP0177 OLD)
    # New behavior, introduced in CMake 3.31, normalizes all DESTINATION values
    # given in any form of the install() command.
    # Conversion to NEW behavior postponed until we require CMake >= 3.31.
endif()

set(CMAKE_MODULE_PATH
    ${CMAKE_SOURCE_DIR}
    ${CMAKE_SOURCE_DIR}/cmake/find)
set(CONFIGURABLES_DIR ${CMAKE_SOURCE_DIR}/cmake/configurables)

include(cmake/BornAgain/Git)
message(STATUS "Git branch '${GIT_BRANCH}' at commit ${GIT_COMMIT}, tag: '${GIT_TAG}'")

include(cmake/commons/PreventInSourceBuilds)

### Project settings

project(BornAgain
    VERSION 22.0 # will always remain binary; minor equals patch-level
    DESCRIPTION "BornAgain: simulate and fit reflectometry and grazing-incidence scattering."
    HOMEPAGE_URL https://www.bornagainproject.org
    LANGUAGES CXX)

### Options

# options that are on by default (switch off for accelerated builds of limited scope)
option(BA_CPP20 "Use C++20 standard" OFF)
option(BORNAGAIN_PYTHON "Build with Python support" ON)
option(BA_GUI "Build a graphical user interface" ON)
option(BA_TIFF_SUPPORT "Tiff files read/write support" ON)
option(BA_TESTS "Build tests" ON)
option(BA_DOCS "Build documentation" ON)
option(BA_APPLE_HOMEBREW "Build BornAgain with Homebrew under MacOS" OFF)
option(BA_PY_PACK "Build BornAgain Python wheel" ON)

# options that are off by default (switch on for additional functionality)
option(CONFIGURE_BINDINGS "Generate python bindings during build (requires swig)" OFF)
option(CONFIGURE_EXAMPLES "Configure examples (requires ruby)" OFF)
option(CONFIGURE_DOXY "Configure Doxygen source documentation" OFF)
option(ALLCONFIG "Regenerate Py wrappers, examples, Doxyfiles" OFF)
option(ZERO_TOLERANCE "Terminate compilation on warnings" OFF)
option(DEVELOPER_CHECKS "Checks required from developers but not from external users" OFF)
option(DEV "Development: turns ALLCONFIG, ZERO_TOLERANCE, DEVELOPER_CHECKS on" OFF)
option(BA_COVERAGE "Build with test coverage information" OFF)
option(BA_DEBUG_OPTIMIZATION "Build with debug optimization (gcc only)" OFF)
option(BA_TIDY "Invokes clang-tidy" OFF)
option(ALGORITHM_DIAGNOSTIC "Let some algorithms set diagnostic variables" OFF)
option(BA_APPLE_BUNDLE "Create a MacOS bundle" OFF)
option(BA_CPP_API "Install header files" OFF)
option(BA_3ARCH
    "Reduced tolerance for persistence tests on architectures not covered by maintainer CI" OFF)

# options with non-boolean value

set(SEARCH_PYTHON_PATH "" CACHE INTERNAL "Search path for Python platform to be used for packaging")
string(STRIP "${SEARCH_PYTHON_PATH}" SEARCH_PYTHON_PATH)

# options that set other options

if(DEV)
    set(ALLCONFIG ON)
    set(ZERO_TOLERANCE ON)
    set(DEVELOPER_CHECKS ON)
endif()
if(ALLCONFIG)
    set(CONFIGURE_BINDINGS ON)
    set(CONFIGURE_EXAMPLES ON)
    set(CONFIGURE_DOXY ON)
    # TODO what about targets 'figures', 'excopy'? see Tests/Examples/CMakeLists.txt
    # TODO what about target 'src_graphs'? see Doc/graph/CMakeLists.txt
endif()

# check compatibility of options

if(BA_APPLE_BUNDLE AND NOT APPLE)
    message(FATAL_ERROR "BA_APPLE_BUNDLE=ON although hardware is not APPLE")
endif()

if(BA_APPLE_BUNDLE AND BA_APPLE_HOMEBREW)
    message(FATAL_ERROR "BA_BUNDLE and BA_APPLE_HOMEBREW are mutually exclusive options.")
endif()

if(BA_TIDY)
    if (BORNAGAIN_PYTHON)
        message(FATAL_ERROR "BA_TIDY is incompatible with BORNAGAIN_PYTHON")
    endif()
    if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        message(FATAL_ERROR "BA_TIDY requires compiler Clang")
    endif()
endif()

if(BA_TESTS AND NOT BA_PY_PACK)
    message(FATAL_ERROR "Tests need the BA Python package, hence BA_PY_PACK must be ON.")
endif()

### set C++ standard

if(BA_CPP20)
    set(CMAKE_CXX_STANDARD 20)
else()
    set(CMAKE_CXX_STANDARD 17)
endif()

### Generator type (Release|Debug)

# Show info about generator type; set CMAKE_BUILD_TYPE if not given
if(CMAKE_CONFIGURATION_TYPES)
    message(STATUS "Generator type: Multi-configuration generator")
else()
    message(STATUS "Generator type: Single-configuration generator")
endif()
message("    CMAKE_CONFIGURATION_TYPES: ${CMAKE_CONFIGURATION_TYPES}")
# The following is not correct/does not have any effect for multi-configuration generators.
# But when correcting this, be aware that CMAKE_BUILD_TYPE is used in more scripts!
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
    message("    CMAKE_BUILD_TYPE type not given - forced to 'Release'")
else()
    string(TOUPPER "${CMAKE_BUILD_TYPE}" _upper_build_type)
    set(BUILD_${_upper_build_type} 1)
endif()
message("    CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
configure_file("${PROJECT_SOURCE_DIR}/cmake/configurables/config_build.h.in"
    "${PROJECT_BINARY_DIR}/inc/config_build.h"
    @ONLY)


### Configure tests

if(BA_TESTS)
    # GoogleTest: `gtest_discover_tests` discovers tests by asking the _compiled test executable_
    # to enumerate its tests. `PRE_TEST` delays test discovery until just prior to test execution;
    # this avoids calling the executables during the build phase.
    set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)

    include(CTest) # equivalent to "enable_testing() ???
    add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -LE Fullcheck)
       # => 'make check' is an alias for 'ctest'
    add_custom_target(fullcheck COMMAND ${CMAKE_CTEST_COMMAND})
       # => 'make check' is an alias for 'ctest'
endif()


### Operating system and compilation flags

# no support for systems with addresses shorter than 64 bits
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
    message(FATAL "Found a 32bit system - no longer supported")
elseif(CMAKE_SIZEOF_VOID_P LESS 8)
    message(FATAL "Found a ${CMAKE_SIZEOF_VOID_P} byte system - very unexpected and not supported")
endif()

# introduce `LINUX` flag (whereas `APPLE` and `WIN32` are set by CMake)
if(UNIX AND CMAKE_SYSTEM_NAME MATCHES Linux)
    set(LINUX ON)
endif()

# the host system must be Linux, Apple or Windows
if(NOT (LINUX OR APPLE OR WIN32))
    message(FATAL_ERROR "Operating system ${CMAKE_SYSTEM_NAME} is not supported")
endif()

if(LINUX)
    include(cmake/BornAgain/Linux)
elseif(APPLE)
    include(cmake/BornAgain/MacOS)
elseif(WIN32)
    include(cmake/BornAgain/Windows)
endif()

if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    set(CLANG ON)
    string(APPEND CMAKE_CXX_FLAGS " -Wno-misleading-indentation")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    set(GCC ON)
    string(APPEND CMAKE_CXX_FLAGS " -fdiagnostics-color=always")
elseif(NOT WIN32)
    message(FATAL_ERROR "Unsupported compiler, id=${CMAKE_CXX_COMPILER_ID}")
endif()

set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
if(ZERO_TOLERANCE)
    if (WIN32)
        string(APPEND CMAKE_CXX_FLAGS " /WX")
    else()
        string(APPEND CMAKE_CXX_FLAGS " -Werror")
    endif()
endif()
if(ALGORITHM_DIAGNOSTIC)
    string(APPEND CMAKE_CXX_FLAGS " -DALGORITHM_DIAGNOSTIC=ON")
endif()
string(APPEND CMAKE_CXX_FLAGS_DEBUG " -DBA_DEBUG") # changes behavior of ASSERT

include(cmake/BornAgain/CompilerInfo)

### Further configuration

# function definitions
include(cmake/commons/GetFilenameComponent) # fct get_filename_component (overwrites CMake's built-in)
if(BORNAGAIN_PYTHON)
    include(cmake/multipython/SwigLib) # fct SwigLib
endif()
include(cmake/BornAgain/MakeLib) # fct MakeLib

# main settings
include(cmake/BornAgain/Directories)
include(cmake/BornAgain/FindLibraries)

if(WIN32)
    include(cmake/BornAgain/InstallDll)
endif()

# Python
if(BORNAGAIN_PYTHON)
    include(cmake/multipython/PyDependences)
endif()

if(BA_GUI)
    include(cmake/BornAgain/Qt)
    string(APPEND CMAKE_CXX_FLAGS " -DHAVE_QT=ON")
endif()

include(cmake/BornAgain/ConfigureFiles)

### Configure source components

# library Fit
add_subdirectory(Fit/3rdparty)
add_subdirectory(Fit)

# other components
set(CoreComponents "Base")
if (BORNAGAIN_PYTHON)
    set(CoreComponents "${CoreComponents};PyCore")
endif()
set(CoreComponents "${CoreComponents};Param;Sample;Resample;Device;Sim")
set(AllComponents "${CoreComponents};Img3D;GUI")

# code analysis
if(LINUX AND DEVELOPER_CHECKS)
    include(cmake/BornAgain/SourceChecks)
endif()
if(BA_COVERAGE)
  include(cmake/BornAgain/Coverage)
endif()

# third-party code
add_subdirectory(3rdparty/common)
add_subdirectory(3rdparty/Core)
if(BA_GUI)
    add_subdirectory(3rdparty/GUI/qcustomplot)
endif()

# from here on our own code, occasionally scrutinized by clang-tidy
if(BA_TIDY)
    set(CMAKE_CXX_CLANG_TIDY "clang-tidy") # has effect only if compiler is clang; uses .clang-tidy
endif()

# configure core component libraries
foreach(lib ${CoreComponents})
    add_subdirectory(${lib})
endforeach()
if(BORNAGAIN_PYTHON)
    if (CONFIGURE_EXAMPLES)
        add_subdirectory(rawEx)
    endif()
endif()

# GUI
if(BA_GUI)
    add_subdirectory(Img3D)
    add_subdirectory(GUI)
    add_subdirectory(App)
endif()

# tests
if(BA_TESTS)
    if(BA_3ARCH)
        add_compile_definitions(BA_OTHER_ARCH=ON)
    endif()
    add_subdirectory(Tests/Unit)
    if(BORNAGAIN_PYTHON)
        add_subdirectory(Tests/Examples)
        add_subdirectory(Tests/Py)
    endif()
    add_subdirectory(Tests/SimFactory)
    add_subdirectory(Tests/Functional)
    add_subdirectory(Tests/Suite)
endif()


# documentation
if (BA_DOCS)
    add_subdirectory(Doc/man)

    if(BORNAGAIN_PYTHON AND BA_TESTS)
        # NOTE: target 'figures' is defined in Tests and is needed for the documentation
        add_subdirectory(hugo)
        add_subdirectory(Doc/graph)
    endif()

    if(CONFIGURE_DOXY)
        add_subdirectory(Doc/Doxygen)
    endif()
endif()

# to show CMake and other auxiliary scripts in Qt creator
file(GLOB_RECURSE ScriptFiles cmake/configurables/* deploy/*)
add_custom_target(scripts SOURCES ${ScriptFiles})

### Finalize

# make package targets
include(cmake/BornAgain/Pack)

# local installation (only for Linux)
if(LINUX)
    set(BA_INSTALL_SCRIPT "${CMAKE_BINARY_DIR}/cmake_install.cmake")
    # install only the BornAgain executable, BornAgain libraries and the examples
    add_custom_target(install_local
        COMMAND ${CMAKE_COMMAND} -D COMPONENT=Applications -P ${BA_INSTALL_SCRIPT}
        COMMAND ${CMAKE_COMMAND} -D COMPONENT=Libraries -P ${BA_INSTALL_SCRIPT}
        COMMAND ${CMAKE_COMMAND} -D COMPONENT=Manual -P ${BA_INSTALL_SCRIPT}
        COMMAND ${CMAKE_COMMAND} -D COMPONENT=Examples -P ${BA_INSTALL_SCRIPT}
        )
endif()

# build log
include(cmake/BornAgain/BuildLog)
install(FILES ${BA_BUILD_LOG} DESTINATION ${destination_share})
message(STATUS "BornAgain build log written to '${BA_BUILD_LOG}'")

message(STATUS "CMake done")
