cmake_minimum_required(VERSION 3.20)

# Allow dependencies with older CMake minimum versions
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)

if(APPLE
   AND DEFINED ENV{MACOSX_DEPLOYMENT_TARGET}
   AND NOT CMAKE_OSX_DEPLOYMENT_TARGET)
  set(CMAKE_OSX_DEPLOYMENT_TARGET
      "$ENV{MACOSX_DEPLOYMENT_TARGET}"
      CACHE STRING "Minimum macOS version" FORCE)
endif()

set(DFTRACER_UTILS_VERSION_MAJOR 0)
set(DFTRACER_UTILS_VERSION_MINOR 0)
set(DFTRACER_UTILS_VERSION_PATCH 9)
set(DFTRACER_UTILS_VERSION
    "${DFTRACER_UTILS_VERSION_MAJOR}.${DFTRACER_UTILS_VERSION_MINOR}.${DFTRACER_UTILS_VERSION_PATCH}"
)

# Use Ninja as default generator if available and not already specified
if(NOT DEFINED ENV{CMAKE_GENERATOR} AND NOT CMAKE_GENERATOR)
  find_program(NINJA_EXECUTABLE ninja)
  if(NINJA_EXECUTABLE)
    set(CMAKE_GENERATOR
        "Ninja"
        CACHE INTERNAL "Default generator" FORCE)
    message(STATUS "Using Ninja generator")
  endif()
endif()

project(
  dftracer_utils
  VERSION ${DFTRACER_UTILS_VERSION}
  LANGUAGES C CXX)

find_program(CCACHE_EXECUTABLE ccache)
if(CCACHE_EXECUTABLE)
  foreach(lang C CXX ASM)
    if(NOT CMAKE_${lang}_COMPILER_LAUNCHER)
      set(CMAKE_${lang}_COMPILER_LAUNCHER
          "${CCACHE_EXECUTABLE}"
          CACHE STRING "Compiler launcher for ${lang}" FORCE)
    endif()
  endforeach()
  message(STATUS "Using ccache: ${CCACHE_EXECUTABLE}")
endif()

set(DFTRACER_UTILS_PACKAGE ${PROJECT_NAME})
set(DFTRACER_UTILS_PACKAGE_NAME ${PROJECT_NAME})
set(DFTRACER_UTILS_PACKAGE_VERSION "${PROJECT_VERSION}")
set(DFTRACER_UTILS_PACKAGE_VERSION_MAJOR
    "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
set(DFTRACER_UTILS_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_PATCH}")
set(DFTRACER_UTILS_PACKAGE_STRING
    "${DFTRACER_UTILS_PACKAGE_NAME} ${DFTRACER_UTILS_PACKAGE_VERSION}")
set(DFTRACER_UTILS_PACKAGE_TARNAME "${DFTRACER_UTILS_PACKAGE}")
set(DEPENDENCY_LIBRARY_DIRS "")

# ##############################################################################
# Options
# ##############################################################################

option(DFTRACER_UTILS_TESTS "Build tests" OFF)
option(DFTRACER_UTILS_COVERAGE "Enable coverage reporting" OFF)
option(DFTRACER_UTILS_DEBUG "Enable debug mode including verbose logging" OFF)
option(DFTRACER_UTILS_BUILD_SHARED "Build shared library" ON)
option(DFTRACER_UTILS_BUILD_STATIC "Build static library" ON)
option(DFTRACER_UTILS_BUILD_BINARIES "Build command-line binaries" ON)
option(DFTRACER_UTILS_BUILD_PYTHON "Build Python bindings" OFF)
option(DFTRACER_UTILS_ENABLE_PCH "Enable precompiled headers" ON)
option(DFTRACER_UTILS_ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(DFTRACER_UTILS_ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)
option(DFTRACER_UTILS_ENABLE_TSAN "Enable ThreadSanitizer" OFF)
option(DFTRACER_UTILS_ENABLE_MPI "Enable MPI support for call tree" OFF)
option(DFTRACER_USE_ZLIB_NG "Use zlib-ng (compat) instead of madler/zlib; falls back to madler on failure" ON)
option(DFTRACER_UTILS_ENABLE_ARROW "Enable Arrow C Data Interface via nanoarrow" ON)
option(DFTRACER_UTILS_ENABLE_ARROW_IPC "Enable Arrow IPC file read/write via nanoarrow" ON)
option(DFTRACER_UTILS_ENABLE_ZSTD "Enable ZSTD compression for RocksDB" ON)
option(DFTRACER_UTILS_ENABLE_LZ4 "Enable LZ4 compression for RocksDB" OFF)
option(
  DFTRACER_UTILS_LOCAL_PACKAGES
  "Prefer system-installed bundled dependencies (RocksDB, zstd, simdjson, ...) when available; set OFF to build them from source for portable wheels"
  ON)

if(DFTRACER_UTILS_TESTS)
  message(STATUS "Building tests")
  set(DFTRACER_UTILS_TESTS
      ON
      CACHE BOOL "" FORCE)
  if(NOT DFTRACER_UTILS_ENABLE_TSAN)
    set(DFTRACER_UTILS_COVERAGE
        ON
        CACHE BOOL "" FORCE)
  else()
    set(DFTRACER_UTILS_COVERAGE
        OFF
        CACHE BOOL "" FORCE)
  endif()
  # set(DFTRACER_UTILS_DEBUG
  #     ON
  #     CACHE BOOL "" FORCE)
  set(DFTRACER_UTILS_BUILD_SHARED
      ON
      CACHE BOOL "" FORCE)
  set(DFTRACER_UTILS_BUILD_STATIC
      ON
      CACHE BOOL "" FORCE)
  set(DFTRACER_UTILS_BUILD_BINARIES
      ON
      CACHE BOOL "" FORCE)
  # set(DFTRACER_UTILS_ENABLE_ASAN ON CACHE BOOL "" FORCE)
  # set(DFTRACER_UTILS_ENABLE_UBSAN ON CACHE BOOL "" FORCE)
  # set(DFTRACER_UTILS_ENABLE_TSAN ON CACHE BOOL "" FORCE)
endif()

if(DFTRACER_UTILS_ENABLE_MPI)
  find_package(MPI REQUIRED)
endif()

# Validation: Ensure at least one library type is enabled
if(NOT DFTRACER_UTILS_BUILD_SHARED AND NOT DFTRACER_UTILS_BUILD_STATIC)
  message(
    FATAL_ERROR
      "At least one of DFTRACER_UTILS_BUILD_SHARED or DFTRACER_UTILS_BUILD_STATIC must be enabled"
  )
endif()

# io_uring: detect kernel header (no external library needed)
include(CheckIncludeFile)
include(CheckSymbolExists)
check_include_file("linux/io_uring.h" HAVE_LINUX_IO_URING_H)
if(HAVE_LINUX_IO_URING_H)
  check_symbol_exists(__NR_io_uring_setup "sys/syscall.h" HAVE_IO_URING_SYSCALL)
endif()
if(HAVE_LINUX_IO_URING_H AND HAVE_IO_URING_SYSCALL)
  set(DFTRACER_UTILS_HAVE_IO_URING ON)
  message(STATUS "io_uring support: enabled (kernel header found)")
else()
  set(DFTRACER_UTILS_HAVE_IO_URING OFF)
  message(STATUS "io_uring support: disabled (kernel header or syscall not found)")
endif()

# kqueue: detect availability (macOS, FreeBSD, etc.)
check_include_file("sys/event.h" HAVE_SYS_EVENT_H)
if(HAVE_SYS_EVENT_H)
  set(DFTRACER_UTILS_HAVE_KQUEUE ON)
  message(STATUS "kqueue support: enabled (sys/event.h found)")
else()
  set(DFTRACER_UTILS_HAVE_KQUEUE OFF)
  message(STATUS "kqueue support: disabled (sys/event.h not found)")
endif()

# lustreapi: optional for stripe_count / stripe_size queries. When absent, the
# parallel writer treats Lustre like an opaque POSIX filesystem.
check_include_file("lustre/lustreapi.h" HAVE_LUSTRE_LUSTREAPI_H)
find_library(LUSTREAPI_LIBRARY NAMES lustreapi)
if(HAVE_LUSTRE_LUSTREAPI_H AND LUSTREAPI_LIBRARY)
  set(DFTRACER_UTILS_HAVE_LUSTREAPI ON)
  message(STATUS "lustreapi support: enabled (${LUSTREAPI_LIBRARY})")
else()
  set(DFTRACER_UTILS_HAVE_LUSTREAPI OFF)
  message(STATUS "lustreapi support: disabled (header or library not found)")
endif()

# Set C++ standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Enable optimization

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# ##############################################################################
# Internal Paths for cmake libraries and Setup install and output Directories
# ##############################################################################

# This sets where to look for dependent libraries
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${CMAKE_BINARY_DIR}
                      ${CMAKE_INSTALL_PREFIX})
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/CompilerWarnings.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/LibraryHelpers.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/InstallHelpers.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/CPM.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/Dependencies.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/Utils.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/Rpath.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/PythonHelpers.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/Sanitizer.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/PrecompiledHeader.cmake)

configure_sanitizers()
print_sanitizer_status()

if(SKBUILD)
  set(CMAKE_INSTALL_LIBDIR
      "dftracer/lib"
      CACHE STRING "Library install directory" FORCE)
  set(CMAKE_INSTALL_BINDIR
      # "${SKBUILD_SCRIPTS_DIR}"
      "dftracer/bin"
      CACHE STRING "Binary install directory" FORCE)
  set(CMAKE_INSTALL_VENV_BIN_DIR
      "${SKBUILD_SCRIPTS_DIR}"
      CACHE STRING "Virtualenv build directory" FORCE)
  set(CMAKE_INSTALL_INCLUDEDIR
      "dftracer/include"
      CACHE STRING "Include install directory" FORCE)
endif()

if(DFTRACER_UTILS_DEBUG)
  configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/configure_files/dftracer_utils_config.dbg.h.in"
    "${CMAKE_BINARY_DIR}/include/dftracer/utils/core/common/config.h"
    @ONLY)
else()
  configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/configure_files/dftracer_utils_config.h.in"
    "${CMAKE_BINARY_DIR}/include/dftracer/utils/core/common/config.h"
    @ONLY)
endif()

set_coverage_compiler_flags(DFTRACER_UTILS_TESTS AND DFTRACER_UTILS_COVERAGE)

check_std_filesystem()
add_subdirectory(src)

if(DFTRACER_UTILS_TESTS)
  enable_testing()
  add_subdirectory(tests)
endif()
