# Copyright 2020 The IREE Authors
#
# Licensed under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

from typing import Dict, List
import re


class TargetConverter:
    def __init__(self, repo_map: Dict[str, str]):
        self._explicit_target_mapping = {}
        self._repo_map = repo_map

        iree_core_repo = self._repo_alias("@iree_core")
        self._update_target_mappings(
            {
                # Internal utilities to emulate various binary/library options.
                f"{iree_core_repo}//build_tools:default_linkopts": [],
                f"{iree_core_repo}//build_tools:dl": ["${CMAKE_DL_LIBS}"],
                f"{iree_core_repo}//compiler/src/iree/compiler/API:CAPI": [
                    "IREECompilerCAPILib"
                ],
                # IREE llvm-external-projects
                f"{iree_core_repo}//llvm-external-projects/iree-dialects:CAPI": [
                    "IREEDialectsCAPI"
                ],
                # Disable all hard-coded codegen targets (they are expanded dynamically
                # in CMake).
                "@llvm-project//llvm:AArch64AsmParser": ["IREELLVMCPUTargetDeps"],
                "@llvm-project//llvm:AArch64CodeGen": ["IREELLVMCPUTargetDeps"],
                "@llvm-project//llvm:ARMAsmParser": ["IREELLVMCPUTargetDeps"],
                "@llvm-project//llvm:ARMCodeGen": ["IREELLVMCPUTargetDeps"],
                "@llvm-project//llvm:RISCVAsmParser": ["IREELLVMCPUTargetDeps"],
                "@llvm-project//llvm:RISCVCodeGen": ["IREELLVMCPUTargetDeps"],
                "@llvm-project//llvm:WebAssemblyAsmParser": ["IREELLVMCPUTargetDeps"],
                "@llvm-project//llvm:WebAssemblyCodeGen": ["IREELLVMCPUTargetDeps"],
                "@llvm-project//llvm:X86AsmParser": ["IREELLVMCPUTargetDeps"],
                "@llvm-project//llvm:X86CodeGen": ["IREELLVMCPUTargetDeps"],
                # Clang
                "@llvm-project//clang": ["${IREE_CLANG_TARGET}"],
                # LLD
                "@llvm-project//lld": ["${IREE_LLD_TARGET}"],
                "@llvm-project//lld:COFF": ["lldCOFF"],
                "@llvm-project//lld:Common": ["lldCommon"],
                "@llvm-project//lld:ELF": ["lldELF"],
                "@llvm-project//lld:MachO": ["lldMachO"],
                "@llvm-project//lld:Wasm": ["lldWasm"],
                # LLVM
                "@llvm-project//llvm:config": [],
                "@llvm-project//llvm:IPO": ["LLVMipo"],
                "@llvm-project//llvm:FileCheck": ["FileCheck"],
                "@llvm-project//llvm:not": ["not"],
                "@llvm-project//llvm:llvm-link": ["${IREE_LLVM_LINK_TARGET}"],
                "@llvm-project//llvm:NVPTXUtilsAndDesc": [
                    "LLVMNVPTXDesc",
                ],
                # MLIR
                "@llvm-project//mlir:AllPassesAndDialects": ["MLIRAllDialects"],
                "@llvm-project//mlir:CommonFolders": [""],
                "@llvm-project//mlir:DialectUtils": [""],
                "@llvm-project//mlir:GPUDialect": ["MLIRGPUDialect"],
                "@llvm-project//mlir:GPUTransforms": ["MLIRGPUTransforms"],
                "@llvm-project//mlir:LinalgStructuredOpsIncGen": [
                    "MLIRLinalgStructuredOpsIncGenLib"
                ],
                "@llvm-project//mlir:ShapeTransforms": ["MLIRShapeOpsTransforms"],
                "@llvm-project//mlir:ToLLVMIRTranslation": ["MLIRTargetLLVMIRExport"],
                "@llvm-project//mlir:mlir-translate": ["mlir-translate"],
                "@llvm-project//mlir:MlirLspServerLib": ["MLIRLspServerLib"],
                "@llvm-project//mlir:MlirTableGenMain": ["MLIRTableGen"],
                "@llvm-project//mlir:MlirOptLib": ["MLIROptLib"],
                "@llvm-project//mlir:VectorOps": ["MLIRVector"],
                # StableHLO.
                "@stablehlo//:chlo_ops": [
                    "ChloOps",
                ],
                "@stablehlo//:stablehlo_ops": [
                    "StablehloOps",
                ],
                "@stablehlo//:broadcast_utils": [
                    "StablehloBroadcastUtils",
                ],
                # NCCL
                "@nccl//:headers": [
                    "nccl::headers",
                ],
                # Torch-MLIR.
                "@torch-mlir-dialects//:TorchMLIRTMTensorDialect": [
                    "TorchMLIRTMTensorDialect"
                ],
                # Tracy.
                "@tracy_client//:runtime": ["tracy_client::runtime"],
                # Vulkan
                "@vulkan_headers": ["Vulkan::Headers"],
                # Misc single targets
                "@com_google_benchmark//:benchmark": ["benchmark"],
                "@com_github_dvidelabs_flatcc//:flatcc": ["flatcc"],
                "@com_github_dvidelabs_flatcc//:parsing": ["flatcc::parsing"],
                "@com_github_dvidelabs_flatcc//:runtime": ["flatcc::runtime"],
                "@com_github_yaml_libyaml//:yaml": ["yaml"],
                "@com_google_googletest//:gtest": ["gmock", "gtest"],
                "@spirv_cross//:spirv_cross_lib": ["spirv-cross-msl"],
                "@cpuinfo": ["${IREE_CPUINFO_TARGET}"],
                "@vulkan_memory_allocator//:impl_header_only": [
                    "vulkan_memory_allocator"
                ],
                "@webgpu_headers": [],
            }
        )

        self._initialize()

    def _initialize(self):
        pass

    def _repo_alias(self, repo_name: str) -> str:
        """Returns the prefix of a repo (i.e. '@iree_core') given the repo map."""
        return self._repo_map.get(repo_name, repo_name)

    def _update_target_mappings(self, mappings: Dict[str, List[str]]):
        self._explicit_target_mapping.update(mappings)

    def _convert_mlir_target(self, target):
        # Default to a pattern substitution approach.
        # Take "MLIR" and append the name part of the full target identifier, e.g.
        #   "@llvm-project//mlir:IR"   -> "MLIRIR"
        #   "@llvm-project//mlir:Pass" -> "MLIRPass"
        # MLIR does not have header-only targets apart from the libraries. Here
        # we redirect any request for a CAPI{Name}Headers to a target within IREE
        # that sets this up.
        label = target.rsplit(":")[-1]
        if label.startswith("CAPI") and label.endswith("Headers"):
            return [f"IREELLVMIncludeSetup"]
        else:
            return [f"MLIR{label}"]

    def _convert_llvm_target(self, target):
        # Default to a pattern substitution approach.
        # Prepend "LLVM" to the Bazel target name.
        #   "@llvm-project//llvm:AsmParser" -> "LLVMAsmParser"
        #   "@llvm-project//llvm:Core" -> "LLVMCore"
        return ["LLVM" + target.rsplit(":")[-1]]

    def _convert_iree_cuda_target(self, target):
        # Convert like:
        #   @iree_cuda//:libdevice_embedded -> iree_cuda::libdevice_embedded
        label = target.rsplit(":")[-1]
        return [f"iree_cuda::{label}"]

    def _convert_iree_dialects_target(self, target):
        # Just take the target name as-is.
        return [target.rsplit(":")[-1]]

    def _convert_to_cmake_path(self, bazel_path_fragment: str) -> str:
        cmake_path = bazel_path_fragment
        # Bazel `//iree/base`     -> CMake `iree::base`
        # Bazel `//iree/base:foo` -> CMake `iree::base::foo`
        if cmake_path.startswith("//"):
            cmake_path = cmake_path[len("//") :]
        cmake_path = cmake_path.replace(":", "::")  # iree/base::foo or ::foo
        cmake_path = cmake_path.replace("/", "::")  # iree::base
        return cmake_path

    def convert_target(self, target):
        """Converts a Bazel target to a list of CMake targets.

        IREE targets are expected to follow a standard form between Bazel and CMake
        that facilitates conversion. External targets *may* have their own patterns,
        or they may be purely special cases.

        Multiple target in Bazel may map to a single target in CMake and a Bazel
        target may map to multiple CMake targets.

        Returns:
          A list of converted targets if it was successfully converted.

        Raises:
          KeyError: No conversion was found for the target.
        """
        iree_core_repo = self._repo_alias("@iree_core")
        if target in self._explicit_target_mapping:
            return self._explicit_target_mapping[target]
        if target.startswith("@llvm-project//llvm"):
            return self._convert_llvm_target(target)
        if target.startswith("@llvm-project//mlir"):
            return self._convert_mlir_target(target)
        if target.startswith("@iree_cuda//"):
            return self._convert_iree_cuda_target(target)
        if target.startswith(f"{iree_core_repo}//"):
            return self._convert_iree_core_target(target)
        if target.startswith("@"):
            raise KeyError(f"No conversion found for target '{target}'")

        # Pass through package-relative targets
        #   :target_name
        #   file_name.txt
        if target.startswith(":") or (":" not in target and not target.startswith("/")):
            return [self._convert_to_cmake_path(target)]

        return self._convert_unmatched_target(target)

    def _convert_iree_core_target(self, target):
        iree_core_repo = self._repo_alias("@iree_core")
        if target.startswith(f"{iree_core_repo}//llvm-external-projects/iree-dialects"):
            return self._convert_iree_dialects_target(target)

        # IREE root paths map to package names based on explicit rules.
        #   * src/iree/ directories (compiler/src/iree/ and runtime/src/iree/)
        #     creating their own root paths by trimming down to just "iree"
        #   * tools/ uses an empty root, for binary targets names like "iree-compile"
        #   * other top level directories add back an 'iree' prefix
        # If changing these, make the corresponding change in iree_macros.cmake
        # (iree_package_ns function).

        # Map //compiler/src/iree/(.*) -> iree::\1 (i.e. iree::compiler::\1)
        m = re.match(f"^{iree_core_repo}//compiler/src/iree/(.+)", target)
        if m:
            return ["iree::" + self._convert_to_cmake_path(m.group(1))]

        # Map //runtime/src/iree/(.*) -> iree::\1
        m = re.match(f"^{iree_core_repo}//runtime/src/iree/(.+)", target)
        if m:
            return ["iree::" + self._convert_to_cmake_path(m.group(1))]

        # Map //tools/(.*) -> \1
        m = re.match(f"^{iree_core_repo}//tools[/|:](.+)", target)
        if m:
            return [self._convert_to_cmake_path(m.group(1))]

        return self._convert_unmatched_target(target)

    def _convert_unmatched_target(self, target: str) -> str:
        """Converts unmatched targets in a repo specific way."""
        raise ValueError(f"No target matching for {target}")
