# cmake file to build the project and tests
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
include(CheckLanguage)
include(CheckCXXCompilerFlag)
# ----------------------------------------------------------------------------------------
#                              ===== Project Setup =====
project(SCAMPmain LANGUAGES CXX)
set(VERSION_MAJOR 1)
set(VERSION_MINOR 1)

set(CUDA_MINIMUM_VERSION "9.0")

# Release build by default
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release)
endif()

# Thread libraries
find_package(Threads REQUIRED)

if(FORCE_NO_CUDA)
  unset(CMAKE_CUDA_COMPILER)
else()
  # Use cuda if available
  check_language(CUDA)
  if(CMAKE_CUDA_COMPILER)
    enable_language(CUDA)
    # For CUFFT libraries
    find_package(CUDA ${CMAKE_CUDA_VERSION} REQUIRED)
  endif()
endif()

# Do not use an unsupported cuda version
if (CMAKE_CUDA_COMPILER)
  if (CUDA_VERSION VERSION_LESS ${CUDA_MINIMUM_VERSION})
    if(FORCE_CUDA)
      message(FATAL_ERROR "CUDA version ${CUDA_VERSION} is less than the mininmum required version ${CUDA_MINIMUM_VERSION} defaulting to no-cuda build")
    else()
      message(WARNING "CUDA version ${CUDA_VERSION} is less than the mininmum required version ${CUDA_MINIMUM_VERSION} defaulting to no-cuda build")
      unset(CMAKE_CUDA_COMPILER)
    endif()
  endif()
endif()

if(NOT CMAKE_CUDA_COMPILER)
  if(FORCE_CUDA)
    message(FATAL_ERROR "No CUDA compiler found, cannot proceed to build CUDA binary")
  else()
    message(STATUS "No CUDA compiler found, building SCAMP without CUDA.")
  endif()
else()
  message(STATUS "CUDA compiler found: ${CMAKE_CUDA_COMPILER}")
  message(STATUS "Using cufft libraries: ${CUDA_CUFFT_LIBRARIES}")
endif()

# Use clang tidy if available
find_program(
  CLANG_TIDY_EXE
  NAMES clang-tidy
  DOC "Path to clang-tidy executable (v5+)"
  )
if(NOT CLANG_TIDY_EXE)
  message(STATUS "clang-tidy not found.")
else()
  message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}")
  set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" "-checks=*,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-fuchsia-default-arguments,-hicpp-vararg,-cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-bounds-constant-array-index,-google-runtime-int")
endif()

# Use clang format if available
find_program(
  CLANG_FORMAT_EXE
  NAMES clang-format-6.0 clang-format-5.0 clang-format
  DOC "Path to clang-format executable (v5+)"
  )
if(NOT CLANG_FORMAT_EXE)
  message(STATUS "clang-format not found.")
else()
  message(STATUS "clang-format found: ${CLANG_FORMAT_EXE}")
  set(DO_CLANG_FORMAT "${CLANG_FORMAT}" "-i -style=file")
endif()

set(
  PROJECT_SOURCE_FILES
  src/core/*.cpp
  src/core/*.cu
  src/core/*.h
  src/common/*.cpp
  src/common/*.h
  src/distributed/*.cc
  src/distributed/*.cpp
  src/distributed/*.h
  src/python/*.cpp
  )

# ----------------------------------------------------------------------------------------
#                         ===== Compiler Configuration =====

set(CMAKE_CXX_STANDARD 17)

# CUDA_CONFIG
if (CMAKE_CUDA_COMPILER)
  set(CMAKE_CUDA_STANDARD 14)
  include_directories(${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
  set(CUDA_SEPARABLE_COMPILATION ON)
  set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler=-fPIC -lineinfo")
  if (CUDA_VERSION VERSION_GREATER_EQUAL "11.0")
    set(CUDA_GENCODE_FLAGS "${CUDA_GENCODE_FLAGS} -gencode arch=compute_80,code=sm_80")
  endif()
  if (CUDA_VERSION VERSION_GREATER_EQUAL "10.0")
    set(CUDA_GENCODE_FLAGS "${CUDA_GENCODE_FLAGS} -gencode arch=compute_75,code=sm_75")
  endif()
  set(CUDA_GENCODE_FLAGS "${CUDA_GENCODE_FLAGS} -gencode arch=compute_60,code=sm_70")
  set(CUDA_GENCODE_FLAGS "${CUDA_GENCODE_FLAGS} -gencode arch=compute_61,code=sm_61")
  set(CUDA_GENCODE_FLAGS "${CUDA_GENCODE_FLAGS} -gencode arch=compute_60,code=sm_60")
  set(CUDA_GENCODE_FLAGS "${CUDA_GENCODE_FLAGS} -gencode arch=compute_50,code=sm_50")
  if (CUDA_VERSION VERSION_LESS "11.0")
    set(CUDA_GENCODE_FLAGS "${CUDA_GENCODE_FLAGS} -gencode arch=compute_37,code=sm_37")
    set(CUDA_GENCODE_FLAGS "${CUDA_GENCODE_FLAGS} -gencode arch=compute_35,code=sm_35")
    set(CUDA_GENCODE_FLAGS "${CUDA_GENCODE_FLAGS} -gencode arch=compute_30,code=sm_30")
  endif()
endif()

CHECK_CXX_COMPILER_FLAG("-fPIC" COMPILER_OPT_PIC_SUPPORTED)
CHECK_CXX_COMPILER_FLAG("-march=native" COMPILER_OPT_ARCH_NATIVE_SUPPORTED)
CHECK_CXX_COMPILER_FLAG("-O3" COMPILER_OPT_O3_SUPPORTED)
CHECK_CXX_COMPILER_FLAG("-funroll-loops" COMPILER_OPT_UNROLL_LOOPS_SUPPORTED)
CHECK_CXX_COMPILER_FLAG("-ffp-contract=fast" COMPILER_OPT_FPCONTRACT_FAST_SUPPORTED)
CHECK_CXX_COMPILER_FLAG("-Wall" COMPILER_OPT_WARN_ALL_SUPPORTED)
CHECK_CXX_COMPILER_FLAG("-Wno-sign-compare" COMPILER_OPT_NO_WARN_SIGN_COMPARE_SUPPORTED)

if (COMPILER_OPT_PIC_SUPPORTED)
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fPIC")
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fPIC")
endif()

if (COMPILER_OPT_ARCH_NATIVE_SUPPORTED)
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=native")
endif()

if (COMPILER_OPT_O3_SUPPORTED)
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
endif()
  
if (COMPILER_OPT_UNROLL_LOOPS_SUPPORTED)
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -funroll-loops")
endif()

if (COMPILER_OPT_FPCONTRACT_FAST_SUPPORTED)
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffp-contract=fast")
endif()

if (COMPILER_OPT_WARN_ALL_SUPPORTED)
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wall")
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall")
endif()

if (COMPILER_OPT_NO_WARN_SIGN_COMPARE_SUPPORTED)
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-sign-compare")
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wno-sign-compare")
endif()

CHECK_CXX_COMPILER_FLAG("-fsanitize=address" COMPILER_OPT_SANITIZE_ADDRESS_SUPPORTED)
CHECK_CXX_COMPILER_FLAG("-fno-omit-frame-pointer" COMPILER_OPT_NO_OMIT_FP_SUPPORTED)

if (COMPILER_OPT_SANITIZE_ADDRESS_SUPPORTED)
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address")
endif()

if (COMPILER_OPT_NO_OMIT_FP_SUPPORTED)
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer")
endif()


# ----------------------------------------------------------------------------------------
#                              ===== Build targets =====

include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/")

# Build pybind11 if needed
if (BUILD_PYTHON_MODULE)
  find_package(PythonLibs REQUIRED)
  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/third_party/pybind11)
endif()

# Add common targets.
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/common)

# Add core functional libraries.
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/core)

# Python module
if (BUILD_PYTHON_MODULE)
  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/python)
endif()

if (BUILD_BENCHMARKS)
  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/benchmark)
endif()


if (BUILD_CLIENT_SERVER)
  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/third_party/grpc EXCLUDE_FROM_ALL)
  message(STATUS "Using gRPC via add_subdirectory.")
  
  # After using add_subdirectory, we can now use the grpc targets directly from
  # this build.
  set(_PROTOBUF_LIBPROTOBUF libprotobuf)
  set(_PROTOBUF_PROTOC $<TARGET_FILE:protoc>)
  set(_GRPC_GRPCPP_UNSECURE grpc++_unsecure)
  set(_GRPC_GRPCPP grpc++)
  set(_GRPC_GRPC grpc)
  set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:grpc_cpp_plugin>)

  # Add include directories we will use later
  include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third_party/grpc/include/) 
  include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third_party/grpc/third_party/gflags/include/)
  include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third_party/grpc/third_party/protobuf/src/)
  include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third_party/grpc/third_party/abseil-cpp/)

  # Build scamp client and scamp server
  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/distributed)
endif()

if(NOT TARGET gflags)
  add_subdirectory(third_party/gflags)
  include_directories(third_party/gflags/include/)
endif()

# C++/CUDA executable
add_executable(SCAMP ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp)
if (CMAKE_CUDA_COMPILER)
  target_compile_definitions(SCAMP PUBLIC -D_HAS_CUDA_)
  target_link_libraries(SCAMP ${CUDA_cudart_LIBRARY} gflags common scamp_interface scamp_utils)
else()
  target_link_libraries(SCAMP gflags common scamp_interface scamp_utils)
endif() 

# clang-format target
function(prepend var prefix)
  set(listVar "")

  foreach(f ${ARGN})
    list(APPEND listVar "${prefix}/${f}")
  endforeach()

  set(${var} "${listVar}" PARENT_SCOPE)
endfunction()

if(CLANG_FORMAT_EXE)
  prepend(FILES_TO_FORMAT ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_FILES})

  add_custom_target(
    clang-format
    COMMAND ${CLANG_FORMAT_EXE} -i -style=file ${FILES_TO_FORMAT}
  )
endif()
