cmake_minimum_required(VERSION 3.18)

set(QOCO_VERSION_MAJOR "0")
set(QOCO_VERSION_MINOR "2")
set(QOCO_VERSION_PATCH "2")
set(QOCO_VERSION ${QOCO_VERSION_MAJOR}.${QOCO_VERSION_MINOR}.${QOCO_VERSION_PATCH})

# Project name
project(qoco VERSION ${QOCO_VERSION})
include(GNUInstallDirs)
message(STATUS "Building QOCO v${QOCO_VERSION}")

# Detect operating system.
message(STATUS "We are on a ${CMAKE_SYSTEM_NAME} system")

if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
    set(IS_LINUX 1)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
    set(IS_MACOS 1)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
    set(IS_WINDOWS 1)
endif()

# If build type is not specified set to Release.
if(QOCO_BUILD_TYPE STREQUAL Debug)
    set(QOCO_BUILD_TYPE ${QOCO_BUILD_TYPE})
    add_compile_definitions(QOCO_DEBUG)
    set(CMAKE_C_FLAGS "-g -Wall")

    if(IS_LINUX OR IS_MACOS)
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,undefined")
    endif()
else()
    set(QOCO_BUILD_TYPE Release)
    set(CMAKE_C_FLAGS "-O3 -Wall")
endif()

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure/qoco_config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/include/qoco_config.h @ONLY)

message(STATUS "Build Type: " ${QOCO_BUILD_TYPE})
message(STATUS "Build Flags: " ${CMAKE_C_FLAGS})

# Set integer size to 32 bits.
message(STATUS "Using 32 bit integers")
set(QDLDL_LONG OFF)

# Option for single precision floating point.
option(QOCO_SINGLE_PRECISION "Use single precision (float) instead of double precision" OFF)

# Set floating points precision.
if(QOCO_SINGLE_PRECISION)
    message(STATUS "Using single precision floating point")
    add_compile_definitions(QOCO_SINGLE_PRECISION)
    set(QDLDL_FLOAT ON CACHE BOOL "Use float numbers instead of doubles" FORCE)
else()
    message(STATUS "Using double precision floating point")
endif()

# Add -fPIC option explicity.
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# Add qdldl.
set(QDLDL_BUILD_STATIC_LIB OFF CACHE BOOL "Do not build qdldl static library" FORCE)
set(QDLDL_BUILD_SHARED_LIB OFF CACHE BOOL "Do not build qdldl shared library" FORCE)
set(QDLDL_BUILD_DEMO_EXE OFF CACHE BOOL "Do not build qdldl demo" FORCE)
set(QDLDL_UNITTESTS OFF CACHE BOOL "Do not build qdldl unit tests" FORCE)
add_subdirectory(lib/qdldl)

# Add amd
add_subdirectory(lib/amd)

# Are we building for matlab. If so we will define printf as mexPrintf to print into matlab terminal.
if(${MATLAB})
    add_compile_definitions(MATLAB)
endif()

set_property(GLOBAL APPEND PROPERTY QOCO_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/qoco_api.c"
    ${CMAKE_CURRENT_SOURCE_DIR}/src/input_validation.c
    ${CMAKE_CURRENT_SOURCE_DIR}/src/common_linalg.c
    ${CMAKE_CURRENT_SOURCE_DIR}/src/kkt.c
    ${CMAKE_CURRENT_SOURCE_DIR}/src/qoco_status.c
    ${CMAKE_CURRENT_SOURCE_DIR}/src/equilibration.c
    ${CMAKE_CURRENT_SOURCE_DIR}/src/qoco_utils.c
)

set_property(GLOBAL APPEND PROPERTY QOCO_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/qoco.h"
    ${CMAKE_CURRENT_SOURCE_DIR}/include/qoco_api.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/input_validation.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/qoco_linalg.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/kkt.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/cone.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/qoco_status.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/equilibration.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/enums.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/definitions.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/structs.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/timer.h
    ${CMAKE_CURRENT_SOURCE_DIR}/include/qoco_utils.h
)

set_property(GLOBAL APPEND PROPERTY QOCO_INCLUDE
    ${PROJECT_SOURCE_DIR}/include
    ${PROJECT_SOURCE_DIR}/lib/amd
    ${PROJECT_SOURCE_DIR}/lib/qdldl/include
)

# Include timer file depending on OS.
if(IS_LINUX)
    set_property(GLOBAL APPEND PROPERTY QOCO_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/timer_linux.c)
elseif(IS_MACOS)
    set_property(GLOBAL APPEND PROPERTY QOCO_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/timer_macos.c)
elseif(IS_WINDOWS)
    set_property(GLOBAL APPEND PROPERTY QOCO_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/timer_windows.c)
endif()

# Add algebra (must be done after defining qoco_sources and qoco_headers, since we append to these varibles)
if(NOT DEFINED QOCO_ALGEBRA_BACKEND)
    set(QOCO_ALGEBRA_BACKEND "builtin" CACHE STRING "The Algebra to use")
endif()
message(STATUS "Using ${QOCO_ALGEBRA_BACKEND} algebra")

# Enable CUDA if using CUDA backend
if(${QOCO_ALGEBRA_BACKEND} STREQUAL "cuda")
    enable_language(CUDA)
endif()

# QOCO_CUDA_LIBS is set here if needed.
add_subdirectory(algebra)

get_property(qoco_sources GLOBAL PROPERTY QOCO_SOURCES)
get_property(qoco_headers GLOBAL PROPERTY QOCO_HEADERS)
get_property(qoco_include GLOBAL PROPERTY QOCO_INCLUDE)

# Get CUDA libraries if using CUDA backend.
get_property(qoco_cuda_libs GLOBAL PROPERTY QOCO_CUDA_LIBS)

# Select cone implementation based on backend
if(${QOCO_ALGEBRA_BACKEND} STREQUAL "cuda")
    list(APPEND qoco_sources ${CMAKE_CURRENT_SOURCE_DIR}/src/cone.cu)
else()
    list(APPEND qoco_sources ${CMAKE_CURRENT_SOURCE_DIR}/src/cone.c)
endif()

# Build qoco shared library.
add_library(qoco SHARED)
target_link_libraries(qoco qdldlobject amd)
# CUDA libraries are loaded dynamically via dlopen() in cudss_setup()
if(IS_LINUX OR IS_MACOS)
    target_link_libraries(qoco dl)
endif()

if(IS_LINUX OR IS_MACOS)
    target_link_libraries(qoco m)
endif()

target_include_directories(qoco PUBLIC ${qoco_include})
target_sources(qoco PRIVATE ${qoco_sources})

# Build qoco static library.
add_library(qocostatic STATIC)
target_link_libraries(qocostatic qdldlobject amd)
# CUDA libraries are loaded dynamically via dlopen() in cudss_setup()
if(IS_LINUX OR IS_MACOS)
    target_link_libraries(qocostatic dl)
endif()

if(IS_LINUX OR IS_MACOS)
    target_link_libraries(qocostatic m)
endif()

target_include_directories(qocostatic PUBLIC ${qoco_include})
target_sources(qocostatic PRIVATE ${qoco_sources})

# Build qoco demo.
if(BUILD_QOCO_DEMO)
    add_executable(qoco_demo ${PROJECT_SOURCE_DIR}/examples/qoco_demo.c)
    target_include_directories(qoco_demo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
    target_link_libraries(qoco_demo qocostatic)
endif()

# Build benchmark runner.
if(BUILD_QOCO_BENCHMARK_RUNNER)
    add_executable(benchmark_runner ${PROJECT_SOURCE_DIR}/benchmarks/benchmark_runner.c)
    target_include_directories(benchmark_runner PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
    target_link_libraries(benchmark_runner qocostatic)
endif()

# Test definitions.
if(ENABLE_TESTING)
    configure_file(CMakeLists.txt.in
        googletest-download/CMakeLists.txt)
    execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download)
    execute_process(COMMAND ${CMAKE_COMMAND} --build .
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download)
    add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
        ${CMAKE_BINARY_DIR}/googletest-build)
    enable_testing()
    add_subdirectory(tests)
endif()

install(
    TARGETS qocostatic
    EXPORT qocostatic
    LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
    ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
    RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
)
install(
    TARGETS qoco
    EXPORT qoco
    LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
    ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
    RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
)
install(FILES ${qoco_headers}
    DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/qoco"
)