# Minimum version required
cmake_minimum_required (VERSION 3.2)

# Project name
project (osqp)

set(OSQP_VERSION "0.0.0" CACHE STRING "The version number of OSQP")
if(NOT OSQP_VERSION STREQUAL "0.0.0")
    configure_file (
        "${PROJECT_SOURCE_DIR}/configure/version.h.in"
        "${PROJECT_SOURCE_DIR}/include/version.h"
    )
endif()

# Export compile commands
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Set the output folder where your program will be created
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/out)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/out)

# Some non-standard CMake modules
LIST(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/configure/cmake)
INCLUDE(FindPythonModule)
INCLUDE(Utils)
# include(FindMKL)  # Find MKL module


# Detect operating system
# ----------------------------------------------
message(STATUS "We are on a ${CMAKE_SYSTEM_NAME} system")
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
    set(IS_LINUX ON)
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
    set(IS_MAC ON)
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
    set(IS_WINDOWS ON)
endif()



# Set options
# ----------------------------------------------

# Are unittests generated?
option (UNITTESTS "Enable unittests generation" OFF)

# Is the code generated for embedded platforms?
#   1 :   Yes. Matrix update not allowed.
#   2 :   Yes. Matrix update allowed.
if (NOT DEFINED EMBEDDED)
    message(STATUS "Embedded is OFF")
else()
    message(STATUS "Embedded is ${EMBEDDED}")
endif()

# Is printing enabled?
option (PRINTING "Enable solver printing" ON)
if (DEFINED EMBEDDED)
    message(STATUS "Disabling printing for embedded")
    set(PRINTING OFF)
endif()
message(STATUS "Printing is ${PRINTING}")


# Is profiling enabled?
option (PROFILING "Enable solver profiling (timing)" ON)
if (DEFINED EMBEDDED)
    message(STATUS "Disabling profiling for embedded")
    set(PROFILING OFF)
endif()
message(STATUS "Profiling is ${PROFILING}")

# Is user interrupt enabled?
option (CTRLC "Enable user interrupt (Ctrl-C)" ON)
if (DEFINED EMBEDDED)
    message(STATUS "Disabling user interrupt for embedded")
    set(CTRLC OFF)
endif()
message(STATUS "User interrupt is ${CTRLC}")

# Use floats instead of integers
option (DFLOAT "Use float numbers instead of doubles" OFF)
message(STATUS "Floats are ${DFLOAT}")

# Use long integers for indexing
option (DLONG "Use long integers (64bit) for indexing" ON)
if (NOT (CMAKE_SIZEOF_VOID_P EQUAL 8))
	message(STATUS "Disabling long integers (64bit) on 32bit machine")
	set(DLONG OFF)
endif()
message(STATUS "Long integers (64bit) are ${DLONG}")


option (DEBUG "Debug mode" OFF)
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
	set (DEBUG ON)
	message(STATUS "Debug mode is ${DEBUG}")
endif()

# Add code coverage
option (COVERAGE "Perform code coverage" OFF)
message(STATUS "Code coverage is ${COVERAGE}")


# Memory allocators
# ----------------------------------------------

#Report on custom user header options.  This is intended to allow
#users to provide definitions of their own memory functions
# The header should define the functions as follows
#
# define c_malloc mymalloc
# define c_calloc mycalloc
# define c_realloc myrealloc
# define c_free myfree

if(OSQP_CUSTOM_MEMORY)
	message(STATUS "User custom memory management header: ${OSQP_CUSTOM_MEMORY}")
endif()



# Linear solvers dependencies
# ---------------------------------------------
option (ENABLE_MKL_PARDISO "Enable MKL Pardiso solver" ON)
if (DFLOAT)
	message(STATUS "Disabling MKL Pardiso Solver with floats")
	set(ENABLE_MKL_PARDISO OFF)
elseif(DEFINED EMBEDDED)
	message(STATUS "Disabling MKL Pardiso Solver for embedded")
	set(ENABLE_MKL_PARDISO OFF)
endif()
message(STATUS "MKL Pardiso: ${ENABLE_MKL_PARDISO}")


# Generate header file with the global options
# ---------------------------------------------
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure/osqp_configure.h.in
               ${CMAKE_CURRENT_SOURCE_DIR}/include/osqp_configure.h
               NEWLINE_STYLE LF)

# Set Compiler flags
# ----------------------------------------------
set(CMAKE_POSITION_INDEPENDENT_CODE ON)  # -fPIC


if (NOT MSVC)

    if (COVERAGE)
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage")
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
	if(FORTRAN)
		set(CMAKE_FORTRAN_FLAGS "${CMAKE_FORTRAN_FLAGS} --coverage")
	endif(FORTRAN)
    endif()

    if (DEBUG)
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g")
    else()
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3")
    endif()

    set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lm")      # Include math
    # Include real time library in linux
    if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
        set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lrt -ldl")
    endif()
endif (NOT MSVC)

# Set sources and includes
# ----------------------------------------------
add_subdirectory (src)
add_subdirectory (include)


# if we are building the Python interface, let's look for Python
# and set some options
# -----------------------------------------------------------------
if (PYTHON)

    # Python include directories need to be passed by the python compilation process
    if (NOT PYTHON_INCLUDE_DIRS)
            message( FATAL_ERROR "You need Python include directories to build the Python interface" )
    endif (NOT PYTHON_INCLUDE_DIRS)

    # Include directories for Python headers
    include_directories(${PYTHON_INCLUDE_DIRS})

    # Pass PYTHON flag to C compiler
    add_definitions(-DPYTHON)

    if (UNITTESTS)
        # Disable unittests
        message(STATUS "Disabling UNITTESTS because we are building Python interface")
        set(UNITTESTS OFF)
    endif (UNITTESTS)

endif (PYTHON)


# if we are building the Matlab interface, let's look for Matlab
# and set some options
# -----------------------------------------------------------------
if (MATLAB)

    find_package(Matlab)

    if (NOT Matlab_FOUND)
        message( FATAL_ERROR "You need Matlab libraries to build the Matlab interface" )
    endif (NOT Matlab_FOUND)

    # Include directories for Matlab headers
    include_directories(${Matlab_INCLUDE_DIRS})

    message(STATUS "Matlab root is " ${Matlab_ROOT_DIR})

    # Pass MATLAB flag to C compiler
    add_definitions(-DMATLAB)

    # Insist on the pre 2018 complex data API
    # so that mxGetPr will work correctly
    add_definitions(-DMATLAB_MEXSRC_RELEASE=R2017b)

    message(STATUS "Using Matlab pre-2018a API for mxGetPr compatibility")

    if (UNITTESTS)
        # Disable unittests
        message(STATUS "Disabling UNITTESTS because we are building Matlab interface")
        set(UNITTESTS OFF)
    endif (UNITTESTS)

endif (MATLAB)

# if we are building the R interface, let's look for R
# and set some options
# -----------------------------------------------------------------
if (R_LANG)

    message(STATUS "We are building the R interface")

    # Look for R libraries
    find_package(R)

    if (NOT R_FOUND)
            message( FATAL_ERROR "You need R libraries to build the R interface" )
    endif (NOT R_FOUND)

    message(STATUS "R exec is: " ${R_EXEC})
    message(STATUS "R root dir is: " ${R_ROOT_DIR})
    message(STATUS "R includes are in: " ${R_INCLUDE_DIRS})

    # Include directories for R headers
    include_directories(${R_INCLUDE_DIRS})

    # Pass R_LANG flag to C compiler
    add_definitions(-DR_LANG)

    if (UNITTESTS)
        # Disable unittests
        message(STATUS "Disabling UNITTESTS because we are building the R interface")
        set(UNITTESTS OFF)
    endif (UNITTESTS)

endif (R_LANG)


# Create Static Library
# ----------------------------------------------

# Add linear system solvers cumulative library
add_subdirectory(lin_sys)

# Static library
add_library (osqpstatic STATIC ${osqp_src} ${osqp_headers} ${linsys_solvers})
# Give same name to static library output
set_target_properties(osqpstatic PROPERTIES OUTPUT_NAME osqp)

# Include directories for linear system solvers
target_include_directories(osqpstatic PRIVATE ${linsys_solvers_includes})

# Declare include directories for the cmake exported target
target_include_directories(osqpstatic
                           PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
                                  "$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}/osqp>")

# Install Static Library
# ----------------------------------------------

include(GNUInstallDirs)

install(TARGETS osqpstatic
        EXPORT  ${PROJECT_NAME}
        ARCHIVE       DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        LIBRARY       DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        RUNTIME       DESTINATION "${CMAKE_INSTALL_BINDIR}")


# Install Headers
# ----------------------------------------------

install(FILES ${osqp_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/osqp")



if (MATLAB)
target_link_libraries (osqpstatic ${Matlab_LIBRARIES})
endif (MATLAB)

# If we are building Python/Matlab/R interface:
#   - do not build shared library
#   - do not build demo
if (NOT PYTHON AND NOT MATLAB AND NOT R_LANG AND NOT EMBEDDED)
    # Create osqp shared library
    # NB: Add all the linear system solvers here
    add_library (osqp SHARED ${osqp_src} ${osqp_headers} ${linsys_solvers})

    # Include directories for linear system solvers
    target_include_directories(osqp PRIVATE ${linsys_solvers_includes})

    # Declare include directories for the cmake exported target
    target_include_directories(osqp
                               PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
                                      "$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}/osqp>")

    # Install osqp shared library
    install(TARGETS osqp
            EXPORT  ${PROJECT_NAME}
            LIBRARY       DESTINATION "${CMAKE_INSTALL_LIBDIR}"
            ARCHIVE       DESTINATION "${CMAKE_INSTALL_LIBDIR}"
            RUNTIME       DESTINATION "${CMAKE_INSTALL_BINDIR}")

    # Create demo executable (linked to static library)
    add_executable (osqp_demo ${PROJECT_SOURCE_DIR}/examples/osqp_demo.c)
    target_link_libraries (osqp_demo osqpstatic)

endif (NOT PYTHON AND NOT MATLAB AND NOT R_LANG AND NOT EMBEDDED)

# Create CMake packages for the build directory
# ----------------------------------------------
include(CMakePackageConfigHelpers)

export(EXPORT ${PROJECT_NAME}
  FILE "${CMAKE_CURRENT_BINARY_DIR}/osqp-targets.cmake"
  NAMESPACE osqp::)

if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/osqp-config.cmake)
  file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/osqp-config.cmake "include(\"\${CMAKE_CURRENT_LIST_DIR}/osqp-targets.cmake\")\n")
endif()


# Create CMake packages for the install directory
# ----------------------------------------------

set(ConfigPackageLocation ${CMAKE_INSTALL_LIBDIR}/cmake/osqp)

install(EXPORT ${PROJECT_NAME}
        FILE osqp-targets.cmake
        NAMESPACE osqp::
        DESTINATION ${ConfigPackageLocation})

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/osqp-config.cmake
        DESTINATION ${ConfigPackageLocation})



# Add uninstall command
# ----------------------------------------------
if(NOT TARGET uninstall)
    configure_file(
        "${CMAKE_CURRENT_SOURCE_DIR}/configure/cmake/cmake_uninstall.cmake.in"
        "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
        IMMEDIATE @ONLY)

    add_custom_target(uninstall
        COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
endif()



# Add testing
# ----------------------------------------------
# Add custom command to generate tests
if (UNITTESTS)
    find_package(PythonInterp)
    if(NOT PYTHONINTERP_FOUND)
        message( FATAL_ERROR "You need python installed to generate unittests. If you do not want to compile the unittests pass -DUNITTESTS=OFF to cmake." )
    endif()

    INCLUDE(FindPythonModule)
    find_python_module(numpy)
    IF(NOT NUMPY_FOUND)
        message( FATAL_ERROR "You need numpy python module installed to generate unittests. If you do not want to compile the unittests pass -DUNITTESTS=OFF to cmake." )
    ENDIF()

    find_python_module(scipy)
    # Check scipy version for sparse.random functionalities
    IF((NOT SCIPY_FOUND) OR (SCIPY_VERSION VERSION_LESS 0.17.0))
        message( FATAL_ERROR "You need scipy python module installed to generate unittests. If you do not want to compile the unittests pass -DUNITTESTS=OFF to cmake." )
    ENDIF()

    find_python_module(__future__)
    IF(NOT __FUTURE___FOUND)
        message( FATAL_ERROR "You need future python module installed to generate unittests. If you do not want to compile the unittests pass -DUNITTESTS=OFF to cmake." )
    ENDIF()

    # Add test_headers and codegen_test_headers
    add_subdirectory(tests)

    # Generating tests.stamp so that the test data are not always generated
    # set(data_timestamp ${PROJECT_SOURCE_DIR}/tests/tests_data.stamp)
    add_custom_command(
        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests
        COMMAND ${PYTHON_EXECUTABLE} generate_tests_data.py
        DEPENDS ${PROJECT_SOURCE_DIR}/tests/generate_tests_data.py
        OUTPUT ${codegen_test_headers}
        COMMENT "Generating unittests data files using Python"
    )

    # Direct linear solver testing
    include_directories(tests)
    add_executable(osqp_tester
            ${PROJECT_SOURCE_DIR}/tests/osqp_tester.cpp
            ${PROJECT_SOURCE_DIR}/tests/osqp_tester.h
            ${test_headers}
            ${codegen_test_headers}
            )
    set_target_properties(osqp_tester
            PROPERTIES
                CXX_STANDARD 11
                CXX_STANDARD_REQUIRED YES
                CXX_EXTENSIONS NO
            )
    target_link_libraries (osqp_tester osqpstatic ${CMAKE_DL_LIBS})

    # Add custom memory target
    add_executable(osqp_tester_custom_memory
            EXCLUDE_FROM_ALL
            ${PROJECT_SOURCE_DIR}/tests/osqp_tester.cpp
            ${PROJECT_SOURCE_DIR}/tests/osqp_tester.h
            ${test_headers}
            ${codegen_test_headers}
		   ${PROJECT_SOURCE_DIR}/tests/custom_memory/custom_memory.c
		   ${PROJECT_SOURCE_DIR}/tests/custom_memory/custom_memory.h
		)
    target_link_libraries (osqp_tester_custom_memory osqpstatic ${CMAKE_DL_LIBS})

    # Add testing
    include(CTest)
    enable_testing()
    add_test(NAME tester COMMAND $<TARGET_FILE:osqp_tester>)
endif()
