cmake_minimum_required(VERSION 3.20)

project(
  SpectraREML
  VERSION 0.1.0
  DESCRIPTION "Reusable batched spectral AI-REML engine"
  LANGUAGES CXX
)

include(GNUInstallDirs)
include(CTest)

list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

set(REML_BLAS "MKL" CACHE STRING "BLAS/LAPACKE backend: MKL or OPENBLAS")
set_property(CACHE REML_BLAS PROPERTY STRINGS MKL OPENBLAS)
string(TOUPPER "${REML_BLAS}" REML_BLAS)

option(REML_ENABLE_OPENMP "Parallelize independent REML tasks with OpenMP" ON)
option(REML_ENABLE_NATIVE_ARCH "Enable compiler tuning for the build host" OFF)

add_library(reml_blas_lapacke INTERFACE)
add_library(REML::BLAS_LAPACKE ALIAS reml_blas_lapacke)

if(REML_BLAS STREQUAL "MKL")
  # These variables are consumed by Intel's MKLConfig.cmake.  Sequential is
  # deliberately the portable default because tasks are parallelized with
  # OpenMP; a threaded BLAS can otherwise create nested thread pools.
  set(MKL_INTERFACE "lp64" CACHE STRING "oneMKL integer interface")
  set(MKL_LINK "dynamic" CACHE STRING "oneMKL link type")
  set(MKL_THREADING "sequential" CACHE STRING "oneMKL threading layer")
  set_property(CACHE MKL_INTERFACE PROPERTY STRINGS lp64 ilp64)
  set_property(CACHE MKL_LINK PROPERTY STRINGS dynamic static)
  set_property(CACHE MKL_THREADING PROPERTY STRINGS sequential intel_thread gnu_thread tbb_thread)

  find_package(MKL CONFIG REQUIRED)
  if(NOT TARGET MKL::MKL)
    message(FATAL_ERROR "MKL was found, but its MKL::MKL CMake target is missing")
  endif()
  target_link_libraries(reml_blas_lapacke INTERFACE MKL::MKL)
  target_compile_definitions(reml_blas_lapacke INTERFACE REML_BLAS_MKL=1)
elseif(REML_BLAS STREQUAL "OPENBLAS")
  find_package(REML_OpenBLAS REQUIRED)
  target_link_libraries(reml_blas_lapacke INTERFACE REML::OpenBLASLAPACKE)
  target_compile_definitions(reml_blas_lapacke INTERFACE REML_BLAS_OPENBLAS=1)
else()
  message(FATAL_ERROR "Unsupported REML_BLAS='${REML_BLAS}'. Choose MKL or OPENBLAS.")
endif()

if(REML_ENABLE_OPENMP)
  find_package(OpenMP REQUIRED COMPONENTS CXX)
endif()

add_library(spectra_reml_core STATIC
  src/linalg.cpp
  src/grm.cpp
  src/reml_core.cpp
  src/batch_io.cpp
)
add_library(SpectraREML::core ALIAS spectra_reml_core)

target_compile_features(spectra_reml_core PUBLIC cxx_std_17)
target_include_directories(spectra_reml_core
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_link_libraries(spectra_reml_core PUBLIC REML::BLAS_LAPACKE)

if(REML_ENABLE_OPENMP)
  target_link_libraries(spectra_reml_core PUBLIC OpenMP::OpenMP_CXX)
  target_compile_definitions(spectra_reml_core PUBLIC REML_USE_OPENMP=1)
endif()

if(MSVC)
  target_compile_options(spectra_reml_core PRIVATE /W4 /permissive- /EHsc)
else()
  target_compile_options(spectra_reml_core PRIVATE -Wall -Wextra -Wpedantic)
  target_compile_definitions(spectra_reml_core PUBLIC _FILE_OFFSET_BITS=64)
  if(REML_ENABLE_NATIVE_ARCH)
    target_compile_options(spectra_reml_core PRIVATE -march=native)
  endif()
endif()

add_executable(spectra_reml src/main.cpp)
target_link_libraries(spectra_reml PRIVATE SpectraREML::core)
target_compile_features(spectra_reml PRIVATE cxx_std_17)

if(MSVC)
  target_compile_options(spectra_reml PRIVATE /W4 /permissive- /EHsc)
else()
  target_compile_options(spectra_reml PRIVATE -Wall -Wextra -Wpedantic)
  if(REML_ENABLE_NATIVE_ARCH)
    target_compile_options(spectra_reml PRIVATE -march=native)
  endif()
endif()

if(BUILD_TESTING)
  add_executable(test_reml_synthetic tests/cpp/test_reml_synthetic.cpp)
  target_link_libraries(test_reml_synthetic PRIVATE SpectraREML::core)
  add_test(NAME reml_synthetic COMMAND test_reml_synthetic)

  add_executable(test_grm_io tests/cpp/test_grm_io.cpp)
  target_link_libraries(test_grm_io PRIVATE SpectraREML::core)
  add_test(NAME grm_io COMMAND test_grm_io)
endif()

install(TARGETS spectra_reml_core spectra_reml
  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
  LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

message(STATUS "SpectraREML ${PROJECT_VERSION}")
message(STATUS "  BLAS/LAPACKE backend : ${REML_BLAS}")
message(STATUS "  OpenMP               : ${REML_ENABLE_OPENMP}")
message(STATUS "  Native architecture  : ${REML_ENABLE_NATIVE_ARCH}")
