# Rapid Optimization Library

if( COMMAND TRIBITS_PACKAGE )
  set( STANDALONE_ROL FALSE )
else()
  set( STANDALONE_ROL TRUE )
endif()

if( STANDALONE_ROL )
  cmake_minimum_required(VERSION 3.23)

  set(CMAKE_POSITION_INDEPENDENT_CODE ON)

  project( ROL
           VERSION 2.0
           DESCRIPTION "Rapid Optimization Library"
           LANGUAGES CXX )
  set(CMAKE_CXX_STANDARD 17)
  set(CMAKE_CXX_STANDARD_REQUIRED ON)
  set(CMAKE_CXX_EXTENSIONS OFF)
  set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})

  include(GNUInstallDirs)
  include(CMakePackageConfigHelpers)

  find_package(BLAS REQUIRED)
  find_package(LAPACK REQUIRED)

  # ---- Optional real MPI for the PinT validation target (OFF => serial, unchanged) ----
  option(ENABLE_MPI "Enable real MPI (native Teuchos-free ROL::GlobalMPISession + PinT)" OFF)
  if(ENABLE_MPI)
    find_package(MPI REQUIRED COMPONENTS CXX)
    message(STATUS "ROL: MPI enabled (CXX compiler: ${MPI_CXX_COMPILER})")
    # adapters/mpi is header-only; expose its include dirs for the example target.
    set(ROL_MPI_ADAPTER_INCLUDES
        ${PROJECT_SOURCE_DIR}/adapters/mpi/src/vector
        ${PROJECT_SOURCE_DIR}/adapters/mpi/src/function)
  endif()

  if(NOT CMAKE_BUILD_TYPE)
      set(CMAKE_BUILD_TYPE RELEASE CACHE STRING "Build type" FORCE)
  endif()

  # Set F77_BLAS_MANGLE macro based on Fortran-C interface (similar to Kokkos Kernels)
  if("${F77_BLAS_MANGLE}" STREQUAL "")
    enable_language(C)
    enable_language(Fortran)
    include(FortranCInterface)
    FortranCInterface_HEADER(${CMAKE_CURRENT_BINARY_DIR}/FortranCInterface.h MACRO_NAMESPACE "F77_")
    if(FortranCInterface_GLOBAL_SUFFIX STREQUAL "")
      set(F77_BLAS_MANGLE "(name,NAME) ${FortranCInterface_GLOBAL_PREFIX}name")
    else()
      set(F77_BLAS_MANGLE "(name,NAME) ${FortranCInterface_GLOBAL_PREFIX}name ## ${FortranCInterface_GLOBAL_SUFFIX}")
    endif()
    message(STATUS "Detected Fortran name mangling: ${F77_BLAS_MANGLE}")
  endif()

  # Add pugixml for XML parameter file support.
  # Prefer an installed CMake package so installed ROLConfig.cmake can
  # reproduce the same public dependency for downstream consumers.  Fall back
  # to FetchContent for standalone source/CI builds where pugixml is not
  # preinstalled.
  include(FetchContent)
  find_package(pugixml CONFIG QUIET)

  if(NOT pugixml_FOUND)
    FetchContent_Declare(
      pugixml
      GIT_REPOSITORY https://github.com/zeux/pugixml.git
      GIT_TAG        v1.14
    )
    FetchContent_MakeAvailable(pugixml)
  endif()

  if(TARGET pugixml::pugixml)
    set(ROL_PUGIXML_TARGET pugixml::pugixml)
  elseif(TARGET pugixml)
    set(ROL_PUGIXML_TARGET pugixml)
  elseif(TARGET pugixml-static)
    set(ROL_PUGIXML_TARGET pugixml-static)
  elseif(TARGET pugixml::static)
    set(ROL_PUGIXML_TARGET pugixml::static)
  else()
    message(FATAL_ERROR "Could not find or create a pugixml CMake target")
  endif()

  include(ROLUtils)
  
  option(ROL_ENABLE_TEUCHOS_LITE_OBJECTS
    "Compile Teuchos-lite object implementations into standalone ROL"
    ON)

  # Configure the config header
  configure_file("${PROJECT_SOURCE_DIR}/cmake/ROL_config.h.in" ROL_config.h @ONLY)

  add_library(rol)

  set(SRC ${PROJECT_SOURCE_DIR}/src)

  # Set up compatibility includes (specific directories to avoid conflicts).
  # real/mpi and simple/mpi BOTH define guard ROL_GlobalMPISession_HPP, so exactly ONE
  # of them may be on the include path. ENABLE_MPI selects which.
  if(ENABLE_MPI)
    set(ROL_MPI_COMPAT_DIR ${SRC}/compatibility/real/mpi)
  else()
    set(ROL_MPI_COMPAT_DIR ${SRC}/compatibility/simple/mpi)
  endif()
  set(ROL_COMPATIBILITY_INCLUDES ${SRC}/compatibility/teuchos/blas
                                 ${SRC}/compatibility/teuchos/la
                                 ${SRC}/compatibility/simple/lapack
                                 ${ROL_MPI_COMPAT_DIR}
                                 ${SRC}/compatibility/simple/parameterlist
                                 ${SRC}/compatibility/std/shared_ptr
                                 ${SRC}/compatibility/teuchos/stacktrace
                                 ${SRC}/compatibility/teuchos-lite)

  if(ROL_ENABLE_TEUCHOS_LITE_OBJECTS)
    add_library(Teuchos_BLAS OBJECT ${SRC}/compatibility/teuchos-lite/Teuchos_BLAS.cpp)
    add_library(Teuchos_CompObject OBJECT ${SRC}/compatibility/teuchos-lite/Teuchos_CompObject.cpp)
    add_library(Teuchos_TestForException OBJECT ${SRC}/compatibility/teuchos-lite/Teuchos_TestForException.cpp)
  endif()

  # Use GLOB_RECURSE to find all directories in core components
  set(ROL_CORE_DIRS ${SRC}/algorithm
                    ${SRC}/elementwise
                    ${SRC}/function
                    ${SRC}/mol
                    ${SRC}/oed
                    ${SRC}/sol
                    ${SRC}/status
                    ${SRC}/step
                    ${SRC}/utils
                    ${SRC}/vector
                    ${SRC}/zoo)

  set(ROL_CORE_INCLUDES)
  foreach(core_dir ${ROL_CORE_DIRS})
    file(GLOB_RECURSE subdirs LIST_DIRECTORIES true "${core_dir}/*")
    list(APPEND ROL_CORE_INCLUDES ${core_dir})
    foreach(subdir ${subdirs})
      if(IS_DIRECTORY ${subdir})
        list(APPEND ROL_CORE_INCLUDES ${subdir})
      endif()
    endforeach()
  endforeach()

  set(ROL_BUILD_INTERFACE_INCLUDES
      ${CMAKE_CURRENT_BINARY_DIR}
      ${ROL_COMPATIBILITY_INCLUDES}
      ${ROL_CORE_INCLUDES})

  set(ROL_BUILD_INTERFACE_INCLUDE_GENERATOR_EXPRESSIONS)
  foreach(dir IN LISTS ROL_BUILD_INTERFACE_INCLUDES)
    list(APPEND ROL_BUILD_INTERFACE_INCLUDE_GENERATOR_EXPRESSIONS "$<BUILD_INTERFACE:${dir}>")
  endforeach()

  target_include_directories(rol
    PUBLIC
      ${ROL_BUILD_INTERFACE_INCLUDE_GENERATOR_EXPRESSIONS}
      $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
  )

  if(ROL_ENABLE_TEUCHOS_LITE_OBJECTS)
    target_sources(rol
      PRIVATE
        $<TARGET_OBJECTS:Teuchos_BLAS>
        $<TARGET_OBJECTS:Teuchos_CompObject>
        $<TARGET_OBJECTS:Teuchos_TestForException>
    )
  else()
    # guard against risk that CMake will complain about not having any compiled sources:
    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/rol_empty.cpp"
      "namespace { const int rol_empty_translation_unit = 0; }
")
    target_sources(rol PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/rol_empty.cpp")
  endif()

  target_link_libraries(rol PUBLIC ${BLAS_LIBRARIES} ${LAPACK_LIBRARIES}
                                    ${ROL_PUGIXML_TARGET})

  add_library(rol::rol ALIAS rol)
  add_library(ROL::rol ALIAS rol)

  option(ENABLE_TESTS OFF)
  option(ENABLE_EXAMPLES OFF)

  if(ENABLE_TESTS)
    enable_testing()
    add_subdirectory(test)
  endif()

  if(ENABLE_EXAMPLES)
    add_subdirectory(example)
  endif()
  
  # Collect headers recursively from all roots
  # If something doesn't show up, provide a change here
  set(PROJECT_HEADERS)
  foreach(dir IN LISTS ROL_CORE_DIRS)
      file(GLOB_RECURSE headers_in_dir
          CONFIGURE_DEPENDS
          "${dir}/*.h"
          "${dir}/*.hpp"
          "${dir}/*.hh"
          "${dir}/*.hxx"
      )
      list(APPEND PROJECT_HEADERS ${headers_in_dir})
  endforeach()
  foreach(dir IN LISTS ROL_COMPATIBILITY_INCLUDES)
      file(GLOB_RECURSE headers_in_dir
          CONFIGURE_DEPENDS
          "${dir}/*.h"
          "${dir}/*.hpp"
          "${dir}/*.hh"
          "${dir}/*.hxx"
      )
      list(APPEND PROJECT_HEADERS ${headers_in_dir})
  endforeach()

  # Remove duplicates in case patterns overlap
  list(REMOVE_DUPLICATES PROJECT_HEADERS)
  
  # Check for basename collisions when flattening
  set(_seen_names)
  set(_duplicate_names)

  foreach(hdr IN LISTS PROJECT_HEADERS)
      get_filename_component(hdr_name "${hdr}" NAME)
      list(FIND _seen_names "${hdr_name}" _idx)
      if(_idx GREATER -1)
          list(APPEND _duplicate_names "${hdr_name}")
      else()
          list(APPEND _seen_names "${hdr_name}")
      endif()
  endforeach()
  
  install(
    TARGETS rol
    EXPORT ROLTargets
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  )

  install(
    EXPORT ROLTargets
    FILE ROLTargets.cmake
    NAMESPACE ROL::
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ROL
  )

  configure_package_config_file(
    "${PROJECT_SOURCE_DIR}/cmake/ROLConfig.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/ROLConfig.cmake"
    INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ROL
    PATH_VARS CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_LIBDIR
  )

  write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/ROLConfigVersion.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion
  )

  install(
    FILES
      "${CMAKE_CURRENT_BINARY_DIR}/ROLConfig.cmake"
      "${CMAKE_CURRENT_BINARY_DIR}/ROLConfigVersion.cmake"
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ROL
  )

  # Install all headers into one flat include directory
  foreach(hdr IN LISTS PROJECT_HEADERS)
      install(FILES "${hdr}" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
  endforeach()
  install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ROL_config.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
else() # Build with Trilinos
  include(cmake/TrilinosROL.cmake)
endif()
