import os

from setuptools.command.build_ext import build_ext
from distutils.unixccompiler import UnixCCompiler

# This is based on
# https://github.com/rmcgibbo/npcuda-example/blob/dd2768d8ccb5688c0f08678dd8f1ad5afe3e4332/cython/setup.py
# published under BSD 2-Clause "Simplified" License


def find_in_path(name, path):
    """Find a file in a search path"""

    # Adapted fom http://code.activestate.com/recipes/52224
    for dir in path.split(os.pathsep):
        binpath = os.path.join(dir, name)
        if os.path.exists(binpath):
            return os.path.abspath(binpath)

    return None


def locate_cuda():
    """Locate the CUDA environment on the system

    Returns a dict with keys 'cuda_root', 'nvcc', 'include', and 'lib64'
    and values giving the absolute path to each directory.

    Starts by looking for the CUDAHOME and CUDA_ROOT env variables.
    If not found, everything is based on finding 'nvcc' in the PATH.
    """

    # First check if any common env variable is in use
    if "CUDAHOME" in os.environ:
        cuda_root = os.environ["CUDAHOME"]
        nvcc = os.path.join(cuda_root, "bin", "nvcc")
    elif "CUDA_ROOT" in os.environ:
        cuda_root = os.environ["CUDA_ROOT"]
        nvcc = os.path.join(cuda_root, "bin", "nvcc")
    else:
        # Otherwise, search the PATH for NVCC
        nvcc = find_in_path("nvcc", os.environ["PATH"])
        if nvcc is not None:
            cuda_root = os.path.dirname(os.path.dirname(nvcc))
        else:
            cuda_root = None

    if cuda_root is None:
        return {
            "cuda_root": "",
            "nvcc": "nvcc",
            "include": [],
            "lib64": [],
            "cflags": [],
        }

    cflags = ["-c", "--compiler-options", "'-fPIC'", "-std=c++11"]

    cm = os.environ.get("CUDA_COMPUTE_CAPABILITY")
    if cm is not None:
        cflags.append("-gencode=arch=compute_{cm},code=compute_{cm}".format(cm=cm))
    else:
        print(
            "Warning: Consider settings the CUDA_COMPUTE_CAPABILITY environment "
            "variable to your GPU's compute capability."
        )

    return {
        "cuda_root": cuda_root,
        "nvcc": nvcc,
        "include": [os.path.join(cuda_root, "include")],
        "lib64": [os.path.join(cuda_root, "lib64")],
        "cflags": cflags,
    }


def customize_compiler_for_nvcc(self):
    if not isinstance(self, UnixCCompiler):
        # Just give up
        default_compile = self._compile

        def _compile(obj, src, ext, cc_args, extra_postargs, pp_opts):
            postargs = extra_postargs["gcc"]
            return default_compile(obj, src, ext, cc_args, postargs, pp_opts)

        self._compile = _compile
        return

    # Tell the compiler it can process .cu
    self.src_extensions.append(".cu")

    # Save references to the default compiler_so and _compile methods
    default_compiler_so = self.compiler_so
    default_compile = self._compile

    # Now redefine the _compile method. This gets executed for each
    # object but distutils doesn't have the ability to change compilers
    # based on source extension: we add it.
    def _compile(obj, src, ext, cc_args, extra_postargs, pp_opts):
        if os.path.splitext(src)[1] == ".cu":
            # use the cuda for .cu files
            self.set_executable("compiler_so", cuda_info["nvcc"])
            # use only a subset of the extra_postargs, which are 1-1
            # translated from the extra_compile_args in the Extension class
            postargs = extra_postargs["nvcc"]
        else:
            postargs = extra_postargs["gcc"]

        default_compile(obj, src, ext, cc_args, postargs, pp_opts)
        # Reset the default compiler_so, which we might have changed for cuda
        self.compiler_so = default_compiler_so

    # Inject our redefined _compile method into the class
    self._compile = _compile


# Run the customize_compiler
class custom_build_ext(build_ext):
    def build_extensions(self):
        customize_compiler_for_nvcc(self.compiler)
        super().build_extensions()


cuda_info = locate_cuda()
