#
# CMakeLists.txt for OpenFHE
#
# This script will build machine-specific header files for compile
# as it generates the Makefile
#
# Note many user options are handled using an OPTION in CMake
# An option has the value of ON or OFF
# See below for the list of options

cmake_minimum_required(VERSION 3.16.3)

find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
endif()

# To use gcc/g++ on a Macintosh, you must set the Compilers
# here, not inside the project
#
# if(APPLE)
#     set(CMAKE_C_COMPILER "/usr/local/bin/gcc-10")
#     set(CMAKE_CXX_COMPILER "/usr/local/bin/g++-10")
# endif()
#
# TODO: for now, we use CLang for Mac

project (OpenFHE C CXX)

set(OPENFHE_VERSION_MAJOR 1)
set(OPENFHE_VERSION_MINOR 6)
set(OPENFHE_VERSION_PATCH 1)
set(OPENFHE_VERSION ${OPENFHE_VERSION_MAJOR}.${OPENFHE_VERSION_MINOR}.${OPENFHE_VERSION_PATCH})

set(CMAKE_CXX_STANDARD 17)
set(CXX_STANDARD_REQUIRED ON)

#--------------------------------------------------------------------
# Build options
#--------------------------------------------------------------------
if(CMAKE_BUILD_TYPE)
    set(RELEASE_TYPES Debug Release RelWithDebInfo MinSizeRel)
    list(FIND RELEASE_TYPES ${CMAKE_BUILD_TYPE} INDEX_FOUND)
    if(${INDEX_FOUND} EQUAL -1)
        message(FATAL_ERROR "CMAKE_BUILD_TYPE must be one of Debug, Release, RelWithDebInfo, or MinSizeRel")
    endif()
else()
    # if no build type is chosen, default to Release mode
    set(CMAKE_BUILD_TYPE Release CACHE STRING
        "Choose the type of build, options are: None, Debug, Release, RelWithDebInfo, or MinSizeRel." FORCE)
endif()

message(STATUS "Building in ${CMAKE_BUILD_TYPE} mode")

if(WITH_COVTEST AND NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU"))
    # Generating the coverage report:
    # 1. Make sure that "g++ --version" and "gcov --version" display the same version number
    # 2. Ubuntu terminal commands:
    #    - cmake .. -DWITH_COVTEST=ON
    #    - make -j6 testall
    #    - make cov
    # 3. Open coverage/assets/index.html in a browser to see the report
    message(WARNING "WITH_COVTEST can be used with g++ only. Disabling coverage.")
    set(WITH_COVTEST OFF)
endif()

if (EMSCRIPTEN)
    set(BUILD_SHARED OFF)
    message("Shared library is not supported by Emscripten")
    option(BUILD_STATIC "Set to ON to build static versions of the library"            ON  )
    option(BUILD_UNITTESTS "Set to ON to build unit tests for the library"             OFF )
    option(BUILD_EXAMPLES "Set to ON to build examples for the library"                OFF )
    option(BUILD_BENCHMARKS "Set to ON to build benchmarks for the library"            OFF )
    set(WITH_OPENMP OFF)
    message("OpenMP is not supported by Emscripten")
else()
    option(BUILD_SHARED "Set to ON to build shared versions of the library"            ON  )
    option(BUILD_STATIC "Set to ON to build static versions of the library"            OFF )
    option(BUILD_UNITTESTS "Set to ON to build unit tests for the library"             ON  )
    option(BUILD_EXAMPLES "Set to ON to build examples for the library"                ON  )
    option(BUILD_BENCHMARKS "Set to ON to build benchmarks for the library"            ON  )
    option(WITH_OPENMP "Use OpenMP to enable <omp.h>"                                  ON  )
endif()

option(BUILD_EXTRAS "Set to ON to build extras for the library"                        OFF )
option(GIT_SUBMOD_AUTO "Submodules auto-update"                                        ON  )
option(WITH_BE2 "Include MATHBACKEND 2 in build by setting WITH_BE2 to ON"             OFF )
option(WITH_BE4 "Include MATHBACKEND 4 in build by setting WITH_BE4 to ON"             OFF )
option(WITH_NTL "Include MATHBACKEND 6 and NTL in build by setting WITH_NTL to ON"     OFF )
option(WITH_TCM "Activate tcmalloc by setting WITH_TCM to ON"                          OFF )
option(WITH_MALLOC_TUNING "Tune glibc malloc to retain freed memory for reuse"         ON  )
option(WITH_NATIVEOPT "Use machine-specific optimizations"                             OFF )
option(WITH_COVTEST "Turn on to enable coverage testing (can be used with g++ only)"   OFF )
option(WITH_NOISE_DEBUG "Use only when running lattice estimator; not for production"  OFF )
option(WITH_REDUCED_NOISE "Enable reduced noise within HKS and BFV HPSPOVERQ modes"    OFF )
option(WITH_NUMA_INTERLEAVE "Interleave memory across NUMA nodes at library load"      ON  )
option(USE_MACPORTS "Use MacPorts installed packages"                                  OFF )
option(WITH_DEFAULT_DIAGNOSTIC_SINK "Build the built-in diagnostic output sink"        ON  )

# Set required number of bits for native integer in build by setting NATIVE_SIZE to 64 or 128
if(NOT NATIVE_SIZE)
    set(NATIVE_SIZE 64)
endif()

if(NOT CKKS_M_FACTOR)
    set(CKKS_M_FACTOR 1)
endif()

# Radix of the rotation-fold accumulations (EvalPartialSumInPlace, EvalSum); power of two >= 2.
# Higher radices reduce digit decompositions (log2(size)/log2(radix) per fold) at the cost of
# more rotation keys ((radix-1)/log2(radix)*log2(size)).
if(NOT PARTIAL_SUM_RADIX)
    set(PARTIAL_SUM_RADIX 4)
endif()

# Name of the separate sink library built when the built-in sink is kept out of
# the library (see src/core/CMakeLists.txt); empty otherwise. In-tree executables
# put it on their link line so both configurations build and run.
if(NOT WITH_DEFAULT_DIAGNOSTIC_SINK)
    set(DIAGNOSTIC_SINK_LIB OPENFHEdiagnosticsink)
endif()

# Print options
message(STATUS "BUILD_UNITTESTS:              ${BUILD_UNITTESTS}")
message(STATUS "BUILD_EXAMPLES:               ${BUILD_EXAMPLES}")
message(STATUS "BUILD_BENCHMARKS:             ${BUILD_BENCHMARKS}")
message(STATUS "BUILD_EXTRAS:                 ${BUILD_EXTRAS}")
message(STATUS "BUILD_STATIC:                 ${BUILD_STATIC}")
message(STATUS "BUILD_SHARED:                 ${BUILD_SHARED}")
message(STATUS "GIT_SUBMOD_AUTO:              ${GIT_SUBMOD_AUTO}")
message(STATUS "WITH_BE2:                     ${WITH_BE2}")
message(STATUS "WITH_BE4:                     ${WITH_BE4}")
message(STATUS "WITH_NTL:                     ${WITH_NTL}")
message(STATUS "WITH_TCM:                     ${WITH_TCM}")
message(STATUS "WITH_MALLOC_TUNING:           ${WITH_MALLOC_TUNING}")
message(STATUS "WITH_OPENMP:                  ${WITH_OPENMP}")
message(STATUS "NATIVE_SIZE:                  ${NATIVE_SIZE}")
message(STATUS "CKKS_M_FACTOR:                ${CKKS_M_FACTOR}")
message(STATUS "PARTIAL_SUM_RADIX:            ${PARTIAL_SUM_RADIX}")
message(STATUS "WITH_NATIVEOPT:               ${WITH_NATIVEOPT}")
message(STATUS "WITH_COVTEST:                 ${WITH_COVTEST}")
message(STATUS "WITH_NOISE_DEBUG:             ${WITH_NOISE_DEBUG}")
message(STATUS "WITH_REDUCED_NOISE:           ${WITH_REDUCED_NOISE}")
message(STATUS "WITH_DEFAULT_DIAGNOSTIC_SINK: ${WITH_DEFAULT_DIAGNOSTIC_SINK}")
message(STATUS "USE_MACPORTS:                 ${USE_MACPORTS}")

#--------------------------------------------------------------------
# Compiler logic
#--------------------------------------------------------------------
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0)
        message(WARNING "GCC version should be at least 9.0.")
    endif()
elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
    if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10)
        message(WARNING "Clang version should be at least 10.0.")
    endif()
else()
    message(WARNING "You are using ${CMAKE_CXX_COMPILER_ID} version ${CMAKE_CXX_COMPILER_VERSION}, which is unsupported.")
endif()

# Use, i.e. don't skip the full RPATH for the build tree
set(CMAKE_SKIP_BUILD_RPATH FALSE)

# When building, don't use the install RPATH already
# (but later on when installing)
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)

# Add the automatically determined parts of the RPATH,
# which point to directories outside the build tree to the install RPATH
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

# Compiler flags
# The r_pkg branch intentionally drops -Werror from the upstream
# defaults: when OpenFHE is embedded in the openfhe R package, R's
# Makeconf supplies -pedantic (seen on Linux R 4.5 and rtools45)
# which upstream -Werror promotes to errors inside cereal's __int128
# typedefs and other benign header patterns. -Werror is upstream's CI
# discipline; -Wall alone is sufficient for the embedded build.
set(C_COMPILE_FLAGS "-Wall -DOPENFHE_VERSION=${OPENFHE_VERSION}")
set(CXX_COMPILE_FLAGS "-Wall -DOPENFHE_VERSION=${OPENFHE_VERSION}")
if(WITH_NATIVEOPT)
    # probe rather than hardcode: -march=native is the x86 spelling, some aarch64
    # toolchains reject it, and cmake does not validate compiler flags at configure time.
    # The C and C++ frontends may accept different options, so probe each language.
    include(CheckCCompilerFlag)
    include(CheckCXXCompilerFlag)
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64|armv8)")
        set(NATIVEOPT_CANDIDATES "-mcpu=native" "-march=native")
    else()
        set(NATIVEOPT_CANDIDATES "-march=native" "-mcpu=native")
    endif()

    set(NATIVEOPT_C_FLAG "")
    foreach(flag ${NATIVEOPT_CANDIDATES})
        string(MAKE_C_IDENTIFIER "C_COMPILER_SUPPORTS${flag}" flagvar)
        check_c_compiler_flag("${flag}" ${flagvar})
        if(${flagvar})
            set(NATIVEOPT_C_FLAG "${flag}")
            break()
        endif()
    endforeach()

    set(NATIVEOPT_CXX_FLAG "")
    foreach(flag ${NATIVEOPT_CANDIDATES})
        string(MAKE_C_IDENTIFIER "CXX_COMPILER_SUPPORTS${flag}" flagvar)
        check_cxx_compiler_flag("${flag}" ${flagvar})
        if(${flagvar})
            set(NATIVEOPT_CXX_FLAG "${flag}")
            break()
        endif()
    endforeach()

    if(NATIVEOPT_CXX_FLAG)
        message(STATUS "WITH_NATIVEOPT CXX flag: ${NATIVEOPT_CXX_FLAG}")
        string(APPEND CXX_COMPILE_FLAGS " ${NATIVEOPT_CXX_FLAG}")
        if(NATIVEOPT_C_FLAG)
            message(STATUS "WITH_NATIVEOPT C flag:   ${NATIVEOPT_C_FLAG}")
            string(APPEND C_COMPILE_FLAGS " ${NATIVEOPT_C_FLAG}")
        else()
            message(STATUS "WITH_NATIVEOPT: ${CMAKE_C_COMPILER_ID} accepts neither candidate "
                           "flag; C sources build without machine-specific optimization.")
        endif()
    else()
        message(WARNING "WITH_NATIVEOPT is ON, but ${CMAKE_CXX_COMPILER_ID} on "
                        "${CMAKE_SYSTEM_PROCESSOR} supports neither -march=native nor "
                        "-mcpu=native. Building without machine-specific optimization.")
        set(WITH_NATIVEOPT OFF)
    endif()
    unset(NATIVEOPT_CANDIDATES)
    unset(NATIVEOPT_C_FLAG)
    unset(NATIVEOPT_CXX_FLAG)
    unset(flagvar)
endif()

set(IGNORE_WARNINGS "") # initialize IGNORE_WARNINGS
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    # Add -Wno-parentheses for compatibility with g++
    set (IGNORE_WARNINGS "-Wno-parentheses")
    # We can use GNU built-in functions provided by GCC for debugging. ex: __builtin_LINE (), __builtin_FUNCTION (), __builtin_FILE ()
    add_definitions(-DBUILTIN_INFO_AVAILABLE)
    message(STATUS "BUILTIN_INFO_AVAILABLE is defined")
elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
    if(CMAKE_BUILD_TYPE STREQUAL "Debug")
        set(IGNORE_WARNINGS "-Wno-unused-private-field -Wno-shift-op-parentheses")
    endif()
endif()
if(IGNORE_WARNINGS)
    # For C++ only
    string(APPEND CXX_COMPILE_FLAGS " ${IGNORE_WARNINGS}")
endif()

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    set(CMAKE_C_FLAGS_RELEASE "-O0 -DNDEBUG")
    set(CMAKE_CXX_FLAGS_RELEASE "-O0 -DNDEBUG")

    set(C_COMPILE_FLAGS "${C_COMPILE_FLAGS} -O0 -g")
    set(CXX_COMPILE_FLAGS "${CXX_COMPILE_FLAGS} -O0 -g")
else()
    set(C_COMPILE_FLAGS "${C_COMPILE_FLAGS} -O3")
    set(CXX_COMPILE_FLAGS "${CXX_COMPILE_FLAGS} -O3")
endif()

if(EMSCRIPTEN)
    set(EMSCRIPTEN_IGNORE_WARNINGS "-Wno-unused-but-set-variable -Wno-unknown-warning-option")
    set(C_COMPILE_FLAGS "${C_COMPILE_FLAGS} ${EMSCRIPTEN_IGNORE_WARNINGS}")
    set(CXX_COMPILE_FLAGS "${CXX_COMPILE_FLAGS} ${EMSCRIPTEN_IGNORE_WARNINGS}")
    add_compile_options(-fexceptions)
    add_link_options(
        -Wno-limited-postlink-optimizations
        -sINITIAL_MEMORY=2047MB -sMAXIMUM_MEMORY=4GB -sALLOW_MEMORY_GROWTH=1
        -sMALLOC=emmalloc -sDISABLE_EXCEPTION_CATCHING=0)
endif()

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_COMPILE_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_COMPILE_FLAGS}")

if(WITH_COVTEST)
    set(CMAKE_C_FLAGS             "${CMAKE_C_FLAGS} --coverage")
    set(CMAKE_CXX_FLAGS           "${CMAKE_CXX_FLAGS} --coverage")
    set(CMAKE_EXE_LINKER_FLAGS    "${CMAKE_EXE_LINKER_FLAGS} --coverage")
    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage")
    set(BUILDDIR ${CMAKE_BINARY_DIR})
    set(COVDIR ${BUILDDIR}/coverage/)
endif()

if(UNIX AND NOT APPLE AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    # OpenFHE may use an external shared object provided by user for PRNG and linked with g++ on Linux.
    # In order to ensure that OpenFHE can dynamically load shared objects at runtime, add an additional library:
    set(ADDITIONAL_LIBS "-ldl")
endif()

if(BUILD_STATIC)
    set(OpenFHE_STATIC_LIBS OPENFHEcore_static OPENFHEpke_static OPENFHEbinfhe_static)
endif()

if(BUILD_SHARED)
    set(OpenFHE_SHARED_LIBS OPENFHEcore OPENFHEpke OPENFHEbinfhe)
endif()

set(OpenFHE_PACKAGE_LIBS ${OpenFHE_STATIC_LIBS} ${OpenFHE_SHARED_LIBS})

#--------------------------------------------------------------------
# Installation logic
#--------------------------------------------------------------------
# set up for install
# Seed the historical 'lib' default before GNUInstallDirs runs: it only derives
# lib64/<arch-triplet> when CMAKE_INSTALL_LIBDIR is unset, so an explicit
# -DCMAKE_INSTALL_LIBDIR from a packager is still honored.
if(NOT DEFINED CMAKE_INSTALL_LIBDIR)
    set(CMAKE_INSTALL_LIBDIR lib CACHE PATH "Object code libraries")
endif()
include(GNUInstallDirs)

# INSTALL_LIB_DIR and INSTALL_INCLUDE_DIR are superseded by the CMAKE_INSTALL_*DIR variables
# above. Neither was ever honored by any install rule (every destination was hardcoded), so a
# non-default value only changed the paths advertised in OpenFHEConfig.cmake and pointed
# consumers at directories that were never populated.
#
# Warn only when a value other than the historical default is present: the old code cached
# those defaults unconditionally, so every build tree created before this change carries them,
# and warning on their mere presence would report variables the user never set on every
# reconfigure (cmake re-runs on make). Then drop the entries.
if(DEFINED INSTALL_LIB_DIR AND NOT "${INSTALL_LIB_DIR}" STREQUAL "lib")
    message(WARNING "INSTALL_LIB_DIR is deprecated and ignored; use -DCMAKE_INSTALL_LIBDIR instead.")
endif()
if(DEFINED INSTALL_INCLUDE_DIR AND NOT "${INSTALL_INCLUDE_DIR}" STREQUAL "include/openfhe")
    message(WARNING "INSTALL_INCLUDE_DIR is deprecated and ignored; use -DCMAKE_INSTALL_INCLUDEDIR instead.")
endif()
unset(INSTALL_LIB_DIR CACHE)
unset(INSTALL_INCLUDE_DIR CACHE)

# INSTALL_CMAKE_DIR, unlike the two variables above, has always been honored by the install
# rules for the package files, so -DINSTALL_CMAKE_DIR=share/cmake/OpenFHE keeps working. Only
# its default changes: the package files now go next to the libraries they describe, so that a
# libdir-based layout (lib64, lib/<arch-triplet>) does not create a second tree under
# <prefix>/lib solely to hold them.
#
# Deliberately no longer declared as a cache entry of our own. Doing that pins an existing build
# tree to whatever default was derived when it was first configured, so adding
# -DCMAKE_INSTALL_LIBDIR later moves the libraries but leaves the package files behind. It also
# avoids the PATH cache type, for which cmake rewrites a relative value into an absolute path
# against the current working directory - that is what made the example above install into the
# directory cmake was invoked from rather than under CMAKE_INSTALL_PREFIX. The cost is that the
# variable no longer shows up in ccmake/cmake-gui.
if(WIN32 AND NOT CYGWIN)
    set(DEF_INSTALL_CMAKE_DIR "CMake")
else()
    set(DEF_INSTALL_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/OpenFHE")
endif()

# Earlier versions of this file did cache the derived default, for every user, so a build tree
# configured by one of them carries a value nobody chose. Discard that and re-derive, otherwise
# such a tree stays pinned to lib/OpenFHE when CMAKE_INSTALL_LIBDIR is changed. Two things have
# to hold for the value to be one of those rather than a deliberate choice: it still has the help
# string those set(... CACHE) calls gave it, and it is one of the defaults they hardcoded. cmake
# gives any entry that came from the command line its own help string instead, whether or not a
# type was supplied, so -DINSTALL_CMAKE_DIR=lib/OpenFHE and -DINSTALL_CMAKE_DIR:PATH=lib/OpenFHE
# are both left alone even alongside a different CMAKE_INSTALL_LIBDIR.
get_property(_install_cmake_dir_help CACHE INSTALL_CMAKE_DIR PROPERTY HELPSTRING)
if("${_install_cmake_dir_help}" STREQUAL "Installation directory for CMake files"
        AND ("${INSTALL_CMAKE_DIR}" STREQUAL "lib/OpenFHE" OR "${INSTALL_CMAKE_DIR}" STREQUAL "CMake"))
    unset(INSTALL_CMAKE_DIR CACHE)
endif()
unset(_install_cmake_dir_help)

if(NOT DEFINED INSTALL_CMAKE_DIR)
    set(INSTALL_CMAKE_DIR "${DEF_INSTALL_CMAKE_DIR}")
endif()

# Destinations for install(): the relative forms are passed to install(DESTINATION), which
# resolves them against CMAKE_INSTALL_PREFIX. The CMAKE_INSTALL_FULL_* variables computed by
# GNUInstallDirs are retained for the build-tree config. The installed config uses
# package-relative paths instead; explicit absolute destinations are also supported.
set(OpenFHE_INSTALL_LIBDIR          "${CMAKE_INSTALL_LIBDIR}")
set(OpenFHE_INSTALL_BINDIR          "${CMAKE_INSTALL_BINDIR}")
set(OpenFHE_INSTALL_INCLUDEDIR      "${CMAKE_INSTALL_INCLUDEDIR}/openfhe")
set(OpenFHE_FULL_INSTALL_LIBDIR     "${CMAKE_INSTALL_FULL_LIBDIR}")
set(OpenFHE_FULL_INSTALL_BINDIR     "${CMAKE_INSTALL_FULL_BINDIR}")
set(OpenFHE_FULL_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_FULL_INCLUDEDIR}/openfhe")

# Shared OpenFHE libraries are installed together. Resolve their dependencies relative
# to the loading library so moving the installation does not retain the old prefix.
if(APPLE)
    set(OpenFHE_INSTALL_RPATH "@loader_path")
elseif(UNIX)
    set(OpenFHE_INSTALL_RPATH "$ORIGIN")
endif()

message("***** INSTALL IS AT ${CMAKE_INSTALL_PREFIX}; to change, run cmake with -DCMAKE_INSTALL_PREFIX=/your/path")
set(CMAKE_INSTALL_MESSAGE LAZY)

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

#--------------------------------------------------------------------
# Uninstall logic
#--------------------------------------------------------------------
# Clobber cleans and deletes the third-party stuff
add_custom_target(clobber COMMAND make clean WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
add_custom_target("uninstall" COMMENT "Uninstall OpenFHE files")
add_custom_command(
    TARGET "uninstall"
    POST_BUILD
    COMMENT "Uninstall files within install_manifest.txt"
    COMMAND ../scripts/uninstall_openfhe.sh
    USES_TERMINAL)

#--------------------------------------------------------------------
# Machine-specific checks
#--------------------------------------------------------------------
# Determine the architecture on a Linux/Unix/macOS/MinGW system
if(CMAKE_HOST_UNIX OR MINGW)
    EXECUTE_PROCESS(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE ARCHITECTURE)
else()
    set(ARCHITECTURE "unknown")
endif()

if(ARCHITECTURE)
    if(${ARCHITECTURE} MATCHES "i386")
        message(SEND_ERROR "The " ${ARCHITECTURE} " architecture is not supported")
    else()
        message(STATUS "Architecture is " ${ARCHITECTURE})
    endif()
endif()

# Size checks
include(CheckTypeSize)
check_type_size("__int128" INT128)
check_type_size("uint64_t" INT64)

# TODO: check this
# dsuponit: uncomment the following "if" block if HAVE_INT64 is false. It may happen when compiling with pthreads enabled.
# Seems like I have disabled link with pthreads for Emscripten in all makefiles
if(EMSCRIPTEN)
    # Emscripten always(!) supports uint64_t, but check_type_size() may not work correctly due to the way
    # Emscripten handles cross-compilation. Instead of checking, we can hardcode the result.
    set(HAVE_INT64 1)
endif()

if(NOT(BUILD_SHARED OR BUILD_STATIC))
    message(SEND_ERROR "Either BUILD_SHARED or BUILD_STATIC neeed to be turned on.")
endif()

if("${NATIVE_SIZE}" EQUAL 128)
    if(EMSCRIPTEN)
        message(SEND_ERROR "NATIVE_SIZE == 128 is not supported for EMSCRIPTEN")
    endif()
    if(${HAVE_INT128})
        set(NATIVEINT 128)
        message(STATUS "NATIVEINT is set to " ${NATIVEINT})
    else()
        message(SEND_ERROR "Cannot support NATIVE_SIZE == 128")
    endif()
elseif("${NATIVE_SIZE}" EQUAL 64)
#    if(EMSCRIPTEN)
#        set(HAVE_INT128 FALSE)
#    endif()
    if(${HAVE_INT64})
        set(NATIVEINT 64)
        message(STATUS "NATIVEINT is set to " ${NATIVEINT})
    else()
        message(SEND_ERROR "Cannot support NATIVE_SIZE == 64")
    endif()
elseif("${NATIVE_SIZE}" EQUAL 32)
    if(${HAVE_INT64})
        set(NATIVEINT 32)
        set(HAVE_INT128 FALSE)
        message (STATUS "NATIVEINT is set to " ${NATIVEINT})
    else()
        message(SEND_ERROR "Cannot support NATIVE_SIZE == 32")
    endif()
else()
    message(SEND_ERROR "NATIVE_SIZE is " ${NATIVE_SIZE})
    message(SEND_ERROR "***ERROR*** need a Native implementation")
endif()


#--------------------------------------------------------------------
# Backend logic
#--------------------------------------------------------------------
if(NOT MATHBACKEND)
    set(MATHBACKEND 4)
endif()

message(STATUS "MATHBACKEND is set to " ${MATHBACKEND})

if("${NATIVEINT}" EQUAL 128)
    if("${MATHBACKEND}" EQUAL 6)
        set(WITH_NTL OFF)
        set(MATHBACKEND 4)
        message(STATUS "MATHBACKEND 6 is not compatible with 128-bit native backend. Setting MATHBACKEND to 4.")
    elseif(WITH_NTL)
        set(WITH_NTL OFF)
        message(STATUS "MATHBACKEND 6 is not compatible with 128-bit native backend. Setting WITH_NTL to OFF.")
    endif()
endif()

if("${MATHBACKEND}" EQUAL 2)
    if(NOT WITH_BE2)
        set(WITH_BE2 ON)
        message(STATUS "MATHBACKEND set to 2. Setting WITH_BE2 to ON")
    endif()
elseif("${MATHBACKEND}" EQUAL 4)
    if(NOT WITH_BE4)
        set(WITH_BE4 ON)
        message(STATUS "MATHBACKEND set to 4. Setting WITH_BE4 to ON")
    endif()
elseif("${MATHBACKEND}" EQUAL 6)
    if(NOT WITH_NTL)
        set(WITH_NTL ON)
        message(STATUS "MATHBACKEND set to 6. Setting WITH_NTL to ON")
    endif()
else()
    message(SEND_ERROR "MATHBACKEND must be 2, 4 or 6")
endif()

set(OpenFHE_BACKEND_FLAGS "-DMATHBACKEND=${MATHBACKEND}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenFHE_BACKEND_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenFHE_BACKEND_FLAGS}")

if(WITH_TCM)
    message(STATUS "tcmalloc is turned ON")
    if(MINGW)
        message(SEND_ERROR "***ERROR*** tcmalloc is not supported for MinGW")
    endif()
endif()

# Build configure_core.h to make options available
configure_file(./configure/config_core.in src/core/config_core.h)
install(FILES ${CMAKE_BINARY_DIR}/src/core/config_core.h DESTINATION ${OpenFHE_INSTALL_INCLUDEDIR}/core)

# TODO (dsuponit): do we need TAR?
find_program(TAR "gtar")
find_program(TAR "tar")

if(WITH_TCM OR WITH_NTL)
    # tcmalloc, NTL and GMP require autoconf/automake/libtool to be installed.
    # We need to make sure that they are:
    execute_process(COMMAND autogen --version OUTPUT_VARIABLE AUTOGEN_VER)
    # execute_process in MINGW by default does not run in a shell
    if(MINGW)
        execute_process(COMMAND sh autoconf --version OUTPUT_VARIABLE AUTOCONF_VER)
    else()
        execute_process(COMMAND autoconf --version OUTPUT_VARIABLE AUTOCONF_VER)
    endif()

    string(LENGTH "${AUTOCONF_VER}" AUTOCONF_VER_LEN)
    if(${AUTOCONF_VER_LEN} EQUAL 0)
        message(SEND_ERROR "Autoconf is not installed.")
    endif()
endif()

#--------------------------------------------------------------------
# OpenMP logic
#--------------------------------------------------------------------
if(WITH_OPENMP)
    # Used to optionally compile openmp code
    add_definitions(-DPARALLEL)

    # Set OpenMP configuration manually for macOS
    if(APPLE)
        # Allow external specification of libomp location via cmake -D flags.
        # This is needed when embedding OpenFHE in environments that ship their
        # own libomp (e.g. R framework), where mixing two libomp copies in one
        # process causes crashes.
        if(NOT DEFINED OPENMP_LIBRARIES OR NOT DEFINED OPENMP_INCLUDES)
            if(USE_MACPORTS)
                # Macports-based installation
                message(STATUS "Using Macports setup")
                set(OPENMP_LIBRARIES "/opt/local/lib/libomp")
                set(OPENMP_INCLUDES "/opt/local/include/libomp")
            else(USE_MACPORTS)
                # Homebrew-based installation
                if(${ARCHITECTURE} MATCHES "arm64")
                    message(STATUS "Apple M1 detected")
                    set(OPENMP_LIBRARIES "/opt/homebrew/opt/libomp/lib")
                    set(OPENMP_INCLUDES "/opt/homebrew/opt/libomp/include")
                else()
                    message(STATUS "Apple Intel detected")
                    set(OPENMP_LIBRARIES "/usr/local/opt/libomp/lib")
                    set(OPENMP_INCLUDES "/usr/local/opt/libomp/include")
                endif()
            endif(USE_MACPORTS)
        else()
            message(STATUS "Using externally specified OpenMP: ${OPENMP_LIBRARIES}")
        endif()

        if(CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "AppleClang")
            set(OpenMP_C_FLAGS "-Xpreprocessor -fopenmp -lomp -Wno-unused-command-line-argument")
            set(OpenMP_C_LIB_NAMES "libomp")
            set(OpenMP_libomp_LIBRARY ${OpenMP_C_LIB_NAMES})
        endif()
        if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang")
            set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -lomp -Wno-unused-command-line-argument")
            set(OpenMP_CXX_LIB_NAMES "libomp")
            set(OpenMP_libomp_LIBRARY ${OpenMP_CXX_LIB_NAMES})
        endif()

        include_directories("${OPENMP_INCLUDES}")
        link_directories("${OPENMP_LIBRARIES}")
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")

        message(STATUS "OPENMP_LIBRARIES: " ${OPENMP_LIBRARIES})
        message(STATUS "OPENMP_INCLUDES: " ${OPENMP_INCLUDES})
        message(STATUS "OpenMP_CXX_FLAGS: " ${OpenMP_CXX_FLAGS})
        message(STATUS "OpenMP_CXX_LIB_NAMES: " ${OpenMP_CXX_LIB_NAMES})
    endif()

    find_package(OpenMP)
    # OpenMP_CXX_FOUND was added in cmake 3.9.x so we are also checking the OpenMP_FOUND flag
    if(OpenMP_CXX_FOUND OR OpenMP_FOUND)
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
        set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
    else()
        message(SEND_ERROR "** ERROR ** OpenMP is not installed. If using macOS/clang, please run 'cmake ..' again.")
    endif()

    if(OpenMP_C_FOUND OR OpenMP_FOUND)
        set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    endif()
else()  # WITH_OPENMP == OFF
    if(NOT EMSCRIPTEN)
        find_package(Threads REQUIRED)
    endif()
    # Disable unknown #pragma omp warning
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unknown-pragmas")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas")
endif()

#--------------------------------------------------------------------
# Pthreads logic (only for Google benchmark)
#--------------------------------------------------------------------
# In order to have the Threads_FOUND on some Linux and macOS systems
set(CMAKE_THREAD_LIBS_INIT "-lpthread")
set(CMAKE_HAVE_THREADS_LIBRARY 1)
set(CMAKE_USE_WIN32_THREADS_INIT 0)
set(CMAKE_USE_PTHREADS_INIT 1)
set(THREADS_PREFER_PTHREAD_FLAG ON)

#--------------------------------------------------------------------
# Submodule Update logic
#--------------------------------------------------------------------
if(GIT_SUBMOD_AUTO AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
    # Update submodules as needed
    find_package(Git REQUIRED)
    message(STATUS "Submodule update")
    if(NOT GIT_SUBMODULE_SYNCED)
        # "git submodule sync --recursive" should run only once, when CMakeCache.txt doesn't exist'
        execute_process(COMMAND ${GIT_EXECUTABLE} submodule sync --recursive
                        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                        RESULT_VARIABLE GIT_SUBMODULE_RESULT)
        if(NOT GIT_SUBMODULE_RESULT EQUAL "0")
            message(FATAL_ERROR "\"git submodule sync --recursive\" failed with ${GIT_SUBMODULE_RESULT}, please checkout submodules")
        endif()
    endif()

    execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
                    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                    RESULT_VARIABLE GIT_SUBMODULE_RESULT)
    if(NOT GIT_SUBMODULE_RESULT EQUAL "0")
        if(NOT GIT_SUBMODULE_SYNCED)
            # print this message only if update has never happened
            message(FATAL_ERROR "\"git submodule update --init\" failed with ${GIT_SUBMODULE_RESULT}, please checkout submodules")
        else()
            message(SEND_ERROR "\"git submodule update --init\" failed with ${GIT_SUBMODULE_RESULT}, please checkout submodules or disable autoupdate with -DGIT_SUBMOD_AUTO=OFF")
        endif()
    endif()

    if(NOT GIT_SUBMODULE_SYNCED)
        set(GIT_SUBMODULE_SYNCED ON CACHE BOOL "" FORCE)
    endif()
endif()

#--------------------------------------------------------------------
# Coverage logic
#--------------------------------------------------------------------
if(WITH_COVTEST)
    find_program(LCOV lcov)
    find_program(GENHTML genhtml)
    if(LCOV AND GENHTML)
        # gcov version should match the compiler major version
        string(REGEX MATCH "^[0-9]+" GCC_MAJOR "${CMAKE_CXX_COMPILER_VERSION}")
        find_program(GCOV NAMES gcov-${GCC_MAJOR} gcov)
        if(NOT GCOV)
            message(FATAL_ERROR "Could not find gcov matching GCC-${GCC_MAJOR}.To install it: 'sudo apt-get install -y lcov gcc-${GCC_MAJOR} g++-${GCC_MAJOR}'")
        endif()

        set(LCOV_EXCLUDES --exclude '*/usr/include/*' --exclude '*/third-party/*')

        # as some options are not available for older versions of lcov, i am adding a version-based guard for them
        # get lcov version. ${LCOV_VERSION_OUT} should contain something like this "lcov: LCOV version 2.0-1"
        execute_process(COMMAND ${LCOV} --version
                        OUTPUT_VARIABLE LCOV_VERSION_OUT
                        OUTPUT_STRIP_TRAILING_WHITESPACE
        )
        string(REGEX MATCH "[0-9]+\\.[0-9]+" LCOV_VERSION "${LCOV_VERSION_OUT}")
        message(STATUS "LCOV version: ${LCOV_VERSION}")

        # set ${LCOV_VERSION_OUT} for LCOV versions starting with 2.0 only
        if(LCOV_VERSION VERSION_GREATER_EQUAL "2.0")
            # keep double “mismatch” and "unused" for mismatch, multiple mismatch, unused, multiple unused and empty
            list(APPEND LCOV_IGNORE_ERRORS
                --ignore-errors mismatch
                --ignore-errors mismatch
                --ignore-errors unused
                --ignore-errors unused
                --ignore-errors empty
            )
        # else()
        #     # older supported option values:
        #     # set(LCOV_IGNORE_ERRORS "--ignore-errors gcov,source,graph")
        endif()

        set(LCOV_OPTIONS
            ${LCOV_IGNORE_ERRORS}
            --rc geninfo_unexecuted_blocks=1
            --gcov-tool ${GCOV}
        )

        # Creates the command: make cov
        add_custom_target(cov
            DEPENDS core_tests pke_tests binfhe_tests
            COMMAND cd ${BUILDDIR} && mkdir -p coverage
            COMMAND cd ${BUILDDIR}/src/core/CMakeFiles/core_tests.dir/unittest/ && ${LCOV} --capture --directory . --output-file ${COVDIR}/core.info ${LCOV_EXCLUDES} ${LCOV_OPTIONS}
            COMMAND cd ${BUILDDIR}/src/core/CMakeFiles/coreobj.dir/ && ${LCOV} --capture --directory . --output-file ${COVDIR}/core_cpp.info ${LCOV_EXCLUDES} ${LCOV_OPTIONS}
            COMMAND cd ${BUILDDIR}/src/pke/CMakeFiles/pke_tests.dir/unittest/ && ${LCOV} --capture --directory . --output-file ${COVDIR}/pke.info ${LCOV_EXCLUDES} ${LCOV_OPTIONS}
            COMMAND cd ${BUILDDIR}/src/pke/CMakeFiles/pkeobj.dir/ && ${LCOV} --capture --directory . --output-file ${COVDIR}/pke_cpp.info ${LCOV_EXCLUDES} ${LCOV_OPTIONS}
            COMMAND cd ${BUILDDIR}/src/binfhe/CMakeFiles/binfhe_tests.dir/unittest/ && ${LCOV} --capture --directory . --output-file ${COVDIR}/binfhe.info ${LCOV_EXCLUDES} ${LCOV_OPTIONS}
            COMMAND cd ${BUILDDIR}/src/binfhe/CMakeFiles/binfheobj.dir/ && ${LCOV} --capture --directory . --output-file ${COVDIR}/binfhe_cpp.info ${LCOV_EXCLUDES} ${LCOV_OPTIONS}
            COMMAND cd ${COVDIR} && mkdir -p assets && ${GENHTML} -t "Coverage Test" -o ${COVDIR}/assets/ *.info)
        message(STATUS "lcov found in ${LCOV}")
    else()
        message(STATUS "lcov needs to be installed to generate a coverage report")
    endif()
endif()


#--------------------------------------------------------------------
# Third-party logic
#--------------------------------------------------------------------
include(ExternalProject)

set(THIRDPARTYDIR ${CMAKE_CURRENT_SOURCE_DIR}/third-party)
include_directories(${THIRDPARTYDIR}/include)

# R_PKG PATCH: cereal vendored at the source-tree top level (cereal/),
# not third-party/, so the shipped CRAN tarball path stays <=100 bytes
# (the longer `openfhe.R/` package prefix otherwise tips the deepest
# cereal header over the limit). Re-apply on each OpenFHE bump.
set(CEREALDIR ${CMAKE_CURRENT_SOURCE_DIR}/cereal)
include_directories(${CEREALDIR}/include)
install(DIRECTORY ${CEREALDIR}/include/ DESTINATION ${OpenFHE_INSTALL_INCLUDEDIR})

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third-party/google-test/googletest)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third-party/google-test/googletest/include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/core/include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/binfhe/include)

include_directories(${CMAKE_CURRENT_BINARY_DIR}/src/core)

# Handle third-party gperftools for optional tcmalloc
add_custom_target(
    tcm
    COMMAND ./autogen.sh
    COMMAND ./configure --prefix=${CMAKE_CURRENT_BINARY_DIR}/third-party --enable-minimal
    COMMAND make
    COMMAND make install
    WORKING_DIRECTORY ${THIRDPARTYDIR}/gperftools)

add_custom_target(
    tcm_clean
    COMMAND rm -rf include/gperftools include/google lib/libtcmalloc_minimal* lib/pkgconfig/libtcmalloc* lib/pkgconfig/libprofiler.pc share/doc/gperftools
    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/third-party)

if(BUILD_STATIC)
    add_library(tcmalloc_static STATIC IMPORTED GLOBAL)
    set_target_properties(tcmalloc_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/third-party/lib/libtcmalloc_minimal${CMAKE_STATIC_LIBRARY_SUFFIX})
endif()

if(BUILD_SHARED)
    add_library(tcmalloc SHARED IMPORTED GLOBAL)
    set_target_properties(tcmalloc PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/third-party/lib/libtcmalloc_minimal${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()

if(WITH_TCM)
    # Trailing slash: install the contents of third-party/lib, so the destination follows
    # CMAKE_INSTALL_LIBDIR instead of always being the literal <prefix>/lib. Consumers look
    # for these next to the OpenFHE libraries, via OpenFHE_LIBDIR.
    install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/third-party/lib/ DESTINATION ${OpenFHE_INSTALL_LIBDIR}
        FILES_MATCHING PATTERN "libtcmalloc_minimal.*")
    list(APPEND THIRDPARTYLIBS "tcmalloc")
    list(APPEND THIRDPARTYSTATICLIBS "tcmalloc_static")
    install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/third-party/include DESTINATION ${OpenFHE_INSTALL_INCLUDEDIR}/third-party/)
endif()

if(WITH_NTL)
    # Find gmp and ntl libraries. They must be installed by the user
    list(APPEND general_paths "/usr" "/usr/local" "/opt" "/opt/local")
    list(APPEND header_suffixes "include" "include/NTL" "include/${CMAKE_LIBRARY_ARCHITECTURE}")
    list(APPEND lib_suffixes "lib" "lib/${CMAKE_LIBRARY_ARCHITECTURE}")
    if(NOT(NTL_INCLUDE_DIR AND NTL_LIBRARIES))
        find_path(NTL_INCLUDE_DIR
            NAMES RR.h
            PATHS ${general_paths}
            PATH_SUFFIXES ${header_suffixes})
        find_library(NTL_LIBRARIES
            NAMES ntl libntl
            ONLY_CMAKE_FIND_ROOT_PATH
            PATHS ${general_paths}
            PATH_SUFFIXES ${lib_suffixes})
        include(FindPackageHandleStandardArgs)
        FIND_PACKAGE_HANDLE_STANDARD_ARGS(NTL DEFAULT_MSG NTL_INCLUDE_DIR NTL_LIBRARIES)
        if(NTL_FOUND)
            get_filename_component(NTL_LIBRARIES ${NTL_LIBRARIES} DIRECTORY)
        else()
            message(FATAL_ERROR "** ERROR ** libntl is not found."
                "In order to use MATHBACKEND 6, install libntl or pass -DNTL_INCLUDE_DIR=<dir> and -DNTL_LIBRARIES=<dir> to cmake")
        endif()
    endif()

    if (NOT(GMP_INCLUDE_DIR AND GMP_LIBRARIES))
        find_path(GMP_INCLUDE_DIR
            NAMES gmp.h
            PATHS ${general_paths}
            PATH_SUFFIXES ${header_suffixes})
        find_library(GMP_LIBRARIES
            NAMES gmp libgmp
            ONLY_CMAKE_FIND_ROOT_PATH
            PATHS ${general_paths}
            PATH_SUFFIXES ${lib_suffixes})

        include(FindPackageHandleStandardArgs)
        FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES)
        if(GMP_FOUND)
            get_filename_component(GMP_LIBRARIES ${GMP_LIBRARIES} DIRECTORY)
        else()
            message(FATAL_ERROR "** ERROR ** libgmp is not found."
                "In order to use MATHBACKEND 6, install libgmp or pass -DGMP_INCLUDE_DIR=<dir> and -GMPL_LIBRARIES=<dir> to cmake")
        endif()
    endif()
    mark_as_advanced(NTL_INCLUDE_DIR NTL_LIBRARIES)
    mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES)
    include_directories(${NTL_INCLUDE_DIR})
    include_directories(${GMP_INCLUDE_DIR})
    link_directories(${NTL_LIBRARIES})
    link_directories(${GMP_LIBRARIES})

    list(APPEND THIRDPARTYLIBS "ntl")
    list(APPEND THIRDPARTYLIBS "gmp")
    list(APPEND THIRDPARTYSTATICLIBS "ntl")
    list(APPEND THIRDPARTYSTATICLIBS "gmp")
endif()

set(DEMODATAPATH ${CMAKE_CURRENT_SOURCE_DIR}/demoData)
set(BINDEMODATAPATH ${CMAKE_CURRENT_BINARY_DIR}/demoData)

# Copies demoData folder from the root of the repo to build/demoData if the folder does not exist.
# The target itself is always defined (src/core depends on it); the copy runs only when the
# source folder is present. A source tree that omits demoData (for example a distribution
# tarball that strips it) would otherwise print a spurious "cp: can't stat" line on every
# build, masked by the trailing echo.
if(EXISTS ${DEMODATAPATH})
    add_custom_target(third-party ALL
        COMMAND [ ! -d ${BINDEMODATAPATH} ] && cp -R ${DEMODATAPATH} ${BINDEMODATAPATH} && echo "-- Copied demoData files" || echo "-- demoData folder already exists")
else()
    add_custom_target(third-party ALL)
endif()

# When running "make clean", additionally deletes the demoData folder and CMake cache file
set(ADDITIONAL_CLEAN_FILES "")
LIST(APPEND ADDITIONAL_CLEAN_FILES ${BINDEMODATAPATH})
LIST(APPEND ADDITIONAL_CLEAN_FILES ${CMAKE_CURRENT_BINARY_DIR}/CMakeCache.txt)

if(BUILD_UNITTESTS)
    set(UNITTESTMAIN ${PROJECT_SOURCE_DIR}/test/Main_TestAll.cpp)
endif()

add_subdirectory(src/core)
add_subdirectory(src/pke)
add_subdirectory(src/binfhe)

# Build the google test handlers
if(BUILD_UNITTESTS)
    add_subdirectory(third-party/google-test EXCLUDE_FROM_ALL)
    ############################################################################################################
    # Handle third-party/google-test/googletest's uninitialized variable dummy. Remove
    # add_compile_options(-Wno-error=maybe-uninitialized)
    # once googletest has been updated to v1.11.0 or later.
    ############################################################################################################
    # 1. This applies to gtest only
    # 2. Build does not fail because of "uninitialized error"
    # 3. We still see a warning if the variable is truly uninitialized
    # 4. All other warnings still remain errors
    ############################################################################################################
    if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
        target_compile_options(gtest PRIVATE -Wno-error=maybe-uninitialized)
        target_compile_options(gtest_main PRIVATE -Wno-error=maybe-uninitialized)
    elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
        target_compile_options(gtest PRIVATE -Wno-uninitialized)
        target_compile_options(gtest_main PRIVATE -Wno-uninitialized)
    endif()
endif()

if(BUILD_BENCHMARKS)
    set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Enable testing of the benchmark library." FORCE)
    set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "Enable installation of benchmark. (Projects embedding benchmark may want to turn this OFF.)" FORCE)
    set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "Enable building the unit tests which depend on gtest" FORCE)
    add_subdirectory(third-party/google-benchmark EXCLUDE_FROM_ALL)
    add_subdirectory(benchmark)
endif()

if(BUILD_UNITTESTS)
    add_custom_target(testall
        DEPENDS core_tests pke_tests  binfhe_tests
        COMMAND echo core: && unittest/core_tests -t || true
        COMMAND echo pke: && unittest/pke_tests -t || true
        COMMAND echo binfhe: && unittest/binfhe_tests -t)
endif()

if(BUILD_EXAMPLES)
    add_custom_target(allexamples DEPENDS allcoreexamples allpkeexamples allbinfheexamples)
endif()

if(BUILD_EXTRAS)
    add_custom_target(allextras DEPENDS allcoreextras allpkeextras)
endif()

add_custom_target(allmodules DEPENDS ${OpenFHE_PACKAGE_LIBS})

# Add the additional "make clean" files
GET_DIRECTORY_PROPERTY(clean_files ADDITIONAL_MAKE_CLEAN_FILES)
LIST(APPEND            clean_files ${ADDITIONAL_CLEAN_FILES})
LIST(REMOVE_DUPLICATES clean_files)
LIST(REMOVE_ITEM       clean_files "")
SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${clean_files}")

export(EXPORT OpenFHETargets FILE "${PROJECT_BINARY_DIR}/OpenFHETargets.cmake")

export(PACKAGE OpenFHE)

# Create the OpenFHEConfig.cmake and OpenFHEConfigVersion files
# Preserve the build-tree config's existing install-path variables.
set(PACKAGE_INIT "")
set(PACKAGE_OpenFHE_INSTALL_INCLUDEDIR "${OpenFHE_FULL_INSTALL_INCLUDEDIR}")
set(PACKAGE_OpenFHE_INSTALL_LIBDIR "${OpenFHE_FULL_INSTALL_LIBDIR}")
set(PACKAGE_OpenFHE_INSTALL_BINDIR "${OpenFHE_FULL_INSTALL_BINDIR}")
configure_file(OpenFHEConfig.cmake.in "${PROJECT_BINARY_DIR}/OpenFHEConfig.cmake" @ONLY)

# Resolve install-tree paths relative to the package file, including custom libdirs.
include(CMakePackageConfigHelpers)
configure_package_config_file(OpenFHEConfig.cmake.in
    "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenFHEConfig.cmake"
    INSTALL_DESTINATION "${INSTALL_CMAKE_DIR}"
    PATH_VARS OpenFHE_INSTALL_INCLUDEDIR OpenFHE_INSTALL_LIBDIR OpenFHE_INSTALL_BINDIR
    NO_SET_AND_CHECK_MACRO
    NO_CHECK_REQUIRED_COMPONENTS_MACRO)
# ... for both
configure_file(OpenFHEConfigVersion.cmake.in "${PROJECT_BINARY_DIR}/OpenFHEConfigVersion.cmake" @ONLY)

# Install the OpenFHEConfig.cmake and OpenFHEConfigVersion.cmake
install(FILES
  "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenFHEConfig.cmake"
  "${PROJECT_BINARY_DIR}/OpenFHEConfigVersion.cmake"
  DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev)

# Install the export set for use with the install-tree
install(EXPORT OpenFHETargets DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev)
# install(EXPORT OpenFHETargets DESTINATION "${INSTALL_CMAKE_DIR}" NAMESPACE OpenFHE:: COMPONENT dev)
