include(AddFileDependencies)
if(POLICY CMP0042)
  cmake_policy(SET CMP0042 OLD)
endif()

add_custom_target(build_tests ALL VERBATIM)

add_definitions(-DCOMPILE_FOR_UNIT_TESTS) # -DVC_CHECK_ALIGNMENT)
if(Vc_COMPILER_IS_MSVC)
   AddCompilerFlag("/wd4267") # Disable warning "conversion from 'size_t' to 'int', possible loss of data"
   AddCompilerFlag("/wd4723") # Disable warning "potential divide by 0" (suppress doesn't work)
endif()

if(DEFINED Vc_INSIDE_ROOT)
   set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "")  # Reset the ROOT default executable destination
   set(Vc_TEST_TARGET_PREFIX "vc-")
else()
   set(Vc_TEST_TARGET_PREFIX "")
endif()

set(CXX11_FLAG)
if(NOT DEFINED Vc_INSIDE_ROOT)
   set(_cxx11_flags "-std=c++11" "-std=c++0x")
   if(Vc_COMPILER_IS_GCC AND WIN32)
      # MinGW fails to compile POSIX code unless gnu++11 is used
      set(_cxx11_flags "-std=gnu++11" "-std=gnu++0x")
   endif()
   foreach(_flag ${_cxx11_flags})
      string(REGEX REPLACE "[-+/:= ]" "_" _flag_esc "${_flag}")
      check_cxx_compiler_flag("${_flag}" check_cxx_compiler_flag_${_flag_esc})
      if(check_cxx_compiler_flag_${_flag_esc})
         set(CXX11_FLAG ${_flag})
         break()
      endif()
   endforeach()
endif()

macro(vc_add_run_target _target)
   add_custom_target(run_${_target}
      ${_target}
      DEPENDS ${_target}
      COMMENT "Execute ${_target} test"
      VERBATIM
      )
endmacro()

macro(vc_add_test _name)
   foreach(_std cxx98 cxx11)
      set(_extra_flags)
      set(name ${_name})
      foreach(_arg ${ARGN})
         set(_extra_flags "${_extra_flags} -D${_arg}")
         set(name "${name}_${_arg}")
      endforeach()

      if(${_std} STREQUAL cxx11)
         if(NOT CXX11_FLAG)
            break()
         endif()
         set(SAVE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
         if(CMAKE_CXX_FLAGS MATCHES " -ansi ")
            string(REPLACE " -ansi " " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
         endif()
         set(_extra_flags "${_extra_flags} ${CXX11_FLAG}")
         set(name "c++11_${name}")
      endif()

      set(_target "${name}_scalar")
      list(FIND disabled_targets ${_target} _disabled)
      if(_disabled EQUAL -1)
         file(GLOB _extra_deps "${CMAKE_SOURCE_DIR}/scalar/*.tcc" "${CMAKE_SOURCE_DIR}/scalar/*.h" "${CMAKE_SOURCE_DIR}/common/*.h")
         add_file_dependencies(${_name}.cpp "${_extra_deps}")
         add_executable(${_target} EXCLUDE_FROM_ALL ${_name}.cpp)
         target_link_libraries(${_target} Vc)
         add_target_property(${_target} COMPILE_FLAGS "-DVC_IMPL=Scalar ${_extra_flags}")
         add_target_property(${_target} LABELS "Scalar")
         add_dependencies(build_tests ${_target})
         add_dependencies(Scalar ${_target})
         add_test(${Vc_TEST_TARGET_PREFIX}${_target} "${CMAKE_CURRENT_BINARY_DIR}/${_target}")
         set_property(TEST ${Vc_TEST_TARGET_PREFIX}${_target} PROPERTY LABELS "Scalar")
         vc_add_run_target(${_target})
      endif()

      if(USE_SSE2 AND NOT Vc_SSE_INTRINSICS_BROKEN)
         set(DVC_IMPL "-DVC_IMPL=SSE")
         if(USE_XOP)
            set(DVC_IMPL "${DVC_IMPL}+XOP")
         endif()
         if(USE_FMA)
            set(DVC_IMPL "${DVC_IMPL}+FMA")
         elseif(USE_FMA4)
            set(DVC_IMPL "${DVC_IMPL}+FMA4")
         endif()
         set(_target "${name}_sse")
         list(FIND disabled_targets ${_target} _disabled)
         if(_disabled EQUAL -1)
            file(GLOB _extra_deps "${CMAKE_SOURCE_DIR}/sse/*.tcc" "${CMAKE_SOURCE_DIR}/sse/*.h" "${CMAKE_SOURCE_DIR}/common/*.h")
            add_file_dependencies(${_name}.cpp "${_extra_deps}")
            add_executable(${_target} EXCLUDE_FROM_ALL ${_name}.cpp)
            target_link_libraries(${_target} Vc)
            add_target_property(${_target} COMPILE_FLAGS "${DVC_IMPL} ${_extra_flags}")
            add_target_property(${_target} LABELS "SSE")
            add_dependencies(build_tests ${_target})
            add_dependencies(SSE ${_target})
            add_test(${Vc_TEST_TARGET_PREFIX}${_target} "${CMAKE_CURRENT_BINARY_DIR}/${_target}")
            set_property(TEST ${Vc_TEST_TARGET_PREFIX}${_target} PROPERTY LABELS "SSE")
            vc_add_run_target(${_target})
         endif()
      endif()

      if(USE_AVX)
         set(DVC_IMPL "-DVC_IMPL=AVX")
         if(USE_XOP)
            set(DVC_IMPL "${DVC_IMPL}+XOP")
         endif()
         if(USE_FMA)
            set(DVC_IMPL "${DVC_IMPL}+FMA")
         elseif(USE_FMA4)
            set(DVC_IMPL "${DVC_IMPL}+FMA4")
         endif()
         set(_target "${name}_avx")
         list(FIND disabled_targets ${_target} _disabled)
         if(_disabled EQUAL -1)
            file(GLOB _extra_deps "${CMAKE_SOURCE_DIR}/avx/*.tcc" "${CMAKE_SOURCE_DIR}/avx/*.h" "${CMAKE_SOURCE_DIR}/common/*.h")
            add_file_dependencies(${_name}.cpp "${_extra_deps}")
            add_executable(${_target} EXCLUDE_FROM_ALL ${_name}.cpp)
            target_link_libraries(${_target} Vc)
            add_target_property(${_target} COMPILE_FLAGS "${DVC_IMPL} ${_extra_flags}")
            add_target_property(${_target} LABELS "AVX")
            add_dependencies(build_tests ${_target})
            add_dependencies(AVX ${_target})
            add_test(${Vc_TEST_TARGET_PREFIX}${_target} "${CMAKE_CURRENT_BINARY_DIR}/${_target}")
            set_property(TEST ${Vc_TEST_TARGET_PREFIX}${_target} PROPERTY LABELS "AVX")
            vc_add_run_target(${_target})
         endif()
      endif(USE_AVX)
      if(${_std} STREQUAL cxx11)
         set(CMAKE_CXX_FLAGS "${SAVE_CXX_FLAGS}")
      endif()
   endforeach()
endmacro(vc_add_test)

vc_add_test(stlcontainer)
vc_add_test(scalaraccess)
vc_add_test(memory)
vc_add_test(arithmetics)
vc_add_test(implicit_type_conversion)
vc_add_test(expandandmerge)
vc_add_test(load)
vc_add_test(store)
vc_add_test(gather)
vc_add_test(gather VC_USE_BSF_GATHERS)
vc_add_test(gather VC_USE_POPCNT_BSF_GATHERS)
vc_add_test(gather VC_USE_SET_GATHERS)
vc_add_test(scatter)
vc_add_test(scatter VC_USE_BSF_SCATTERS)
vc_add_test(scatter VC_USE_POPCNT_BSF_SCATTERS)
vc_add_test(math)
vc_add_test(math VC_LOG_ILP)
vc_add_test(math VC_LOG_ILP2)
vc_add_test(mask)
vc_add_test(utils)
vc_add_test(deinterleave)
vc_add_test(deinterleave VC_USE_MASKMOV_SCATTER)
vc_add_test(casts)
vc_add_test(swizzles)

if(USE_SSE2 AND NOT Vc_SSE_INTRINSICS_BROKEN)
   list(FIND disabled_targets sse_blend _disabled)
   if(_disabled EQUAL -1)
      add_executable(sse2_blend EXCLUDE_FROM_ALL sse_blend.cpp)
      add_target_property(sse2_blend COMPILE_FLAGS "-DVC_IMPL=SSE2")
      add_target_property(sse2_blend LABELS "SSE")
      add_dependencies(build_tests sse2_blend)
      add_dependencies(SSE sse2_blend)
      add_test(${Vc_TEST_TARGET_PREFIX}sse2_blend "${CMAKE_CURRENT_BINARY_DIR}/sse2_blend")
      set_property(TEST ${Vc_TEST_TARGET_PREFIX}sse2_blend PROPERTY LABELS "SSE")
      target_link_libraries(sse2_blend Vc)

      if(USE_SSE4_1)
         add_executable(sse4_blend EXCLUDE_FROM_ALL sse_blend.cpp)
         add_target_property(sse4_blend COMPILE_FLAGS "-DVC_IMPL=SSE4_1")
         add_target_property(sse4_blend LABELS "SSE")
         add_dependencies(build_tests sse4_blend)
         add_dependencies(SSE sse4_blend)
         add_test(${Vc_TEST_TARGET_PREFIX}sse4_blend "${CMAKE_CURRENT_BINARY_DIR}/sse4_blend")
         set_property(TEST ${Vc_TEST_TARGET_PREFIX}sse4_blend PROPERTY LABELS "SSE")
         target_link_libraries(sse4_blend Vc)
      endif()
   endif()
endif()

add_executable(supportfunctions EXCLUDE_FROM_ALL supportfunctions.cpp)
target_link_libraries(supportfunctions Vc)
add_target_property(supportfunctions LABELS "other")
add_dependencies(build_tests supportfunctions)
add_dependencies(other supportfunctions)
add_test(${Vc_TEST_TARGET_PREFIX}supportfunctions "${CMAKE_CURRENT_BINARY_DIR}/supportfunctions")
set_property(TEST ${Vc_TEST_TARGET_PREFIX}supportfunctions PROPERTY LABELS "other")
vc_add_run_target(supportfunctions)

get_property(_incdirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
set(incdirs)
foreach(_d ${_incdirs})
   list(APPEND incdirs "-I${_d}")
endforeach()

separate_arguments(_flags UNIX_COMMAND ${CMAKE_CXX_FLAGS})
foreach(_f ${_flags})
   if(_f MATCHES "^-m" OR _f MATCHES "^/arch:" OR _f MATCHES "^-x")
      list(REMOVE_ITEM _flags "${_f}")
   endif()
endforeach()

set(TEST_OPERATOR_FAILURES FALSE CACHE BOOL "Run implicit type conversion operator tests.")
if(TEST_OPERATOR_FAILURES)
   macro(vc_test_implicit_type_conversion_failures A B)
      foreach(impl Scalar SSE AVX)
         if("${impl}" STREQUAL "Scalar")
            set(_implFlags)
         elseif("${impl}" STREQUAL "SSE")
            if(Vc_COMPILER_IS_MSVC)
               AddCompilerFlag("/arch:SSE2" CXX_FLAGS _implFlags)
               string(STRIP "${_implFlags}" _implFlags)
            elseif(Vc_COMPILER_IS_INTEL)
               set(_implFlags "-xSSE2")
            else()
               set(_implFlags "-msse2")
            endif()
         elseif("${impl}" STREQUAL "AVX")
            if(Vc_AVX_INTRINSICS_BROKEN)
               break()
            endif()
            if(Vc_COMPILER_IS_MSVC)
               set(_implFlags "/arch:AVX")
            elseif(Vc_COMPILER_IS_INTEL)
               set(_implFlags "-xAVX")
            else()
               set(_implFlags "-mavx")
            endif()
         endif()
         set(type_b ${B})
         foreach(type_a ${A} ${B})
            foreach(op "^" "==" "*") # "/" "+" "-" "&" "|" "!=" "<=" ">=" "<" ">")
               set(name "implicit_type_conversion_failures_${type_a}_${op}_${type_b}_${impl}")
               add_test(NAME "${Vc_TEST_TARGET_PREFIX}${name}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
                  COMMAND
                  ${CMAKE_CXX_COMPILER} ${_flags} ${_implFlags}
                  ${incdirs} -o "${name}.tmp"
                  "-DTYPE_A=${type_a}" "-DTEST_OP=${op}" "-DTYPE_B=${type_b}" "-DVC_IMPL=${impl}"
                  ${CMAKE_CURRENT_SOURCE_DIR}/implicit_type_conversion_failures.cpp
                  )
               set_property(TEST "${name}" PROPERTY LABELS "${impl}")
               set_tests_properties("${name}" PROPERTIES
                  PASS_REGULAR_EXPRESSION "invalid operands to binary expression;error: no match for .*operator\\${op};error C267[789]: binary .*\\${op}.* no (global )?operator found;error: no operator \"\\${op}\" matches these operands"
                  FAIL_REGULAR_EXPRESSION "no such file or directory;undefined reference to"
                  )
            endforeach()
            set(type_b ${A})
         endforeach()
      endforeach()
   endmacro()
   vc_test_implicit_type_conversion_failures("double_v"    "float_v")
   vc_test_implicit_type_conversion_failures("double_v"    "short_v")
   vc_test_implicit_type_conversion_failures("double_v"   "ushort_v")
   vc_test_implicit_type_conversion_failures("double_v"      "int_v")
   vc_test_implicit_type_conversion_failures("double_v"     "uint_v")
   vc_test_implicit_type_conversion_failures( "float_v"     "double")
   vc_test_implicit_type_conversion_failures( "float_v"    "short_v")
   vc_test_implicit_type_conversion_failures( "float_v"   "ushort_v")
   vc_test_implicit_type_conversion_failures("sfloat_v"   "double_v")
   vc_test_implicit_type_conversion_failures("sfloat_v"     "double")
   vc_test_implicit_type_conversion_failures("sfloat_v"    "float_v")
   vc_test_implicit_type_conversion_failures("sfloat_v"      "int_v")
   vc_test_implicit_type_conversion_failures("sfloat_v"     "uint_v")
   vc_test_implicit_type_conversion_failures( "short_v"      "int_v")
   vc_test_implicit_type_conversion_failures( "short_v"     "uint_v")
   vc_test_implicit_type_conversion_failures("ushort_v"      "int_v")
   vc_test_implicit_type_conversion_failures("ushort_v"     "uint_v")
   vc_test_implicit_type_conversion_failures("double_v"       "bool")
   vc_test_implicit_type_conversion_failures("sfloat_v"       "bool")
   vc_test_implicit_type_conversion_failures( "float_v"       "bool")
   vc_test_implicit_type_conversion_failures(   "int_v"       "bool")
   vc_test_implicit_type_conversion_failures(  "uint_v"       "bool")
   vc_test_implicit_type_conversion_failures( "short_v"       "bool")
   vc_test_implicit_type_conversion_failures("ushort_v"       "bool")
endif()

# compile and link test for targets that need to link lots of stuff together
add_library(linkTestLibDynamic1 SHARED EXCLUDE_FROM_ALL linkTestLib0.cpp linkTestLib1.cpp)
add_library(linkTestLibDynamic2 SHARED EXCLUDE_FROM_ALL linkTestLib0.cpp linkTestLib1.cpp)
add_library(linkTestLibStatic STATIC EXCLUDE_FROM_ALL linkTestLib2.cpp linkTestLib3.cpp)
add_executable(linkTest EXCLUDE_FROM_ALL linkTest0.cpp linkTest1.cpp)
add_dependencies(build_tests linkTest)
add_dependencies(other linkTest)
target_link_libraries(linkTestLibDynamic1 Vc)
target_link_libraries(linkTestLibDynamic2 Vc)
add_target_property(linkTestLibDynamic1 COMPILE_FLAGS "-DPOSTFIX=A")
add_target_property(linkTestLibDynamic2 COMPILE_FLAGS "-DPOSTFIX=B")
target_link_libraries(linkTestLibStatic Vc)
target_link_libraries(linkTest Vc linkTestLibDynamic1 linkTestLibDynamic2 linkTestLibStatic)

# Use the following program to generate the sincos-reference-*.dat files
#add_executable(convert-sincos-reference EXCLUDE_FROM_ALL convert-sincos-reference.cpp)

set(_deps)
foreach(fun sincos asin acos atan ln log2 log10)
   foreach(filename reference-${fun}-sp.dat reference-${fun}-dp.dat)
      add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${filename}"
         COMMAND ${CMAKE_COMMAND} -Dfilename=${filename} -P ${CMAKE_CURRENT_SOURCE_DIR}/download.cmake
         DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/download.cmake
         WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
         COMMENT "Downloading Test Data: ${filename}"
         VERBATIM
         )
      list(APPEND _deps "${CMAKE_CURRENT_BINARY_DIR}/${filename}")
   endforeach()
endforeach()
add_custom_target(download-testdata ALL
   DEPENDS ${_deps}
   )
add_dependencies(other download-testdata)
