cmake_minimum_required (VERSION 3.4)

# CMake utils
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

# Forbids in-source builds
include(CheckNotInSources)

#------------------------------------------------------
#----------------- Main configuration -----------------
#------------------------------------------------------

project (Pastis
  VERSION 0.3.0)

#------------------------------------------------------

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

#------------------------------------------------------

set(PASTIS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(PASTIS_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")

# Add new build types
message("* Adding build types...")
SET(CMAKE_CXX_FLAGS_COVERAGE
  "-g -Wall -O0 --coverage"
  CACHE STRING "Flags used by the C++ compiler during coverage builds."
  FORCE )
SET(CMAKE_C_FLAGS_COVERAGE
  "-g -Wall -O0 --coverage"
  CACHE STRING "Flags used by the C compiler during coverage builds."
  FORCE )
SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE
  "--coverage"
  CACHE STRING "Flags used for linking binaries during coverage builds."
  FORCE )
SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
  "--coverage"
  CACHE STRING "Flags used by the shared libraries linker during coverage builds."
  FORCE )
MARK_AS_ADVANCED(
  CMAKE_CXX_FLAGS_COVERAGE
  CMAKE_C_FLAGS_COVERAGE
  CMAKE_EXE_LINKER_FLAGS_COVERAGE
  CMAKE_SHARED_LINKER_FLAGS_COVERAGE )

# Default build type is RelWIthDebInfo
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
      "Choose the type of build: Debug Release RelWithDebInfo MinSizeRel Coverage."
      FORCE)
endif(NOT CMAKE_BUILD_TYPE)

#------------------------------------------------------

# Checks if compiler version is compatible with Pastis sources
set(GNU_CXX_MIN_VERSION "7.0.0")
set(CLANG_CXX_MIN_VERSION "5.0.0")

# Pastis default compiler flags
set(PASTIS_CXX_FLAGS "${PASTIS_CXX_FLAGS} -Wall")

if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GNU_CXX_MIN_VERSION}")
    message(FATAL_ERROR "Pastis build requires at least g++-${GNU_CXX_MIN_VERSION}")
  endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
  if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${CLANG_CXX_MIN_VERSION}")
    message(FATAL_ERROR "Pastis build requires at least llvm/clang ++-${CLANG_CXX_MIN_VERSION}")
  endif()
  set(PASTIS_CXX_FLAGS "${PASTIS_CXX_FLAGS} -Wsign-compare -Wunused -Wunused-member-function -Wunused-private-field")
endif()

#------------------------------------------------------
# defaults use of MPI
set(PASTIS_ENABLE_MPI AUTO CACHE STRING
  "Choose one of: AUTO ON OFF")

if (NOT PASTIS_ENABLE_MPI MATCHES "^(AUTO|ON|OFF)$")
  message(FATAL_ERROR "PASTIS_ENABLE_MPI='${PASTIS_ENABLE_MPI}'. Must be set to one of AUTO, ON or OFF")
endif()

# checks for MPI
if (PASTIS_ENABLE_MPI MATCHES "^(AUTO|ON)$")
  find_package(MPI)
endif()

if (${MPI_FOUND})
  set(PASTIS_CXX_FLAGS "${PASTIS_CXX_FLAGS} ${MPI_CXX_COMPILER_FLAGS}")
  include_directories(${MPI_CXX_INCLUDE_DIRS})
elseif(PASTIS_ENABLE_MPI STREQUAL "ON")
  message(FATAL_ERROR "Could not find MPI library while requested")
endif()

set(PASTIS_HAS_MPI ${MPI_FOUND})

#------------------------------------------------------

# setting Kokkos defaults to OpenMP when available
include(FindOpenMP)
if(${OPENMP_FOUND})
  set(KOKKOS_ENABLE_OPENMP ON CACHE BOOL "")
endif()

# do not build ETI by default
set(KOKKOS_ENABLE_EXPLICIT_INSTANTIATION OFF CACHE BOOL "")

# do not use Kokkos deprecated code
set(KOKKOS_ENABLE_DEPRECATED_CODE OFF CACHE BOOL "")

# Kokkos compiler flags
add_subdirectory(${PASTIS_SOURCE_DIR}/packages/kokkos)
include_directories(${Kokkos_INCLUDE_DIRS_RET})
include(GetKokkosCompilerFlags)

# sets Kokkos debug flags when non release build
if (CMAKE_BUILD_TYPE MATCHES "^Release$")
  set (KOKKOS_ENABLE_DEBUG OFF)
else()
  set (KOKKOS_ENABLE_DEBUG ON)
endif()

# C++ 17 flags
if(${CMAKE_VERSION} VERSION_LESS "3.8.0")
  message(WARNING "Please consider to switch to CMake >= 3.8")
  set(PASTIS_CXX_FLAGS "${PASTIS_CXX_FLAGS}  -std=gnu++1z")
  if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    set(PASTIS_CXX_FLAGS "${PASTIS_CXX_FLAGS}  -Wno-c++17-extensions")
  endif()
else()
  set(CMAKE_CXX_STANDARD "17")
endif()

# Compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PASTIS_CXX_FLAGS}")

# Add debug mode for Standard C++ library
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG -D_LIBCPP_DEBUG=1 ${PASTIS_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -D_GLIBCXX_DEBUG -D_LIBCPP_DEBUG=1 ${PASTIS_CXX_FLAGS}")

#------------------------------------------------------

# Rang (colors? Useless thus necessary!)
include_directories(${PASTIS_SOURCE_DIR}/packages/rang/include)

# CLI11
include_directories(${PASTIS_SOURCE_DIR}/packages/CLI11/include)

# Pastis src
add_subdirectory(src)

include_directories(src)
include_directories(src/algebra)
include_directories(src/mesh)
include_directories(src/output)
include_directories(src/utils)
include_directories(src/scheme)

# Pastis tests

set(CATCH_MODULE_PATH "${PASTIS_SOURCE_DIR}/packages/Catch2")
set(CATCH_INCLUDE_PATH "${CATCH_MODULE_PATH}/single_include")

include("${CATCH_MODULE_PATH}/contrib/ParseAndAddCatchTests.cmake")
add_subdirectory("${CATCH_MODULE_PATH}")

add_subdirectory(tests)
enable_testing()

# unit tests coverage

if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
  find_program(GCOVR gcovr)
  if(NOT GCOVR)
    message(FATAL_ERROR "gcovr not found, cannot perform coverage.")
  endif()

  if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    string(REPLACE "clang++" "llvm-cov" LLVM_COV "${CMAKE_CXX_COMPILER}")
    if(NOT EXISTS "${LLVM_COV}")
      message(FATAL_ERROR "could not find ${LLVM_COV}, cannot perform coverage (using g++ for coverage is recommended).")
    endif()

    set(GCOVR_EXTRA "--gcov-executable=${LLVM_COV} gcov")
  endif()

  set (GCOVR_EXCLUDE
    -e "${PASTIS_SOURCE_DIR}/src/main.cpp"
    -e "${PASTIS_SOURCE_DIR}/src/utils/BacktraceManager.cpp"
    -e "${PASTIS_SOURCE_DIR}/src/utils/BacktraceManager.hpp"
    )

  set(GCOVR_OPTIONS --object-directory="${PASTIS_BINARY_DIR}" -r "${PASTIS_SOURCE_DIR}/src" ${GCOVR_EXCLUDE} ${GCOVR_EXTRA})

  add_custom_target(run_unit_tests
    ALL
    COMMAND ${CMAKE_CTEST_COMMAND} -j ${PROCESSOR_COUNT}
    DEPENDS unit_tests pastis
    COMMENT "Executing unit tests."
    )

  add_custom_target(coverage
    ALL
    COMMAND ${GCOVR} ${GCOVR_OPTIONS}
    DEPENDS run_unit_tests
    COMMENT "Running gcovr to build coverage report."
    )

  add_custom_target(coverage-report
    ALL
    COMMAND ${CMAKE_COMMAND} -E remove_directory "${PASTIS_BINARY_DIR}/coverage"
    COMMAND ${CMAKE_COMMAND} -E make_directory "${PASTIS_BINARY_DIR}/coverage"
    COMMAND ${GCOVR} ${GCOVR_OPTIONS} --delete --html --html-details -o "${PASTIS_BINARY_DIR}/coverage/index.html"
    DEPENDS coverage
    COMMENT "Building coverage html report."
    WORKING_DIRECTORY "${PASTIS_BINARY_DIR}"
    )

endif()

#------------------------------------------------------
# Search for ParMETIS

if(${MPI_FOUND})
  find_path(PARMETIS_INCLUDE_DIR parmetis.h
    PATH_SUFFIX include parmetis
    NO_DEFAULT_PATH
    DOC "Custom include directory for parmetis.h")
  find_path(PARMETIS_INCLUDE_DIR parmetis.h
    PATH_SUFFIX include parmetis)

  if (EXISTS "${PARMETIS_INCLUDE_DIR}/parmetis.h")
    message("-- Found parmetis.h in ${PARMETIS_INCLUDE_DIR}")
    find_library(LIB_PARMETIS parmetis)
    if("${LIB_PARMETIS}" STREQUAL "LIB_PARMETIS-NOTFOUND")
      message(FATAL_ERROR "Could not find parmetis library")
    endif()
    find_library(LIB_METIS metis)
    if("${LIB_PARMETIS}" STREQUAL "LIB_METIS-NOTFOUND")
      message(FATAL_ERROR "Could not find metis library")
    endif()
    set(PARMETIS_LIBRARIES ${LIB_PARMETIS} ${LIB_METIS})
    message("-- Found parmetis/metis libraries ${PARMETIS_LIBRARIES}")
  else()
    message(FATAL_ERROR "Could not find parmetis.h")
  endif()
endif()

# -----------------------------------------------------

link_libraries("-rdynamic")

# ------------------- Source files --------------------
# Pastis binary
add_executable(
  pastis
  src/main.cpp)

# Libraries
target_link_libraries(
  pastis
  kokkos
  ${PARMETIS_LIBRARIES}
  ${MPI_CXX_LINK_FLAGS} ${MPI_CXX_LIBRARIES}
  PastisUtils
  PastisMesh)