#===============================================================================
# Copyright (C) 2020 Intel Corporation
#
# This software and the related documents are Intel copyrighted  materials,  and
# your use of  them is  governed by the  express license  under which  they were
# provided to you (License).  Unless the License provides otherwise, you may not
# use, modify, copy, publish, distribute,  disclose or transmit this software or
# the related documents without Intel's prior written permission.
#
# This software and the related documents  are provided as  is,  with no express
# or implied  warranties,  other  than those  that are  expressly stated  in the
# License.
#===============================================================================

cmake_minimum_required(VERSION 3.13)
enable_testing()

# Set MKL_ROOT directory
function(define_mkl_root POTENTIAL_MKL_ROOT)
  if(EXISTS "${POTENTIAL_MKL_ROOT}/include/mkl.h")
    set(MKL_ROOT "${POTENTIAL_MKL_ROOT}" PARENT_SCOPE)
  else()
    set(MKL_ROOT "" PARENT_SCOPE)
  endif()
endfunction()

if("${MKL_ROOT}" STREQUAL "")
  if(NOT "$ENV{MKLROOT}" STREQUAL "")
    file(TO_CMAKE_PATH "$ENV{MKLROOT}" POTENTIAL_MKL_ROOT)
    define_mkl_root("${POTENTIAL_MKL_ROOT}")
  endif()
  if("${MKL_ROOT}" STREQUAL "" AND (NOT "${MKL_DIR}" STREQUAL ""))
    get_filename_component(POTENTIAL_MKL_ROOT "${MKL_DIR}/../../../" ABSOLUTE)
    define_mkl_root("${POTENTIAL_MKL_ROOT}")
  endif()
  if("${MKL_ROOT}" STREQUAL "")
    get_filename_component(MKL_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH)
    get_filename_component(POTENTIAL_MKL_ROOT "${MKL_CMAKE_PATH}/../../../../../" ABSOLUTE)
    define_mkl_root("${POTENTIAL_MKL_ROOT}")
  endif()
  if("${MKL_ROOT}" STREQUAL "")
    message(STATUS "Cannot infer MKL_ROOT from the MKLROOT environment variable, MKL_DIR variable, or the directory containing CMakeLists.txt.")
  endif()
endif()
if(NOT "${MKL_ROOT}" STREQUAL "")
  file(TO_CMAKE_PATH "${MKL_ROOT}" MKL_ROOT)
  message(STATUS "MKL_ROOT: ${MKL_ROOT}")
endif()

# Add cmake scripts and modules to CMake search path
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
if(NOT "${MKL_ROOT}" STREQUAL "")
  list(APPEND CMAKE_MODULE_PATH "${MKL_ROOT}/share/doc/mkl/examples/cmake")
endif()

# Define language and compiler
set(TEST_LANG CXX)
set(TEST_EXT cpp)
include(setup_examples)

project(MKL_Examples LANGUAGES ${TEST_LANG})
find_package(MKL CONFIG REQUIRED)

# Generate domainList and ${domain}_funcList
include(generate_examples_list)

# limit C++ errors to one
if (WIN32)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ferror-limit:1")
else()
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ferror-limit=1")
endif()

# Override default compile/link lines
if(WIN32)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-function -w")
  set(CMAKE_CXX_CREATE_STATIC_LIBRARY "lib <OBJECTS> /out:<TARGET>")
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO")
  if(CMAKE_VERSION VERSION_LESS "3.25.2")
    set(CMAKE_CXX_COMPILE_OBJECT "<CMAKE_CXX_COMPILER> <DEFINES> <INCLUDES> <FLAGS> /Fo<OBJECT> -c <SOURCE>")
    set(CMAKE_CXX_LINK_EXECUTABLE "<CMAKE_CXX_COMPILER> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
  endif()
endif()

if(FAIL_ON_MISSING_DEVICES)
  list(APPEND DEVICE_OPT -DFAIL_ON_MISSING_DEVICES)
  message(STATUS "FAIL_ON_MISSING_DEVICES: `${FAIL_ON_MISSING_DEVICES}`")
endif()

foreach(device IN LISTS TARGET_DEVICES)
  list(APPEND DEVICE_OPT -DSYCL_DEVICES_${device})
endforeach()

# Data Fitting does not support LP64 on CPU
if(NOT MKL_INTERFACE STREQUAL "ilp64" AND "cpu" IN_LIST TARGET_DEVICES)
  list(REMOVE_ITEM domainList "data_fitting")
endif()

# Define target for each function from each domain
if(domainList)
foreach(domain IN LISTS domainList)
  set(TEST_INCLUDE "")
  set(TEST_LOPT "")
  set(TEST_COPT "")

  list(APPEND TEST_INCLUDE "${PROJECT_SOURCE_DIR}/common" "${PROJECT_SOURCE_DIR}/${domain}")
  list(APPEND TEST_COPT ${DEVICE_OPT})
  # Some tests need this option as well
  if(NOT MKL_LINK STREQUAL "static")
    if (WIN32)
      list(APPEND TEST_LOPT "-fsycl-device-code-split:per_kernel")
    else()
      list(APPEND TEST_LOPT "-fsycl-device-code-split=per_kernel")
    endif()
  endif()

  # Build target for each example
  message(STATUS "Functions list ${domain}: ${${domain}_funcList}")
  foreach(func IN LISTS ${domain}_funcList)
    set(executable "${domain}-${func}")

    if(func STREQUAL "soboluserdirnums-joe-kuo")
      configure_file(${PROJECT_SOURCE_DIR}/${domain}/data/${func}.dat ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
    endif()

    file(GLOB_RECURSE ${domain}_${func}_SRC ${PROJECT_SOURCE_DIR}/${domain}/*/${func}.${TEST_EXT})
    if(NOT ${domain}_${func}_SRC)
      message(FATAL_ERROR "${domain} source file ${func}.${TEST_EXT} was not found")
    endif()

    add_executable(${executable} ${${domain}_${func}_SRC})
    target_include_directories(${executable} PUBLIC ${TEST_INCLUDE} $<TARGET_PROPERTY:MKL::MKL_SYCL,INTERFACE_INCLUDE_DIRECTORIES>)
    target_compile_options(${executable} PUBLIC ${TEST_COPT} $<TARGET_PROPERTY:MKL::MKL_SYCL,INTERFACE_COMPILE_OPTIONS>)
    if(domain STREQUAL "blas")
      target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL_SYCL::BLAS>)
    elseif(domain STREQUAL "lapack")
      target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL_SYCL::LAPACK>)
    elseif(domain STREQUAL "dft")
      target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL_SYCL::DFT>)
    elseif(domain STREQUAL "sparse_blas")
      if(func STREQUAL "csr_conjugate_gradient" OR func STREQUAL "csr_conjugate_gradient_usm" OR func STREQUAL "csr_conjugate_gradient2_usm" )
          # csr_conjugate_gradient impls use BLAS and Sparse BLAS functions
        target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL_SYCL::BLAS> $<LINK_ONLY:MKL::MKL_SYCL::SPARSE>)
      else()
        target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL_SYCL::SPARSE>)
      endif()
    elseif(domain STREQUAL "vml")
      target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL_SYCL::VM>)
    elseif(domain STREQUAL "rng")
      target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL_SYCL::RNG>)
    elseif(domain STREQUAL "stats")
      target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL_SYCL::STATS>)
    elseif(domain STREQUAL "data_fitting")
      target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL_SYCL::DATA_FITTING>)
    else()
      target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL_SYCL>)
    endif()

    # Register example as ctest
    add_test(NAME ${executable} COMMAND ${executable})

    # Add Environment variables
    if(MKL_ENV)
      set_tests_properties(${executable} PROPERTIES ENVIRONMENT "${MKL_ENV}")
    endif()
  endforeach() #${domain}_funcList
endforeach() #domainList
endif() #not empty domainList
