# PARI/GP Number Theory-oriented Computer Algebra System
#         Copyright (C) 2024 The PARI Group, Bordeaux.
# 
# PARI/GP is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version. It is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY WHATSOEVER.
# 
# Check the License for details. You should have received a copy of it, along
# with the package; see the file 'COPYING'. If not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

cmake_minimum_required(VERSION 3.10.0)
project(xeus-gp)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}")

set(XEUS_GP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)

# Versionning
# ===========

file(STRINGS "${XEUS_GP_INCLUDE_DIR}/xeus-gp/xeus_gp_config.hpp" version_defines
     REGEX "#define XEUS_GP_VERSION_(MAJOR|MINOR|PATCH)")
foreach (ver ${version_defines})
    if (ver MATCHES "#define XEUS_GP_VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$")
        set(XEUS_GP_VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}" CACHE INTERNAL "")
    endif ()
endforeach ()
set(${PROJECT_NAME}_VERSION
    ${XEUS_GP_VERSION_MAJOR}.${XEUS_GP_VERSION_MINOR}.${XEUS_GP_VERSION_PATCH})
message(STATUS "Building xeus-gp v${${PROJECT_NAME}_VERSION}")

# Configuration
# =============

include(GNUInstallDirs)

if (NOT DEFINED XEUS_GP_KERNELSPEC_PATH)
    set(XEUS_GP_KERNELSPEC_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/")
endif ()

configure_file (
    "${CMAKE_CURRENT_SOURCE_DIR}/share/jupyter/kernels/xeus-gp/kernel.json.in"
    "${CMAKE_CURRENT_SOURCE_DIR}/share/jupyter/kernels/xeus-gp/kernel.json"
)

# Build options
# =============

option(XEUS_GP_BUILD_STATIC "Build xeus-gp static library" ON)
option(XEUS_GP_BUILD_SHARED "Split xeus-gp build into executable and library" OFF)
option(XEUS_GP_BUILD_EXECUTABLE "Build the xeus-gp executable" ON)

option(XEUS_GP_USE_SHARED_XEUS "Link xeus-gp  with the xeus shared library (instead of the static library)" ON)
option(XEUS_GP_USE_SHARED_XEUS_GP "Link xeus-gp  with the xeus-gp shared library (instead of the static library)" OFF)

option(XEUS_GP_INSTALL_CMAKE_FILES "Install the .cmake files" OFF)

# Dependencies
# ============


set(xeus_REQUIRED_VERSION 5.0.0)
set(xeus-zmq_REQUIRED_VERSION 3.0.0)

if (NOT TARGET xtl)
    find_package(xtl ${xtl_REQUIRED_VERSION} REQUIRED)
endif ()
if (NOT TARGET xeus AND NOT TARGET xeus-static)
    find_package(xeus ${xeus_REQUIRED_VERSION} REQUIRED)
endif ()

# Flags
# =====
include(CheckCXXCompilerFlag)

if (MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251 /wd4141")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4018 /wd4267 /wd4715 /wd4146 /wd4129")
endif ()

if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Intel")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused-parameter -Wextra -Wreorder")
    

    CHECK_CXX_COMPILER_FLAG("-std=c++17" HAS_CPP_17_FLAG)
    if (HAS_CPP_17_FLAG)
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
    else ()
        message(FATAL_ERROR "Unsupported compiler -- xeus requires C++17 support!")
    endif ()
endif ()



# Source files
# ============

set(XEUS_GP_HEADERS
    include/xeus-gp/xeus_gp_config.hpp
    include/xeus-gp/xinterpreter.hpp
)

set(XEUS_GP_SRC
    src/xinterpreter.cpp
)

set(XEUS_GP_MAIN_SRC
    src/main.cpp
)


# Targets and link - Macros
# =========================

include(CheckCXXCompilerFlag)

string(TOUPPER "${CMAKE_BUILD_TYPE}" U_CMAKE_BUILD_TYPE)

set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib; ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")

macro(xeus_gp_set_common_options target_name)
    if (MSVC)
        target_compile_options(${target_name} PUBLIC /wd4251 /wd4141)
        target_compile_options(${target_name} PUBLIC /wd4018 /wd4267 /wd4715 /wd4146 /wd4129)
    endif ()

    if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR
        CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR
        CMAKE_CXX_COMPILER_ID MATCHES "Intel")

        target_compile_options(${target_name} PUBLIC -Wunused-parameter -Wextra -Wreorder)

        # C++17 flag
        CHECK_CXX_COMPILER_FLAG("-std=c++17" HAS_CPP_17_FLAG)
        if (HAS_CPP_17_FLAG)
            target_compile_features(${target_name} PRIVATE cxx_std_17)
        else ()
            message(FATAL_ERROR "Unsupported compiler -- xeus-gp requires C++17 support!")
        endif ()
    endif ()

    if (APPLE)
        set_target_properties(${target_name} PROPERTIES
            MACOSX_RPATH ON
        )
    else ()
        set_target_properties(${target_name} PROPERTIES
            BUILD_WITH_INSTALL_RPATH 1
            SKIP_BUILD_RPATH FALSE
        )
    endif ()

    set_target_properties(${target_name} PROPERTIES
        INSTALL_RPATH_USE_LINK_PATH TRUE
    )
endmacro()

# Common macro kernels (xeus-gp)
macro(xeus_gp_set_kernel_options target_name)
    if (XEUS_GP_USE_SHARED_XEUS_GP)
        target_link_libraries(${target_name} PRIVATE xeus-gp)
        if(CMAKE_DL_LIBS)
            target_link_libraries(${target_name} PRIVATE ${CMAKE_DL_LIBS} util)
        endif()
    else ()
        target_link_libraries(${target_name} PRIVATE xeus-gp-static)
    endif()

    find_package(Threads)
        target_link_libraries(${target_name} PRIVATE ${CMAKE_THREAD_LIBS_INIT})
    
endmacro()

# Common macro for shared and static library
macro(xeus_gp_create_target target_name linkage output_name)
    string(TOUPPER "${linkage}" linkage_upper)

    if (NOT ${linkage_upper} MATCHES "^(SHARED|STATIC)$")
        message(FATAL_ERROR "Invalid library linkage: ${linkage}")
    endif ()

    add_library(${target_name} ${linkage_upper} ${XEUS_GP_SRC} ${XEUS_GP_HEADERS})
    xeus_gp_set_common_options(${target_name})

    set_target_properties(${target_name} PROPERTIES
                          PUBLIC_HEADER "${XEUS_GP_HEADERS}"
                          PREFIX ""
                          VERSION ${${PROJECT_NAME}_VERSION}
                          SOVERSION ${XEUS_GP_VERSION_MAJOR}
                          OUTPUT_NAME "lib${output_name}")

    target_compile_definitions(${target_name} PUBLIC "XEUS_GP_EXPORTS")

    target_compile_features(${target_name} PRIVATE cxx_std_17)

    target_include_directories(${target_name}
                               PUBLIC
                               $<BUILD_INTERFACE:${XEUS_GP_INCLUDE_DIR}>
                               $<INSTALL_INTERFACE:include>)

    if (XEUS_GP_USE_SHARED_XEUS)
        set(XEUS_GP_XEUS_TARGET xeus)
    else ()
        set(XEUS_GP_XEUS_TARGET xeus-static)
    endif ()

    target_link_libraries(${target_name} PUBLIC ${XEUS_GP_XEUS_TARGET})
    if (WIN32 OR CYGWIN)
        #
    elseif (APPLE)
        target_link_libraries(${target_name} PRIVATE "-undefined dynamic_lookup")
    endif ()
    find_package(Threads) # TODO: add Threads as a dependence of xeus-static?
        target_link_libraries(${target_name} PRIVATE ${CMAKE_THREAD_LIBS_INIT})
    
endmacro()

# xeus-gp
# ===========

set(XEUS_GP_TARGETS "")

if (XEUS_GP_BUILD_SHARED)
    # Build libraries
    xeus_gp_create_target(xeus-gp SHARED xeus-gp)
    list(APPEND XEUS_GP_TARGETS xeus-gp)
    target_link_libraries(xeus-gp PRIVATE pari)
    target_link_libraries(xeus-gp PRIVATE readline)
endif ()

if (XEUS_GP_BUILD_STATIC)
    # On Windows, a static library should use a different output name
    # to avoid the conflict with the import library of a shared one.
    if (CMAKE_HOST_WIN32)
        xeus_gp_create_target(xeus-gp-static STATIC xeus-gp-static)
    else ()
        xeus_gp_create_target(xeus-gp-static STATIC xeus-gp)
    endif ()
    list(APPEND XEUS_GP_TARGETS xeus-gp-static)
    target_link_libraries(xeus-gp-static PRIVATE pari)
    target_link_libraries(xeus-gp-static PRIVATE readline)
endif ()

# xeus-gp
# =======
if (XEUS_GP_BUILD_EXECUTABLE)
    find_package(xeus-zmq ${xeus-zmq_REQUIRED_VERSION} REQUIRED)
    add_executable(xeus-gp ${XEUS_GP_MAIN_SRC})
    target_compile_features(xeus-gp PRIVATE cxx_std_17)
    xeus_gp_set_common_options(xeus-gp)
    xeus_gp_set_kernel_options(xeus-gp)
    target_link_libraries(xeus-gp PRIVATE xeus-zmq)
endif()



# Installation
# ============
include(CMakePackageConfigHelpers)

set(XEUS_GP_CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" CACHE STRING "install path for xeus-gpConfig.cmake")

# Install xeus-gp and xeus-gp-static
if (XEUS_GP_BUILD_SHARED)
    install(TARGETS ${XEUS_GP_TARGETS}
            EXPORT ${PROJECT_NAME}-targets
            ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
            LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
            RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
            PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/xeus-gp)

    # Makes the project importable from the build directory
    export(EXPORT ${PROJECT_NAME}-targets
           FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake")
endif ()

# Install xeus-gp
if (XEUS_GP_BUILD_EXECUTABLE)
    install(TARGETS xeus-gp
            RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

    # Configuration and data directories for jupyter and xeus-gp
    set(XJUPYTER_DATA_DIR "share/jupyter"    CACHE STRING "Jupyter data directory")

    # Install xeus-gp Jupyter kernelspec
    set(KERNELSPEC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/share/jupyter/kernels)
    install(DIRECTORY ${KERNELSPEC_DIR}
            DESTINATION ${XJUPYTER_DATA_DIR}
            PATTERN "*.in" EXCLUDE)


    # Extra path for installing Jupyter kernelspec
    if (XEXTRA_JUPYTER_DATA_DIR)
        install(DIRECTORY ${KERNELSPEC_DIR}
                DESTINATION ${XEXTRA_JUPYTER_DATA_DIR}
                PATTERN "*.in" EXCLUDE)
    endif ()
endif ()

# Configure 'xeus-gpConfig.cmake' for a build tree
set(XEUS_GP_CONFIG_CODE "####### Expanded from \@XEUS_GP_CONFIG_CODE\@ #######\n")
set(XEUS_GP_CONFIG_CODE "${XEUS_GP_CONFIG_CODE}set(CMAKE_MODULE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/cmake;\${CMAKE_MODULE_PATH}\")\n")
set(XEUS_GP_CONFIG_CODE "${XEUS_GP_CONFIG_CODE}##################################################")
configure_package_config_file(${PROJECT_NAME}Config.cmake.in
                              "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
                              INSTALL_DESTINATION ${PROJECT_BINARY_DIR})

# Configure 'xeus-gpConfig.cmake.in for an install tree
set(XEUS_GP_CONFIG_CODE "")
configure_package_config_file(${PROJECT_NAME}Config.cmake.in
                              "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}Config.cmake"
                              INSTALL_DESTINATION ${XEUS_GP_CMAKECONFIG_INSTALL_DIR})

write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
                                 VERSION ${${PROJECT_NAME}_VERSION}
                                 COMPATIBILITY AnyNewerVersion)
if (XEUS_GP_INSTALL_CMAKE_FILES)
  install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}Config.cmake
                ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
                DESTINATION ${XEUS_GP_CMAKECONFIG_INSTALL_DIR})
  if (XEUS_GP_BUILD_SHARED)
      install(EXPORT ${PROJECT_NAME}-targets
              FILE ${PROJECT_NAME}Targets.cmake
              DESTINATION ${XEUS_GP_CMAKECONFIG_INSTALL_DIR})
  endif ()
endif ()

install(FILES xeus-gp.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)

# Build tarball from git

set(ARCHIVE_NAME ${PROJECT_NAME}-${${PROJECT_NAME}_VERSION})
add_custom_target(dist
    COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar.gz HEAD
        > ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.gz
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
