cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
cmake_policy(SET CMP0048 NEW)
project(CyRSoXS VERSION 1.2.0.0 DESCRIPTION BetaVersion LANGUAGES CXX CUDA)


option(DOUBLE_PRECISION "Use 64 bit indices for floating point" OFF)
option(VTI_BINARY "Write VTI in binary with 64 base encoding" ON)
option(DUMP_FILES "Dump files for debugging " OFF)
option(Profiling "Enable Profiling " OFF)
option(EOC "Ewald projection on CPU" OFF)
option(BIAXIAL "Biaxial Computation" OFF)
option(BUILD_DOCS "Build Documentation" OFF)
option(PYBIND "Pybind support for CyRSoXS" OFF)
option(USE_64_BIT_INDICES, "Use 64 Bit Indices" OFF)
option(USE_SUBMODULE_PYBIND,"Use submodule Pybind instead of system" OFF)
option(ENABLE_TEST, "Enable test" ON)
option(OUTPUT_BASE_NAME, "Output base name" "CyRSoXS")

if(SKBUILD)
    set(PYBIND ON CACHE BOOL "Pybind support for CyRSoXS" FORCE)
    set(USE_SUBMODULE_PYBIND OFF CACHE BOOL "Use submodule Pybind instead of system" FORCE)
endif()

if(NOT OUTPUT_BASE_NAME)
        set(OUTPUT_BASE_NAME "CyRSoXS")
endif()

set(CYRSOXS_CUDA_ARCHITECTURES "70;75;80;86" CACHE STRING "CUDA GPU architectures (semicolon-separated), or native with CMake>=3.24")

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules")
if (DOUBLE_PRECISION)
    add_definitions(-DDOUBLE_PRECISION)
    message(Configuring with Double precision)
endif ()

if (DUMP_FILES)
    add_definitions(-DDUMP_FILES)
    message(Dumping all files.)
endif ()

if (PROFILING)
    add_definitions(-DPROFILING)
    message(Enabling Profiling.)
endif ()

if (EOC)
    add_definitions(-DEOC)
    find_package(OpenCV REQUIRED)
    message("Ewald on CPU")
endif ()

if (VTI_BINARY)
    add_definitions(-DVTI_BINARY)
    message("Binary writing enabled")
else ()
    add_definitions(-DVTI_ASCII)
    message("ASCII writing enabled")
endif ()

if (USE_64_BIT_INDICES)
    add_definitions(-DUSE_64_BIT_INDICES)
    message("Using 64 Bit indices")
else ()
    message("Using 32 Bit indices")
endif ()

if (BIAXIAL)
    add_definitions(-DBIAXIAL)
    message("Performing Biaxial computation")
else ()
    message("Performing Uniaxial computation")
endif ()

if (PYBIND)
    add_definitions(-DPYBIND)
    message("WARNING: Compiling for PyBind. This will not produce any executable")
endif ()

if (ENABLE_TEST)
    add_definitions(-DENABLE_TEST)
    message("Test enabled")
endif ()

enable_language(CUDA)
find_package(CUDAToolkit)
# x peter commenting out to use new CUDA machinery in cmake >= 3.8 find_package(CUDA 9 REQUIRED)
find_package(OpenMP REQUIRED)

if (OPENMP_FOUND)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
endif ()

# BEGIN code that was contributed by employees of the National Institute of Standards and Technology (NIST), 
# an agency of the Federal Government and is being made available as a public service.
# Pursuant to title 17 United States Code Section 105, works of NIST employees are not subject to copyright protection in the United States

if (PYBIND)
    find_package(Python 3 COMPONENTS Interpreter Development.Module REQUIRED)
    if(USE_SUBMODULE_PYBIND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/pybind11/CMakeLists.txt")
        add_subdirectory(external/pybind11)
    else()
        find_package(pybind11 CONFIG REQUIRED)
    endif()
else ()
    find_package(Config++)
    if (NOT ${CONFIG++_FOUND})
        message(FATAL_ERROR "Libconfig++ could not be located.")
    endif ()
endif ()


# END code that was contributed by employees of NIST


include(cmake/CyRSoXSHDF5.cmake)
if(CYRSOXS_HDF5_EP_TARGET)
    find_package(Threads REQUIRED)
endif()



set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
set(CYRSOXS_SRC
        src/RotationMatrix.cpp
        src/cudaMain.cu)

set(CYRSOXS_INC
        include/cudaUtils.h
        include/cudaMain.h
        include/cudaHeaders.h
        include/Datatypes.h
        include/uniaxial.h
        include/Output/writeVTI.h
        include/Output/cencode.h
        include/Output/outputUtils.h
        include/Input/InputData.h
        include/Input/Input.h
        include/Output/writeH5.h
        include/utils.h
        include/Rotation.h
        include/RotationMatrix.h
        )


find_library(CUDART_LIBRARY cudart ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})
include(cmake/version.cmake)
generate_version_h()

if (NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Release" CACHE STRING
            "Choose the type of build, options are: Debug Release " FORCE)
endif ()


set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler -fopenmp -DNDEBUG --use_fast_math")


if (PYBIND)
    if (MAX_NUM_MATERIAL)
        add_definitions(-DMAX_NUM_MATERIAL=${MAX_NUM_MATERIAL})
    else ()
        add_definitions(-DMAX_NUM_MATERIAL=32)
    endif()
    configure_file(src/pymain.cpp src/pymain-tmp.cpp)
    set(PYBIND_INC
            include/PyClass/RefractiveIndex.h
            include/PyClass/VoxelData.h
            include/PyClass/ScatteringPattern.h
            include/PyClass/Polarization.h
            )
    set(PYBIND_SRC
            src/pymain-tmp.cpp
            )


    add_library(${OUTPUT_BASE_NAME} SHARED
            ${PYBIND_SRC} ${CYRSOXS_SRC} ${CYRSOXS_INC})
    target_include_directories(${OUTPUT_BASE_NAME} PUBLIC ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES} ${HDF5_INCLUDE_DIR} include)
    target_include_directories(${OUTPUT_BASE_NAME} PRIVATE ${PROJECT_BINARY_DIR}/generated)
    target_link_libraries(${OUTPUT_BASE_NAME} PRIVATE pybind11::module)
    cyrsoxs_target_link_hdf5(${OUTPUT_BASE_NAME})
    target_link_libraries(${OUTPUT_BASE_NAME} PRIVATE
            CUDA::cublas CUDA::cufft CUDA::nppc CUDA::nppial CUDA::nppicc CUDA::nppidei CUDA::nppif CUDA::nppig CUDA::nppim CUDA::nppist CUDA::nppisu CUDA::nppitc CUDA::npps)
    if(CYRSOXS_HDF5_EP_TARGET)
        target_link_libraries(${OUTPUT_BASE_NAME} PRIVATE Threads::Threads)
    endif()
    cyrsoxs_target_apply_hdf5_rpath(${OUTPUT_BASE_NAME})
    #${Python_LIBRARIES} ${HDF5_CXX_LIBRARIES} ${HDF5_HL_LIBRARIES} -lcufft -lcublas -lcudart "${CUDA_nppc_LIBRARY};${CUDA_nppial_LIBRARY};${CUDA_nppicc_LIBRARY};${CUDA_nppidei_LIBRARY};${CUDA_nppif_LIBRARY};${CUDA_nppig_LIBRARY};${CUDA_nppim_LIBRARY};${CUDA_nppist_LIBRARY};${CUDA_nppisu_LIBRARY};${CUDA_nppitc_LIBRARY};${CUDA_npps_LIBRARY}" )
    

    if (EOC)
        target_include_directories(${OUTPUT_BASE_NAME} PRIVATE ${OpenCV_INCLUDE_DIRS})
        target_link_libraries(${OUTPUT_BASE_NAME} ${OpenCV_LIBS})
    endif ()

    set_target_properties(${OUTPUT_BASE_NAME} PROPERTIES
            CUDA_SEPARABLE_COMPILATION ON
            PREFIX ""
            )

    pybind11_extension(${OUTPUT_BASE_NAME})
    if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo)
        # Strip unnecessary sections of the binary on Linux/macOS
        pybind11_strip(${OUTPUT_BASE_NAME})
    endif()

    set_target_properties(${OUTPUT_BASE_NAME} PROPERTIES CXX_VISIBILITY_PRESET "hidden"
                                         CUDA_VISIBILITY_PRESET "hidden") 
else ()
    set(EXE_SRC
            src/main.cpp
            )
    add_executable(${OUTPUT_BASE_NAME} ${CYRSOXS_INC} ${CYRSOXS_SRC} ${EXE_SRC})

    set_target_properties(
            ${OUTPUT_BASE_NAME}
            PROPERTIES
            CUDA_SEPARABLE_COMPILATION ON
    )
    target_include_directories(${OUTPUT_BASE_NAME} PUBLIC ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES} ${HDF5_INCLUDE_DIR} ${CONFIG++_INCLUDE_DIR} include)
    target_include_directories(${OUTPUT_BASE_NAME} PRIVATE ${PROJECT_BINARY_DIR}/generated)
    cyrsoxs_target_link_hdf5(${OUTPUT_BASE_NAME})
    target_link_libraries(${OUTPUT_BASE_NAME}
            CUDA::cufft CUDA::cublas CUDA::nppc CUDA::nppial CUDA::nppicc CUDA::nppidei CUDA::nppif CUDA::nppig CUDA::nppim CUDA::nppist CUDA::nppisu CUDA::nppitc CUDA::npps ${CONFIG++_LIBRARY})
    if(CYRSOXS_HDF5_EP_TARGET)
        target_link_libraries(${OUTPUT_BASE_NAME} PRIVATE Threads::Threads)
    endif()
    cyrsoxs_target_apply_hdf5_rpath(${OUTPUT_BASE_NAME})
    if (EOC)
        target_include_directories(${OUTPUT_BASE_NAME} PRIVATE ${OpenCV_INCLUDE_DIRS})
        target_link_libraries(${OUTPUT_BASE_NAME} ${OpenCV_LIBS})
    endif ()
endif ()
if(CYRSOXS_HDF5_EP_TARGET)
    add_dependencies(${OUTPUT_BASE_NAME} ${CYRSOXS_HDF5_EP_TARGET})
endif()
if(CYRSOXS_CUDA_ARCHITECTURES STREQUAL "native")
    set_property(TARGET ${OUTPUT_BASE_NAME} PROPERTY CUDA_ARCHITECTURES native)
else()
    set_property(TARGET ${OUTPUT_BASE_NAME} PROPERTY CUDA_ARCHITECTURES "${CYRSOXS_CUDA_ARCHITECTURES}")
endif()

if (ENABLE_TEST AND NOT SKBUILD)
    if (PYBIND)
        message("No tests with pybind with gtest framework")
    else ()
        add_definitions(-DCMAKE_ROOT=\"${CMAKE_SOURCE_DIR}\")
        if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/googletest/CMakeLists.txt")
            add_subdirectory(external/googletest)
        else()
            include(FetchContent)
            FetchContent_Declare(googletest
                    GIT_REPOSITORY https://github.com/google/googletest.git
                    GIT_TAG v1.14.0)
            FetchContent_MakeAvailable(googletest)
        endif()
        set(TEST_SRC
                tests/tests.cpp
                )
        set(TEST_INC
                tests/include/basicFramework.h
                tests/include/testUtils.h
                tests/include/unitTest.h
                tests/include/fullFramework.h
                )
        add_executable(test ${CYRSOXS_INC} ${CYRSOXS_SRC} ${TEST_SRC} ${TEST_INC})
        target_include_directories(test PUBLIC ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES} ${HDF5_INCLUDE_DIR} ${CONFIG++_INCLUDE_DIR} include tests/include ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
        target_include_directories(test PRIVATE ${PROJECT_BINARY_DIR}/generated)
        target_link_libraries(test gtest gtest_main)
        cyrsoxs_target_link_hdf5(test)
        target_link_libraries(test PRIVATE
                -lcufft -lcublas "${CUDA_nppc_LIBRARY};${CUDA_nppial_LIBRARY};${CUDA_nppicc_LIBRARY};${CUDA_nppidei_LIBRARY};${CUDA_nppif_LIBRARY};${CUDA_nppig_LIBRARY};${CUDA_nppim_LIBRARY};${CUDA_nppist_LIBRARY};${CUDA_nppisu_LIBRARY};${CUDA_nppitc_LIBRARY};${CUDA_npps_LIBRARY}" ${CONFIG++_LIBRARY})
        if(CYRSOXS_HDF5_EP_TARGET)
            add_dependencies(test ${CYRSOXS_HDF5_EP_TARGET})
            target_link_libraries(test PRIVATE Threads::Threads)
        endif()
        cyrsoxs_target_apply_hdf5_rpath(test)
    endif ()
endif ()

if (BUILD_DOCS)
    find_package(Doxygen)
    if (DOXYGEN_FOUND)
        # set input and output files
        set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile)
        set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)

        # request to configure the file
        configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
        message("Doxygen build started")

        # note the option ALL which allows to build the docs together with the application
        add_custom_target(doc_doxygen ALL
                COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
                WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
                COMMENT "Generating API documentation with Doxygen"
                VERBATIM)
    else (DOXYGEN_FOUND)
        message("Doxygen need to be installed to generate the doxygen documentation")
    endif (DOXYGEN_FOUND)
endif ()

if (PYBIND)
    install(TARGETS ${OUTPUT_BASE_NAME}
            LIBRARY DESTINATION .
            RUNTIME DESTINATION .
            ARCHIVE DESTINATION .)
else ()
    install(TARGETS ${OUTPUT_BASE_NAME}
            RUNTIME DESTINATION bin
            PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
endif ()


if (BUILD_DOCS)
	add_custom_command(
		TARGET doc_doxygen 
		POST_BUILD
	        WORKING_DIRECTORY "./latex"	
		COMMAND make pdf
		VERBATIM)
	add_custom_command(
		TARGET doc_doxygen 
		POST_BUILD 
		COMMAND mv ${CMAKE_CURRENT_BINARY_DIR}/latex/refman.pdf ${CMAKE_CURRENT_BINARY_DIR}/latex/CyRSoXS_Manual.pdf
		VERBATIM)
endif ()

unset(OUTPUT_BASE_NAME CACHE)


