# Builds the LaueMatching C indexer at `pip install` time and installs it under
# <site-packages>/laue_index/bin/. laue_index.indexer.binary_path() finds it
# there. The CUDA binaries are built too, but only on request -- see the CUDA
# section at the bottom of this file.
#
# NEVER FAIL THE INSTALL. If there is no C compiler, or no OpenMP, this returns
# early and `pip install laue-index` still succeeds with the Python-only path —
# indexer.available() then reports False at runtime and the error names every
# location it looked in. Same contract as MIDAS packages/midas_index.
#
# There is no external dependency to find: the optimiser is vendored
# (src/nelder_mead.c), so nothing is downloaded during a pip install. That is
# what makes this viable at all — the previous NLopt ExternalProject would have
# meant a network fetch and a full NLopt build inside pip.
cmake_minimum_required(VERSION 3.20)
project(laue_index_c LANGUAGES NONE)

# c_src/ is where the C LIVES -- the only copy in the repository. It has to be
# inside the package because pip builds from this package's sdist, which
# contains only what is under packages/laue_index/: a CMakeLists reaching up to
# a repo-root ../../src works in a checkout and silently produces no binary for
# every pip user. The repo-root build reaches DOWN into this same directory, so
# a checkout and a pip install compile identical sources by construction rather
# than by a sync check.
set(LAUE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/c_src")

if(NOT EXISTS "${LAUE_SRC}/LaueMatchingCPU.c")
  message(WARNING
    "Vendored C sources not found at ${LAUE_SRC} — installing the Python "
    "package only. laue_index.indexer.available() will report False; set "
    "LAUEMATCHING_BIN to an existing binary if you have one.")
  return()
endif()

include(CheckLanguage)
check_language(C)
if(NOT CMAKE_C_COMPILER)
  message(WARNING
    "No C compiler found — the indexer will not be built. The Python-only "
    "path remains available; laue_index.indexer.available() will report False.")
  return()
endif()
enable_language(C)

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)

find_package(OpenMP COMPONENTS C)
if(NOT OpenMP_C_FOUND)
  message(WARNING
    "OpenMP not found — the indexer will not be built. On macOS install libomp "
    "via `brew install libomp`; on Linux libgomp usually comes with gcc. The "
    "Python-only path remains available.")
  return()
endif()

add_executable(LaueMatchingCPU
  "${LAUE_SRC}/LaueMatchingCPU.c"
  "${LAUE_SRC}/nelder_mead.c"
)
target_include_directories(LaueMatchingCPU PRIVATE "${LAUE_SRC}")
target_compile_options(LaueMatchingCPU PRIVATE -fPIC -O3)
target_link_libraries(LaueMatchingCPU PRIVATE m OpenMP::OpenMP_C)

# Lands at <site-packages>/laue_index/bin/LaueMatchingCPU.
install(TARGETS LaueMatchingCPU RUNTIME DESTINATION laue_index/bin)

message(STATUS "laue_index: building LaueMatchingCPU from ${LAUE_SRC}")

# ── CUDA: opt-in, never automatic ───────────────────────────────────────────
#
#   LAUEMATCHING_CUDA=1 pip install laue-index
#
# Auto-detecting nvcc was considered and rejected. A toolkit that cannot compile
# these sources -- a host-compiler mismatch, an architecture the toolkit dropped
# -- fails at BUILD time, and once a target is added CMake cannot try-and-
# continue: the whole `pip install` dies and takes the working CPU binary with
# it. Opt-in confines that risk to people who deliberately asked for the GPU.
#
# Accepts either the environment variable or -Dcmake.define.LAUE_CUDA=ON via
# pip's --config-settings.
option(LAUE_CUDA "Build the CUDA binaries (LaueMatchingGPU, LaueMatchingGPUStream)" OFF)
set(_laue_cuda_env "$ENV{LAUEMATCHING_CUDA}")
if(_laue_cuda_env AND NOT _laue_cuda_env STREQUAL "0")
  set(LAUE_CUDA ON)
endif()

if(NOT LAUE_CUDA)
  return()
endif()

include(CheckLanguage)
check_language(CUDA)
if(NOT CMAKE_CUDA_COMPILER)
  message(WARNING
    "LAUEMATCHING_CUDA is set, but no CUDA compiler was found -- the GPU binaries "
    "will not be built. Install the CUDA toolkit (nvcc), or point at it with "
    "-Dcmake.define.CMAKE_CUDA_COMPILER=/path/to/nvcc. The CPU binary is unaffected.")
  return()
endif()

# Architectures BEFORE enable_language(CUDA), which compiles a test program with
# them. Never a hardcoded list -- the repo-root build had `70;80;86;90` and CUDA
# 13, which dropped Volta, refuses that outright before compiling a line:
#     nvcc fatal : Unsupported gpu architecture 'compute_70'
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
  include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/LaueCudaArch.cmake")
  laue_cuda_default_architectures(_laue_archs)
  set(CMAKE_CUDA_ARCHITECTURES "${_laue_archs}")
endif()
message(STATUS "laue_index: CUDA architectures = ${CMAKE_CUDA_ARCHITECTURES}")

enable_language(CUDA)

# The .cu wrap LaueMatchingHeaders.h in extern "C", so the vendored simplex --
# compiled here as C -- links into a translation unit nvcc compiles as C++.
# Mirrors the repo-root build, which CI compiles on every push.
foreach(_gpu_target LaueMatchingGPU LaueMatchingGPUStream)
  add_executable(${_gpu_target}
    "${LAUE_SRC}/${_gpu_target}.cu"
    "${LAUE_SRC}/nelder_mead.c"
  )
  set_target_properties(${_gpu_target} PROPERTIES
    CUDA_SEPARABLE_COMPILATION ON      # -rdc=true
    CUDA_RESOLVE_DEVICE_SYMBOLS ON
  )
  target_include_directories(${_gpu_target} PRIVATE "${LAUE_SRC}")
  target_compile_options(${_gpu_target} PRIVATE
    $<$<COMPILE_LANGUAGE:CUDA>:-O3 -w -Xcompiler=${OpenMP_C_FLAGS}>
    $<$<COMPILE_LANGUAGE:C>:-fPIC -O3>
  )
  # Link the OpenMP runtime BY NAME and let the compiler driver locate it.
  # Two other spellings were tried and both failed, in opposite places:
  #   find_library(NAMES gomp) resolves on RHEL (/usr/lib64) but not on Ubuntu,
  #     where the linker symlink lives inside GCC's own directory -- the CUDA
  #     link then died with undefined GOMP_* and omp_get_wtime;
  #   -Xcompiler=-fopenmp as a link option also reaches the DEVICE link step,
  #     which invokes the host compiler directly: "gcc: error: unrecognized
  #     command-line option '-Xcompiler=-fopenmp'".
  # A bare name survives both steps on both distributions, which is what the
  # repo-root build has always done.
  foreach(_omp_lib IN LISTS OpenMP_C_LIB_NAMES)
    target_link_libraries(${_gpu_target} PRIVATE ${_omp_lib})
  endforeach()
  if(NOT OpenMP_C_LIB_NAMES)
    target_link_libraries(${_gpu_target} PRIVATE gomp)
  endif()
  target_link_libraries(${_gpu_target} PRIVATE m)
  install(TARGETS ${_gpu_target} RUNTIME DESTINATION laue_index/bin)
endforeach()

# The streaming daemon serves results over TCP from worker threads.
target_link_libraries(LaueMatchingGPUStream PRIVATE pthread)

message(STATUS "laue_index: building the CUDA binaries for ${CMAKE_CUDA_ARCHITECTURES}")
