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 -----------------
#------------------------------------------------------

# custom variable allowing to define version suffixes such as -rc*, -beta*, ...
set(PUGS_VERSION "0.4.1")

# deduce PUGS_SHORT_VERSION using regex
string(REGEX MATCH "^[0-9]+\.[0-9]+\.[0-9]+" PUGS_SHORT_VERSION ${PUGS_VERSION})
if("${PUGS_SHORT_VERSION}" STREQUAL "")
  message(FATAL_ERROR "Unable to compute short version from PUGS_VERSION=${PUGS_VERSION}")
endif()

# set project version as PUGS_SHORT_VERSION
project (Pugs VERSION ${PUGS_SHORT_VERSION})

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

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

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

set(PUGS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(PUGS_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")

# Change RelWithDebInfo to compile assertions
SET("CMAKE_CXX_FLAGS_RELWITHDEBINFO"
   "-g -O2"
  CACHE STRING "Flags used by the compiler during release builds with debug info and assertions"
  FORCE )
SET("CMAKE_C_FLAGS_RELWITHDEBINFO"
   "-g -O2"
  CACHE STRING "Flags used by the compiler during release builds with debug info and assertions"
  FORCE )

# Add new build types
set(CMAKE_CXX_FLAGS_COVERAGE
  "-g -O0 --coverage"
  CACHE STRING "Flags used by the C++ compiler during coverage builds."
  FORCE )
set(CMAKE_C_FLAGS_COVERAGE
  "-g -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 )

if(CMAKE_BUILD_TYPE)
  string(REGEX MATCH "(Debug|Release|RelWithDebInfo|MinSizeRel|Coverage)" VALID_BUILD_TYPE "${CMAKE_BUILD_TYPE}")
  if(NOT VALID_BUILD_TYPE)
    message(FATAL_ERROR "Invalid CMAKE_BUILD_TYPE: '${CMAKE_BUILD_TYPE}'")
  endif()
endif()

# 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()

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

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

# Pugs default compiler flags
set(PUGS_CXX_FLAGS "${PUGS_CXX_FLAGS} -Wall -Wextra")

if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GNU_CXX_MIN_VERSION}")
    message(FATAL_ERROR "Pugs 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 "Pugs build requires at least llvm/clang ++-${CLANG_CXX_MIN_VERSION}")
  endif()
  set(PUGS_CXX_FLAGS "${PUGS_CXX_FLAGS} -Wsign-compare -Wunused -Wunused-member-function -Wunused-private-field")
endif()

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

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

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


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

if(${MPI_FOUND})
  find_package(ParMETIS)
  if (NOT PARMETIS_LIBRARIES)
    if(PUGS_ENABLE_MPI MATCHES "^AUTO$")
      message(WARNING "MPI support deactivated: ParMETIS cannot be found!")
      unset(MPI_FOUND)
      unset(MPI_CXX_LINK_FLAGS)
      unset(MPI_CXX_LIBRARIES)
    else()
      message(FATAL_ERROR "MPI support requires ParMETIS which cannot be found!")
    endif()
  endif()
endif()

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

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

set(PUGS_HAS_MPI ${MPI_FOUND})

#------------------------------------------------------
# search for clang-format

find_program(CLANG_FORMAT clang-format)
if (CLANG_FORMAT)
  add_custom_target(clang-format
    COMMAND echo "running ${CLANG_FORMAT} ..."
    COMMAND ${CMAKE_COMMAND}
    -DPUGS_SOURCE_DIR="${PUGS_SOURCE_DIR}"
    -DCLANG_FORMAT="${CLANG_FORMAT}"
    -P ${PUGS_SOURCE_DIR}/cmake/ClangFormatProcess.cmake)
else ()
  message(WARNING "clang-format no found!")
endif()

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

# 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(${PUGS_SOURCE_DIR}/packages/kokkos)
include_directories(SYSTEM ${Kokkos_INCLUDE_DIRS_RET})
include(GetKokkosCompilerFlags)

# do not pollute compilation with Kokkos internal warnings
set_target_properties(kokkos PROPERTIES COMPILE_FLAGS "-w")

# 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(PUGS_CXX_FLAGS "${PUGS_CXX_FLAGS}  -std=gnu++1z")
  if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    set(PUGS_CXX_FLAGS "${PUGS_CXX_FLAGS}  -Wno-c++17-extensions")
  endif()
else()
  set(CMAKE_CXX_STANDARD "17")
endif()

# Compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PUGS_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 ${PUGS_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -D_GLIBCXX_DEBUG -D_LIBCPP_DEBUG=1 ${PUGS_CXX_FLAGS}")

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

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

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

# PGETL
include_directories(${PUGS_SOURCE_DIR}/packages/PEGTL/include/tao)

# Pugs src
add_subdirectory(src)

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

# Pugs generated sources
include_directories(${PUGS_BINARY_DIR}/src/utils)

# Pugs tests

set(CATCH_MODULE_PATH "${PUGS_SOURCE_DIR}/packages/Catch2")
include("${CATCH_MODULE_PATH}/contrib/ParseAndAddCatchTests.cmake")
add_subdirectory("${CATCH_MODULE_PATH}")

add_subdirectory(tests)
enable_testing()

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

# unit tests coverage

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

  if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    string(REGEX MATCH "^[0-9]+" GCC_VERSION
      "${CMAKE_CXX_COMPILER_VERSION}")
    find_program(GCOV_BIN NAMES gcov-${GCC_VERSION} gcov
      HINTS ${COMPILER_PATH})
  elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
    string(REGEX MATCH "^[0-9]+" LLVM_VERSION
      "${CMAKE_CXX_COMPILER_VERSION}")

    if(LLVM_VERSION VERSION_GREATER 7)
      find_program(LLVM_COV_BIN NAMES "llvm-cov-${LLVM_VERSION}"
        "llvm-cov" HINTS ${COMPILER_PATH})
      mark_as_advanced(LLVM_COV_BIN)

      if (LLVM_COV_BIN)
        file(MAKE_DIRECTORY "${PUGS_BINARY_DIR}/tools/tmp")

        file(WRITE "${PUGS_BINARY_DIR}/tools/tmp/llvm-cov.sh" "#! /bin/sh\n")
        file(APPEND "${PUGS_BINARY_DIR}/tools/tmp/llvm-cov.sh" "'${LLVM_COV_BIN}' gcov \"\$@\"\n")

        file(COPY "${PUGS_BINARY_DIR}/tools/tmp/llvm-cov.sh"
             DESTINATION "${PUGS_BINARY_DIR}/tools"
             FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)

        set(GCOV_BIN "${PUGS_BINARY_DIR}/tools/llvm-cov.sh")
      endif()
    endif()
  endif()

  if(NOT GCOV_BIN)
     message(FATAL_ERROR "Cannot find a proper gcov tool, cannot perform coverage.")
  endif()

  add_custom_target(coverage
    ALL # in coverage mode we do coverage!
    # Cleanup previously generated profiling data
    COMMAND ${LCOV} -q --gcov "${GCOV_BIN}" --base-directory "${PUGS_SOURCE_DIR}/src" --directory "${PUGS_BINARY_DIR}" --zerocounters
    # Initialize profiling data with zero coverage for every instrumented line of the project
    # This way the percentage of total lines covered will always be correct, even when not all source code files were loaded during the test(s)
    COMMAND ${LCOV} -q --gcov "${GCOV_BIN}" --base-directory "${PUGS_SOURCE_DIR}/src" --directory "${PUGS_BINARY_DIR}" --capture --initial --output-file coverage_base.info
    # Run tests
    COMMAND ${CMAKE_CTEST_COMMAND} -j ${PROCESSOR_COUNT}
    # Collect data from executions
    COMMAND ${LCOV} --gcov "${GCOV_BIN}" --base-directory "${PUGS_SOURCE_DIR}/src" --directory "${PUGS_BINARY_DIR}" --capture --output-file coverage_ctest.info
    # Combine base and ctest results
    COMMAND ${LCOV} --gcov "${GCOV_BIN}" -q --add-tracefile coverage_base.info --add-tracefile coverage_ctest.info --output-file coverage_full.info
    # Extract only project data (--no-capture or --remove options may be used to select collected data)
    COMMAND ${LCOV} --gcov "${GCOV_BIN}" -q --extract coverage_full.info "'${PUGS_SOURCE_DIR}/src/*'" --output-file coverage_extract.info

    # Remove unwanted stuff
    COMMAND ${LCOV} --gcov "${GCOV_BIN}" --remove coverage_extract.info  --output-file coverage.info
                                              '${PUGS_SOURCE_DIR}/src/main.cpp'
                                              '${PUGS_SOURCE_DIR}/utils/BacktraceManager.*'

    COMMAND ${LCOV} --gcov "${GCOV_BIN}" --list coverage.info

    DEPENDS unit_tests mpi_unit_tests
    COMMENT "Running test coverage."
    WORKING_DIRECTORY "${PUGS_BINARY_DIR}"
    )

  find_program(GENHTML genhtml)
  if(NOT GENHTML)
    message(WARNING "genhtml not found, cannot perform report-coverage.")
  else()
    add_custom_target(coverage-report
      ALL
      COMMAND ${CMAKE_COMMAND} -E remove_directory "${PUGS_BINARY_DIR}/coverage"
      COMMAND ${CMAKE_COMMAND} -E make_directory "${PUGS_BINARY_DIR}/coverage"
      COMMAND ${GENHTML} -q -o coverage -t "${CMAKE_PROJECT_NAME} test coverage" --ignore-errors source --legend --num-spaces 4 coverage.info
      DEPENDS coverage
      COMMENT "Building coverage html report."
      WORKING_DIRECTORY "${PUGS_BINARY_DIR}"
      )
  endif()
endif()

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

link_libraries("-rdynamic")

# ------------------- Source files --------------------
# Pugs binary
add_executable(
  pugs
  src/main.cpp)

# Libraries
target_link_libraries(
  pugs
  PugsMesh
  PugsUtils
  PugsLanguage
  kokkos
  ${PARMETIS_LIBRARIES}
  ${MPI_CXX_LINK_FLAGS} ${MPI_CXX_LIBRARIES}
)