#
#  Copyright 2009-2016, Jack Poulson
#  All rights reserved.
#
#  This file is part of Elemental and is under the BSD 2-Clause License,
#  which can be found in the LICENSE file in the root directory, or at
#  http://opensource.org/licenses/BSD-2-Clause
#
include(ElCheckCSourceCompiles)

find_package(MPI)
if(NOT MPI_C_FOUND)
  message(FATAL_ERROR "MPI C compiler was not found and is required")
endif()

# NOTE: 
# check_function_exists only supports cdecl calling conventions, despite the
# fact that MS-MPI uses __stdcall. Thus, it is best to avoid using 
# check_function_exists in favor of check_c_source_compiles.
# Thanks to Ahn Vo for discovering this issue!

# Ensure that we have MPI1 by looking for MPI_Reduce_scatter
# ==========================================================
set(CMAKE_REQUIRED_FLAGS "${MPI_C_COMPILE_FLAGS}")
set(CMAKE_REQUIRED_LINKER_FLAGS "${MPI_C_LINK_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}")
set(CMAKE_REQUIRED_INCLUDES ${MPI_C_INCLUDE_PATH})
set(CMAKE_REQUIRED_LIBRARIES ${MPI_C_LIBRARIES})
set(MPI_REDUCE_SCATTER_CODE
  "#include \"mpi.h\"
   int main( int argc, char* argv[] )
   {
     MPI_Init( &argc, &argv );
     int *recvCounts;
     double *a, *b;  
     MPI_Reduce_scatter
     ( a, b, recvCounts, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD );
     MPI_Finalize();
     return 0;
   }")
El_check_c_source_compiles("${MPI_REDUCE_SCATTER_CODE}" 
  EL_HAVE_MPI_REDUCE_SCATTER)
if(NOT EL_HAVE_MPI_REDUCE_SCATTER)
  message(FATAL_ERROR "Could not find MPI_Reduce_scatter")
endif()

# If we have Open MPI, ensure that we have a sufficiently new version
# ===================================================================
# Allow advanced users to override the default minimum version requirement.
# Version 1.6.5 is known to cause problems with complex datatypes, even when
# EL_AVOID_COMPLEX_MPI is enabled. Since 1.7 was a feature branch, 1.8 is the
# lowest stable branch after 1.6.5.
# (e.g., for academic investigations of behavior)
#
# NOTE: MPI_C_INCLUDE_PATH has been observed to be a list containing both
#       an item corresponding to the directory containing mpi.h *and* the full
#       path to mpi.h for Open MPI!
#
if(NOT OMPI_MIN_VERSION)
  set(OMPI_MIN_VERSION 1.8.1)
endif()
foreach(MPI_PATH ${MPI_C_INCLUDE_PATH})
  if(EXISTS "${MPI_PATH}/mpi.h")
    set(MPI_HEADER_PATH ${MPI_PATH})
  endif()
endforeach()
if(MPI_HEADER_PATH)
  message(STATUS "Will parse MPI header ${MPI_HEADER_PATH}/mpi.h")
  file(READ "${MPI_HEADER_PATH}/mpi.h" _mpi_header)
  string(REGEX MATCH "define[ \t]+OMPI_MAJOR_VERSION[ \t]+([0-9]+)"
    _ompi_major_version_match "${_mpi_header}")
  set(OMPI_MAJOR_VERSION "${CMAKE_MATCH_1}")
  if(OMPI_MAJOR_VERSION)
    set(HAVE_OMPI TRUE)
  endif()
  if(HAVE_OMPI)
    string(REGEX MATCH "define[ \t]+OMPI_MINOR_VERSION[ \t]+([0-9]+)"
      _ompi_minor_version_match "${_mpi_header}")
    set(OMPI_MINOR_VERSION "${CMAKE_MATCH_1}")
    string(REGEX MATCH "define[ \t]+OMPI_RELEASE_VERSION[ \t]+([0-9]+)"
      _ompi_release_version_match "${_mpi_header}")
    set(OMPI_RELEASE_VERSION "${CMAKE_MATCH_1}")
    set(OMPI_VERSION
      ${OMPI_MAJOR_VERSION}.${OMPI_MINOR_VERSION}.${OMPI_RELEASE_VERSION})
    if(${OMPI_VERSION} VERSION_LESS ${OMPI_MIN_VERSION})
      message(FATAL_ERROR "Detected Open MPI version ${OMPI_VERSION}, but known bugs in version 1.6.5 of Open MPI have led Elemental to require at least version ${OMPI_MIN_VERSION}")
    else()
      message(STATUS "Using Open MPI version ${OMPI_VERSION}")
    endif()
  endif()
else()
  message(STATUS "Could not find mpi.h to check for OpenMPI workarounds.")
endif()

# Test for whether or not we have Fortran MPI support
# ===================================================
if(MPI_Fortran_FOUND)
  set(MPIF_EXISTS FALSE)
  foreach(FORTRAN_INCLUDE_PATH ${MPI_Fortran_INCLUDE_PATH})
    if(EXISTS "${FORTRAN_INCLUDE_PATH}/mpif.h")
      set(MPIF_EXISTS TRUE)
    endif()
  endforeach()
  if(MPIF_EXISTS)
    set(EL_HAVE_MPI_FORTRAN TRUE)
  else()
    message(WARNING 
      "Fortran MPI support detected, but mpif.h was not found in ${MPI_Fortran_INCLUDE_PATH}")
  endif()
endif()

# Ensure that we have MPI2 by looking for MPI_Type_create_struct
# ==============================================================
set(MPI_TYPE_CREATE_STRUCT_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
         MPI_Init( &argc, &argv );
         int count=2;
         int blockLengths[2];
         MPI_Aint displs[2];
         MPI_Datatype types[2];
         MPI_Datatype newType;
         MPI_Type_create_struct( count, blockLengths, displs, types, &newType );
         MPI_Finalize();
         return 0;
     }")
El_check_c_source_compiles("${MPI_TYPE_CREATE_STRUCT_CODE}" 
  EL_HAVE_MPI_TYPE_CREATE_STRUCT)
if(NOT EL_HAVE_MPI_TYPE_CREATE_STRUCT)
  message(FATAL_ERROR "Could not find MPI_Type_create_struct")
endif()

# Ensure that we have MPI_[UNSIGNED_]LONG_LONG if compiling with 64-bit integers
# ==============================================================================
set(MPI_LONG_LONG_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
         MPI_Init( &argc, &argv );
         MPI_Datatype lli = MPI_LONG_LONG_INT;
         MPI_Datatype llu = MPI_UNSIGNED_LONG_LONG;
         MPI_Finalize();
         return 0;
     }")
El_check_c_source_compiles("${MPI_LONG_LONG_CODE}" EL_HAVE_MPI_LONG_LONG)
if(EL_USE_64BIT_INTS AND NOT EL_HAVE_MPI_LONG_LONG)
  message(FATAL_ERROR 
    "Did not detect MPI_LONG_LONG_INT and MPI_UNSIGNED_LONG_LONG")
endif()

# Check if MPI_LONG_DOUBLE[_COMPLEX] is supported
# ===============================================
set(MPI_LONG_DOUBLE_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
         MPI_Init( &argc, &argv );
         MPI_Datatype longDbl = MPI_LONG_DOUBLE;
         MPI_Finalize();
         return 0;
     }")
set(MPI_LONG_DOUBLE_COMPLEX_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
         MPI_Init( &argc, &argv );
         MPI_Datatype longDblCpx = MPI_LONG_DOUBLE_COMPLEX;
         MPI_Finalize();
         return 0;
     }")
El_check_c_source_compiles("${MPI_LONG_DOUBLE_CODE}" EL_HAVE_MPI_LONG_DOUBLE)
El_check_c_source_compiles("${MPI_LONG_DOUBLE_COMPLEX_CODE}" 
  EL_HAVE_MPI_LONG_DOUBLE_COMPLEX)

# Check if MPI_C_FLOAT_COMPLEX and MPI_C_DOUBLE_COMPLEX exist
# ===========================================================
set(MPI_C_COMPLEX_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
       MPI_Init( &argc, &argv );
       MPI_Datatype floatCpx = MPI_C_FLOAT_COMPLEX;
       MPI_Datatype doubleCpx = MPI_C_DOUBLE_COMPLEX;
       MPI_Finalize();
       return 0;
     }")
El_check_c_source_compiles("${MPI_C_COMPLEX_CODE}" EL_HAVE_MPI_C_COMPLEX)

# Detect support for various optional MPI routines
# ================================================
set(MPI_REDUCE_SCATTER_BLOCK_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
       MPI_Init( &argc, &argv );
       double *a, *b; 
       MPI_Reduce_scatter_block( a, b, 5, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD );
       MPI_Finalize();
       return 0;
     }")
El_check_c_source_compiles("${MPI_REDUCE_SCATTER_BLOCK_CODE}" 
  EL_HAVE_MPI_REDUCE_SCATTER_BLOCK)
set(MPI_IALLGATHER_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
       MPI_Init( &argc, &argv );
       double *a, *b;
       MPI_Request request;
       MPI_Iallgather
       ( a, 5, MPI_DOUBLE, 
         b, 5, MPI_DOUBLE, MPI_COMM_WORLD, &request );
       MPI_Finalize();
       return 0;
     }")
El_check_c_source_compiles("${MPI_IALLGATHER_CODE}" 
  EL_HAVE_MPI3_NONBLOCKING_COLLECTIVES)
set(MPIX_IALLGATHER_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
       MPI_Init( &argc, &argv );
       double *a, *b;
       MPI_Request request;
       MPIX_Iallgather
       ( a, 5, MPI_DOUBLE, 
         b, 5, MPI_DOUBLE, MPI_COMM_WORLD, &request );
       MPI_Finalize();
       return 0;
     }")
El_check_c_source_compiles("${MPIX_IALLGATHER_CODE}" 
  EL_HAVE_MPIX_NONBLOCKING_COLLECTIVES)
set(MPI_INIT_THREAD_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
       int required=MPI_THREAD_SINGLE, provided;
       MPI_Init_thread( &argc, &argv, required, &provided );
       MPI_Finalize();
       return 0;
     }")
El_check_c_source_compiles("${MPI_INIT_THREAD_CODE}" EL_HAVE_MPI_INIT_THREAD)
set(MPI_QUERY_THREAD_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
       MPI_Init( &argc, &argv );
       int provided;
       MPI_Query_thread( &provided );
       MPI_Finalize();
       return 0;
     }")
El_check_c_source_compiles("${MPI_QUERY_THREAD_CODE}" EL_HAVE_MPI_QUERY_THREAD )
set(MPI_COMM_SET_ERRHANDLER_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
       MPI_Init( &argc, &argv );
       MPI_Errhandler handler;
       MPI_Comm_set_errhandler( MPI_COMM_WORLD, handler );
       MPI_Finalize();
       return 0;
     }")
El_check_c_source_compiles("${MPI_COMM_SET_ERRHANDLER_CODE}" 
  EL_HAVE_MPI_COMM_SET_ERRHANDLER)
# Detecting MPI_IN_PLACE and MPI_Comm_f2c requires test compilation
# -----------------------------------------------------------------
set(MPI_IN_PLACE_CODE
    "#include \"mpi.h\"
     int main( int argc, char* argv[] )
     {
         MPI_Init( &argc, &argv );
         float a;
         MPI_Allreduce
         ( MPI_IN_PLACE, &a, 1, MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD );
         MPI_Finalize();
         return 0;
     }")
El_check_c_source_compiles("${MPI_IN_PLACE_CODE}" EL_HAVE_MPI_IN_PLACE)
if(NOT EL_HAVE_MPI_IN_PLACE)
  message(FATAL_ERROR "MPI_IN_PLACE support was not detected")
endif()
set(MPI_COMM_F2C_CODE
"#include \"mpi.h\"
 int main( int argc, char* argv[] )
 {
     MPI_Init( &argc, &argv );
     MPI_Fint fint;
     MPI_Comm comm = MPI_Comm_f2c( fint );
     MPI_Finalize();
     return 0;
 }")
El_check_c_source_compiles("${MPI_COMM_F2C_CODE}" EL_HAVE_MPI_COMM_F2C)

# Detect whether or not MPI_Comm and MPI_Group are implemented as an int
# ======================================================================
# NOTE: These are no longer used internally by Elemental 
set(MPI_COMM_NOT_INT_CODE
    "#include \"mpi.h\"
     void Foo( MPI_Comm comm ) { }
     void Foo( int comm ) { }
     int main( int argc, char* argv[] )
     {
         MPI_Init( &argc, &argv );
         MPI_Finalize();
         return 0;
     }")
set(MPI_GROUP_NOT_INT_CODE
    "#include \"mpi.h\"
     void Foo( MPI_Group group ) { }
     void Foo( int group ) { }
     int main( int argc, char* argv[] )
     {
         MPI_Init( &argc, &argv );
         MPI_Finalize();
         return 0;
     }")
El_check_c_source_compiles("${MPI_COMM_NOT_INT_CODE}" EL_MPI_COMM_NOT_INT)
El_check_c_source_compiles("${MPI_GROUP_NOT_INT_CODE}" EL_MPI_GROUP_NOT_INT)

set(CMAKE_REQUIRED_FLAGS)
set(CMAKE_REQUIRED_LINKER_FLAGS)
set(CMAKE_REQUIRED_INCLUDES)
set(CMAKE_REQUIRED_LIBRARIES)
