#===============================================================================
# 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 Fortran)
set(TEST_EXT f)
set(DATA_EXT d)

# Parse Compiler and Test language
include(setup_examples)

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

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

if(WIN32)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO")
endif()

if(domainList)
# Build mod files
set(TEST_COPT "")

if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
  list(APPEND TEST_COPT -fcray-pointer -fno-range-check -finit-local-zero -cpp)
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "Intel" OR CMAKE_Fortran_COMPILER_ID STREQUAL "IntelLLVM")
  list(APPEND TEST_COPT -fpp)
endif()
add_library(mod_files ${MKL_INCLUDE}/mkl_service.f90
                      ${MKL_INCLUDE}/mkl_rci.f90
                      ${MKL_INCLUDE}/mkl_dfti.f90
                      ${MKL_INCLUDE}/mkl_poisson.f90
                      ${MKL_INCLUDE}/mkl_trig_transforms.f90
                      ${MKL_INCLUDE}/mkl_solvers_ee.f90
                      ${MKL_INCLUDE}/mkl_spblas.f90
                      ${MKL_INCLUDE}/mkl_sparse_qr.f90
                      ${MKL_INCLUDE}/mkl_vsl.f90)
target_compile_options(mod_files PUBLIC ${TEST_COPT} $<TARGET_PROPERTY:MKL::MKL,INTERFACE_COMPILE_OPTIONS>)

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

  if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
    list(APPEND TEST_COPT -fcray-pointer -fno-range-check -finit-local-zero -cpp)
  elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "Intel" OR CMAKE_Fortran_COMPILER_ID STREQUAL "IntelLLVM")
    list(APPEND TEST_COPT -fpp)
  endif()
  list(APPEND TEST_LOPT mod_files)

  # Domain specific options and targets
  if(domain STREQUAL "blas")
    if(MKL_INTERFACE STREQUAL "ilp64")
      list(APPEND TEST_PDEF USE_ILP64)
    endif()
    add_library(blas_common ${PROJECT_SOURCE_DIR}/blas/source/common_func.f)
    target_include_directories(blas_common PUBLIC $<TARGET_PROPERTY:MKL::MKL,INTERFACE_INCLUDE_DIRECTORIES>)
    target_compile_definitions(blas_common PUBLIC ${TEST_PDEF})
    target_compile_options(blas_common PUBLIC ${TEST_COPT} $<TARGET_PROPERTY:MKL::MKL,INTERFACE_COMPILE_OPTIONS>)
    list(APPEND TEST_LOPT blas_common)
  endif()
  if(domain STREQUAL "fftw3x")
    list(APPEND TEST_INCLUDE "${MKL_INCLUDE}/fftw")
  endif()
  # Add these options to suppress "Unused argument" warnings
  if(domain STREQUAL "datafitting")
    if (CMAKE_Fortran_COMPILER MATCHES "gfortran$")
      list(APPEND TEST_COPT -Wno-unused-dummy-argument)
    elseif(CMAKE_Fortran_COMPILER MATCHES "ifort$" OR CMAKE_Fortran_COMPILER MATCHES "ifx$")
      list(APPEND TEST_COPT -warn nounused)
    endif()
  endif()
  # Build VSL Fortran examples with -O0 in the case of GFortran 11 to avoid failures due to incorrect optimizations
  if(domain STREQUAL "vsl" AND CMAKE_Fortran_COMPILER MATCHES "gfortran$" AND CMAKE_Fortran_COMPILER_VERSION MATCHES "^11\.")
    set(CMAKE_Fortran_FLAGS_RELEASE "-O0 -DNDEBUG" CACHE STRING "" FORCE)
  endif()
  if(domain STREQUAL "service")
    if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
      list(APPEND  TEST_COPT -x f77-cpp-input)
    endif()
  endif()
  if(domain STREQUAL "trans")
    add_library(common_func ${PROJECT_SOURCE_DIR}/trans/source/common_func.${TEST_EXT})
    target_include_directories(common_func PUBLIC $<TARGET_PROPERTY:MKL::MKL,INTERFACE_INCLUDE_DIRECTORIES>)
    target_compile_options(common_func PUBLIC ${TEST_COPT} $<TARGET_PROPERTY:MKL::MKL,INTERFACE_COMPILE_OPTIONS>)
    list(APPEND TEST_LOPT common_func)
  endif()

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

    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()

    # Store .mod files in unique folders to enable parallelization
    file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/f_modules/${executable}_mod)
    set(CMAKE_Fortran_MODULE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/f_modules/${executable}_mod")

    if(domain STREQUAL "vsl_essl")
      string(REPLACE "source/example_" "vsl_wrappers/sample_" wrapper_func_name ${${domain}_${func}_SRC})
      list(APPEND ${domain}_${func}_SRC ${wrapper_func_name})
    endif()

    add_executable(${executable} ${${domain}_${func}_SRC})
    target_include_directories(${executable} PUBLIC ${TEST_INCLUDE}
        $<TARGET_PROPERTY:MKL::MKL,INTERFACE_INCLUDE_DIRECTORIES>
        ${CMAKE_CURRENT_BINARY_DIR}/f_modules/${executable}_mod)
    target_compile_definitions(${executable} PUBLIC ${TEST_PDEF})
    target_compile_options(${executable} PUBLIC ${TEST_COPT} $<TARGET_PROPERTY:MKL::MKL,INTERFACE_COMPILE_OPTIONS>)
    target_link_libraries(${executable} PUBLIC ${TEST_LOPT} $<LINK_ONLY:MKL::MKL>)

    # Register example as ctest
    file(GLOB_RECURSE ${domain}_${func}_DATA ${PROJECT_SOURCE_DIR}/${domain}/*/${func}.${DATA_EXT})
    if(EXISTS ${${domain}_${func}_DATA})
      if(domain STREQUAL "vsl")
        add_test(NAME ${executable} COMMAND cmake -E echo \"${${domain}_${func}_DATA}\" | ${executable})
      else()
        if (WIN32)
          add_test(NAME ${executable} COMMAND ${CMAKE_COMMAND} -E chdir $<TARGET_FILE_DIR:${executable}> $ENV{ComSpec} /c "$<TARGET_FILE_NAME:${executable}> < ${${domain}_${func}_DATA}")
        else()
          add_test(NAME ${executable} COMMAND sh -c "./${executable} <${${domain}_${func}_DATA}")
        endif()
      endif()
    else()
      add_test(NAME ${executable} COMMAND ${executable})
    endif()

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