find_program(XXD xxd DOC "Create C include style files from OpenCL kernel sources/SPIR" NO_CMAKE_FIND_ROOT_PATH)
if(NOT XXD)
  message(FATAL_ERROR "xxd not found!")
endif(NOT XXD)

set(OPENCL_SOURCES
    core1/boundsKernel.cl
    core1/mergerKernel.cl
    core1/undistortKernel.cl
    core1/voronoi.cl
    core1/warpCoordInputKernel.cl
    core1/warpKernel.cl
    core1/warpFaceKernel_x.cl
    core1/warpFaceKernel_y.cl
    core1/warpFaceKernel_z.cl
    core1/warpCoordKernel.cl
    core1/warpLookupKernel.cl
    core1/cubemapMapKernel.cl
    core1/zoneKernel.cl
    # coredepth/sphereSweep.cl
    exampleKernel.cl
    image/blur.cl
    image/downsampler.cl
    image/imageOps.cl
    image/imgExtract.cl
    image/imgInsert.cl
    image/sampling.cl
    image/rotate.cl
    image/unpack.cl
    input/checkerBoard.cl
    input/grid.cl
    memset.cl
    render/geometry.cl
    render/render.cl
    score/scoringKernel.cl
    )

# add the OpenCL files to the StitchEm sources so they will be shown in IDE projects created from CMake
foreach(opencl_source ${OPENCL_SOURCES})
  list(APPEND CL_BACKEND_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/${opencl_source}")
endforeach()

set(VS_LIB_ROOT_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..)


# use this compiler to create llvm bitcode / SPIR from the kernel source
if(APPLE)

  find_program(OPENCL_OFFLINE_COMPILER openclc
               PATHS ${OpenCL_LIBRARIES}/Libraries/
                     /System/Library/Frameworks/OpenCL.framework/Libraries
               DOC "OpenCL offline compiler to build spir files from kernel sources")
  if (ALTERNATIVE_OPENCL_SPIR)
    find_program(OPENCL_OFFLINE_COMPILER_ALTERNATIVE openclc
                        PATHS /System/Library/PrivateFrameworks/GPUCompiler.framework/Libraries
                              /System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/Libraries
                        DOC "Alternative OpenCL Offline Compiler")
  endif (ALTERNATIVE_OPENCL_SPIR)

  set(OPENCL_COMPILER_FLAGS
   -emit-llvm
   -c
   -cl-std=CL1.2
   -cl-fast-relaxed-math
   -cl-mad-enable
   -O3
   -Wall -Weverything -Wpedantic
   -Wno-unused-parameter
   -Wno-unused-function
   -Wno-sign-conversion
   -I ${VS_LIB_ROOT_SRC_DIR})

  set(OPENCL_COMPILER_FLAGS_32
      ${OPENCL_COMPILER_FLAGS}
      -arch gpu_32)

  set(OPENCL_COMPILER_FLAGS_64
      ${OPENCL_COMPILER_FLAGS}
      -arch gpu_64)

  if(CMAKE_GENERATOR STREQUAL Ninja)
    set(OPENCL_COMPILER_FLAGS ${OPENCL_COMPILER_FLAGS} -fcolor-diagnostics)
  endif()
endif(APPLE)

if(LINUX)
  if(NOT EXISTS ${CMAKE_SOURCE_DIR}/external_deps/include/clangcl/opencl_spir.h)
    file (DOWNLOAD https://raw.githubusercontent.com/KhronosGroup/SPIR-Tools/9c498a665a08db583c61fd34b9e2486978c2431b/headers/opencl_spir.h ${CMAKE_SOURCE_DIR}/external_deps/include/clangcl/opencl_spir.h)
  endif()
  find_program(OPENCL_OFFLINE_COMPILER clang
               DOC "OpenCL offline compiler to build spir files from kernel sources")
  if(NOT OPENCL_OFFLINE_COMPILER)
    message(FATAL_ERROR "openCL compiler (CLANG) not found")
  endif()
elseif(ANDROID)
  find_program(OPENCL_OFFLINE_COMPILER ${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/clang
               DOC "OpenCL offline compiler to build spir files from kernel sources")
endif()
if(LINUX OR ANDROID)
  set(OPENCL_COMPILER_FLAGS
    -cc1
    -emit-llvm-bc
    -cl-std=CL1.2
    -cl-fast-relaxed-math
    -cl-mad-enable
    -O3
    -I${CMAKE_EXTERNAL_DEPS}/include/clangcl
    -I${VS_LIB_ROOT_SRC_DIR}
    -I${CMAKE_SOURCE_DIR}/lib/src/backend/cl
    -include opencl_spir_wrapper.h)

  set(OPENCL_COMPILER_FLAGS_32
      ${OPENCL_COMPILER_FLAGS}
      -triple spir-unknown-unknown)

  set(OPENCL_COMPILER_FLAGS_64
      ${OPENCL_COMPILER_FLAGS}
      -triple spir64-unknown-unknown)
endif(LINUX OR ANDROID)

if(WINDOWS)
  find_program(OPENCL_OFFLINE_COMPILER ioc64
                   DOC "Intel OpenCL offline compiler to build spir files from kernel sources")
  set(OPENCL_COMPILER_FLAGS
  -cmd=build
  -device=gpu
  -bo="-cl-std=CL1.2 -cl-fast-relaxed-math -cl-mad-enable -I ${VS_LIB_ROOT_SRC_DIR}")

  if (ALTERNATIVE_OPENCL_SPIR)
    find_program(OPENCL_OFFLINE_COMPILER_ALTERNATIVE ${CMAKE_SOURCE_DIR}/bin/clang.exe
                  DOC "OpenCL offline compiler to build spir files from kernel sources")
    set(OPENCL_COMPILER_FLAGS_ALTERNATIVE
      -cc1
      -emit-llvm-bc
      -cl-std=CL1.2
      -cl-fast-relaxed-math
      -cl-mad-enable
      -O0
      -I${CMAKE_EXTERNAL_DEPS}/include/clangcl
      -I${VS_LIB_ROOT_SRC_DIR}
      -include opencl_spir.h)

    set(OPENCL_COMPILER_FLAGS_32
        ${OPENCL_COMPILER_FLAGS_ALTERNATIVE}
        -triple spir-unknown-unknown)

    set(OPENCL_COMPILER_FLAGS_64
        ${OPENCL_COMPILER_FLAGS_ALTERNATIVE}
        -triple spir64-unknown-unknown)
  endif (ALTERNATIVE_OPENCL_SPIR)
endif(WINDOWS)

message(STATUS "Main OpenCL SPIR compiler: ${OPENCL_OFFLINE_COMPILER}")
message(STATUS "Alternative OpenCL SPIR compiler: ${OPENCL_OFFLINE_COMPILER_ALTERNATIVE}")

function(create_spir_with_clang output_32 output_64 compiler)
    # compile OpenCL kernel to SPIR-32
    add_custom_command(OUTPUT ${output_32}
                       COMMAND "${compiler}"
                       ARGS ${OPENCL_COMPILER_FLAGS_32} ${cl_file_full} -o ${output_32}
                       IMPLICIT_DEPENDS C ${cl_file_full}
                       DEPENDS ${cl_file} ${cl_file_deps})
    # compile OpenCL kernel to SPIR-64
    add_custom_command(OUTPUT ${output_64}
                       COMMAND "${compiler}"
                       ARGS ${OPENCL_COMPILER_FLAGS_64} ${cl_file_full} -o ${output_64}
                       IMPLICIT_DEPENDS C ${cl_file_full}
                       DEPENDS ${cl_file} ${cl_file_deps})
   list(APPEND compiled_list ${output_32} ${output_64})
endfunction(create_spir_with_clang)

#put SPIR result into a C file that can be included directly in the source code
function(put_spir_into_file output_32 spir_32 xxd_32 compiled_32 output_64 spir_64 xxd_64 compiled_64)
    add_custom_command(OUTPUT ${output_32}
                       COMMAND ${XXD}
                       ARGS -i "${spir_32}" "${xxd_32}"
                       WORKING_DIRECTORY ${OPENCL_BINARY_DIR}
                       DEPENDS ${compiled_32})

    # put 64 Bit SPIR result into a C file that can be included directly in the source code
    add_custom_command(OUTPUT ${output_64}
                       COMMAND ${XXD}
                       ARGS -i "${spir_64}" "${xxd_64}"
                       WORKING_DIRECTORY ${OPENCL_BINARY_DIR}
                       DEPENDS ${compiled_64})
endfunction(put_spir_into_file)


# combine into a single file
function(concatenate_xxd_files output_xxd)
    if(WINDOWS)
      add_custom_command(OUTPUT ${output_xxd}
                          COMMAND type
                          ARGS ${ARGV} >  ${output_xxd} 2> NUL
                          WORKING_DIRECTORY ${OPENCL_BINARY_DIR}
                          DEPENDS ${ARGN})
    else()
      add_custom_command(OUTPUT ${output_xxd}
                          COMMAND sh
                          ARGS -c 'cat ${ARGV} > ${output_xxd}'
                          WORKING_DIRECTORY ${OPENCL_BINARY_DIR}
                          DEPENDS ${ARGN})
    endif(WINDOWS)
endfunction(concatenate_xxd_files)

include_directories(${VS_LIB_ROOT_SRC_DIR})

make_directory(${OPENCL_BINARY_DIR})

set(target_to_serialize_compilation )

foreach(cl_file ${OPENCL_SOURCES})

  get_filename_component(cl_file_noext ${cl_file} NAME_WE)
  get_filename_component(cl_file_full ${cl_file} ABSOLUTE)
  get_filename_component(cl_file_dir ${cl_file_full} DIRECTORY)

  set(cl_file_xxd "${OPENCL_BINARY_DIR}/${cl_file_noext}.xxd")
  set(cl_file_xxd_32 "${OPENCL_BINARY_DIR}/${cl_file_noext}.xxd32")
  set(cl_file_xxd_64 "${OPENCL_BINARY_DIR}/${cl_file_noext}.xxd64")
  if (ALTERNATIVE_OPENCL_SPIR)
    set(cl_file_xxd_32_alternative "${OPENCL_BINARY_DIR}/${cl_file_noext}_alternative.xxd32")
    set(cl_file_xxd_64_alternative "${OPENCL_BINARY_DIR}/${cl_file_noext}_alternative.xxd64")
  endif (ALTERNATIVE_OPENCL_SPIR)
  # Create a list of deps for OpenCL kernels
  if(WINDOWS)
    # ${CMAKE_C_COMPILER} would be a better choice, but it is set to a 32 Bit variant that doesn't
    # run when generating a Visual Studio project
    find_program(CONFIGURE_TIME_COMPILER cl DOC "Compiler to preprocess OpenCL files at configure time to resolve includes. Use default cl.exe. Did you run vcvarsall.bat?")
    execute_process(COMMAND ${CONFIGURE_TIME_COMPILER} /P /I ${VS_LIB_ROOT_SRC_DIR} /Tc ${cl_file_full} /showIncludes
                    ERROR_VARIABLE cl_file_deps)
    string(REGEX MATCHALL "Note: including file: .*" cl_file_deps "${cl_file_deps}")

    # might not have any includes!
    if (cl_file_deps)
      # only keep the file name (didn't find a way to keep just the subexpressions in MATCHALL)
      string(REPLACE "Note: including file: " " " cl_file_deps ${cl_file_deps})
      string(REPLACE "\n" " " cl_file_deps ${cl_file_deps}) # we don't want newlines
      string(REPLACE "\\" "/" cl_file_deps ${cl_file_deps}) # path backslash to forward slash
      string(REPLACE " " ";" cl_file_deps ${cl_file_deps}) # string --> CMake list
    endif(cl_file_deps)
    string(REPLACE "/" "\\" cl_file_xxd_32 ${cl_file_xxd_32})
    string(REPLACE "/" "\\" cl_file_xxd_64 ${cl_file_xxd_64})
    string(REPLACE "/" "\\" cl_file_xxd ${cl_file_xxd})
    if (ALTERNATIVE_OPENCL_SPIR)
      string(REPLACE "/" "\\" cl_file_xxd_32_alternative ${cl_file_xxd_32_alternative})
      string(REPLACE "/" "\\" cl_file_xxd_64_alternative ${cl_file_xxd_64_alternative})
    endif(ALTERNATIVE_OPENCL_SPIR)

  else(WINDOWS)
    execute_process(COMMAND ${CMAKE_C_COMPILER} -E -M -x c -I${VS_LIB_ROOT_SRC_DIR} ${cl_file_full}
                   OUTPUT_VARIABLE cl_file_deps)
    string(REPLACE "\\" " " cl_file_deps ${cl_file_deps}) # we don't want backslashes
    string(REPLACE "\n" " " cl_file_deps ${cl_file_deps}) # we don't want newlines
    string(REPLACE " " ";" cl_file_deps ${cl_file_deps}) # string --> CMake list
    list(REMOVE_AT cl_file_deps 0) # drop the generated target name
  endif(WINDOWS)

  list(APPEND CL_BACKEND_SOURCES ${cl_file_deps}) # show them in the IDE

  if(DISABLE_OPENCL_SPIR)

    set(cl_file_preprocessed "${OPENCL_BINARY_DIR}/${cl_file_noext}.pre")

    if(WINDOWS)
      set(CL_PREPROCESSOR_ARGS /I ${VS_LIB_ROOT_SRC_DIR} /P /Tc ${cl_file_full} /Fi${cl_file_preprocessed})
      if(CL_ARGS_WORKAROUND)
        set(CL_PREPROCESSOR_ARGS ${CL_PREPROCESSOR_ARGS} /DCL_ARGS_WORKAROUND)
      endif(CL_ARGS_WORKAROUND)
    else(WINDOWS)
      set(CL_PREPROCESSOR_ARGS -E -x c ${cl_file_full} -o ${cl_file_preprocessed} -I${VS_LIB_ROOT_SRC_DIR})
      if(CL_ARGS_WORKAROUND)
        set(CL_PREPROCESSOR_ARGS ${CL_PREPROCESSOR_ARGS} -DCL_ARGS_WORKAROUND)
      endif(CL_ARGS_WORKAROUND)
    endif(WINDOWS)

    # preprocess OpenCL kernel to resolve #include's
    add_custom_command(OUTPUT ${cl_file_preprocessed}
                       COMMAND ${CMAKE_C_COMPILER}
                       ARGS ${CL_PREPROCESSOR_ARGS}
                       IMPLICIT_DEPENDS C ${cl_file_full}
                       DEPENDS ${cl_file} ${cl_file_deps})

    # put preprocessed file into a C file that can be included directly in the source code
    add_custom_command(OUTPUT ${cl_file_xxd}
                       COMMAND ${XXD}
                       ARGS -i ${cl_file_noext}.pre ${cl_file_xxd}
                       WORKING_DIRECTORY ${OPENCL_BINARY_DIR}
                       DEPENDS ${cl_file_preprocessed})

    list(APPEND compiled_list ${cl_file_xxd})

  else(DISABLE_OPENCL_SPIR)

    set(cl_file_compiled_32 "${OPENCL_BINARY_DIR}/${cl_file_noext}.spir32")
    set(cl_file_compiled_64 "${OPENCL_BINARY_DIR}/${cl_file_noext}.spir64")

    if(WINDOWS)
      set(cl_file_compiler_msg_32 "${OPENCL_BINARY_DIR}/${cl_file_noext}.log32.txt")
      set(cl_file_compiler_msg_64 "${OPENCL_BINARY_DIR}/${cl_file_noext}.log64.txt")
      # compile OpenCL kernel to SPIR
      add_custom_command(OUTPUT ${cl_file_compiler_msg_32}
                         COMMAND ${OPENCL_OFFLINE_COMPILER}
                         ARGS ${OPENCL_COMPILER_FLAGS} -input="${cl_file_full}" -spir32="${cl_file_compiled_32}" -output=${cl_file_compiler_msg_32}
                         WORKING_DIRECTORY ${cl_file_dir}
                         IMPLICIT_DEPENDS C ${cl_file_full}
                         DEPENDS ${cl_file} ${cl_file_deps} ${target_to_serialize_compilation})

      add_custom_command(OUTPUT ${cl_file_compiled_32}
                         COMMAND ${CMAKE_COMMAND}
                         -Dcompiler_output=${cl_file_compiler_msg_32}
                         -Dspir_output=${cl_file_compiled_32}
                         -P ${CMAKE_CURRENT_SOURCE_DIR}/windows_opencl_compiler.cmake
                         WORKING_DIRECTORY ${cl_file_dir}
                         DEPENDS ${cl_file_compiler_msg_32})

      add_custom_command(OUTPUT ${cl_file_compiler_msg_64}
                         COMMAND ${OPENCL_OFFLINE_COMPILER}
                         ARGS ${OPENCL_COMPILER_FLAGS} -input="${cl_file_full}" -spir64="${cl_file_compiled_64}" -output=${cl_file_compiler_msg_64}
                         WORKING_DIRECTORY ${cl_file_dir}
                         IMPLICIT_DEPENDS C ${cl_file_full}
                         DEPENDS ${cl_file} ${cl_file_deps} ${cl_file_compiled_32})

      add_custom_command(OUTPUT ${cl_file_compiled_64}
                         COMMAND ${CMAKE_COMMAND}
                         -Dcompiler_output=${cl_file_compiler_msg_64}
                         -Dspir_output=${cl_file_compiled_64}
                         -P ${CMAKE_CURRENT_SOURCE_DIR}/windows_opencl_compiler.cmake
                         WORKING_DIRECTORY ${cl_file_dir}
                         DEPENDS ${cl_file_compiler_msg_64})

      set(target_to_serialize_compilation ${cl_file_compiled_64})

    else(WINDOWS)
      create_spir_with_clang(${cl_file_compiled_32} ${cl_file_compiled_64} ${OPENCL_OFFLINE_COMPILER})
    endif(WINDOWS)

    put_spir_into_file(${cl_file_xxd_32} ${cl_file_noext}.spir32 ${cl_file_noext}.xxd32 ${cl_file_compiled_32} ${cl_file_xxd_64} ${cl_file_noext}.spir64 ${cl_file_noext}.xxd64 ${cl_file_compiled_64})

    if (ALTERNATIVE_OPENCL_SPIR)
      set(cl_file_compiled_32_alternative "${OPENCL_BINARY_DIR}/${cl_file_noext}_alternative.spir32")
      set(cl_file_compiled_64_alternative "${OPENCL_BINARY_DIR}/${cl_file_noext}_alternative.spir64")
      #create the alternative SPIR
      create_spir_with_clang(${cl_file_compiled_32_alternative} ${cl_file_compiled_64_alternative} ${OPENCL_OFFLINE_COMPILER_ALTERNATIVE})

      # put Alternative SPIR result into a C file
      put_spir_into_file(${cl_file_xxd_32_alternative} ${cl_file_noext}_alternative.spir32 ${cl_file_noext}_alternative.xxd32 ${cl_file_compiled_32_alternative}
             ${cl_file_xxd_64_alternative} ${cl_file_noext}_alternative.spir64 ${cl_file_noext}_alternative.xxd64 ${cl_file_compiled_64_alternative})
      # combine into a single file
      concatenate_xxd_files(${cl_file_xxd} ${cl_file_xxd_32} ${cl_file_xxd_64} ${cl_file_xxd_32_alternative} ${cl_file_xxd_64_alternative})


    else(ALTERNATIVE_OPENCL_SPIR)
      concatenate_xxd_files(${cl_file_xxd} ${cl_file_xxd_32} ${cl_file_xxd_64})
    endif(ALTERNATIVE_OPENCL_SPIR)

    list(APPEND compiled_list ${cl_file_xxd})

  endif(DISABLE_OPENCL_SPIR)

endforeach()

set(CL_BACKEND_SOURCES ${CL_BACKEND_SOURCES} PARENT_SCOPE)

add_custom_target(opencl_compilation
                  DEPENDS ${compiled_list})
set_property(TARGET opencl_compilation PROPERTY FOLDER "lib")
