cmake_minimum_required(VERSION 3.16)
project(salmanoff VERSION 0.01.000 LANGUAGES CXX)

include(CMakeDependentOption)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DAPSS.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DebugOpts.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake)

# Set C++ standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Build type
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Debug FORCE)
endif()

# Compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")

# Mind oscillator configuration
set(MIND_VOSCILLATOR_PERIOD_MS 33 CACHE STRING "Mind's virtual osc clock rate (ms)")
if(NOT MIND_VOSCILLATOR_PERIOD_MS GREATER 0)
    message(FATAL_ERROR "MIND_VOSCILLATOR_PERIOD_MS must be a positive integer > 0")
endif()
math(EXPR MIND_VOSCILLATOR_FREQ_MS "1000 / ${MIND_VOSCILLATOR_PERIOD_MS}")

# Device manager reattacher configuration
set(MRNTT_DEVMGR_REATTACHER_PERIOD_MS 2000
	CACHE STRING "Device manager reattacher period (ms)")
if(NOT MRNTT_DEVMGR_REATTACHER_PERIOD_MS GREATER 0)
    message(FATAL_ERROR
		"MRNTT_DEVMGR_REATTACHER_PERIOD_MS must be a positive integer > 0")
endif()

# Stimulus buffer frame period configuration
set(STIMBUFF_FRAME_PERIOD_MS 33
	CACHE STRING "Stimulus buffer frame period (ms)")
if(NOT STIMBUFF_FRAME_PERIOD_MS GREATER 0)
    message(FATAL_ERROR
		"STIMBUFF_FRAME_PERIOD_MS must be a positive integer > 0")
endif()

# Stimulus buffer frame retry delay configuration
set(STIMBUFF_FRAME_RETRY_DELAY_MS 1
	CACHE STRING "Stimulus buffer frame retry delay (ms)")
if(NOT STIMBUFF_FRAME_RETRY_DELAY_MS GREATER 0)
    message(FATAL_ERROR
		"STIMBUFF_FRAME_RETRY_DELAY_MS must be a positive integer > 0")
endif()

# World thread configuration
option(WORLD_USE_BODY_THREAD
	"Use body thread for world component instead of separate world thread" OFF)

# Test configuration
option(ENABLE_TESTS "Enable building tests" OFF)

# Set the debug locks variable for config.h
if(ENABLE_DEBUG_LOCKS)
    set(CONFIG_ENABLE_DEBUG_LOCKS TRUE)
endif()

# Set the debug trace callables variable for config.h
if(ENABLE_DEBUG_TRACE_CALLABLES)
    set(CONFIG_DEBUG_TRACE_CALLABLES TRUE)
    # Suppress frame-address warnings when using __builtin_return_address()
	# with values above 0 (See callableTracer.h).
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-frame-address")
endif()

# Set the world thread variable for config.h
if(WORLD_USE_BODY_THREAD)
    set(CONFIG_WORLD_USE_BODY_THREAD TRUE)
endif()

# Set the timeout variable for config.h
set(CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS ${DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS})
# Set the stimulus buffer frame period variable for config.h
set(CONFIG_STIMBUFF_FRAME_PERIOD_MS ${STIMBUFF_FRAME_PERIOD_MS})
# Set the stimulus buffer frame retry delay variable for config.h
set(CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS ${STIMBUFF_FRAME_RETRY_DELAY_MS})

# Configure config.h
configure_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/include/config.h.in
    ${CMAKE_CURRENT_BINARY_DIR}/include/config.h
    @ONLY
)

# Include directories
include_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/smocore/include
    ${CMAKE_CURRENT_BINARY_DIR}/include
)

# Find core dependencies
# We cannot use header-only Boost.Asio because we need both our dlopen()'d
# libraries and the main binary to refer to the same instances of boost::asio's
# metadata. If we use header-only Boost.Asio, each dlopen()'d library will have
# its own copy of boost::asio's metadata, which will cause a segfault if
# boost::asio objects are used inside of a dlopen()'d library.
#
# Honestly, I never liked this whole "header-only" idea so I'm happy to be rid
# of it.
#
# Tell CMake we're linking against the shared library (not header-only)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_HEADER_ONLY OFF)
find_package(Boost REQUIRED COMPONENTS system log)
find_package(PkgConfig REQUIRED)
find_package(FLEX REQUIRED)
find_package(BISON REQUIRED)

# Need dlopen() and dlsym()
find_library(DL_LIBRARY NAMES dl ldl)
if(NOT DL_LIBRARY)
    message(FATAL_ERROR "Dynamic linking library (libdl/libldl) not found")
endif()

# Add third-party dependencies
if(ENABLE_TESTS)
    add_subdirectory(third_party)
endif()
add_subdirectory(compile)
# Add core components
add_subdirectory(smocore)
add_subdirectory(commonLibs)
add_subdirectory(stimBuffApis)
add_subdirectory(wilzorApis)
add_subdirectory(devices)

# Main executable
add_executable(salmanoff main.cpp)
target_link_libraries(salmanoff
	Boost::system Boost::log
    smocore
    ${DL_LIBRARY}
    attachmentSupport
)

# Verify Boost dynamic dependencies after build
add_custom_command(TARGET salmanoff POST_BUILD
	COMMAND ${CMAKE_COMMAND} -DVERIFY_FILE="$<TARGET_FILE:salmanoff>"
		-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake
	COMMENT "Verifying Boost dynamic dependencies for salmanoff"
)

# Add all registered DAPSS targets as dependencies
add_all_daps_dependencies()

# Add tests if enabled
if(ENABLE_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

install(TARGETS salmanoff DESTINATION bin)

# Install device configuration files (preprocessed .daps files)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/devices/
    DESTINATION share/salmanoff/devices
    FILES_MATCHING PATTERN "*.daps"
)

# Install documentation
install(FILES README.md DESTINATION share/doc/salmanoff)
install(FILES LICENSE DESTINATION share/doc/salmanoff)

# Install example configurations if they exist
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples")
    install(DIRECTORY examples/ DESTINATION share/salmanoff/examples)
endif()

# Include CPack configuration
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CPackConfig.cmake)
include(CPack)
