# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.

cmake_minimum_required(VERSION 3.16)

###################################################
# Project APSI includes the following components: #
#   1. APSI C++ library that includes             #
#      1) common API                              #
#      2) sender API                              #
#      3) receiver API                            #
#   2. APSI unit tests                            #
#   3. APSI integration tests                     #
#   4. APSI command line interface                #
###################################################

# [option] CMAKE_BUILD_TYPE (default: "Release")
# Build in one of the following modes: Release, Debug, MiniSizeRel, or RelWithDebInfo.
# Most generators recognize these and can set the compiler flags accordingly. We set
# the build type here before creating the project to prevent the CMake generator from
# overriding our default of "Release".
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE)
    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
        STRINGS "Release" "Debug" "MinSizeRel" "RelWithDebInfo")
endif()
message(STATUS "Build type (CMAKE_BUILD_TYPE): ${CMAKE_BUILD_TYPE}")

project(APSI VERSION 0.12.0 LANGUAGES CXX C)

# This policy was introduced in CMake 3.13; OLD by default until CMake 3.21
cmake_policy(SET CMP0077 NEW)

########################
# Global configuration #
########################

# CMake modules
include(CMakeDependentOption)
include(CheckCXXCompilerFlag)
include(CheckCXXSourceRuns)
include(CheckLanguage)

# Extra modules
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
include(APSIMacros)

# Always build position-independent-code
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# Make the install target depend on the all target (required by vcpkg)
set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY OFF)

# In Debug mode, define APSI_DEBUG
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    set(APSI_DEBUG ON)
    # In Debug mode, enable extra compiler flags.
    include(EnableCXXCompilerFlags)
else()
    set(APSI_DEBUG OFF)
endif()
message(STATUS "APSI debug mode: ${APSI_DEBUG}")

# [option] APSI_USE_CXX17_OPTION_STR (default: ON)
# Use C++17, use C++14 otherwise. An error will be thrown if SEAL_USE_CXX17 is ON but APSI_USE_CXX17 is OFF.
set(APSI_USE_CXX17_OPTION_STR "Use C++17")
option(APSI_USE_CXX17 ${APSI_USE_CXX17_OPTION_STR} ON)
message(STATUS "APSI_USE_CXX17: ${APSI_USE_CXX17}")

# Enable security-related compile options (MSVC only)
set(APSI_SECURE_COMPILE_OPTIONS_OPTION_STR "Enable Control Flow Guard and Spectre mitigations (MSVC only)")
option(APSI_SECURE_COMPILE_OPTIONS ${APSI_SECURE_COMPILE_OPTIONS_OPTION_STR} OFF)
mark_as_advanced(APSI_SECURE_COMPILE_OPTIONS)

# Enable AVX implementation
set(APSI_USE_AVX_OPTION_STR "Use AVX extensions if available")
option(APSI_USE_AVX ${APSI_USE_AVX_OPTION_STR} ON)
mark_as_advanced(APSI_USE_AVX)

# Enable AVX2 implementation
set(APSI_USE_AVX2_OPTION_STR "Use AVX2 extensions if available")
option(APSI_USE_AVX2 ${APSI_USE_AVX2_OPTION_STR} ON)
mark_as_advanced(APSI_USE_AVX2)

# Enable ASM implementation
set(APSI_USE_ASM_OPTION_STR "Use ASM implementation (static UNIX builds only)")
option(APSI_USE_ASM ${APSI_USE_ASM_OPTION_STR} ON)
mark_as_advanced(APSI_USE_ASM)

# Path for output
set(OUTLIB_PATH "lib")

# Required files and directories
include(GNUInstallDirs)

# Source tree
# set(APSI_CONFIG_IN_FILENAME ${CMAKE_CURRENT_LIST_DIR}/cmake/APSIConfig.cmake.in)
set(APSI_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR})
# set(APSI_CONFIG_H_IN_FILENAME ${CMAKE_CURRENT_LIST_DIR}/common/apsi/config.h.in)

# Build tree
# set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${OUTLIB_PATH})
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${OUTLIB_PATH})
# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
# set(APSI_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR})
# set(APSI_TARGETS_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/cmake/APSITargets.cmake)
# set(APSI_CONFIG_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/cmake/APSIConfig.cmake)
# set(APSI_CONFIG_VERSION_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/cmake/APSIConfigVersion.cmake)
# set(APSI_CONFIG_H_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/common/apsi/config.h)

# Install
# set(APSI_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/APSI-${APSI_VERSION_MAJOR}.${APSI_VERSION_MINOR})
# set(APSI_INCLUDES_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}/APSI-${APSI_VERSION_MAJOR}.${APSI_VERSION_MINOR})
set(APSI_INCLUDES_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fourq)


# pkg-config
# TODO: not provided yet

#########################
# External Dependencies #
#########################
# All dependencies are assumed pre-installed.
# find_package might throw a FATAL_ERROR before"xxx: not found", e.g. with vcpkg.cmake.

# Microsoft SEAL
# find_package(SEAL 4.1 QUIET REQUIRED)
# if(NOT SEAL_FOUND)
#     message(FATAL_ERROR "Microsoft SEAL: not found")
# else()
#     message(STATUS "Microsoft SEAL: found")
# endif()
if(NOT APSI_USE_CXX17 AND SEAL_USE_CXX17)
    message(FATAL_ERROR "CXX standards mismatch: APSI_USE_CXX17 is OFF, SEAL_USE_CXX17 is ON")
endif()

# Microsoft Kuku
# find_package(Kuku 2.1 QUIET REQUIRED)
# if(NOT Kuku_FOUND)
#     message(FATAL_ERROR "Microsoft Kuku: not found")
# else()
#     message(STATUS "Microsoft Kuku: found")
# endif()

# Flatbuffers
# find_package(Flatbuffers REQUIRED)
# if(NOT Flatbuffers_FOUND)
#     message(FATAL_ERROR "Flatbuffers: not found")
# else()
#     message(STATUS "Flatbuffers: found")
#     get_target_property(FLATBUFFERS_FLATC_PATH flatbuffers::flatc IMPORTED_LOCATION_RELEASE)
#     message(STATUS "flatc path: ${FLATBUFFERS_FLATC_PATH}")
#     include(CompileSchemaCXX)
# endif()

# jsoncpp: for parameter configuration
# find_package(jsoncpp REQUIRED)
# if (NOT jsoncpp_FOUND)
#     message(FATAL_ERROR "jsoncpp: not found")
# else()
#     message(STATUS "jsoncpp: found")
# endif()

# [Option] APSI_USE_LOG4CPLUS (default: ON)
# set(APSI_USE_LOG4CPLUS_OPTION_STR "Use Log4cplus for logging")
# option(APSI_USE_LOG4CPLUS ${APSI_USE_LOG4CPLUS_OPTION_STR} ON)
# if(APSI_USE_LOG4CPLUS)
#     # Log4cplus
#     find_package(log4cplus REQUIRED)
#     if(NOT log4cplus_FOUND)
#         message(FATAL_ERROR "log4cplus: not found")
#     else()
#         message(STATUS "log4cplus: found")
#     endif()
# endif()

# [Option] APSI_USE_ZMQ (default: ON)
# set(APSI_USE_ZMQ_OPTION_STR "Use ZeroMQ for networking")
# option(APSI_USE_ZMQ ${APSI_USE_ZMQ_OPTION_STR} ON)
# if(APSI_USE_ZMQ)
#     # ZeroMQ base
#     find_package(ZeroMQ REQUIRED)
#     if(NOT ZeroMQ_FOUND)
#         message(FATAL_ERROR "ZeroMQ: not found")
#     else()
#         message(STATUS "ZeroMQ: found")
#     endif()
#     # cppzmq wrapper
#     find_package(cppzmq REQUIRED)
#     if(NOT cppzmq_FOUND)
#         message(FATAL_ERROR "cppzmq: not found")
#     else()
#         message(STATUS "cppzmq: found")
#     endif()
# endif()

# [Option] APSI_BUILD_TESTS (default: OFF)
# set(APSI_BUILD_TESTS_OPTION_STR "Build unit and integration tests for APSI")
# option(APSI_BUILD_TESTS ${APSI_BUILD_TESTS_OPTION_STR} OFF)
# if(APSI_BUILD_TESTS)
#     # Google Test
#     find_package(GTest CONFIG REQUIRED)
#     if(NOT GTest_FOUND)
#         message(FATAL_ERROR "GTest: not found")
#     else()
#         message(STATUS "GTest: found")
#     endif()
# endif()

# [Option] APSI_BUILD_CLI (default: OFF)
# set(APSI_BUILD_CLI_OPTION_STR "Build example command line interface applications")
# cmake_dependent_option(APSI_BUILD_CLI ${APSI_BUILD_CLI_OPTION_STR} OFF "APSI_USE_ZMQ;APSI_USE_LOG4CPLUS" OFF)
# if (APSI_BUILD_CLI)
#     # TCLAP
#     find_path(TCLAP_INCLUDE_DIRS "tclap/Arg.h")
#     if(TCLAP_INCLUDE_DIRS STREQUAL "TCLAP_INCLUDE_DIRS-NOTFOUND")
#         message(FATAL_ERROR "TCLAP: not found")
#     else()
#         message(STATUS "TCLAP: found")
#         message(STATUS "TCLAP_INCLUDE_DIRS: ${TCLAP_INCLUDE_DIRS}")
#     endif()
# endif()

####################
# APSI C++ library #
####################

# [option] BUILD_SHARED_LIBS (default: OFF)
# Build a shared library if set to ON. Build a static library regardlessly.
set(BUILD_SHARED_LIBS_STR "Build shared library")
option(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_STR} OFF)
# TODO: consider building shared in future
if(BUILD_SHARED_LIBS)
    message(FATAL_ERROR "Only static build is supported; set `BUILD_SHARED_LIBS=OFF`")
endif()

# Create the config file
# configure_file(${APSI_CONFIG_H_IN_FILENAME} ${APSI_CONFIG_H_FILENAME})
# install(
#     FILES ${APSI_CONFIG_H_FILENAME}
#     DESTINATION ${APSI_INCLUDES_INSTALL_DIR}/apsi)

# Create a single library "apsi" for common, sender, and receiver
add_library(apsi STATIC)
# apsi_set_version_filename(apsi)
# apsi_set_language(apsi)
# target_include_directories(apsi PUBLIC
#     $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/common>
#     $<INSTALL_INTERFACE:${APSI_INCLUDES_INSTALL_DIR}>)
# target_include_directories(apsi PUBLIC
#     $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/sender>
#     $<INSTALL_INTERFACE:${APSI_INCLUDES_INSTALL_DIR}>)
# target_include_directories(apsi PUBLIC
#     $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/receiver>
#     $<INSTALL_INTERFACE:${APSI_INCLUDES_INSTALL_DIR}>)
# target_include_directories(apsi PUBLIC
#     $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/common>
#     $<INSTALL_INTERFACE:${APSI_INCLUDES_INSTALL_DIR}>)
# target_include_directories(apsi PUBLIC
#     $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/sender>
#     $<INSTALL_INTERFACE:${APSI_INCLUDES_INSTALL_DIR}>)
# apsi_set_version(apsi)
# apsi_link_threads(apsi)
# apsi_install_target(apsi APSITargets)
target_include_directories(apsi PUBLIC
    ${CMAKE_CURRENT_LIST_DIR})

# target_link_libraries(apsi
#     PUBLIC SEAL::seal
#     PUBLIC Kuku::kuku
#     PUBLIC flatbuffers::flatbuffers
#     PUBLIC jsoncpp_static)
# if(APSI_USE_LOG4CPLUS)
#     target_link_libraries(apsi PUBLIC log4cplus::log4cplus)
# endif()
# if(APSI_USE_ZMQ)
#     target_link_libraries(apsi PUBLIC libzmq-static cppzmq-static)
# endif()

# Configurations for FourQlib: system, arch, SIMD, and assembler
target_compile_options(apsi PUBLIC -DHAVE_CONFIG)
target_compile_options(apsi PUBLIC -DUSE_SECURE_SEED)
target_compile_options(apsi PUBLIC -DUSE_ENDO=true)

# Set system
if(MSVC)
    target_compile_options(apsi PUBLIC -D__WINDOWS__)
elseif (UNIX)
    target_compile_options(apsi PUBLIC -D__LINUX__)
endif()

# Detect architecture
include(DetectArch)
if(APSI_FOURQ_ARM64)
    # _ARM64_ needs to be set if the ARM64 optimizations are used
    # (in UNIX) or the generic implementation is used (Windows)
    target_compile_options(apsi PUBLIC -D_ARM64_)
    set(_ARM64_ ON)
endif()
if(CMAKE_SYSTEM_PROCESSOR STREQUAL x86)
    target_compile_options(apsi PUBLIC -D_X86_)
    set(_X86_ ON)
endif()

if(APSI_FOURQ_AMD64)
    target_compile_options(apsi PUBLIC -D_AMD64_)
    set(_AMD64_ ON)
    message(STATUS "FourQlib optimization: arch=AMD64")
elseif(APSI_FOURQ_ARM64 AND UNIX)
    message(STATUS "FourQlib optimization: arch=ARM64")
else()
    target_compile_options(apsi PUBLIC -D_GENERIC_)
    set(_GENERIC_ ON)
    message(STATUS "FourQlib optimization: arch=GENERIC")
endif()

# Detect AVX instructions
if(APSI_FOURQ_AMD64 AND (APSI_USE_AVX OR APSI_USE_AVX2))
    include(FindAVX)
    check_for_avx(apsi)
    if(HAVE_AVX2_EXTENSIONS AND APSI_USE_AVX2)
        target_compile_options(apsi PUBLIC -D_AVX2_)
        set(_AVX2_ ON)
        set(APSI_USE_AVX OFF CACHE BOOL ${APSI_USE_AVX_OPTION_STR} FORCE)
        message(STATUS "FourQlib optimization: simd=AVX2")
    elseif(HAVE_AVX_EXTENSIONS AND APSI_USE_AVX)
        target_compile_options(apsi PUBLIC -D_AVX_)
        set(_AVX_ ON)
        set(APSI_USE_AVX2 OFF CACHE BOOL ${APSI_USE_AVX2_OPTION_STR} FORCE)
        message(STATUS "FourQlib optimization: simd=AVX")
    else()
        set(APSI_USE_AVX OFF CACHE BOOL ${APSI_USE_AVX_OPTION_STR} FORCE)
        set(APSI_USE_AVX2 OFF CACHE BOOL ${APSI_USE_AVX2_OPTION_STR} FORCE)
        message(STATUS "FourQlib optimization: simd=OFF")
    endif()
else()
    set(APSI_USE_AVX OFF CACHE BOOL ${APSI_USE_AVX_OPTION_STR} FORCE)
    set(APSI_USE_AVX2 OFF CACHE BOOL ${APSI_USE_AVX2_OPTION_STR} FORCE)
    message(STATUS "FourQlib optimization: simd=OFF")
endif()

# Use optimized assembly on UNIX
if(APSI_USE_ASM AND UNIX AND NOT APPLE AND NOT CYGWIN AND NOT MINGW)
    check_language(ASM)
    if(CMAKE_ASM_COMPILER)
        enable_language(ASM)
        target_compile_options(apsi PUBLIC -D_ASM_)
        set(_ASM_ ON)
        message(STATUS "FourQlib optimization: asm=ON")
    endif()
else()
    set(APSI_USE_ASM OFF CACHE BOOL ${APSI_USE_ASM_OPTION_STR} FORCE)
    message(STATUS "FourQlib optimization: asm=OFF")
endif()

# Add source files to library and header files to install
# Must follow configurations for FourQlib
set(APSI_SOURCE_FILES "")
# add_subdirectory(common/apsi)
# add_subdirectory(receiver/apsi)
# add_subdirectory(sender/apsi)
add_subdirectory(fourq)

message(STATUS ${CMAKE_CXX_COMPILER_ID})
if(MSVC)
    set(__WINDOWS__ ON)
endif()
if(UNIX)
    set(__LINUX__ ON)
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
    set(_MSC_VER ON)
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
    set(__GNUC__ ON)
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    set(__clang__ ON)
endif()

configure_file(${CMAKE_CURRENT_LIST_DIR}/fourq/Fourq_define.h.in ${CMAKE_CURRENT_LIST_DIR}/fourq/FourQ_define.h)

target_sources(apsi PRIVATE ${APSI_SOURCE_FILES})

#################################
# Installation and CMake config #
#################################

# Create the CMake config file
# include(CMakePackageConfigHelpers)
# configure_package_config_file(
#     ${APSI_CONFIG_IN_FILENAME} ${APSI_CONFIG_FILENAME}
#     INSTALL_DESTINATION ${APSI_CONFIG_INSTALL_DIR}
# )

# Install the export
# install(
#     EXPORT APSITargets
#     NAMESPACE APSI::
#     DESTINATION ${APSI_CONFIG_INSTALL_DIR})

# Version file; we require exact version match for downstream
# write_basic_package_version_file(
#     ${APSI_CONFIG_VERSION_FILENAME}
#     VERSION ${APSI_VERSION}
#     COMPATIBILITY SameMinorVersion)

# Install config and module files
# install(
#     FILES
#         ${APSI_CONFIG_FILENAME}
#         ${APSI_CONFIG_VERSION_FILENAME}
#     DESTINATION ${APSI_CONFIG_INSTALL_DIR})

# We export SEALTargets from the build tree so it can be used by other projects
# without requiring an install.
# export(
#     EXPORT APSITargets
#     NAMESPACE APSI::
#     FILE ${APSI_TARGETS_FILENAME})

###################
# APSI unit tests #
###################

# if(APSI_BUILD_TESTS)
#     add_executable(unit_tests)
#     add_subdirectory(tests/unit/src)
#     target_link_libraries(unit_tests apsi GTest::gtest)
# endif()

##########################
# APSI integration tests #
##########################

# if(APSI_BUILD_TESTS)
#     add_executable(integration_tests)
#     add_subdirectory(tests/integration/src)
#     target_link_libraries(integration_tests apsi GTest::gtest)
# endif()

##########################
# Command Line Interface #
##########################

# if(APSI_BUILD_CLI)
#     add_library(common_cli OBJECT)
#     add_subdirectory(cli/common)
#     target_include_directories(common_cli PUBLIC cli)
#     target_include_directories(common_cli PUBLIC ${TCLAP_INCLUDE_DIRS})
#     target_link_libraries(common_cli PUBLIC apsi)
#     if (NOT MSVC AND NOT APPLE)
#         target_link_libraries(common_cli PUBLIC stdc++fs)
#     endif()
#     if(NOT APSI_USE_CXX17)
#         message(STATUS "Command line interface is built with C++17 regardless of APSI_USE_CXX17")
#     endif()
#     target_compile_features(common_cli PUBLIC cxx_std_17)
#     # Ensure PDB is generated even in Release mode
#     if(MSVC)
#         target_link_options(common_cli PUBLIC /DEBUG)
#     endif()

#     add_executable(sender_cli)
#     add_subdirectory(cli/sender)
#     target_link_libraries(sender_cli PUBLIC common_cli apsi)
#     if (APPLE)
#         target_compile_options(common_cli PUBLIC -DHAVE_LONG_LONG)
#     endif()

#     add_executable(receiver_cli)
#     add_subdirectory(cli/receiver)
#     target_link_libraries(receiver_cli PUBLIC common_cli apsi)

#     add_executable(pd_tool)
#     add_subdirectory(cli/pd_tool)
#     target_include_directories(pd_tool PRIVATE cli)
#     target_include_directories(pd_tool PRIVATE ${TCLAP_INCLUDE_DIRS})
#     target_link_libraries(pd_tool apsi)
# endif()
