cmake_minimum_required(VERSION 3.12)

project(libsurvive
	LANGUAGES C CXX
	VERSION 0.3)
include(CheckIncludeFile)
include (CheckSymbolExists)

SET(CORE_BUILD_DEFAULT OFF)
if(ANDROID)
  SET(CORE_BUILD_DEFAULT ON)
endif()

macro(VERBOSE_OPTION ARG DESC)
    option(${ARG} ${DESC} ${ARGN})
    message("-- Option: ${ARG}: ${ARGN}")
endmacro()

VERBOSE_OPTION(DO_CORE_BUILD "Only build essentials by default" ${CORE_BUILD_DEFAULT})
VERBOSE_OPTION(BUILD_STATIC "Build as static library" ${DO_CORE_BUILD})

VERBOSE_OPTION(USE_SINGLE_PRECISION "Use float instead of double" OFF)
VERBOSE_OPTION(ENABLE_WARNINGS_AS_ERRORS "Use to flag all warnings as errors" OFF)
SET(IS_WINDOWS OFF)
IF(WIN32)
    SET(IS_WINDOWS ON)
ENDIF()
VERBOSE_OPTION(USE_HIDAPI "Use HIDAPI instead of libusb" ${IS_WINDOWS})
VERBOSE_OPTION(USE_ASAN "Use address sanitizer" OFF)
VERBOSE_OPTION(USE_MSAN "Use memory sanitizer" OFF)
VERBOSE_OPTION(ENABLE_TESTS "Enable build / execution of tests" OFF)
VERBOSE_OPTION(USE_HEX_FLOAT_PRINTF "Use hex floats when recording" OFF)

VERBOSE_OPTION(USE_OPENCV "Use opencv proper for math operations" OFF)
VERBOSE_OPTION(USE_COLUMN_MAJOR_MATRICES "Use column major matrices for math operations" OFF)
VERBOSE_OPTION(USE_CPU_TUNE "Compile for your specific CPU" OFF)

find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
    set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) # Less useful to do it for linking, see edit2
endif(CCACHE_FOUND)

if(USE_SINGLE_PRECISION)
  add_compile_definitions(CN_USE_FLOAT)
endif()

IF(WIN32)
    find_program(NUGET nuget)
    if(NOT NUGET)
      file(DOWNLOAD https://dist.nuget.org/win-x86-commandline/v5.6.0/nuget.exe "${CMAKE_BINARY_DIR}/nuget.exe"
        EXPECTED_MD5 3f8a53a524c54cf2e4efb7aea370a977 STATUS status LOG log)
      find_program(NUGET nuget HINTS "${CMAKE_BINARY_DIR}" )
    endif()
    message("Nuget is found at ${NUGET}")

    if (CMAKE_SIZEOF_VOID_P MATCHES 8)
      set(WIN_PLATFORM "x64")
      set(WIN_PLATFORMx "x64")
    else(CMAKE_SIZEOF_VOID_P MATCHES 8)
      set(WIN_PLATFORM "win32")
      set(WIN_PLATFORMx "x32")
    endif(CMAKE_SIZEOF_VOID_P MATCHES 8)

ENDIF()

VERBOSE_OPTION(USE_OPENBLAS "Use OpenBLAS" ${USE_OPENBLAS_DEFAULT})
VERBOSE_OPTION(BUILD_LH1_SUPPORT "Build LH1 support" ON)

if(BUILD_LH1_SUPPORT)
  add_definitions(-DBUILD_LH1_SUPPORT)
endif()

if(USE_HEX_FLOAT_PRINTF)
  add_definitions(-DSURVIVE_HEX_FLOATS)
endif()

set(CMAKE_REQUIRED_LIBRARIES z)

# Check Symbol exists doesn't like -Werror=pedantic
include(CheckCSourceCompiles)
check_c_source_compiles("
    #include <zlib.h>
    #include <stdarg.h>
    int main(void) {
      va_list args;
      gzvprintf(0, 0, args);
      return 0;
    }
  "
  HAVE_GZVPRINTF
)
if(NOT HAVE_GZVPRINTF)
  add_definitions(-DHAVE_NO_GZVPRINTF)
endif()

SET(SURVIVE_LIBRARY_TYPE SHARED)
if(BUILD_STATIC)
  SET(SURVIVE_LIBRARY_TYPE STATIC)
  add_definitions(-DSURVIVE_DISABLE_PLUGINS)
endif()

set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads)

check_include_file("gattlib.h" HAVE_GATTLIB_H)
VERBOSE_OPTION(BUILD_GATT_SUPPORT "Whether or not to include gatt support for the basestations -- requires gattlib (https://github.com/labapart/gattlib)" ${HAVE_GATTLIB_H})


check_include_file("zlib.h" HAVE_ZLIB_H)
IF(NOT HAVE_ZLIB_H OR ANDROID)
  add_definitions(-DNOZLIB)
ENDIF()

SET(NOT_CORE_BUILD ON)
if(DO_CORE_BUILD)
  SET(NOT_CORE_BUILD OFF)
endif()
VERBOSE_OPTION(BUILD_APPLICATIONS "Build the default libsurvive applications" ${NOT_CORE_BUILD})

#include_directories(${CMAKE_INSTALL_PREFIX}/include)
#link_directories(${CMAKE_INSTALL_PREFIX}/lib)

IF(UNIX)
  set(SHARED_FLAGS "-fPIC -Wall -Wno-unused-variable -Wno-switch -Wno-parentheses -Wno-missing-braces -Werror=return-type -fvisibility=hidden -Werror=vla -fno-math-errno -Werror=pointer-arith")

  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SHARED_FLAGS} -std=gnu99 -Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=missing-field-initializers ")
  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SHARED_FLAGS} -std=c++14")

  if(USE_CPU_TUNE)
      add_compile_options("-mtune=native")
  endif()

  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic")

	if(ENABLE_WARNINGS_AS_ERRORS)
	  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
	endif()
	      	
	if(USE_ASAN)
	  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=undefined")
	  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize=undefined")
	endif()

	if(USE_MSAN)
	  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=memory -fsanitize-memory-track-origins -fsanitize=undefined")
	  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memory  -fsanitize-memory-track-origins -fsanitize=undefined")
	endif()	    
	
ELSEIF(WIN32)
  try_compile(HAS_GENERIC_SUPPORT ${CMAKE_BINARY_DIR} "${CMAKE_SOURCE_DIR}/useful_files/generic_test.c" OUTPUT_VARIABLE COMPILE_TEST             
             COMPILE_DEFINITIONS "/std:c11")  
  message("Has generic support ${HAS_GENERIC_SUPPORT}")
  add_compile_options(/wd4244 /wd4996 /wd4018 /wd4101 /wd4477 /wd4068 /wd4217)
  if(NOT HAS_GENERIC_SUPPORT)
    add_definitions("-DCOMPILER_HAS_NO_GENERIC_SUPPORT")
  else()
    add_compile_options(/std:c11)
  endif()
  set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
  set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
endif()

include_directories(redist include/libsurvive include)

if(USE_SINGLE_PRECISION)
    add_definitions(-DUSE_FLOAT)
endif()

IF(ENABLE_TESTS)
  enable_testing()
ENDIF()

 find_library(OPENVR_LIBRARIES
              NAMES
              openvr_api
              openvr_api64
              PATH_SUFFIXES
              osx32
              linux64
              PATHS
              "C:/Program Files (x86)/OpenVRSDK/lib"
)

find_path(OPENVR_INCLUDE_PATH
          NAMES openvr.h openvr_driver.h
          PATH_SUFFIXES
          openvr
          "C:/Program Files (x86)/OpenVRSDK/include"
)

SET(CMAKE_SKIP_BUILD_RPATH  FALSE)
SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

add_subdirectory(libs/cnkalman)

add_subdirectory(redist)
add_subdirectory(src)
add_subdirectory(tools)

SET(SURVIVE_EXECUTABLES survive-cli api_example sensors-readout survive-solver survive-buttons)
foreach(executable ${SURVIVE_EXECUTABLES})
  VERBOSE_OPTION(ENABLE_${executable} "Build ${executable}" ${BUILD_APPLICATIONS})

  if(${executable} STREQUAL "survive-cli" AND WIN32)
	set(ENABLE_survive-cli ON)
  endif()

  if(ENABLE_${executable})
    add_executable(${executable} ${executable}.c )
    target_link_libraries(${executable} survive ${${executable}_ADDITIONAL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
    set_target_properties(${executable} PROPERTIES FOLDER "apps")
    foreach(plugin ${SURVIVE_BUILT_PLUGINS})
      add_dependencies(${executable} ${plugin})
    endforeach()
    install(TARGETS ${executable} DESTINATION bin/${CMAKE_GENERATOR_PLATFORM})
  endif()
endforeach()

IF(WIN32)
  # This property needs to be set on a target at the root; doesn't matter which
  set_target_properties( survive-cli PROPERTIES VS_USER_PROPS "$(SolutionDir)\\packages\\OpenBLAS.0.2.14.1\\build\\native\\openblas.targets" )
endif()

SET(SURVIVE_WEBSOCKETD ${CMAKE_CURRENT_SOURCE_DIR}/useful_files/survive-websocketd)
if(WIN32)
	SET(SURVIVE_WEBSOCKETD ${CMAKE_CURRENT_SOURCE_DIR}/useful_files/survive-websocketd.ps1)
endif()

IF(TARGET survive-cli)
  add_custom_command(TARGET survive-cli POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy ${SURVIVE_WEBSOCKETD} $<TARGET_FILE_DIR:survive-cli>
    )
  install(PROGRAMS ${SURVIVE_WEBSOCKETD} DESTINATION bin/${CMAKE_GENERATOR_PLATFORM})
endif()

find_program(DOTNET dotnet)
if(DOTNET)
  SET(DOTNET_RUNTIME_FLAGS "")
  if(WIN32)
	if(CMAKE_SIZEOF_VOID_P EQUAL 8)
		#SET(DOTNET_RUNTIME_FLAGS "--runtime;win-x64")

	elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
		#SET(DOTNET_RUNTIME_FLAGS "--runtime;win-x86")
	endif()
  endif()
  
	message("${DOTNET} build -c Release ${DOTNET_RUNTIME_FLAGS}")
  execute_process(
    COMMAND ${DOTNET} build -c Release ${DOTNET_RUNTIME_FLAGS}
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bindings/cs/)

  file(GLOB_RECURSE CS_BINDINGS
    "${CMAKE_CURRENT_SOURCE_DIR}/bindings/cs/libsurvive.net/bin/Release/*.dll")

  install(FILES ${CS_BINDINGS} DESTINATION "bin")

  if(WIN32)
    file(GLOB_RECURSE CS_DEMO
      "${CMAKE_CURRENT_SOURCE_DIR}/bindings/cs/Demo/bin/Release/Demo.*")

    install(FILES ${CS_DEMO} DESTINATION "bin")
  endif()
endif()

if(EXISTS ${CMAKE_INSTALL_PREFIX}/etc/bash_completion.d)
  install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/survive_autocomplete.sh DESTINATION "${CMAKE_INSTALL_PREFIX}/etc/bash_completion.d")
endif()

install(DIRECTORY include/libsurvive DESTINATION include)

file(GLOB REDIST_HEADERS
  "redist/*.h"
)
install(FILES ${REDIST_HEADERS} DESTINATION include/libsurvive/redist)

if(NOT WIN32 AND PYTHON_GENERATED_DIR)

  find_program(CTYPESGEN ctypesgen)

  if(CTYPESGEN)
    message("-- Building python bindings file to ${PYTHON_GENERATED_DIR}")
    message("-- ctypesgen found at ${CTYPESGEN}") 
    get_property(include_directories DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
    set(INCLUDE_FLAGS "")
    foreach(include_directory ${include_directories})
      list(APPEND INCLUDE_FLAGS "-I${include_directory}")
    endforeach()

    add_custom_target(pysurvive ALL COMMAND ${Python3_EXECUTABLE} ${CTYPESGEN} ${CMAKE_SOURCE_DIR}/include/libsurvive/*.h ${INCLUDE_FLAGS} --no-macros -L$<TARGET_FILE_DIR:survive> -lsurvive
            --strip-prefix=survive_ -P Survive -o ${PYTHON_GENERATED_DIR}pysurvive_generated.py)
  endif()

endif()

include(GNUInstallDirs)
configure_file(survive.pc.in survive.pc @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/survive.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
