project(hecuba_core)
cmake_minimum_required(VERSION 3.3)

list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) #folder with FindCmakes

set(CORE_SRC_DIR "${CMAKE_SOURCE_DIR}/src")

# Default Options
option(USE_ARROW  "Enable ARROW support" FALSE)
option(USE_EXTRAE "Enable EXTRAE support" FALSE)

# CMAKE_BUILD_PARALLEL_LEVEL controls the level of parallelism, use it for Make also
message(STATUS "===> CMAKE_BUILD_PARALLEL_LEVEL=$ENV{CMAKE_BUILD_PARALLEL_LEVEL}")
if (NOT $ENV{CMAKE_BUILD_PARALLEL_LEVEL} STREQUAL "")
    set(CMAKE_BUILD_PARALLEL_LEVEL $ENV{CMAKE_BUILD_PARALLEL_LEVEL})
    set(JOBS ${CMAKE_BUILD_PARALLEL_LEVEL})
else ()
    set(JOBS 1)
endif ()

include(ExternalProject)


# Compiler related configuration
set(CMAKE_CXX_STANDARD 11)

#If the default compiler doesn't support C++11, set the environment vars [CC,CXX] pointing to the compilers binaries
# desired to compile C and C++ respectively
IF (NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release) #Release|Debug|RelWithDebInfo|MinSizeRel
ENDIF (NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)


message("CMAKE_BUILD_TYPE is ${CMAKE_BUILD_TYPE}")

set(C_BINDING_INSTALL_PREFIX ${PROJECT_BINARY_DIR} CACHE FILEPATH "Path to install Hecuba deps")
message("Dependencies install path ${C_BINDING_INSTALL_PREFIX}")


set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${C_BINDING_INSTALL_PREFIX}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${C_BINDING_INSTALL_PREFIX}/bin)

#Add environment $CPLUS_INCLUDE_PATH include paths
if (NOT $ENV{CPLUS_INCLUDE_PATH} STREQUAL "")
    string(REPLACE ":" " " INCLUDES $ENV{CPLUS_INCLUDE_PATH})
    string(REPLACE "//" "/" INCLUDES ${INCLUDES})
    string(REGEX MATCHALL "[^ ]+" LIST_INCLUDES ${INCLUDES})
endif (NOT $ENV{CPLUS_INCLUDE_PATH} STREQUAL "")
include_directories(${LIST_INCLUDES})


#Add environment $C_INCLUDE_PATH include paths
if (NOT $ENV{C_INCLUDE_PATH} STREQUAL "")
    string(REPLACE ":" " " INCLUDES $ENV{C_INCLUDE_PATH})
    string(REPLACE "//" "/" INCLUDES ${INCLUDES})
    string(REGEX MATCHALL "[^ ]+" LIST_INCLUDES ${INCLUDES})
endif (NOT $ENV{C_INCLUDE_PATH} STREQUAL "")
include_directories(${LIST_INCLUDES})


#Add environment $CPATH include paths
if (NOT $ENV{CPATH} STREQUAL "")
    string(REPLACE ":" " " INCLUDES $ENV{CPATH})
    string(REPLACE "//" "/" INCLUDES ${INCLUDES})
    string(REGEX MATCHALL "[^ ]+" LIST_INCLUDES ${INCLUDES})
endif (NOT $ENV{CPATH} STREQUAL "")
include_directories(${LIST_INCLUDES})

#Add includes generated by this project
include_directories(${CORE_SRC_DIR} ${CORE_SRC_DIR}/api ${C_BINDING_INSTALL_PREFIX}/include)


#LD_LIBRARY being picked by the compiler (probably)
if (NOT $ENV{LD_LIBRARY_PATH} STREQUAL "")
    string(REPLACE ":" " " ENV_LIBS $ENV{LD_LIBRARY_PATH})
    string(REPLACE "//" "/" ENV_LIBS ${ENV_LIBS})
    string(REGEX MATCHALL "[^ ]+" LIST_LIBS ${ENV_LIBS})
endif (NOT $ENV{LD_LIBRARY_PATH} STREQUAL "")


# SUBPROJECTS

#if the user has set custom compiler paths, forward to submodules
if (${CC})
    set(CMAKE_SUBPROJECT_FLAGS ${CMAKE_SUBPROJECT_FLAGS} " -DCMAKE_C_COMPILER=${CC}")
endif ()

if (${CXX})
    set(CMAKE_SUBPROJECT_FLAGS ${CMAKE_SUBPROJECT_FLAGS} " -DCMAKE_CXX_COMPILER=${CXX}")
endif ()


if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
    set(CMAKE_CXX_FLAGS "-gcc --std=c++11 -pragma-optimization-level=GCC ${CMAKE_CXX_FLAGS}")
endif ()


#LINK DIRECTORIES discouraged, not working properly sometimes
LINK_DIRECTORIES(${C_BINDING_INSTALL_PREFIX}/lib64 ${C_BINDING_INSTALL_PREFIX}/lib)


# SOURCES

file(GLOB CORE_H_FILES ${CORE_SRC_DIR}/*.h)
file(GLOB CORE_SRC_FILES ${CORE_SRC_DIR}/*.cpp)

file(GLOB API_H_FILES ${CORE_SRC_DIR}/api/*.h)
file(GLOB API_SRC_FILES ${CORE_SRC_DIR}/api/*.cpp)

set(HEADER_FILES ${CORE_H_FILES} ${API_H_FILES})
set(SOURCE_FILES ${CORE_SRC_FILES} ${API_SRC_FILES})

#set(CMAKE_SKIP_RPATH 1)
#set(MY_PATHS "${ORIGIN}")
#CMake main target

# 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 TRUE)

# don't 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 FALSE)


add_library(hfetch SHARED ${SOURCE_FILES} ${HEADER_FILES})

set_target_properties(hfetch
        PROPERTIES
        #	PREFIX ""
        INSTALL_RPATH "$ORIGIN/"
        BUILD_WITH_INSTALL_RPATH 1)


# Compile options

#set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall")
target_compile_options(hfetch PUBLIC $<$<COMPILE_LANGUAGE:CXX>:-Wall>)
set_property(TARGET hfetch PROPERTY POSITION_INDEPENDENT_CODE ON) # Add -fPIC option


#Dependencies
FIND_LIBRARY(CASS NAMES cassandra PATHS ${C_BINDING_INSTALL_PREFIX}/lib ENV LD_LIBRARY_PATH)
FIND_LIBRARY(LIBUV NAMES uv PATHS ${C_BINDING_INSTALL_PREFIX}/lib ENV LD_LIBRARY_PATH)
FIND_LIBRARY(TBB NAMES tbb PATHS ${C_BINDING_INSTALL_PREFIX}/lib ENV LD_LIBRARY_PATH)
FIND_LIBRARY(ARROW NAMES arrow PATHS ${C_BINDING_INSTALL_PREFIX}/lib ENV LD_LIBRARY_PATH)
#yolandab
FIND_LIBRARY(KAFKA NAMES rdkafka PATHS ${C_BINDING_INSTALL_PREFIX}/lib ENV LD_LIBRARY_PATH)
FIND_PROGRAM(KAFKATOOLS NAMES kafka-server-start.sh zookeeper-server-start.sh PATHS ${C_BINDING_INSTALL_PREFIX}/bin ENV PATH)

if (USE_EXTRAE)
    # Locate Extrae library path (must contain 'include' and 'lib' directories)
    FIND_PATH(EXTRAE include/extrae.h
              PATHS ${C_BINDING_INSTALL_PREFIX}/lib ENV LD_LIBRARY_PATH ENV EXTRAE_HOME)

    if (NOT EXTRAE)
        message("EXTRAE NOT FOUND")
    else ()
        message(STATUS "Using system's EXTRAE Library: " ${EXTRAE})
        #set(ALL_LIBS ${ALL_LIBS} ${EXTRAE}/lib/libpttrace.so)
        set(ALL_LIBS ${ALL_LIBS} ${EXTRAE}/lib/libptmpitrace.so)
        include_directories(${EXTRAE}/include)

        add_compile_definitions(EXTRAE)    # Define EXTRAE preprocessor macro to enable extrae use
    endif ()
else(USE_EXTRAE)
    message(STATUS "EXTRAE is disabled by user!")
endif(USE_EXTRAE)

#if (NOT KAFKATOOLS)
#    message("Downloading Kafka TOOLS")
#    unset(KAFKATOOLS) #to avoid name clash
#    ExternalProject_Add(
#        KAFKATOOLS    #KAFKA-prefix
#        DOWNLOAD_DIR ${CMAKE_CURRENT_LIST_DIR}/dependencies
#        URL "https://dlcdn.apache.org/kafka/3.2.0/kafka_2.13-3.2.0.tgz"
#        URL_HASH SHA1=5021e19cab9f5e09d13875946e43605f99acec75
#        INSTALL_DIR ${C_BINDING_INSTALL_PREFIX}
#
#    )
#else ()
#    message(STATUS "Using system's KAFKA TOOLS: " ${KAFKATOOLS})
#endif ()

if (NOT KAFKA)
    message("Downloading Kafka library")
    unset(KAFKA) #to avoid name clash
    ExternalProject_Add(
        KAFKA   #KAFKA-prefix
        DOWNLOAD_DIR ${CMAKE_CURRENT_LIST_DIR}/dependencies
        URL "https://github.com/edenhill/librdkafka/archive/refs/tags/v1.9.2.zip"
        URL_HASH SHA256=4ecb0a3103022a7cab308e9fecd88237150901fa29980c99344218a84f497b86
        INSTALL_DIR ${C_BINDING_INSTALL_PREFIX}
        CMAKE_COMMAND echo
        BUILD_COMMAND ./configure --prefix=${C_BINDING_INSTALL_PREFIX}
        BUILD_IN_SOURCE 1
        INSTALL_COMMAND make -j ${JOBS} COMMAND make install
    )
    add_dependencies(hfetch KAFKA)
    set(ALL_LIBS ${ALL_LIBS} ${C_BINDING_INSTALL_PREFIX}/lib/librdkafka.so)
else ()
    message(STATUS "Using system's KAFKA Library: " ${KAFKA})
    set(ALL_LIBS ${ALL_LIBS} ${KAFKA})
endif ()

IF (NOT LIBUV)
    message("Downloading LIBUV (requisite for Cassandra C++ driver)")
    unset(LIBUV) #to avoid name clash
    ExternalProject_Add(
            LIBUV
            DOWNLOAD_DIR ${CMAKE_CURRENT_LIST_DIR}/dependencies
            URL "https://github.com/libuv/libuv/archive/v1.11.0.tar.gz"
            URL_HASH SHA1=54f0972aa0d3f6a6036d477b381c01f030f9a2b5
            CMAKE_COMMAND echo
            BUILD_COMMAND sh autogen.sh COMMAND ./configure --prefix=${C_BINDING_INSTALL_PREFIX}
            BUILD_IN_SOURCE 1
            INSTALL_COMMAND make -j ${JOBS} COMMAND make install
    )
    set(LIBUV_EXTERNAL LIBUV)
    set(LIBUV_ROOT_DIR ${C_BINDING_INSTALL_PREFIX})
    # Cassandra driver needs libuv but it does not set RPATH, therefore
    # add a fake requirement in hfetch for libuv to ensure that it is found with hfetch's RPATH
    # (which is the one that will be installed by python and will call the cassandra driver)
    add_dependencies(hfetch LIBUV)
    target_link_libraries(hfetch uv)
else ()
    message(STATUS "Using system's LIBUV : " ${LIBUV})
    set(LIBUV_EXTERNAL)
    get_filename_component(LIBUV_ROOT_DIR ${LIBUV} DIRECTORY)
    set(ALL_LIBS ${ALL_LIBS} ${LIBUV})
endif ()


if (NOT CASS)
    message("Downloading C++ Cassandra Driver")
    unset(CASS) #to avoid name clash

    if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
        set(CASS_URL https://github.com/polsm91/cpp-driver/archive/2.7.1.0.tar.gz)
        set(CASS_SHA1 814878b3750ebd379b66df239bd3aa23981044b5)
    else ()
        set(CASS_URL https://github.com/datastax/cpp-driver/archive/2.14.1.tar.gz)
        set(CASS_SHA1 c6399a03d49d1391ee53415e75e58d64bc32795c)
    endif ()
    ExternalProject_Add(
            CASS
            DEPENDS ${LIBUV_EXTERNAL}
            DOWNLOAD_DIR ${CMAKE_CURRENT_LIST_DIR}/dependencies
            URL ${CASS_URL}
            URL_HASH SHA1=${CASS_SHA1}
            INSTALL_DIR ${C_BINDING_INSTALL_PREFIX}
            CMAKE_ARGS ${CMAKE_SUBPROJECT_FLAGS}
            -DCMAKE_INSTALL_PREFIX=${C_BINDING_INSTALL_PREFIX} -DCASS_USE_LIBSSH2=OFF -DCASS_USE_OPENSSL=OFF -DLIBUV_ROOT_DIR=${LIBUV_ROOT_DIR}
    )
    # Hecuba needs access to the hash used to generate keys, aka murmur3, which are not public by default
    ExternalProject_Add_Step(
            CASS
            AFTER_INSTALL
            DEPENDEES install
            COMMAND mkdir -p ${C_BINDING_INSTALL_PREFIX}/include
            COMMAND mkdir -p ${C_BINDING_INSTALL_PREFIX}/lib/${PYTHON_VERSION}/site-packages
            COMMAND cp -r ${CMAKE_CURRENT_BINARY_DIR}/CASS-prefix/src/CASS/src/macros.hpp ${C_BINDING_INSTALL_PREFIX}/include
            COMMAND cp -r ${CMAKE_CURRENT_BINARY_DIR}/CASS-prefix/src/CASS/src/murmur3.hpp ${C_BINDING_INSTALL_PREFIX}/include
    )

    add_dependencies(hfetch CASS)
    set(ALL_LIBS ${ALL_LIBS} ${C_BINDING_INSTALL_PREFIX}/lib/libcassandra.so)
else ()
    message(STATUS "Using system's C++ Cassandra driver: " ${CASS})
    set(ALL_LIBS ${ALL_LIBS} ${CASS})
endif ()



if (NOT TBB)
    message("Downloading Intel TBB")
    unset(TBB)
    set(build_prefix tbb_prefix)
    ExternalProject_Add(
            TBB
            DOWNLOAD_DIR ${CMAKE_CURRENT_LIST_DIR}/dependencies
            URL "https://github.com/intel/tbb/archive/v2020.0.tar.gz"
            URL_HASH SHA1=fd97f9c60e289fb6a45d3238ae5ac9de63b06c50
            CMAKE_COMMAND echo
            BUILD_COMMAND make -j ${JOBS} tbb tbb_build_prefix=${build_prefix}
            BUILD_IN_SOURCE 1
            INSTALL_COMMAND mkdir -p ${C_BINDING_INSTALL_PREFIX}/lib
            COMMAND mkdir -p ${C_BINDING_INSTALL_PREFIX}/include
            COMMAND cp -r build/${build_prefix}_release/libtbb.so build/${build_prefix}_release/libtbb.so.2 ${C_BINDING_INSTALL_PREFIX}/lib
            COMMAND cp -r include/tbb ${C_BINDING_INSTALL_PREFIX}/include
    )
    add_dependencies(hfetch TBB)
    set(ALL_LIBS ${ALL_LIBS} ${C_BINDING_INSTALL_PREFIX}/lib/libtbb.so)
else ()
    message(STATUS "Using system's TBB: " ${TBB})
    set(ALL_LIBS ${ALL_LIBS} ${TBB})
endif ()

if (USE_ARROW)
if (NOT ARROW)
    message("Downloading Apache ARROW")
    unset(ARROW) #to avoid name clash
    # The following code is a hack to solve an issue with the boost library.
    # By default, the installation detects the Boost library correctly, but
    # when creating the scripts to link, a wrong name is used for the static
    # libraries adding a '_static' suffix to the library name...  (which is
    # a file that does not exist) making the linkage step to fail.
    # This hack executes the following steps to solve the situation:
    # 1) Make an initial (wrong) compilation, but it generates the link scripts,
    # 2) modify the scripts to use the right names, and
    # 3) re-compile and install as expected.
    ExternalProject_Add(
            ARROW	#ARROW-prefix
            DOWNLOAD_DIR ${CMAKE_CURRENT_LIST_DIR}/dependencies
            URL "https://archive.apache.org/dist/arrow/arrow-0.15.1/apache-arrow-0.15.1.tar.gz"
            URL_HASH SHA256=9a2c58c72310eafebb4997244cbeeb8c26696320d0ae3eb3e8512f75ef856fc9
            CONFIGURE_COMMAND ""
            BUILD_COMMAND     ${CMAKE_COMMAND} -E echo "==== Starting configure build"
            COMMAND           ${CMAKE_COMMAND} -j ${JOBS} cpp -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_INSTALL_PREFIX=${C_BINDING_INSTALL_PREFIX} -DARROW_BUILD_STATIC=false
            COMMAND           ${CMAKE_COMMAND} -E echo "==== Starting Patch"
            COMMAND           sed -i s/_static//g src/arrow/CMakeFiles/arrow_shared.dir/link.txt
            COMMAND           ${CMAKE_COMMAND} -E echo "==== Finished Patch"
            INSTALL_COMMAND   make -j ${JOBS}
            COMMAND           make install
            BUILD_IN_SOURCE 1
            INSTALL_DIR ${C_BINDING_INSTALL_PREFIX}
    )
    add_dependencies(hfetch ARROW)
    set(ALL_LIBS ${ALL_LIBS} ${C_BINDING_INSTALL_PREFIX}/lib/libarrow.so)
else ()
    message(STATUS "Using system's ARROW " ${ARROW})
    set(ALL_LIBS ${ALL_LIBS} ${ARROW})
endif ()
add_compile_definitions(ARROW)    # Define ARROW preprocessor macro to enable arrow use
else(USE_ARROW)
    message(STATUS "ARROW (${ARROW}) is disabled by user!")
endif(USE_ARROW)

target_link_libraries(hfetch ${ALL_LIBS})

set_target_properties(hfetch
        PROPERTIES PUBLIC_HEADER "${HEADER_FILES}")
INSTALL(TARGETS hfetch PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE WORLD_READ LIBRARY DESTINATION ${C_BINDING_INSTALL_PREFIX}/lib PUBLIC_HEADER DESTINATION ${C_BINDING_INSTALL_PREFIX}/include/hecuba)


if (DEFINED ENV{GTEST_ROOT})
    #Tests dependencies
    FIND_LIBRARY(PT NAMES pthread PATHS /lib)
    if (PT)
        find_package(GTest)
        if (GTest_FOUND)
            include_directories(${GTEST_INCLUDE_DIRS})

            enable_testing()

            # Pure cpp tests
            add_executable(hfetch_test tests/runtests.cpp)
            set_target_properties(hfetch_test
                    PROPERTIES BUILD_WITH_INSTALL_RPATH 0)
            target_link_libraries(hfetch_test ${ALL_LIBS} ${GTEST_BOTH_LIBRARIES} hfetch ${PT})
            add_dependencies(hfetch_test hfetch)

            add_test(test hfetch_test)

            # Cache tests
            add_executable(cache_imp_test tests/cache_tests.cpp)
            set_target_properties(cache_imp_test
                    PROPERTIES BUILD_WITH_INSTALL_RPATH 0)
            target_link_libraries(cache_imp_test ${ALL_LIBS} ${GTEST_BOTH_LIBRARIES} hfetch ${PT})
            add_dependencies(cache_imp_test hfetch)
            add_test(test cache_imp_test)

            #Arrow tests
            #add_executable(cache_imp_test tests/enrictests_stdcassandra.cpp) # Enric tests std cassandra
            #add_executable(cache_imp_test tests/enrictests_intelcassandra.cpp) # Enric tests intel cassandra
            #set_target_properties(cache_imp_test
            #        PROPERTIES BUILD_WITH_INSTALL_RPATH 0)
            #target_link_libraries(cache_imp_test ${ALL_LIBS} ${GTEST_BOTH_LIBRARIES} hfetch ${PT})
            #add_dependencies(cache_imp_test hfetch)
            #add_test(test cache_imp_test)

            # Meta manager tests
            add_executable(meta_manager_test tests/meta_manager_tests.cpp)
            #add_executable(meta_manager_test_laia tests/meta_manager_tests_laia.cpp)
            set_target_properties(meta_manager_test
                    PROPERTIES BUILD_WITH_INSTALL_RPATH 0)
            target_link_libraries(meta_manager_test ${ALL_LIBS} ${GTEST_BOTH_LIBRARIES} hfetch ${PT})
            add_dependencies(meta_manager_test hfetch)
            add_test(test meta_manager_test)

            # Mixed Python Tests
            find_package(Python3 COMPONENTS Interpreter Development)

            # Before CMake 3.15 locating numpy had limited support
            # See CMake commit 513e77550daaac083cb13b3df53be955a7d0b429
            execute_process(
                    COMMAND "${Python3_EXECUTABLE}" -c
                    "from __future__ import print_function\ntry: import numpy; print(numpy.get_include(), end='')\nexcept:pass\n"
                    RESULT_VARIABLE _Error_code
                    OUTPUT_VARIABLE Python3_NumPy_INCLUDE_DIRS)

            set(Python3_NumPy_FOUND 0)

            if(NOT _Error_code)
                set(Python3_NumPy_FOUND 1)
            endif(NOT _Error_code)

            if (Python3_NumPy_FOUND)
                message("NumPy include dirs: ${Python3_NumPy_INCLUDE_DIRS}")
                include_directories(${Python3_INCLUDE_DIRS} ${Python3_NumPy_INCLUDE_DIRS})
                set(ALL_LIBS ${ALL_LIBS} ${Python3_LIBRARIES})


                file(GLOB COREPY_H_FILES ${CORE_SRC_DIR}/py_interface/*.h)
                file(GLOB COREPY_SRC_FILES ${CORE_SRC_DIR}/py_interface/*.cpp)
                set(PY_H_FILES ${COREPY_H_FILES})
                set(PY_SRC_FILES ${COREPY_SRC_FILES})

                #set(PY_SRC_FILES py_interface/HCache.cpp PythonParser.cpp UnitParser.cpp NumpyStorage.cpp)
                #set(PY_H_FILES HCache.h PythonParser.h UnitParser.h NumpyStorage.h)
                add_library(pyinterface SHARED ${PY_SRC_FILES} ${PY_H_FILES})
                set_target_properties(pyinterface
                        PROPERTIES PREFIX ""
                        INSTALL_RPATH "$ORIGIN/"
                        BUILD_WITH_INSTALL_RPATH 1)
                add_dependencies(pyinterface hfetch)

                target_link_libraries(pyinterface ${ALL_LIBS})


                add_executable(py_hfetch_test tests/pytests.cpp)
                set_target_properties(py_hfetch_test
                        PROPERTIES BUILD_WITH_INSTALL_RPATH 0)
                target_link_libraries(py_hfetch_test ${ALL_LIBS} ${GTEST_BOTH_LIBRARIES} pyinterface hfetch ${PT})
                add_dependencies(py_hfetch_test hfetch)
                add_test(test py_hfetch_test)
            else ()
                message("SEND_ERROR NumPy not found. Not compiling Python-Cpp tests.")
            endif (Python3_NumPy_FOUND)

        else ()
            message(STATUS "If GTest is installed, point the environment var GTEST_ROOT to its root (/home/user/local)")
        endif (GTest_FOUND)
    endif (PT)
endif (DEFINED ENV{GTEST_ROOT})

# ARROW_HELPER
add_executable(arrow_helper ${CORE_SRC_DIR}/arrow_helper/server.cpp)


