#  Copyright (C) GridGain Systems. All Rights Reserved.
#  _________        _____ __________________        _____
#  __  ____/___________(_)______  /__  ____/______ ____(_)_______
#  _  / __  __  ___/__  / _  __  / _  / __  _  __ `/__  / __  __ \
#  / /_/ /  _  /    _  /  / /_/ /  / /_/ /  / /_/ / _  /  _  / / /
#  \____/   /_/     /_/   \_,__/   \____/   \__,_/  /_/   /_/ /_/

cmake_minimum_required(VERSION 3.18)

include(CMakePackageConfigHelpers)

file(READ _version.txt IGNITE_VERSION_BASE)
if(NOT DEFINED IGNITE_VERSION_BASE)
  set(IGNITE_VERSION_BASE 9.0.0.0)
  message(WARNING "Failed to read version from _version.txt file. Using default version \"${IGNITE_VERSION_BASE}\".")
endif()

project(gridgain9 VERSION ${IGNITE_VERSION_BASE} LANGUAGES CXX)

message(STATUS "Project version full: ${CMAKE_PROJECT_VERSION}")

if(CMAKE_PROJECT_VERSION_TWEAK)
    set(IGNITE_VERSION_4 "${CMAKE_PROJECT_VERSION}")
else()
    set(IGNITE_VERSION_4 "${CMAKE_PROJECT_VERSION}.0")
endif()
set(IGNITE_VERSION_3 "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}")
message(STATUS "Project version short: ${IGNITE_VERSION_3}")

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

option(USE_LOCAL_DEPS "Use local dependencies." OFF)
option(ENABLE_CLIENT "Build Ignite.C++ Client module" ON)
option(ENABLE_EXTENSION "Build Ignite Extension module" OFF)
option(ENABLE_ODBC "Build Ignite ODBC driver module" OFF)
option(ENABLE_PROTOCOL "Build Ignite Protocol library" OFF)
option(ENABLE_NETWORK "Build Ignite Network library" OFF)
option(ENABLE_TESTS "Build Ignite.C++ tests" OFF)
option(ENABLE_COMPATIBILITY_TESTS "Build Ignite.C++ compatibility tests" OFF)
option(ENABLE_BENCHMARKS "Build Ignite.C++ micro-benchmarks" OFF)
option(ENABLE_ADDRESS_SANITIZER "If address sanitizer is enabled" OFF)
option(ENABLE_UB_SANITIZER "If undefined behavior sanitizer is enabled" OFF)
option(WARNINGS_AS_ERRORS "Treat warning as errors" OFF)
option(INSTALL_IGNITE_FILES "Install Ignite files" ON)
option(ENABLE_IWYU "Enable include-what-you-use tool" OFF)
option(ENABLE_CLANG_TIDY "Enable clang-tidy static analyzer" OFF)
option(ENABLE_CPPCHECK "Enable cppcheck static analyzer" OFF)
option(ENABLE_FUZZER "Build libFuzzer harnesses (requires Clang)" OFF)
option(ENABLE_PACKAGING "Enable cmake packaging via CPack" ON)

list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR})

get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)

set(IGNITE_CMAKE_TOP_DIR ${CMAKE_CURRENT_LIST_DIR})

include(dependencies)

if(ENABLE_IWYU)
    find_program(IWYU_PATH NAMES include-what-you-use iwyu REQUIRED)
    set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${IWYU_PATH})
    set(CMAKE_C_INCLUDE_WHAT_YOU_USE ${IWYU_PATH})
endif()

if(ENABLE_CLANG_TIDY)
    find_program(CLANG_TIDY_BIN clang-tidy REQUIRED)
    message(STATUS "clang-tidy: ${CLANG_TIDY_BIN}")
    set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_BIN})
    set(CMAKE_C_CLANG_TIDY ${CLANG_TIDY_BIN})
    set(CMAKE_CXX_CLANG_TIDY_EXPORT_FIXES_DIR "clang-tidy-reports-dir")
endif()

if(ENABLE_CPPCHECK)
    find_program(CPPCHECK_BIN cppcheck REQUIRED)
    message(STATUS "cppcheck: ${CPPCHECK_BIN}")
    message(STATUS "Please do not use parallel build with cppcheck enabled! Cppcheck writes results to the file and result might be corrupted in case of parallel build.")
    set(CMAKE_CXX_CPPCHECK ${CPPCHECK_BIN} --enable=all --xml --output-file=cppcheck_report.xml)
    add_custom_target(cppcheck_html_report ALL
        COMMAND cppcheck-htmlreport
            --file ${CMAKE_CURRENT_BINARY_DIR}/ignite/tuple/cppcheck_report.xml
            --file ${CMAKE_CURRENT_BINARY_DIR}/ignite/common/cppcheck_report.xml
            $<$<OR:$<BOOL:${ENABLE_CLIENT}>,$<BOOL:${ENABLE_ODBC}>,$<BOOL:${ENABLE_PROTOCOL}>>:--file ${CMAKE_CURRENT_BINARY_DIR}/ignite/protocol/cppcheck_report.xml>
            $<$<OR:$<BOOL:${ENABLE_CLIENT}>,$<BOOL:${ENABLE_ODBC}>,$<BOOL:${ENABLE_PROTOCOL}>>:--file ${CMAKE_CURRENT_BINARY_DIR}/ignite/network/cppcheck_report.xml>
            $<$<BOOL:${ENABLE_TESTS}>:--file ${CMAKE_CURRENT_BINARY_DIR}/tests/client-test/cppcheck_report.xml>
            $<$<BOOL:${ENABLE_TESTS}>:--file ${CMAKE_CURRENT_BINARY_DIR}/tests/fake_server/cppcheck_report.xml>
            $<$<BOOL:${ENABLE_TESTS}>:--file ${CMAKE_CURRENT_BINARY_DIR}/tests/odbc-test/cppcheck_report.xml>
            $<$<BOOL:${ENABLE_TESTS}>:--file ${CMAKE_CURRENT_BINARY_DIR}/tests/test-common/cppcheck_report.xml>
            $<$<BOOL:${ENABLE_ODBC}>:--file ${CMAKE_CURRENT_BINARY_DIR}/ignite/odbc/cppcheck_report.xml>
            $<$<BOOL:${ENABLE_CLIENT}>:--file ${CMAKE_CURRENT_BINARY_DIR}/ignite/client/cppcheck_report.xml>
            --report-dir=${CMAKE_CURRENT_BINARY_DIR}/cppcheck_report
        DEPENDS
            gridgain-tuple
            gridgain-common
            $<$<OR:$<BOOL:${ENABLE_CLIENT}>,$<OR:$<BOOL:${ENABLE_ODBC}>,$<BOOL:${ENABLE_PROTOCOL}>>>:gridgain-protocol>
            $<$<OR:$<BOOL:${ENABLE_CLIENT}>,$<OR:$<BOOL:${ENABLE_ODBC}>,$<BOOL:${ENABLE_PROTOCOL}>>>:gridgain-network>
            $<$<BOOL:${ENABLE_TESTS}>:gridgain-client-test>
            $<$<BOOL:${ENABLE_TESTS}>:gridgain-fake-server>
            $<$<BOOL:${ENABLE_TESTS}>:gridgain-odbc-test>
            $<$<BOOL:${ENABLE_TESTS}>:gridgain-test-common>
            $<$<BOOL:${ENABLE_ODBC}>:gridgain9-odbc>
            $<$<BOOL:${ENABLE_CLIENT}>:gridgain9-client>
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
        VERBATIM
    )
endif()

# Configure build paths.
if (is_multi_config)
    foreach(TYPE ${CMAKE_CONFIGURATION_TYPES})
        string(TOUPPER ${TYPE} UTYPE)

        set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${UTYPE} ${CMAKE_BINARY_DIR}/${TYPE}/lib)
        set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${UTYPE} ${CMAKE_BINARY_DIR}/${TYPE}/lib)
        set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${UTYPE} ${CMAKE_BINARY_DIR}/${TYPE}/bin)
    endforeach()
else()
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
endif()

if (WIN32)
    add_definitions(-DNOMINMAX)
else()
    include(GNUInstallDirs)
endif()
# Replace with the variables from GNUInstallDirs
set(IGNITE_INSTALL_ARCHIVE_DIR lib)
set(IGNITE_INSTALL_LIBRARY_DIR lib)
set(IGNITE_INSTALL_RUNTIME_DIR bin)
set(IGNITE_INSTALL_INCLUDE_DIR include)
set(IGNITE_INSTALL_DATAROOT_DIR share)

set(IGNITE_INCLUDEDIR ${IGNITE_INSTALL_INCLUDE_DIR}/ignite)

if (ENABLE_PACKAGING)
    include(cmake/packaging.cmake)
    include(CPack)
endif()

message(STATUS "IGNITE_INCLUDEDIR=${IGNITE_INCLUDEDIR}")
include(ignite_install_headers)
include(ignite_check_headers)
include(ignite_collect_public_headers)

# Turn on DLL export directives.
add_definitions(-DIGNITE_IMPL)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

if (MSVC)
    # Enable more warnings
    add_compile_options(/W3)
    add_compile_options(/source-charset:utf-8 /execution-charset:utf-8)
    add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS)
endif()

if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
    add_compile_options(-Wall -Wextra -Wno-variadic-macros)

    if (ENABLE_ADDRESS_SANITIZER)
        add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-fsanitize=address>)
        add_link_options($<$<COMPILE_LANGUAGE:CXX>:-fsanitize=address>)
    endif()

    if (ENABLE_UB_SANITIZER)
        add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-fsanitize=undefined>)
        add_link_options($<$<COMPILE_LANGUAGE:CXX>:-fsanitize=undefined>)
    endif()
endif()

if (ENABLE_FUZZER)
    if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        message(FATAL_ERROR "ENABLE_FUZZER requires Clang (libFuzzer is a Clang-only feature)")
    endif()
    add_compile_options(-fsanitize=fuzzer-no-link)
endif()

if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    add_compile_options("-fstandalone-debug")
endif()

if (${WARNINGS_AS_ERRORS})
    if (MSVC)
        add_compile_options(/WX)
    else()
        add_compile_options(-Werror)
    endif()
endif()

# Setup gtest for unit & integration tests.
if (${ENABLE_TESTS})
    include(GoogleTest)
    enable_testing()
endif()

include(ignite_test)

# Accumulated list of all installed public headers (relative to the include
# root, e.g. "ignite/common/big_integer.h"). Each module appends its own
# headers below; ignite_compile_headers.cmake reads this list.
set(IGNITE3_ALL_PUBLIC_HEADERS "")

# Add common libraries along with their unit tests if any.
add_subdirectory(ignite/common)
add_subdirectory(ignite/tuple)

# Add protocol library.
if (${ENABLE_CLIENT} OR ${ENABLE_ODBC} OR ${ENABLE_PROTOCOL})
    add_subdirectory(ignite/protocol)
endif()

# Add network library.
if (${ENABLE_CLIENT} OR ${ENABLE_ODBC} OR ${ENABLE_NETWORK})
    add_subdirectory(ignite/network)
endif()

# Add client library.
if (${ENABLE_CLIENT})
    add_subdirectory(ignite/client)
endif()

# Add extension library.
if (${ENABLE_EXTENSION})
    if (NOT ${ENABLE_CLIENT})
        message(FATAL_ERROR "ENABLE_EXTENSION requires ENABLE_CLIENT")
    endif()
    add_subdirectory(ignite/extension)
endif()

# Add client libraries.
if (${ENABLE_ODBC})
    add_subdirectory(ignite/odbc)
endif()

# Add fuzz harnesses.
if (ENABLE_FUZZER)
    add_subdirectory(tests/fuzz)
endif()

# Add integration tests.
if (${ENABLE_TESTS})
    if (${ENABLE_CLIENT} OR ${ENABLE_ODBC} OR ${ENABLE_COMPATIBILITY_TESTS})
        add_subdirectory(tests/test-common)
    endif()

    if (${ENABLE_CLIENT})
        add_subdirectory(tests/client-test)
        add_subdirectory(tests/fake_server)
    endif()

    if (${ENABLE_EXTENSION})
        add_subdirectory(tests/extension-test)
    endif()

    if (${ENABLE_ODBC})
        add_subdirectory(tests/odbc-test)
    endif()

    if (${ENABLE_COMPATIBILITY_TESTS})
        add_subdirectory(tests/compatibility-tests)
    endif()
endif()

# Add micro-benchmarks.
if (${ENABLE_BENCHMARKS})
    if (${ENABLE_EXTENSION})
        add_subdirectory(benchmarks/extension-benchmark)
    endif()
endif()

include(ignite_compile_headers)

# Source code formatting with clang-format.
# TODO: require clang-format version 13 or higher
find_program(CLANG_FORMAT_BIN clang-format)
if (CLANG_FORMAT_BIN)
    message(STATUS "Found clang-format: ${CLANG_FORMAT_BIN}")
    message(STATUS "Add 'format' target to run clang-format for entire source code.")

    file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h)

    add_custom_target(format
        COMMENT "Running clang-format to change files"
        COMMAND ${CLANG_FORMAT_BIN} -i ${ALL_SOURCE_FILES})
else()
    message(STATUS "Failed to find clang-format. So there is no 'format' target now.")
endif()

if (${ENABLE_CLIENT})
    list(APPEND GRIDGAIN_AVAILABLE_COMPONENTS client)
endif()
if (${ENABLE_ODBC})
    list(APPEND GRIDGAIN_AVAILABLE_COMPONENTS odbc)
endif()

configure_package_config_file(
    "cmake/gridgain-config.cmake.in"
    "cmake/gridgain-config.cmake"
    INSTALL_DESTINATION "cmake"
)

add_subdirectory(pkgconfig)

write_basic_package_version_file(
    "cmake/gridgain-config-version.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion)

install(FILES
    "${CMAKE_CURRENT_BINARY_DIR}/cmake/gridgain-config.cmake"
    "${CMAKE_CURRENT_BINARY_DIR}/cmake/gridgain-config-version.cmake"
    COMPONENT client
    DESTINATION "${IGNITE_INSTALL_LIBRARY_DIR}/cmake/gridgain"
)

# Install example sources for the client component.
# NOTE: we deliberately avoid `install(DIRECTORY examples/ COMPONENT client ...)` because the
# CPack Archive generator on Windows silently drops files staged via install(DIRECTORY) from
# the resulting ZIP, even though they appear in the install staging directory. Enumerating
# files with file(GLOB_RECURSE) and using install(FILES) is the reliable workaround.
# Related: https://gitlab.kitware.com/cmake/cmake/-/issues/21113
file(GLOB_RECURSE IGNITE_EXAMPLE_FILES
    LIST_DIRECTORIES FALSE
    RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/examples"
    "${CMAKE_CURRENT_SOURCE_DIR}/examples/*"
)
foreach(_rel IN LISTS IGNITE_EXAMPLE_FILES)
    # Skip build artifacts that the old install(DIRECTORY) PATTERN ... EXCLUDE would have skipped.
    if(_rel MATCHES "(^|/)(build|cmake-build-[^/]*|CMakeFiles)(/|$)")
        continue()
    endif()
    if(_rel MATCHES "(^|/)(CMakeCache\\.txt|Makefile)$" OR _rel MATCHES "\\.cmake$")
        continue()
    endif()
    get_filename_component(_dir "${_rel}" DIRECTORY)
    install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/examples/${_rel}"
        COMPONENT client
        DESTINATION "${IGNITE_INSTALL_DATAROOT_DIR}/gridgain/examples/${_dir}"
    )
endforeach()

# Do not export the package by default
cmake_policy(SET CMP0090 NEW)

# Make this package visible to the system
export(PACKAGE gridgain)
