cmake_minimum_required(VERSION 3.21)
cmake_policy(SET CMP0048 NEW) # enable project VERSION
cmake_policy(SET CMP0056 NEW) # honor link flags in try_compile()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

project(lager VERSION 0.1.0)

if(MSVC)
  set(CMAKE_CXX_STANDARD 20)
  add_compile_options(/Wall /Zc:preprocessor /bigobj)
else()
  set(CMAKE_CXX_STANDARD 20)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
endif()

set(CMAKE_EXPORT_COMPILE_COMMANDS on)
set(CMAKE_CXX_EXTENSIONS off)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qunused-arguments")
endif()

include(GNUInstallDirs)

option(lager_BUILD_TESTS "Build tests" "${PROJECT_IS_TOP_LEVEL}")
option(lager_BUILD_FAILURE_TESTS "Build failure tests" "${PROJECT_IS_TOP_LEVEL}")
option(lager_BUILD_EXAMPLES "Build examples" "${PROJECT_IS_TOP_LEVEL}")
option(lager_BUILD_DEBUGGER_EXAMPLES "Build examples that showcase the web based debugger" "${PROJECT_IS_TOP_LEVEL}")
option(lager_BUILD_DOCS "Build docs" "${PROJECT_IS_TOP_LEVEL}")
option(lager_EMBED_RESOURCES_PATH "Embed installation paths for easier, non-portable resource location" ON)
option(lager_DISABLE_STORE_DEPENDENCY_CHECKS "Disable compile-time checks for store dependencies" OFF)
option(lager_ENABLE_EXCEPTIONS "Always enable exceptions regardless of detected compiler support" OFF)
option(lager_DISABLE_EXCEPTIONS "Always disable exceptions regardless of detected compiler support" OFF)

if (lager_ENABLE_EXCEPTIONS AND lager_DISABLE_EXCEPTIONS)
  message(FATAL_ERROR "Cannot both enable and disable exceptions")
endif()

if (NOT lager_EMBED_RESOURCES_PATH AND lager_BUILD_EXAMPLES)
  message(FATAL_ERROR "Examples require embedded resources path")
endif()

if (MSVC AND NOT lager_DISABLE_STORE_DEPENDENCY_CHECKS)
  message(WARNING "compile-time checks for store dependencies are currently not supported with msvc")
  set(lager_DISABLE_STORE_DEPENDENCY_CHECKS ON)
endif()

if (NOT lager_BUILD_EXAMPLES AND lager_BUILD_DEBUGGER_EXAMPLES)
    message(WARNING "examples using the web-based debugger are disabled when examples are disabled")
    set(lager_BUILD_DEBUGGER_EXAMPLES OFF)
endif()

find_program(CCACHE ccache)
if (CCACHE)
  message(STATUS "Using ccache: ${CCACHE}")
  set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
  set(CMAKE_CXX_LINK_LAUNCHER ${CCACHE})
else()
  message(STATUS "Could not find ccache")
endif()

#  Targets
#  =======

# the library
add_library(lager INTERFACE)
add_library(lager::lager ALIAS lager)
target_include_directories(lager INTERFACE
  $<BUILD_INTERFACE:${lager_BINARY_DIR}/>
  $<BUILD_INTERFACE:${lager_SOURCE_DIR}/>
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)

if(lager_DISABLE_STORE_DEPENDENCY_CHECKS)
  message(STATUS "Disabling compile-time checks for store dependencies")
  target_compile_definitions(lager INTERFACE LAGER_DISABLE_STORE_DEPENDENCY_CHECKS)
endif()

if(lager_ENABLE_EXCEPTIONS)
  message(STATUS "Explicitly enabling exceptions")
  target_compile_definitions(lager INTERFACE LAGER_USE_EXCEPTIONS)
endif()

if(lager_DISABLE_EXCEPTIONS)
  message(STATUS "Explicitly disabling exceptions")
  target_compile_definitions(lager INTERFACE LAGER_NO_EXCEPTIONS)
endif()

target_sources(lager PUBLIC FILE_SET HEADERS
  FILES
    lager/commit.hpp
    lager/config.hpp
    lager/constant.hpp
    lager/context.hpp
    lager/cursor.hpp
    lager/debug/debugger.hpp
    lager/debug/http_server.hpp
    lager/debug/tree_debugger.hpp
    lager/deps.hpp
    lager/detail/access.hpp
    lager/detail/lens_nodes.hpp
    lager/detail/merge_nodes.hpp
    lager/detail/no_value.hpp
    lager/detail/nodes.hpp
    lager/detail/signal.hpp
    lager/detail/smart_lens.hpp
    lager/detail/xform_nodes.hpp
    lager/effect.hpp
    lager/event_loop/boost_asio.hpp
    lager/event_loop/manual.hpp
    lager/event_loop/qml.hpp
    lager/event_loop/qt.hpp
    lager/event_loop/queue.hpp
    lager/event_loop/safe_queue.hpp
    lager/event_loop/sdl.hpp
    lager/extra/cereal/enum.hpp
    lager/extra/cereal/immer_array.hpp
    lager/extra/cereal/immer_box.hpp
    lager/extra/cereal/immer_flex_vector.hpp
    lager/extra/cereal/immer_map.hpp
    lager/extra/cereal/immer_set.hpp
    lager/extra/cereal/immer_table.hpp
    lager/extra/cereal/immer_vector.hpp
    lager/extra/cereal/inline.hpp
    lager/extra/cereal/json.hpp
    lager/extra/cereal/optional_nvp.hpp
    lager/extra/cereal/struct.hpp
    lager/extra/cereal/tuple.hpp
    lager/extra/cereal/variant_with_name.hpp
    lager/extra/derive.hpp
    lager/extra/derive/cereal.hpp
    lager/extra/derive/eq.hpp
    lager/extra/derive/hana.hpp
    lager/extra/derive/hash.hpp
    lager/extra/derive/size_check.hpp
    lager/extra/enum.hpp
    lager/extra/qt.hpp
    lager/extra/struct.hpp
    lager/extra/thunk.hpp
    lager/future.hpp
    lager/lens.hpp
    lager/lenses.hpp
    lager/lenses/at.hpp
    lager/lenses/at_or.hpp
    lager/lenses/attr.hpp
    lager/lenses/optional.hpp
    lager/lenses/tuple.hpp
    lager/lenses/unbox.hpp
    lager/lenses/variant.hpp
    lager/reader.hpp
    lager/resources_path.hpp.in
    lager/sensor.hpp
    lager/setter.hpp
    lager/state.hpp
    lager/store.hpp
    lager/tags.hpp
    lager/util.hpp
    lager/watch.hpp
    lager/with.hpp
    lager/writer.hpp
)

install(TARGETS lager EXPORT LagerConfig FILE_SET HEADERS)

# requirements for tests and examples
if (lager_BUILD_TESTS OR lager_BUILD_EXAMPLES)
  find_package(Boost 1.56 CONFIG)
  find_package(Threads REQUIRED)
  find_package(Immer REQUIRED)
  find_package(Zug REQUIRED)
endif()

# the library, local development target
if(lager_BUILD_TESTS)
  find_package(Catch2 REQUIRED)

  add_library(lager-dev INTERFACE)
  target_include_directories(lager-dev SYSTEM INTERFACE
    ${lager_SOURCE_DIR}
    ${Boost_INCLUDE_DIR}
  )
  target_link_libraries(lager-dev INTERFACE lager
    ${Boost_LIBRARIES}
    ${CMAKE_THREAD_LIBS_INIT}
    immer
    zug
  )
  if (ENABLE_COVERAGE)
    target_compile_options(lager-dev INTERFACE "--coverage")
    target_link_libraries(lager-dev INTERFACE "--coverage")
  endif()
  if (ENABLE_ASAN)
    target_compile_options(lager-dev INTERFACE
      -fno-omit-frame-pointer -fsanitize=address)
    target_link_options(lager-dev INTERFACE -fsanitize=address)
  endif()

  enable_testing()
  add_custom_target(check
    COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
    COMMENT "Build and run all the tests and examples.")

  add_subdirectory(test)
endif()

# the library, with http debugger
if (lager_BUILD_EXAMPLES)

  add_library(lager-example INTERFACE)
  target_include_directories(lager-example INTERFACE
     $<BUILD_INTERFACE:${lager_BINARY_DIR}/>
     $<BUILD_INTERFACE:${lager_SOURCE_DIR}/>
     $<INSTALL_INTERFACE:include>
    ${Boost_INCLUDE_DIR}
  )
  target_link_libraries(lager-example INTERFACE
    lager
    immer
    ${CMAKE_THREAD_LIBS_INIT}
    ${Boost_LIBRARIES}
  )

  install(TARGETS lager-example EXPORT LagerConfig)

  # examples with the http debugger
  if (lager_BUILD_DEBUGGER_EXAMPLES)
    add_library(lager-debugger-example INTERFACE)
    target_link_libraries(lager-debugger-example INTERFACE
      lager-example)

    add_custom_target(gui ALL
      COMMAND make
      WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/resources/gui"
      COMMENT "Build debugger web UI")

    install(TARGETS lager-debugger-example  EXPORT LagerConfig)

    install(FILES
      resources/gui/gui.js
      resources/gui/gui.css
      resources/gui/index.html
      DESTINATION "${CMAKE_INSTALL_DATADIR}/lager/gui")
    endif()

  add_subdirectory(example)
endif()

if (lager_BUILD_DOCS)
  add_subdirectory(doc)
endif()

# Also configure and install the resources_path.hpp file if wanted
if (lager_EMBED_RESOURCES_PATH)
  set(LAGER_PREFIX_PATH ${CMAKE_INSTALL_PREFIX})
  configure_file(lager/resources_path.hpp.in
    ${CMAKE_BINARY_DIR}/include/lager/resources_path.hpp)
  target_include_directories(lager
    INTERFACE $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>)
  install(FILES ${CMAKE_BINARY_DIR}/include/lager/resources_path.hpp DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/lager")
endif()

install(EXPORT LagerConfig DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Lager")
install(DIRECTORY lager DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" PATTERN "*.hpp")
