# Copyright (C) 2016-2023 Dan Cazarin (https://www.kfrlib.com)
# This file is part of KFR
#
# KFR 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.
#
# KFR is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with KFR.

cmake_minimum_required(VERSION 3.10)

project(kfr CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)

if (CPU_ARCH)
    if (NOT KFR_ARCH)
        message(
            WARNING
                "CPU_ARCH is deprecated. Use KFR_ARCH instead. KFR will use machine native architecture by default"
        )
    else ()
        message(
            WARNING
                "Both KFR_ARCH and CPU_ARCH are defined. KFR will use architecture set by KFR_ARCH (${KFR_ARCH})"
        )
    endif ()
endif ()

if (WIN32 AND CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
    set(CMAKE_INSTALL_PREFIX
        ""
        CACHE STRING "Reset install prefix on Win32" FORCE)
endif ()

set(X86 FALSE)
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
    set(X86 TRUE)
endif ()
if (CMAKE_OSX_ARCHITECTURES MATCHES "x86_64")
    set(X86 TRUE)
endif ()

if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL
                                              "AppleClang")
    set(CLANG 1)
else ()
    set(CLANG 0)
endif ()

# Include autogenerated list of source files
include(sources.cmake)
include(CMakeDependentOption)

option(ENABLE_TESTS "Enable KFR tests" OFF)
cmake_dependent_option(ENABLE_EXAMPLES "Enable KFR examples" ON "ENABLE_TESTS" OFF)
if (CLANG)
    option(KFR_ENABLE_DFT "Enable DFT and related algorithms." ON)
    option(KFR_ENABLE_DFT_NP "Enable Non-power of 2 DFT" ON)
    if (X86)
        option(
            KFR_ENABLE_DFT_MULTIARCH
            "Build DFT static libraries for various architectures. Requires Clang"
            OFF)
    endif ()
else ()
    option(KFR_ENABLE_DFT "Enable DFT and related algorithms." OFF)
    option(KFR_ENABLE_DFT_NP "Enable Non-power of 2 DFT" OFF)
endif ()
option(KFR_ENABLE_ASMTEST "Enable writing disassembly" OFF)
option(KFR_REGENERATE_TESTS "Regenerate auto tests" OFF)
option(KFR_DISABLE_CLANG_EXTENSIONS "Disable Clang vector extensions" OFF)
option(KFR_EXTENDED_TESTS "Extended tests (up to hour)" OFF)
option(KFR_SKIP_TESTS "Build tests but don't run tests" OFF)
option(KFR_STD_COMPLEX "Use std::complex instead of custom complex type" OFF)
option(KFR_ENABLE_CAPI_BUILD "Enable KFR C API building" OFF)
option(KFR_INSTALL_HEADERS "Include headers in installation" ON)
option(KFR_INSTALL_LIBRARIES "Include libraries in installation" ON)
mark_as_advanced(KFR_ENABLE_ASMTEST)
mark_as_advanced(KFR_REGENERATE_TESTS)
mark_as_advanced(KFR_DISABLE_CLANG_EXTENSIONS)

if (NOT KFR_ARCH)
    set(KFR_ARCH detect)
endif ()

if (KFR_ARCH STREQUAL "detect" AND X86)
    try_run(
        RUN_RESULT COMPILE_RESULT "${CMAKE_CURRENT_BINARY_DIR}/tmpdir"
        ${CMAKE_CURRENT_SOURCE_DIR}/cmake/detect_cpu.cpp
        CMAKE_FLAGS
            "-DINCLUDE_DIRECTORIES=${CMAKE_CURRENT_SOURCE_DIR}/include"
            -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=ON
            -DCMAKE_CXX_EXTENSIONS=ON
        COMPILE_OUTPUT_VARIABLE COMPILE_OUT
        RUN_OUTPUT_VARIABLE RUN_OUT)
    if (COMPILE_RESULT AND RUN_RESULT EQUAL 0)
        message(
            STATUS
                "Default CPU architecture for KFR is ${RUN_OUT} (set KFR_ARCH to override)"
        )
        set(KFR_ARCH
            ${RUN_OUT}
            CACHE STRING "Detected CPU" FORCE)
    else ()
        message(STATUS COMPILE_RESULT = ${COMPILE_RESULT})
        message(STATUS RUN_RESULT = ${RUN_RESULT})
        message(STATUS COMPILE_OUT = ${COMPILE_OUT})
        message(STATUS RUN_OUT = ${RUN_OUT})
    endif ()
else ()
    message(
        STATUS
            "Default CPU architecture for KFR is ${KFR_ARCH} (set by cmake variable)"
    )
endif ()

include(cmake/target_set_arch.cmake)

add_library(use_arch INTERFACE)
target_set_arch(use_arch INTERFACE ${KFR_ARCH})

if (WIN32)
    add_definitions(-D_CRT_SECURE_NO_WARNINGS)
    add_definitions(-D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS)
    add_definitions(-D_ENABLE_EXTENDED_ALIGNED_STORAGE)
endif ()

if (IOS)
    set(STD_LIB)
else ()
    set(STD_LIB stdc++)
endif ()

if (ANDROID)
    set(PTHREAD_LIB)
else ()
    set(PTHREAD_LIB pthread)
endif ()

if (MSVC AND CLANG)
    set(CLANG_ARG_PREFIX "SHELL:-Xclang ")
else ()
    set(CLANG_ARG_PREFIX "")
endif ()

# KFR library
add_library(kfr INTERFACE)
target_sources(kfr INTERFACE ${KFR_SRC})
target_include_directories(kfr INTERFACE include)
target_compile_options(kfr INTERFACE "$<$<CONFIG:DEBUG>:-DKFR_DEBUG>")
if (APPLE)
    target_compile_options(kfr INTERFACE -faligned-allocation)
endif ()
if (NOT IOS)
    if (NOT MSVC OR CLANG)
        target_compile_options(kfr
                               INTERFACE "${CLANG_ARG_PREFIX}-mstackrealign")
    endif ()
endif ()
if (MSVC)
    target_compile_options(kfr INTERFACE -bigobj -EHsc)
else ()
    target_link_libraries(kfr INTERFACE ${STD_LIB} ${PTHREAD_LIB} m)
endif ()
if (KFR_DISABLE_CLANG_EXTENSIONS)
    target_compile_definitions(kfr INTERFACE -DCMT_DISABLE_CLANG_EXT)
endif ()
if (KFR_STD_COMPLEX)
    target_compile_definitions(kfr INTERFACE -DKFR_STD_COMPLEX)
endif ()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    target_compile_options(kfr INTERFACE -Wno-ignored-qualifiers -Wno-psabi)
endif ()
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    target_compile_options(kfr INTERFACE -Wno-c++1z-extensions -Wno-psabi)
endif ()
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    target_compile_options(kfr INTERFACE /wd4141)
endif ()

if (NOT KFR_ENABLE_DFT)
    target_compile_definitions(kfr INTERFACE -DKFR_NO_DFT)
endif ()
if (KFR_EXTENDED_TESTS)
    target_compile_definitions(kfr INTERFACE -DKFR_EXTENDED_TESTS)
endif ()

if (X86)
    add_executable(detect_cpu ${CMAKE_CURRENT_SOURCE_DIR}/cmake/detect_cpu.cpp)
    target_link_libraries(detect_cpu PRIVATE kfr)
    target_set_arch(detect_cpu PRIVATE generic)
endif ()

function (add_arch_library NAME ARCH SRCS DEFS)
    add_library(${NAME}_${ARCH} ${SRCS})
    target_link_libraries(${NAME}_${ARCH} kfr)
    target_set_arch(${NAME}_${ARCH} PRIVATE ${ARCH})
    target_compile_options(${NAME}_${ARCH} PRIVATE ${DEFS})
    target_link_libraries(${NAME}_all INTERFACE ${NAME}_${ARCH})
    target_compile_options(${NAME}_${ARCH} PRIVATE -flto)
endfunction ()

if (KFR_ENABLE_DFT)

    set(KFR_DFT_DEFS "${CLANG_ARG_PREFIX}-ffp-contract=fast")

    if (KFR_ENABLE_DFT_MULTIARCH)
        add_library(kfr_dft INTERFACE)
        add_library(kfr_dft_all INTERFACE)
        target_link_libraries(kfr_dft INTERFACE kfr kfr_dft_all)
        target_compile_definitions(
            kfr_dft
            INTERFACE -DKFR_DFT_MULTI=1
                      -DCMT_MULTI=1
                      -DCMT_MULTI_ENABLED_SSE2=1
                      -DCMT_MULTI_ENABLED_SSE41=1
                      -DCMT_MULTI_ENABLED_AVX=1
                      -DCMT_MULTI_ENABLED_AVX2=1
                      -DCMT_MULTI_ENABLED_AVX512=1)

        add_arch_library(kfr_dft sse2 "${KFR_DFT_SRC}" "${KFR_DFT_DEFS}")
        add_arch_library(kfr_dft sse41 "${KFR_DFT_SRC}" "${KFR_DFT_DEFS}")
        add_arch_library(kfr_dft avx "${KFR_DFT_SRC}" "${KFR_DFT_DEFS}")
        add_arch_library(kfr_dft avx2 "${KFR_DFT_SRC}" "${KFR_DFT_DEFS}")
        add_arch_library(kfr_dft avx512 "${KFR_DFT_SRC}" "${KFR_DFT_DEFS}")

    else ()
        add_library(kfr_dft ${KFR_DFT_SRC})
        target_link_libraries(kfr_dft kfr use_arch)
        target_compile_options(kfr_dft PRIVATE "${KFR_DFT_DEFS}")
        if (KFR_ENABLE_DFT_NP)
            target_compile_definitions(kfr_dft PUBLIC -DKFR_DFT_NPo2)
        else ()
            target_compile_definitions(kfr_dft PUBLIC -DKFR_DFT_NO_NPo2)
        endif ()

    endif ()

    if (KFR_ENABLE_CAPI_BUILD)
        add_subdirectory(capi)
    endif ()
endif ()

if (ENABLE_EXAMPLES)
    add_subdirectory(examples)
    add_subdirectory(tools)
endif ()
if (ENABLE_TESTS)
    add_subdirectory(tests)
endif ()

add_library(kfr_io ${KFR_IO_SRC})
target_link_libraries(kfr_io kfr)
target_link_libraries(kfr_io use_arch)

if (KFR_INSTALL_LIBRARIES)
    install(
        TARGETS kfr kfr_io
        ARCHIVE DESTINATION lib
        LIBRARY DESTINATION lib
        RUNTIME DESTINATION bin)

    if (KFR_ENABLE_DFT AND KFR_ENABLE_CAPI_BUILD)
        install(
            TARGETS kfr_capi
            ARCHIVE DESTINATION lib
            LIBRARY DESTINATION lib
            RUNTIME DESTINATION bin)
    endif ()
endif ()

set(kfr_defines)

function (append_defines_from target)
    get_target_property(compile_defs ${target} INTERFACE_COMPILE_DEFINITIONS)
    if (compile_defs)
        list(APPEND kfr_defines "${compile_defs}")
    endif ()
    set(kfr_defines
        ${kfr_defines}
        PARENT_SCOPE)
endfunction ()

append_defines_from(kfr)
if (KFR_ENABLE_DFT)
    append_defines_from(kfr_dft)
endif ()
append_defines_from(kfr_io)

string(REPLACE "=" " " kfr_defines "${kfr_defines}")
string(REPLACE ";" "\n#define " kfr_defines "${kfr_defines}")
set(kfr_defines "#define ${kfr_defines}\n")

file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/kfr_config.h "${kfr_defines}")

if (KFR_ENABLE_DFT AND KFR_INSTALL_LIBRARIES)
    if (KFR_ENABLE_DFT_MULTIARCH)
        install(
            TARGETS kfr_dft_sse2 kfr_dft_sse41 kfr_dft_avx kfr_dft_avx2
                    kfr_dft_avx512
            ARCHIVE DESTINATION lib
            LIBRARY DESTINATION lib
            RUNTIME DESTINATION bin)
    else ()
        install(
            TARGETS kfr_dft
            ARCHIVE DESTINATION lib
            LIBRARY DESTINATION lib
            RUNTIME DESTINATION bin)
    endif ()
endif ()

if (KFR_INSTALL_HEADERS)
    install(DIRECTORY include/kfr DESTINATION include)

    install(
        FILES ${CMAKE_CURRENT_BINARY_DIR}/kfr_config.h
        DESTINATION include/kfr
        RENAME config.h)
endif ()

# uninstall target
if (NOT TARGET uninstall)
    configure_file(
        "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
        "${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake" IMMEDIATE
        @ONLY)

    add_custom_target(
        uninstall
        COMMAND ${CMAKE_COMMAND} -P
                ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake)
endif ()
