if (POLICY CMP0048)
    cmake_policy(SET CMP0048 NEW)
endif (POLICY CMP0048)

cmake_minimum_required(VERSION 3.10.2)

set(PIPSIPMpp_VERSION_MAJOR 0)
set(PIPSIPMpp_VERSION_MINOR 3)
set(PIPSIPMpp_VERSION_PATCH 0)

project(PIPSIPMpp
    VERSION ${PIPSIPMpp_VERSION_MAJOR}.${PIPSIPMpp_VERSION_MINOR}.${PIPSIPMpp_VERSION_PATCH}
    DESCRIPTION "A parallel interior-point solver for doubly bordered block diagonal Linear Programs."
    HOMEPAGE_URL https://pips-ipmpp.gitlab.io/
    LANGUAGES CXX C)

if((NOT ${CMAKE_SYSTEM_NAME} MATCHES "Linux") AND (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin"))
    message(FATAL_ERROR "Operating system not supported")
endif()

if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Release")
endif()
message(STATUS "CMake build type is ${CMAKE_BUILD_TYPE}")

find_package(MPI REQUIRED)
message(STATUS "MPI_C_COMPILER=${MPI_C_COMPILER}")
message(STATUS "MPI_C_WORKS=${MPI_C_WORKS}")
message(STATUS "MPI_C_LIBRARIES=${MPI_C_LIBRARIES}")
message(STATUS "MPI_C_INCLUDE_DIRS=${MPI_C_INCLUDE_DIRS}")
message(STATUS "MPI_C_LINK_FLAGS=${MPI_C_LINK_FLAGS}")
message(STATUS "MPI_CXX_COMPILER=${MPI_CXX_COMPILER}")
message(STATUS "MPI_CXX_WORKS=${MPI_CXX_WORKS}")
message(STATUS "MPI_CXX_LIBRARIES=${MPI_CXX_LIBRARIES}")
message(STATUS "MPI_CXX_INCLUDE_DIRS=${MPI_CXX_INCLUDE_DIRS}")
message(STATUS "MPI_CXX_LINK_FLAGS=${MPI_CXX_LINK_FLAGS}")
message(STATUS "MPIEXEC=${MPIEXEC}")
message(STATUS "MPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}")

include_directories(SYSTEM ${MPI_CXX_INCLUDE_DIRS})

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# our intermediate static libs need to be build with PIC
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# enable many compiler warnings
set(WARN_FLAGS -Wall -Wextra -Wpedantic)
set(WARN_FLAGS_CXX -Wsuggest-override)
list(APPEND WARN_FLAGS -Wno-unused-parameter)
list(APPEND WARN_FLAGS -Wno-unused-but-set-parameter)

add_compile_options("$<$<COMPILE_LANGUAGE:C>:${WARN_FLAGS}>")
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:${WARN_FLAGS}>" "$<$<COMPILE_LANGUAGE:CXX>:${WARN_FLAGS_CXX}>")

# for MPI stacktraces
string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_UPPER)
if(BUILD_TYPE_UPPER STREQUAL "DEBUG" OR BUILD_TYPE_UPPER STREQUAL "RELWITHDEBINFO")
    add_compile_options(-fno-omit-frame-pointer)
    add_link_options(-rdynamic)
    message(STATUS "Added compiler flag -fno-omit-frame-pointer and linker flag -rdynamic for stack traces")
endif()

find_program(GIT git)
if((GIT) AND(EXISTS ${PROJECT_SOURCE_DIR}/.git))
    execute_process(
        COMMAND ${GIT} describe --always --dirty
        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
        OUTPUT_VARIABLE GITHASH OUTPUT_STRIP_TRAILING_WHITESPACE)
else()
    set(GITHASH "n/a")
endif()
message(STATUS "Git hash: " ${GITHASH})

# with testing of (selected) executables
option(WITH_MAKETEST "Enable 'make test'" OFF)
message(STATUS "WITH_MAKETEST=${WITH_MAKETEST}")

option(WITH_MKL_PARDISO "Building with MKL Pardiso (if available) instead of Panua-Pardiso" OFF)
message(STATUS "WITH_MKL_PARDISO=${WITH_MKL_PARDISO}")

option(WITH_TIMING "Build with timing recording and reporting for PIPS-IPM" OFF)
message(STATUS "WITH_TIMING=${WITH_TIMING}")

if(WITH_TIMING)
    add_definitions(-DTIMING)
endif(WITH_TIMING)

option(WITH_MEMORY_SANITIZER "Build with sanitizer flag - useful for finding memory leaks - slower!" OFF)
message(STATUS "WTIH_MEMORY_SANITIZER=${WITH_MEMORY_SANITIZER}")

if(WITH_MEMORY_SANITIZER)
    set(SANITIZER_FLAGS -fsanitize=address,undefined -fno-omit-frame-pointer -g)
    add_compile_options("$<$<COMPILE_LANGUAGE:C>:${SANITIZER_FLAGS}>")
    add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:${SANITIZER_FLAGS}>")

    add_link_options(-fsanitize=address,undefined)

    MESSAGE(STATUS "Enabled address and undefined behavior sanitizers. Ensure the following environment variable is set:")
    MESSAGE(STATUS "export LSAN_OPTIONS=suppressions=${CMAKE_SOURCE_DIR}/lsan_suppression.txt:symbolize=1:fast_unwind_on_malloc=0")
    MESSAGE(STATUS "See https://pips-ipmpp.gitlab.io/COMPILE.html for some troubleshooting information.")
endif(WITH_MEMORY_SANITIZER)

find_package(ZLIB REQUIRED)
# workaround for macOS SDK 26.4 not happy about an -isystem /path/to/macOS26.4.sdk/usr/include without a prior -isystem for C++ system headers
# removing the SYSTEM property for ZLIB (which is part of the macOS SDK) changes the -isystem to an -I
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
  set_property(TARGET ZLIB::ZLIB PROPERTY SYSTEM FALSE)
endif()

find_package(OpenMP REQUIRED COMPONENTS CXX)

if(MATH_LIBS)
    message(STATUS "Using user-defined MATH_LIBS=${MATH_LIBS}")
elseif(IS_DIRECTORY $ENV{MKLROOT})
    set( MATH_LIBS  "-Wl,--start-group $ENV{MKLROOT}/lib/intel64/libmkl_intel_lp64.a $ENV{MKLROOT}/lib/intel64/libmkl_gnu_thread.a $ENV{MKLROOT}/lib/intel64/libmkl_core.a -Wl,--end-group -lgomp -lpthread -lm -ldl" )
    # set(MATH_LIBS "-Wl,--start-group -L$ENV{MKLROOT}/lib/intel64 -lmkl_intel_lp64 -lmkl_gnu_thread -lmkl_core -Wl,--end-group -lgomp -lgfortran -lm -ldl")
    message(STATUS "Using MKLROOT:$ENV{MKLROOT} MATH_LIBS=${MATH_LIBS}")
else(MATH_LIBS)
    message(STATUS "Searching system path for MATH_LIBS")
    find_package(LAPACK REQUIRED)
    set(MATH_LIBS ${LAPACK_LIBRARIES})
    message(STATUS "MATH_LIBS=${MATH_LIBS}")
endif(MATH_LIBS)


########################################
# ThirdPartyLibs
########################################

set(SHARED_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ThirdPartyLibs)
set(SOLVERS_FOUND "")

########################################
# HSL library
########################################

include(CheckFortranFunctionExists)
include(CMakePushCheckState)

# check for libhsl or libcoinhsl, to initialize MA27_LIBRARY, MC30_LIBRARY, and MA57_LIBRARY, if not set
if((NOT MA27_LIBRARY) OR (NOT MC30_LIBRARY) OR (NOT MA57_LIBRARY))
    message(STATUS "Looking for library with HSL codes.")

    # if HSLLIB_DIR set in env, and not via -DHSLLIB_DIR, then take value from env
    if(NOT HSLLIB_DIR AND DEFINED ENV{HSLLIB_DIR})
        set(HSLLIB_DIR $ENV{HSLLIB_DIR})
    endif(NOT HSLLIB_DIR AND DEFINED ENV{HSLLIB_DIR})

    find_library(HSLLIB NAMES hsl coinhsl PATHS ${HSLLIB_DIR})

    if(HSLLIB)
        message(STATUS "Found HSL: ${HSLLIB}")

        # check which of MA27, MC30, MA57 are included in HSL library
        # with cmake, we require Fortran to do that
        enable_language(Fortran)

        cmake_push_check_state(RESET)
        set(CMAKE_REQUIRED_LIBRARIES ${HSLLIB})

        if(NOT MA27_LIBRARY)
            check_fortran_function_exists(ma27ad HSLLIB_HAS_MA27)
            if(HSLLIB_HAS_MA27)
                set(MA27_LIBRARY ${HSLLIB})
            endif(HSLLIB_HAS_MA27)
        endif(NOT MA27_LIBRARY)
        if(NOT MC30_LIBRARY)
            check_fortran_function_exists(mc30ad HSLLIB_HAS_MC30)
            if(HSLLIB_HAS_MC30)
                set(MC30_LIBRARY ${HSLLIB})
            endif(HSLLIB_HAS_MC30)
        endif(NOT MC30_LIBRARY)
        if(NOT MA57_LIBRARY)
            check_fortran_function_exists(ma57ad HSLLIB_HAS_MA57)
            if(HSLLIB_HAS_MA57)
                set(MA57_LIBRARY ${HSLLIB})
            endif(HSLLIB_HAS_MA57)
        endif(NOT MA57_LIBRARY)

        cmake_pop_check_state()
    else(HSLLIB)
        message(STATUS "Did not find any HSL library.")
    endif(HSLLIB)
endif((NOT MA27_LIBRARY) OR (NOT MC30_LIBRARY) OR (NOT MA57_LIBRARY))

########################################
# MA27
########################################

if(MA27_LIBRARY)
    # if user specified a library, then use it
    set(HAVE_MA27 TRUE)
    set(SOLVERS_FOUND "${SOLVERS_FOUND} MA27")
    message(STATUS "Using MA27_LIBRARY = ${MA27_LIBRARY}")

else(MA27_LIBRARY)
    # look for MA27 source in user-given MA27_DIR or under ThirdPartyLibs/MA27/
    if(DEFINED ENV{MA27_DIR})
        set(MA27_DIR "$ENV{MA27_DIR}")
    else(DEFINED ENV{MA27_DIR})
        set(MA27_DIR ${SHARED_DIR}/MA27)
    endif(DEFINED ENV{MA27_DIR})

    # check for MA27 source file to be compiled into PIPS-IPM++
    file(GLOB_RECURSE ma27src ${MA27_DIR}/ma27d.f)
    if(ma27src)
        set(HAVE_MA27 TRUE)
        message(STATUS "Found MA27 source files in ${MA27_DIR}")
    else()
        set(HAVE_MA27 FALSE)
    endif()

endif(MA27_LIBRARY)

########################################
# MC30 (used with MA27)
########################################

if(HAVE_MA27)
    if(MC30_LIBRARY)
        # if user specified a library, then use it
        set(HAVE_MC30 TRUE)
        message(STATUS "Using MC30_LIBRARY = ${MC30_LIBRARY}")

    else(MC30_LIBRARY)
        # look for MC30 source in user-given MC30_DIR or under ThirdPartyLibs/MC30/
        if(DEFINED ENV{MC30_DIR})
            set(MC30_DIR "$ENV{MC30_DIR}")
        else(DEFINED ENV{MC30_DIR})
            set(MC30_DIR ${SHARED_DIR}/MC30)
        endif(DEFINED ENV{MC30_DIR})

        # check for MC30 source file to be compiled into PIPS-IPM++
        file(GLOB_RECURSE mc30src ${MC30_DIR}/mc30d.f)
        if(mc30src)
            set(HAVE_MC30 TRUE)
            message(STATUS "Found MC30 source files in ${MC30_DIR}")
        else()
            set(HAVE_MC30 FALSE)
        endif()
    endif(MC30_LIBRARY)
endif(HAVE_MA27)

########################################
# MA57
########################################

if(MA57_LIBRARY)
    # if user specified a library, then use it
    set(HAVE_MA57 TRUE)
    set(SOLVERS_FOUND "${SOLVERS_FOUND} MA57")
    message(STATUS "Using MA57_LIBRARY = ${MA57_LIBRARY}")

else(MA57_LIBRARY)
    # look for MA57 source in user-given MA57_DIR or under ThirdPartyLibs/MA57/
    if(DEFINED ENV{MA57_DIR})
        set(MA57_DIR "$ENV{MA57_DIR}")
    else(DEFINED ENV{MA57_DIR})
        set(MA57_DIR ${SHARED_DIR}/MA57)
    endif(DEFINED ENV{MA57_DIR})

    # check for MA57 source files to be compiled into PIPS-IPM++
    file(GLOB_RECURSE ma57src ${MA57_DIR}/ma57d.f ${MA57_DIR}/ddeps.f)
    if(ma57src)
        set(HAVE_MA57 TRUE)
        message(STATUS "Found MA57 source files in ${MA57_DIR}")
    else()
        set(HAVE_MA57 FALSE)
    endif()

    # if MA57 source exists, then check for METIS dependency
    if(HAVE_MA57)
        find_library(METIS_LIBRARY metis)
        if(METIS_LIBRARY)
            find_path(METIS_INCLUDE_DIR metis.h)
            if(METIS_INCLUDE_DIR)
                message(STATUS "Found METIS lib ${METIS_LIBRARY} and metis.h in ${METIS_INCLUDE_DIR}")
                set(HAVE_METIS TRUE)
            else(METIS_INCLUDE_DIR)
                message(STATUS "No METIS header file found. MA57 will not use METIS.")
                set(HAVE_METIS FALSE)
            endif(METIS_INCLUDE_DIR)
        else(METIS_LIBRARY)
            message(STATUS "No METIS library found. MA57 will not use METIS.")
            set(HAVE_METIS FALSE)
        endif(METIS_LIBRARY)
    endif(HAVE_MA57)
endif(MA57_LIBRARY)

########################################
# Fortran compiler and runtime libraries
########################################

# if MA27 or MA57 source needs to be compiled, then a Fortran compiler is needed
# if MA27 or MA57 libraries are given, we assume we need to link against the Fortran runtime libs, which we find by enabling Fortran
if(HAVE_MA27 OR HAVE_MA57)
    enable_language(Fortran)
endif()

########################################
# MUMPS
########################################

message(STATUS "Looking for MUMPS")
if(DEFINED ENV{MUMPS_DIR})
    set(MUMPS_LIB_DIR "$ENV{MUMPS_DIR}/lib")
    set(MUMPS_INC_DIR "$ENV{MUMPS_DIR}/include")
endif(DEFINED ENV{MUMPS_DIR})

find_library(MUMPS_D_LIBRARY dmumps PATHS ${MUMPS_LIB_DIR})

find_path(MUMPS_INCLUDE_DIR NAME dmumps_c.h HINTS ${MUMPS_INC_DIR})

if( MUMPS_D_LIBRARY AND MUMPS_INCLUDE_DIR )
    set(HAVE_MUMPS TRUE)
    set(SOLVERS_FOUND "${SOLVERS_FOUND} MUMPS")
    message(STATUS "MUMPS_D_LIBRARY = ${MUMPS_D_LIBRARY}")
    message(STATUS "MUMPS_INCLUDE_DIR = ${MUMPS_INCLUDE_DIR}")
else( MUMPS_D_LIBRARY AND MUMPS_INCLUDE_DIR )
    set(HAVE_MUMPS FALSE)
    message(STATUS "Did not find MUMPS library or headers")
endif( MUMPS_D_LIBRARY AND MUMPS_INCLUDE_DIR )

########################################
# MKL_PARDISO Solver
########################################

if( WITH_MKL_PARDISO )
    message(STATUS "Looking for MKL_PARDISO")

    if( IS_DIRECTORY $ENV{MKLROOT} )
        find_library(MKL_INTEL_LP64_LIBRARY NAME libmkl_intel_lp64.a HINTS $ENV{MKLROOT}/lib/intel64)
        find_library(MKL_CORE_LIBRARY NAME libmkl_core.a HINTS $ENV{MKLROOT}/lib/intel64)
        find_library(MKL_SEQUENTIAL_LIBRARY NAME libmkl_sequential.a HINTS $ENV{MKLROOT}/lib/intel64)
        find_path(MKL_INCLUDE_DIR mkl_pardiso.h $ENV{MKLROOT}/include)

        if( !MKL_INTEL_LP64_LIBRARY OR !MKL_CORE_LIBRARY OR !MKL_SEQUENTIAL_LIBRARY OR !MKL_INCLUDE_DIR )
            message(STATUS "Did not find MKL_PARDISO" )
            set( HAVE_MKL_PARDISO FALSE )
        else( !MKL_INTEL_LP64_LIBRARY OR !MKL_CORE_LIBRARY OR !MKL_SEQUENTIAL_LIBRARY OR !MKL_INCLUDE_DIR )
            set( HAVE_MKL_PARDISO TRUE )
            set(SOLVERS_FOUND "${SOLVERS_FOUND} MKL_PARDISO")
            set(MKL_PARDISO_LIBS "-Wl,--start-group ${MKL_INTEL_LP64_LIBRARY} ${MKL_CORE_LIBRARY} ${MKL_SEQUENTIAL_LIBRARY} -Wl,--end-group -lgfortran -lpthread -lm")
            message(STATUS "MKL_INTEL_LP64_LIBRARY = ${MKL_INTEL_LP64_LIBRARY}")
            message(STATUS "MKL_CORE_LIBRARY = ${MKL_CORE_LIBRARY}")
            message(STATUS "MKL_SEQUENTIAL_LIBRARY = ${MKL_SEQUENTIAL_LIBRARY}")
            message(STATUS "MKL_INCLUDE_DIR = ${MKL_INCLUDE_DIR}")
        endif( !MKL_INTEL_LP64_LIBRARY OR !MKL_CORE_LIBRARY OR !MKL_SEQUENTIAL_LIBRARY OR !MKL_INCLUDE_DIR )
    else( IS_DIRECTORY $ENV{MKLROOT} )
        message( STATUS "Did not find MKL_PARDISO")
        set( HAVE_MKL_PARDISO FALSE )
    endif( IS_DIRECTORY $ENV{MKLROOT} )
else( WITH_MKL_PARDISO )
    message( STATUS "NOT looking for MKL_PARDISO")
    set( HAVE_MKL_PARDISO FALSE )
endif( WITH_MKL_PARDISO )

########################################
# Panua-Pardiso
########################################

if( NOT WITH_MKL_PARDISO )
    message(STATUS "Looking for Panua-PARDISO")

    if(DEFINED ENV{PARDISO_DIR})
        set(PARDISO_DIR "$ENV{PARDISO_DIR}")
    else(DEFINED ENV{PARDISO_DIR})
        set(PARDISO_DIR ${SHARED_DIR}/PARDISO/src)
    endif(DEFINED ENV{PARDISO_DIR})

    find_library(PARDISO_LIBRARY NAME libpardiso.so HINTS ${PARDISO_DIR})

    if( PARDISO_LIBRARY )
        set(HAVE_PARDISO TRUE)
        set(SOLVERS_FOUND "${SOLVERS_FOUND} Panua-Pardiso")
        message( STATUS "PARDISO_LIBRARY = ${PARDISO_LIBRARY}")
    else( PARDISO_LIBRARY )
        set(HAVE_PARDISO FALSE)
        message(STATUS "Did not find Panua-Pardiso")
    endif( PARDISO_LIBRARY )
else( NOT WITH_MKL_PARDISO )
    message(STATUS "NOT looking for Panua-Pardiso")
    set(HAVE_PARDISO FALSE)
endif( NOT WITH_MKL_PARDISO )

#####################
# print solvers found
#####################

message( STATUS "Found the following solvers: ${SOLVERS_FOUND}")

###############################################################################
###############################################################################

add_subdirectory(PIPS-IPM)

##########################################################
# CMake Tests
##########################################################
if(WITH_MAKETEST)
    enable_testing()

    set(INSTALL_GTEST OFF)
    include(GoogleTest)
    add_subdirectory(PIPS-IPM/Test/)

    #add_test(NAME PIPS-IPM-linkingConsTest COMMAND sh ${PROJECT_SOURCE_DIR}/PIPS-IPM/Test/pipsipmLinkConsTest.sh $<TARGET_FILE:pipsipmCallbackExample> )
endif(WITH_MAKETEST)
