###############################################################################
#  For more information, please see: http://software.sci.utah.edu
#
#  The MIT License
#
#  Copyright (c) 2007-2008
#  Scientific Computing and Imaging Institute, University of Utah
#
#  License for the specific language governing rights and limitations under
#  Permission is hereby granted, free of charge, to any person obtaining a
#  copy of this software and associated documentation files (the "Software"),
#  to deal in the Software without restriction, including without limitation
#  the rights to use, copy, modify, merge, publish, distribute, sublicense,
#  and/or sell copies of the Software, and to permit persons to whom the
#  Software is furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included
#  in all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
#  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
#  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#  DEALINGS IN THE SOFTWARE.
#
# This script locates the Nvidia Compute Unified Driver Architecture (CUDA) 
# tools. It should work on linux, windows, and mac and should be reasonably 
# up to date with cuda releases.
#
# The script will prompt the user to specify CUDA_INSTALL_PREFIX if the 
# prefix cannot be determined by the location of nvcc in the system path. To
# use a different installed version of the toolkit set the environment variable
# CUDA_BIN_PATH before running cmake (e.g. CUDA_BIN_PATH=/usr/local/cuda1.0 
# instead of the default /usr/local/cuda).
#
# Set CUDA_BUILD_TYPE to "Device" or "Emulation" mode.
# _DEVICEEMU is defined in "Emulation" mode.
#
# Set CUDA_BUILD_CUBIN to "ON" or "OFF" to enable and extra compilation pass
# with the -cubin option in Device mode. The output is parsed and register, 
# shared memory usage is printed during build. Default ON.
# 
# The script creates the following macros:
# CUDA_INCLUDE_DIRECTORIES( path0 path1 ... )
# -- Sets the directories that should be passed to nvcc 
#    (e.g. nvcc -Ipath0 -Ipath1 ... ). These paths usually contain other .cu 
#    files.
# 
# CUDA_ADD_LIBRARY( cuda_target file0 file1 ... )
# -- Creates a shared library "cuda_target" which contains all of the source 
#    (*.c, *.cc, etc.) specified and all of the nvcc'ed .cu files specified.
#    All of the specified source files and generated .cpp files are compiled 
#    using the standard CMake compiler, so the normal INCLUDE_DIRECTORIES, 
#    LINK_DIRECTORIES, and TARGET_LINK_LIBRARIES can be used to affect their
#    build and link.
#
# CUDA_ADD_EXECUTABLE( cuda_target file0 file1 ... )
# -- Same as CUDA_ADD_LIBRARY except that an exectuable is created.
#
# CUDA_COMPILE( cuda_files file0 file1 ... )
# -- Returns a list of build commands in the first argument to be used with 
#    ADD_LIBRARY or ADD_EXECUTABLE.
# 
# The script defines the following variables:
#
# ( Note CUDA_ADD_* macros setup cuda/cut library dependencies automatically. 
# These variables are only needed if a cuda API call must be made from code in 
# a outside library or executable. )
#
# CUDA_INCLUDE         -- Include directory for cuda headers.
# CUDA_TARGET_LINK     -- Cuda RT library. 
# CUDA_CUT_INCLUDE     -- Include directory for cuda SDK headers (cutil.h).   
# CUDA_CUT_TARGET_LINK -- SDK libraries.
# CUDA_NVCC_FLAGS      -- Additional NVCC command line arguments. NOTE: 
#                         multiple arguments must be semi-colon delimited 
#                         e.g. --compiler-options;-Wall
# CUBLAS_TARGET_LINK-- cublas library name.
# CUFFT_TARGET_LINK -- cubfft library name.
# 
# The nvcc flag "--host-compilation;c++" should be used if functions declared
# as __host__ contain C++ code.
#
# It might be necessary to set CUDA_INSTALL_PATH manually on certain platforms,
# or to use a cuda runtime not installed in the default location. In newer 
# versions of the toolkit the cuda library is included with the graphics 
# driver- be sure that the driver version matches what is needed by the cuda 
# runtime version.
# 
# -- Abe Stephens SCI Institute -- http://www.sci.utah.edu/~abe/FindCuda.html
###############################################################################

# FindCuda.cmake
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)

INCLUDE(${FINDCUDA_DIR}/CudaDependency.cmake)

###############################################################################
###############################################################################
# Locate CUDA, Set Build Type, etc.
###############################################################################
###############################################################################

# Parse CUDA build type.
IF (NOT CUDA_BUILD_TYPE)
  SET(CUDA_BUILD_TYPE "Device" CACHE STRING "Cuda build type: Emulation or Device")
ENDIF(NOT CUDA_BUILD_TYPE)

# Emulation if the card isn't present.
IF (CUDA_BUILD_TYPE MATCHES "Emulation")
  # Emulation.
  SET(nvcc_flags --device-emulation -D_DEVICEEMU -g)
ELSE(CUDA_BUILD_TYPE MATCHES "Emulation")
  # Device present.
  SET(nvcc_flags "")
ENDIF(CUDA_BUILD_TYPE MATCHES "Emulation")

SET(CUDA_BUILD_CUBIN TRUE CACHE BOOL "Generate and parse .cubin files in Device mode.")
# Don't set CUDA_NVCC_FLAGS here, set them in CMakeLists.txt
# SET(CUDA_NVCC_FLAGS "--maxrregcount=32;-gencode;arch=compute_11,code=sm_11;-gencode;arch=compute_13,code=sm_13;-use_fast_math;-O0" CACHE STRING "Semi-colon delimit multiple arguments.")

# Search for the cuda distribution.
IF(NOT CUDA_INSTALL_PREFIX)
  FIND_PATH(CUDA_INSTALL_PREFIX
    NAMES nvcc nvcc.exe
    PATHS /usr/local/cuda C:/CUDA
    PATH_SUFFIXES bin
    ENV CUDA_BIN_PATH
    DOC "Toolkit location."
    )
  IF (CUDA_INSTALL_PREFIX) 
    STRING(REGEX REPLACE "[/\\\\]?bin[/\\\\]?$" "" CUDA_INSTALL_PREFIX ${CUDA_INSTALL_PREFIX})
  ENDIF(CUDA_INSTALL_PREFIX)
  # IF (NOT EXISTS ${CUDA_INSTALL_PREFIX})
    # MESSAGE(FATAL_ERROR "Specify CUDA_INSTALL_PREFIX")
  # ENDIF (NOT EXISTS ${CUDA_INSTALL_PREFIX})
ENDIF (NOT CUDA_INSTALL_PREFIX)

# CUDA_NVCC
IF (NOT CUDA_NVCC)
  FIND_PROGRAM(CUDA_NVCC 
    nvcc
    PATHS ${CUDA_INSTALL_PREFIX}/bin $ENV{CUDA_BIN_PATH}
    )
  # IF(NOT CUDA_NVCC)
    # MESSAGE(FATAL_ERROR "Could not find nvcc")
  # ELSE(NOT CUDA_NVCC)
  #  MARK_AS_ADVANCED(CUDA_NVCC)
  # ENDIF(NOT CUDA_NVCC)
ENDIF(NOT CUDA_NVCC)

# CUDA_NVCC_INCLUDE_ARGS
# IF (NOT FOUND_CUDA_NVCC_INCLUDE)
  FIND_PATH(FOUND_CUDA_NVCC_INCLUDE
    device_functions.h # Header included in toolkit
    PATHS ${CUDA_INSTALL_PREFIX}/include 
          $ENV{CUDA_INC_PATH}
    )
  
  # IF(NOT FOUND_CUDA_NVCC_INCLUDE)
  #   MESSAGE(FATAL_ERROR "Could not find Cuda headers")
  # ELSE(NOT FOUND_CUDA_NVCC_INCLUDE)
  IF(FOUND_CUDA_NVCC_INCLUDE)
    # Set the initial include dir.
    SET (CUDA_NVCC_INCLUDE_ARGS "-I"${FOUND_CUDA_NVCC_INCLUDE})
	SET (CUDA_INCLUDE ${FOUND_CUDA_NVCC_INCLUDE})

    MARK_AS_ADVANCED(
      FOUND_CUDA_NVCC_INCLUDE
      CUDA_NVCC_INCLUDE_ARGS
      )
  ENDIF(FOUND_CUDA_NVCC_INCLUDE)
  
# ENDIF(NOT FOUND_CUDA_NVCC_INCLUDE)

  
# CUDA_TARGET_LINK
IF (NOT CUDA_TARGET_LINK)

  FIND_LIBRARY(FOUND_CUDART
    cudart
    PATHS ${CUDA_INSTALL_PREFIX}/lib $ENV{CUDA_LIB_PATH}
    DOC "\"cudart\" library"
    )
  
  # Check to see if cudart library was found.
  # IF(NOT FOUND_CUDART)
  #   MESSAGE(FATAL_ERROR "Could not find cudart library (cudart)")
  # ENDIF(NOT FOUND_CUDART)  

  # 1.1 toolkit on linux doesn't appear to have a separate library on 
  # some platforms.
  FIND_LIBRARY(FOUND_CUDA
    cuda
    PATHS ${CUDA_INSTALL_PREFIX}/lib
    DOC "\"cuda\" library (older versions only)."
    NO_DEFAULT_PATH
    NO_CMAKE_ENVIRONMENT_PATH
    NO_CMAKE_PATH
    NO_SYSTEM_ENVIRONMENT_PATH
    NO_CMAKE_SYSTEM_PATH
    )

  # Add cuda library to the link line only if it is found.
  IF (FOUND_CUDA)
    SET(CUDA_TARGET_LINK ${FOUND_CUDA})
  ENDIF(FOUND_CUDA)

  # Always add cudart to the link line.
  IF(FOUND_CUDART)
    SET(CUDA_TARGET_LINK
      ${CUDA_TARGET_LINK} ${FOUND_CUDART}
      )
    MARK_AS_ADVANCED(
      CUDA_TARGET_LINK 
      CUDA_LIB
      FOUND_CUDA
      FOUND_CUDART
      )
  # ELSE(FOUND_CUDART)
  #   MESSAGE(FATAL_ERROR "Could not find cuda libraries.")
  ENDIF(FOUND_CUDART)
  
ENDIF(NOT CUDA_TARGET_LINK)

# CUDA_CUT_INCLUDE
IF(NOT CUDA_CUT_INCLUDE)
  FIND_PATH(FOUND_CUT_INCLUDE
    cutil.h
    PATHS ${CUDA_INSTALL_PREFIX}/local/NVSDK0.2/common/inc
          ${CUDA_INSTALL_PREFIX}/NVSDK0.2/common/inc
          ${CUDA_INSTALL_PREFIX}/NV_CUDA_SDK/common/inc
          $ENV{HOME}/NVIDIA_CUDA_SDK/common/inc
          $ENV{HOME}/NVIDIA_CUDA_SDK_MACOSX/common/inc
          $ENV{NVSDKCUDA_ROOT}/common/inc
    DOC "Location of cutil.h"
    )
  IF(FOUND_CUT_INCLUDE)
    SET(CUDA_CUT_INCLUDE ${FOUND_CUT_INCLUDE})
    MARK_AS_ADVANCED(
      FOUND_CUT_INCLUDE
      )
  ENDIF(FOUND_CUT_INCLUDE)
ENDIF(NOT CUDA_CUT_INCLUDE)


# CUDA_CUT_TARGET_LINK
IF(NOT CUDA_CUT_TARGET_LINK)
  FIND_LIBRARY(FOUND_CUT
    cutil
    cutil32
    PATHS ${CUDA_INSTALL_PREFIX}/local/NVSDK0.2/lib
          ${CUDA_INSTALL_PREFIX}/NVSDK0.2/lib
          ${CUDA_INSTALL_PREFIX}/NV_CUDA_SDK/lib
          $ENV{HOME}/NVIDIA_CUDA_SDK/lib
          $ENV{HOME}/NVIDIA_CUDA_SDK_MACOSX/lib
          $ENV{NVSDKCUDA_ROOT}/common/lib
    NO_DEFAULT_PATH
    NO_CMAKE_ENVIRONMENT_PATH
    NO_CMAKE_PATH
    NO_SYSTEM_ENVIRONMENT_PATH
    NO_CMAKE_SYSTEM_PATH
    DOC "Location of cutil library"
    )
  IF(FOUND_CUT)
    SET(CUDA_CUT_TARGET_LINK ${FOUND_CUT})
    MARK_AS_ADVANCED(
      FOUND_CUT
      )
  ENDIF(FOUND_CUT)

# Add variables for cufft and cublas target link
FIND_LIBRARY(FOUND_CUFFTEMU
  cufftemu
  PATHS ${CUDA_INSTALL_PREFIX}/lib $ENV{CUDA_LIB_PATH}
  DOC "\"cufftemu\" library"
  )
FIND_LIBRARY(FOUND_CUBLASEMU
  cublasemu
  PATHS ${CUDA_INSTALL_PREFIX}/lib $ENV{CUDA_LIB_PATH}
  DOC "\"cublasemu\" library"
  )
FIND_LIBRARY(FOUND_CUFFT
  cufft
  PATHS ${CUDA_INSTALL_PREFIX}/lib $ENV{CUDA_LIB_PATH}
  DOC "\"cufft\" library"
  )
FIND_LIBRARY(FOUND_CUBLAS
  cublas
  PATHS ${CUDA_INSTALL_PREFIX}/lib $ENV{CUDA_LIB_PATH}
  DOC "\"cublas\" library"
  )

IF (CUDA_BUILD_TYPE MATCHES "Emulation")
  SET(CUFFT_TARGET_LINK  ${FOUND_CUFFTEMU})
  SET(CUBLAS_TARGET_LINK ${FOUND_CUBLASEMU})
ELSE(CUDA_BUILD_TYPE MATCHES "Emulation")
  SET(CUFFT_TARGET_LINK  ${FOUND_CUFFT})
  SET(CUBLAS_TARGET_LINK ${FOUND_CUBLAS})
ENDIF(CUDA_BUILD_TYPE MATCHES "Emulation")

ENDIF(NOT CUDA_CUT_TARGET_LINK)

# Set CUDA_FOUND variable (requires cmake version 2.6)
set(CUDA_FOUND TRUE)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(CUDA DEFAULT_MSG
    CUDA_INSTALL_PREFIX
    CUDA_NVCC
    FOUND_CUDA_NVCC_INCLUDE
    FOUND_CUDART
)
if(NOT CUDA_FOUND)
    mark_as_advanced(
        CUDA_BUILD_TYPE
        CUDA_INSTALL_PREFIX
        CUDA_SDK_ROOT_DIR
        CUDA_TOOLKIT_ROOT_DIR
        FOUND_CUBLAS
        FOUND_CUBLASEMU
        FOUND_CUDA
        FOUND_CUDART
        FOUND_CUFFT
        FOUND_CUFFTEMU
        FOUND_CUT
        FOUND_CUT_INCLUDE
    )
endif(NOT CUDA_FOUND)

###############################################################################
# Add include directories to pass to the nvcc command.
MACRO(CUDA_INCLUDE_DIRECTORIES)
  FOREACH(dir ${ARGN})
    SET(CUDA_NVCC_INCLUDE_ARGS ${CUDA_NVCC_INCLUDE_ARGS} -I${dir})
  ENDFOREACH(dir ${ARGN})
ENDMACRO(CUDA_INCLUDE_DIRECTORIES)


##############################################################################
##############################################################################
# This helper macro populates the following variables and setups up custom commands and targets to
# invoke the nvcc compiler. The compiler is invoked once with -M to generate a dependency file and
# a second time with -cuda to generate a .cpp file
# ${target_srcs}
# ${cuda_cu_sources}
##############################################################################
##############################################################################

MACRO(CUDA_add_custom_commands cuda_target)

  SET(target_srcs "")
  SET(cuda_cu_sources "")

  # Iterate over the macro arguments and create custom
  # commands for all the .cu files.
  FOREACH(file ${ARGN})
    IF(${file} MATCHES ".*\\.cu$")
    
    # Add a custom target to generate a c file.
    GET_FILENAME_COMPONENT(filename ${file} NAME)
    SET(generated_file  "${CMAKE_BINARY_DIR}/src/cuda/${filename}_${cuda_target}_generated.cpp")
    SET(generated_target "${filename}_target")
    
    FILE(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/src/cuda)

    SET(source_file ${file})

    # MESSAGE("${CUDA_NVCC} ${source_file} ${CUDA_NVCC_FLAGS} ${nvcc_flags} -cuda -o ${generated_file} ${CUDA_NVCC_INCLUDE_ARGS}")
    
    # Bring in the dependencies.  Creates a variable CUDA_NVCC_DEPEND
	SET(cmake_dependency_file "${generated_file}.depend")
	CUDA_INCLUDE_NVCC_DEPENDENCIES(${cmake_dependency_file})
	SET(NVCC_generated_dependency_file "${generated_file}.NVCC-depend")


	# Build the NVCC made dependency file
  IF (CUDA_BUILD_TYPE MATCHES "Device" AND CUDA_BUILD_CUBIN)
    SET(NVCC_generated_cubin_file "${generated_file}.NVCC-cubin.txt")
	  ADD_CUSTOM_COMMAND(

      # Generate the .cubin output.
      OUTPUT ${NVCC_generated_cubin_file}
      COMMAND ${CUDA_NVCC}
      ARGS ${source_file} 
      ${CUDA_NVCC_FLAGS}
      ${nvcc_flags}
      -DNVCC
      -cubin
      -o ${NVCC_generated_cubin_file} 
      ${CUDA_NVCC_INCLUDE_ARGS}

      # Execute the parser script.
      COMMAND  ${CMAKE_COMMAND}
      ARGS 
      -D input_file="${NVCC_generated_cubin_file}"
      -P "${FINDCUDA_DIR}/parse_cubin.cmake"


      # MAIN_DEPENDENCY ${source_file}
      DEPENDS ${source_file}
      DEPENDS ${CUDA_NVCC_DEPEND}



	    COMMENT "Building (${CUDA_BUILD_TYPE}) NVCC -cubin File: ${NVCC_generated_cubin_file}\n"
      )
  ELSE (CUDA_BUILD_TYPE MATCHES "Device" AND CUDA_BUILD_CUBIN)
    # Depend on something that will exist.
    SET(NVCC_generated_cubin_file "${source_file}")
  ENDIF (CUDA_BUILD_TYPE MATCHES "Device"AND CUDA_BUILD_CUBIN)

	# Build the NVCC made dependency file
	ADD_CUSTOM_COMMAND(
      OUTPUT ${NVCC_generated_dependency_file}
      COMMAND ${CUDA_NVCC}
      ARGS ${source_file} 
           ${CUDA_NVCC_FLAGS}
           ${nvcc_flags}
           -DNVCC
           -M
           -o ${NVCC_generated_dependency_file} 
           ${CUDA_NVCC_INCLUDE_ARGS}
      # MAIN_DEPENDENCY ${source_file}
      DEPENDS ${source_file}
      DEPENDS ${CUDA_NVCC_DEPEND}
	  COMMENT "Building (${CUDA_BUILD_TYPE}) NVCC Dependency File: ${NVCC_generated_dependency_file}\n"
    )
    
    # Build the CMake readible dependency file
	ADD_CUSTOM_COMMAND(
	  OUTPUT ${cmake_dependency_file}
      COMMAND ${CMAKE_COMMAND}
      ARGS 
      -D input_file="${NVCC_generated_dependency_file}"
      -D output_file="${cmake_dependency_file}"
      -P "${FINDCUDA_DIR}/make2cmake.cmake"
      MAIN_DEPENDENCY ${NVCC_generated_dependency_file}
      COMMENT "Converting NVCC dependency to CMake (${cmake_dependency_file})"
    )

    ADD_CUSTOM_COMMAND(
      OUTPUT ${generated_file}
      MAIN_DEPENDENCY ${source_file} 
      DEPENDS ${CUDA_NVCC_DEPEND}
      DEPENDS ${cmake_dependency_file}
      DEPENDS ${NVCC_generated_cubin_file}
      COMMAND ${CUDA_NVCC} 
      ARGS ${source_file} 
           ${CUDA_NVCC_FLAGS}
           ${nvcc_flags}
			  ${CUDA_NVCC_BUILD_FLAGS}
           -DNVCC
           --keep
           -cuda -o ${generated_file} 
           ${CUDA_NVCC_INCLUDE_ARGS}
       COMMENT "Building (${CUDA_BUILD_TYPE}) NVCC ${source_file}: ${generated_file}\n"
      )
    	
    SET(cuda_cu_sources ${cuda_cu_sources} ${source_file})

    # Add the generated file name to the source list.
    SET(target_srcs ${target_srcs} ${generated_file})
    
    ELSE(${file} MATCHES ".*\\.cu$")
  
    # Otherwise add the file name to the source list.
    SET(target_srcs ${target_srcs} ${file})
  
    ENDIF(${file} MATCHES ".*\\.cu$")
  ENDFOREACH(file)

ENDMACRO(CUDA_add_custom_commands)

###############################################################################
###############################################################################
# ADD LIBRARY
###############################################################################
###############################################################################
MACRO(CUDA_ADD_LIBRARY cuda_target)

  # Create custom commands and targets for each file.
  CUDA_add_custom_commands( ${cuda_target} ${ARGN} )  
  
  # Add the library.
  ADD_LIBRARY(${cuda_target}
    ${target_srcs}
    ${cuda_cu_sources}
    )

  TARGET_LINK_LIBRARIES(${cuda_target}
    ${CUDA_TARGET_LINK}
    )

ENDMACRO(CUDA_ADD_LIBRARY cuda_target)


###############################################################################
###############################################################################
# ADD EXECUTABLE
###############################################################################
###############################################################################
MACRO(CUDA_ADD_EXECUTABLE cuda_target)
  
  # Create custom commands and targets for each file.
  CUDA_add_custom_commands( ${cuda_target} ${ARGN} )
  
  # Add the library.
  ADD_EXECUTABLE(${cuda_target}
    ${target_srcs}
    ${cuda_cu_sources}
    )

  TARGET_LINK_LIBRARIES(${cuda_target}
    ${CUDA_TARGET_LINK}
    )


ENDMACRO(CUDA_ADD_EXECUTABLE cuda_target)

###############################################################################
###############################################################################
# ADD EXECUTABLE
###############################################################################
###############################################################################
MACRO(CUDA_COMPILE file_variable)
  
  # Create custom commands and targets for each file.
  CUDA_add_custom_commands( cuda_compile ${ARGN} )
  
  SET(file_variable ${target_srcs} ${cuda_cu_sources})

ENDMACRO(CUDA_COMPILE)
