# Copyright (c) 2025 The YAC Authors
#
# SPDX-License-Identifier: BSD-3-Clause

cmake_minimum_required(VERSION 3.18)

# Calling project sets YAC_VERSION, YAC_VERSION_MAJOR, YAC_VERSION_MINOR,
# YAC_VERSION_PATCH, YAC_VERSION_TWEAK
project(
  YAC
  VERSION "3.20.0.0"
  LANGUAGES C
)

set(YAC_CORE_VERSION_MAJOR 1)
set(YAC_CORE_VERSION_MINOR 2)
set(YAC_CORE_VERSION_PATCH 0)
set(YAC_CORE_VERSION_TWEAK 0)

# Set the version tweak suffix for the project
include(cmake/Scripts/Versioning.cmake)

include(GNUInstallDirs)

set(CMAKE_MODULE_PATH
    ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/Modules/"
)

option(BUILD_SHARED_LIBS "Build libraries as shared libraries" ON)

# Set install RPATH to allow installed libraries to find each other
if(BUILD_SHARED_LIBS)
  set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
endif()

option(YAC_ENABLE_MCI "Enable the Model Coupling Interface (MCI)" ON)
option(YAC_ENABLE_TOOLS "Enable the tools" ON)
option(YAC_ENABLE_FORTRAN "Enable Fortran bindings" ON)
option(YAC_ENABLE_PYTHON "Enable Python bindings" OFF)
option(
  YAC_ENABLE_EXAMPLES
  "Enable building the examples: Generates and builds example applications for demonstration."
  ON
)
option(
  YAC_ENABLE_EXAMPLES_TESTS
  "Enable testing the examples: Runs example application as part of the tests"
  OFF
)
option(
  YAC_ENABLE_NETCDF
  "Enable NetCDF support: Allows the use of NetCDF files. E.g. for reading grid files."
  ON
)
option(
  YAC_ENABLE_OPENMP
  "Enable OpenMP support: Enables thread-parallel execution using OpenMP directives."
  OFF
)
option(
  YAC_ENABLE_MPI_CHECKS
  "Enable checks for known MPI implementation bugs at configure time"
  ON
)

option(BUILD_TESTING "Build Testing" OFF)
set(YAC_INT
    "int"
    CACHE STRING "C-Type used for global ids"
)
set_property(CACHE YAC_INT PROPERTY STRINGS "int" "long" "long long")

set(YAC_LAPACK_INTERFACE
    "system"
    CACHE STRING "LAPACK provider."
)
set_property(
  CACHE YAC_LAPACK_INTERFACE PROPERTY STRINGS "bundled-clapack" "system"
)

set(YAC_EXTERNAL_MTIME
    OFF
    CACHE BOOL "calls find_package mtime instead of using internal mtime."
)

set(CMAKE_INSTALL_RPATH_USE_LINK_PATH
    ON
    CACHE BOOL "Add paths to linker search and installed rpath."
)

# Check options
if(YAC_ENABLE_TOOLS AND NOT YAC_ENABLE_MCI)
  message(
    FATAL_ERROR
      "TOOLS require the MCI. Either set YAC_ENABLE_MCI=ON or YAC_ENABLE_TOOLS=OFF"
  )
endif()

if(YAC_ENABLE_EXAMPLES AND NOT YAC_ENABLE_MCI)
  message(
    FATAL_ERROR
      "EXAMPLES require the MCI. Either set YAC_ENABLE_MCI=ON or YAC_ENABLE_EXAMPLES=OFF"
  )
endif()

if(YAC_ENABLE_EXAMPLES_TESTS AND NOT BUILD_TESTING)
  message(
    FATAL_ERROR
      "BUILD_TESTING required for YAC_ENABLE_EXAMPLES_TESTS. Either set BUILD_TESTING=ON or YAC_ENABLE_EXAMPLES_TESTS=OFF"
  )
endif()

if(YAC_ENABLE_EXAMPLES_TESTS AND NOT YAC_ENABLE_EXAMPLES)
  message(
    FATAL_ERROR
      "EXAMPLES required for running their tests. Either set YAC_ENABLE_EXAMPLES=ON or YAC_ENABLE_EXAMPLES_TESTS=OFF"
  )
endif()

if(YAC_ENABLE_OPENMP)
  message(
    WARNING
      "You enabled OpenMP. Use this feature with care. In particular you should set the number of threads to be used by OpenMP (OMP_NUM_THREADS)."
  )
endif()

if(YAC_ENABLE_FORTRAN)
  enable_language(Fortran)
  include(cmake/Scripts/FortranConfig.cmake)
  set(ENABLE_FORTRAN_BINDINGS_FALSE "#")
endif()

find_package(MPI REQUIRED)

# Check for known MPI bugs at configure time
if(YAC_ENABLE_MPI_CHECKS)
  add_subdirectory(config/checksrc)
endif()

if(YAC_ENABLE_FORTRAN)
  include(cmake/Scripts/DetectMPIFintKIND.cmake)
endif()

# always enabled:
add_compile_definitions("YAC_UTILS_ENABLED")

if(YAC_INT STREQUAL "int")
  set(YAC_INT_MPIDT "MPI_INT")
  add_compile_definitions(
    "YAC_INT_MAX=INT_MAX" "YAC_INT_MIN=INT_MIN" "YAC_INT_FMT=\"d\""
  )
  if(YAC_ENABLE_FORTRAN)
    add_compile_definitions("YAC_INT_FC_KIND=C_INT")
  endif()
elseif(YAC_INT STREQUAL "long")
  set(YAC_INT_MPIDT "MPI_LONG")
  add_compile_definitions(
    "YAC_INT_MAX=LONG_MAX" "YAC_INT_MIN=LONG_MIN" "YAC_INT_FMT=\"ld\""
  )
  if(YAC_ENABLE_FORTRAN)
    add_compile_definitions("YAC_INT_FC_KIND=C_LONG")
  endif()
elseif(YAC_INT STREQUAL "long long")
  set(YAC_INT_MPIDT "MPI_LONG_LONG")
  add_compile_definitions(
    "YAC_INT_MAX=LONG_LONG_MAX"
    "YAC_INT_MIN=LONG_LONG_MIN"
    "YAC_INT_FMT=\"lld\""
  )
  if(YAC_ENABLE_FORTRAN)
    add_compile_definitions("YAC_INT_FC_KIND=C_LONG_LONG")
  endif()
endif()

find_package(yaxt REQUIRED)

if(YAC_LAPACK_INTERFACE STREQUAL "system")
  if(CMAKE_Fortran_COMPILER_LOADED)
    include(FortranCInterface)
    FortranCInterface_HEADER(src/FC.h MACRO_NAMESPACE "YAC_FC_")
  else()
    file(
      WRITE ${CMAKE_BINARY_DIR}/src/FC.h
      "#ifndef YAC_FC_HEADER_INCLUDED
      #define YAC_FC_HEADER_INCLUDED

      // we assume default name mangling:
      #define YAC_FC_GLOBAL(name,NAME) name##_
      #endif"
    )
  endif()
  find_package(LAPACK REQUIRED)
  add_compile_definitions("YAC_LAPACK_INTERFACE_ID=5")
elseif(YAC_LAPACK_INTERFACE STREQUAL "bundled-clapack")
  add_compile_definitions("YAC_LAPACK_INTERFACE_ID=4")
endif()

if(YAC_ENABLE_OPENMP)
  find_package(OpenMP REQUIRED)
  add_compile_definitions("YAC_OPENMP_ENABLED")
  set(TEST_PTHREAD_FALSE "#")
endif()

if(YAC_ENABLE_NETCDF)
  find_package(NetCDF REQUIRED)
  add_compile_definitions("YAC_NETCDF_ENABLED")
  set(HAVE_NETCDF_FALSE "#")
endif()

configure_file(src/yac_config.h.cmake_in src/yac_config.h)
install(
  FILES ${CMAKE_BINARY_DIR}/src/yac_config.h
  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

# ##############################################################################

if(YAC_LAPACK_INTERFACE STREQUAL "bundled-clapack")
  add_subdirectory(clapack)
endif()

if(YAC_ENABLE_MCI)
  find_package(libfyaml REQUIRED)
  if(YAC_EXTERNAL_MTIME)
    find_package(mtime REQUIRED)
  else()
    add_subdirectory(mtime)
    add_library(mtime::mtime_c ALIAS yac_mtime)
  endif()
  set(ENABLE_MCI_FALSE "#")
endif()

add_subdirectory(src)

if(YAC_ENABLE_TOOLS)
  add_subdirectory(tools)
endif()

if(YAC_ENABLE_PYTHON)
  find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module NumPy)
  add_subdirectory(python)
  set(ENABLE_PYTHON_BINDINGS_FALSE "#")
  set(PYTHON ${Python3_EXECUTABLE})
endif()

if(BUILD_TESTING)
  # lots of the following stuff can be cleaned-up when we drop autotools
  find_program(SHELL sh)
  # PROJECT_VERSION is always the raw major.minor.patch.tweak number (e.g.
  # "3.18.0.0"), but the library itself reports the human-readable
  # YAC_VERSION_STRING (e.g. "3.18.0"), just like Autotools' PACKAGE_VERSION
  # does. Use the same value here so tests/mci's version test (which compares
  # against this substitution) matches what the compiled binary actually prints.
  set(PACKAGE_VERSION ${YAC_VERSION_STRING})
  set(MPI_LAUNCH ${MPIEXEC_EXECUTABLE})
  set(TEST_MPI_FALSE "#")
  set(ENABLE_UTILS_FALSE "#")
  # for compatibility of sh.in files with autotools
  set(top_srcdir ${PROJECT_SOURCE_DIR})
  set(top_builddir ${CMAKE_BINARY_DIR})
  set(abs_top_builddir "${CMAKE_BINARY_DIR}")
  set(abs_top_srcdir "${CMAKE_SOURCE_DIR}")
  include(CTest)
  enable_testing()
  add_subdirectory(grids)
  add_subdirectory(tests)
endif()

if(YAC_ENABLE_EXAMPLES)
  add_subdirectory(examples)
endif()

# ----------------------------------------------------------------------
# Installation
# ----------------------------------------------------------------------

include(CMakePackageConfigHelpers)

# Install libraries
install(
  TARGETS yac_core yac_pak yac_utils
  EXPORT yac_targets
  LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if(YAC_ENABLE_MCI)
  install(
    TARGETS yac_mci
    EXPORT yac_targets
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
  )
endif()

# Export targets
install(
  EXPORT yac_targets
  FILE yac-targets.cmake
  NAMESPACE YAC::
  DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/yac
)

export(
  EXPORT yac_targets
  NAMESPACE YAC::
  FILE "${PROJECT_BINARY_DIR}/yac-targets.cmake"
)

# Create and install package config files
configure_package_config_file(
  ${CMAKE_SOURCE_DIR}/cmake/yac-config.cmake.in
  ${CMAKE_BINARY_DIR}/yac-config.cmake
  INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/yac
)

write_basic_package_version_file(
  ${CMAKE_BINARY_DIR}/yac-config-version.cmake
  VERSION ${PROJECT_VERSION}
  COMPATIBILITY SameMajorVersion
)

install(
  FILES ${CMAKE_BINARY_DIR}/yac-config.cmake
        ${CMAKE_BINARY_DIR}/yac-config-version.cmake
  DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/yac
)

# Install CMake module files for downstream use
install(
  DIRECTORY ${CMAKE_SOURCE_DIR}/cmake/Modules/
  DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/yac
  FILES_MATCHING
  PATTERN "*.cmake"
)

# Copy find modules into build dir to make it usable by downstream proejcts
file(
  COPY cmake/Modules/Findyaxt.cmake
       cmake/Modules/FindNetCDF.cmake
       cmake/Modules/Findlibfyaml.cmake
  DESTINATION ${CMAKE_BINARY_DIR}
)
