## Copyright 2022 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
"""Helpers that build CMake rules.

Each function takes a list of parameters and returns a string ready to be
included in a CMakeLists.txt file. Builder functions handle optional arguments,
lists, formatting, etc.

For example:

build_iree_fetch_artifact(
    target_name="abcd",
    source_url="https://example.com/abcd.tflite",
    output="./abcd.tflite",
    unpack=True)

Outputs:

iree_fetch_artifact(
  NAME
    "abcd"
  SOURCE_URL
    "https://example.com/abcd.tflite"
  OUTPUT
    "./abcd.tflite"
  UNPACK
)
"""

from typing import Dict, List, Optional, Sequence

INDENT_SPACES = " " * 2


def _get_string_list(values: Sequence[str], quote: bool = True) -> List[str]:
    if quote:
        return [f'"{value}"' for value in values]
    return list(values)


def _get_block_body(body: List[str]) -> List[str]:
    return [INDENT_SPACES + line for line in body]


def _get_string_arg_block(
    keyword: str, value: Optional[str], quote: bool = True
) -> List[str]:
    if value is None:
        return []
    if quote:
        value = f'"{value}"'
    return [f"{keyword} {value}"]


def _get_string_list_arg_block(
    keyword: str, values: Sequence[str], quote: bool = True
) -> List[str]:
    if len(values) == 0:
        return []
    body = _get_string_list(values, quote)
    return [keyword] + _get_block_body(body)


def _get_option_arg_block(keyword: str, value: Optional[bool]) -> List[str]:
    if value is True:
        return [keyword]
    return []


def _build_call_rule(
    rule_name: str, parameter_blocks: Sequence[List[str]]
) -> List[str]:
    output = [f"{rule_name}("]
    for block in parameter_blocks:
        if len(block) == 0:
            continue
        output.extend(_get_block_body(block))
    output.append(")")
    return output


def _convert_block_to_string(block: List[str]) -> str:
    # Hack to append the terminating newline and only copies the list instead of
    # the whole string.
    return "\n".join(block + [""])


def build_iree_bytecode_module(
    target_name: str,
    src: str,
    module_name: str,
    flags: List[str] = [],
    compile_tool_target: Optional[str] = None,
    c_identifier: Optional[str] = None,
    static_lib_path: Optional[str] = None,
    deps: List[str] = [],
    friendly_name: Optional[str] = None,
    testonly: bool = False,
    public: bool = True,
) -> str:
    name_block = _get_string_arg_block("NAME", target_name)
    src_block = _get_string_arg_block("SRC", src)
    module_name_block = _get_string_arg_block("MODULE_FILE_NAME", module_name)
    c_identifier_block = _get_string_arg_block("C_IDENTIFIER", c_identifier)
    static_lib_block = _get_string_arg_block("STATIC_LIB_PATH", static_lib_path)
    compile_tool_target_block = _get_string_arg_block(
        "COMPILE_TOOL", compile_tool_target
    )
    flags_block = _get_string_list_arg_block("FLAGS", flags)
    deps_block = _get_string_list_arg_block("DEPS", deps)
    friendly_name_block = _get_string_arg_block("FRIENDLY_NAME", friendly_name)
    testonly_block = _get_option_arg_block("TESTONLY", testonly)
    public_block = _get_option_arg_block("PUBLIC", public)
    return _convert_block_to_string(
        _build_call_rule(
            rule_name="iree_bytecode_module",
            parameter_blocks=[
                name_block,
                src_block,
                module_name_block,
                c_identifier_block,
                compile_tool_target_block,
                static_lib_block,
                flags_block,
                friendly_name_block,
                deps_block,
                testonly_block,
                public_block,
            ],
        )
    )


def build_iree_fetch_artifact(
    target_name: str, source_url: str, output: str, unpack: bool
) -> str:
    name_block = _get_string_arg_block("NAME", target_name)
    source_url_block = _get_string_arg_block("SOURCE_URL", source_url)
    output_block = _get_string_arg_block("OUTPUT", output)
    unpack_block = _get_option_arg_block("UNPACK", unpack)
    return _convert_block_to_string(
        _build_call_rule(
            rule_name="iree_fetch_artifact",
            parameter_blocks=[name_block, source_url_block, output_block, unpack_block],
        )
    )


def build_iree_import_tf_model(
    target_path: str, source: str, import_flags: List[str], output_mlir_file: str
) -> str:
    target_name_block = _get_string_arg_block("TARGET_NAME", target_path)
    source_block = _get_string_arg_block("SOURCE", source)
    import_flags_block = _get_string_list_arg_block("IMPORT_FLAGS", import_flags)
    output_mlir_file_block = _get_string_arg_block("OUTPUT_MLIR_FILE", output_mlir_file)
    return _convert_block_to_string(
        _build_call_rule(
            rule_name="iree_import_tf_model",
            parameter_blocks=[
                target_name_block,
                source_block,
                import_flags_block,
                output_mlir_file_block,
            ],
        )
    )


def build_iree_import_tflite_model(
    target_path: str, source: str, import_flags: List[str], output_mlir_file: str
) -> str:
    target_name_block = _get_string_arg_block("TARGET_NAME", target_path)
    source_block = _get_string_arg_block("SOURCE", source)
    import_flags_block = _get_string_list_arg_block("IMPORT_FLAGS", import_flags)
    output_mlir_file_block = _get_string_arg_block("OUTPUT_MLIR_FILE", output_mlir_file)
    return _convert_block_to_string(
        _build_call_rule(
            rule_name="iree_import_tflite_model",
            parameter_blocks=[
                target_name_block,
                source_block,
                import_flags_block,
                output_mlir_file_block,
            ],
        )
    )


def build_iree_benchmark_suite_module_test(
    target_name: str,
    driver: str,
    expected_output: str,
    platform_module_map: Dict[str, str],
    runner_args: Sequence[str],
    timeout_secs: Optional[int] = None,
    labels: Sequence[str] = [],
    xfail_platforms: Sequence[str] = [],
) -> str:
    name_block = _get_string_arg_block("NAME", target_name)
    driver_block = _get_string_arg_block("DRIVER", driver)
    expected_output_block = _get_string_arg_block("EXPECTED_OUTPUT", expected_output)
    modules_block = _get_string_list_arg_block(
        "MODULES",
        [f"{platform}={path}" for platform, path in platform_module_map.items()],
    )
    timeout_block = _get_string_arg_block(
        "TIMEOUT", str(timeout_secs) if timeout_secs is not None else None
    )
    runner_args_block = _get_string_list_arg_block("RUNNER_ARGS", runner_args)
    labels_block = _get_string_list_arg_block("LABELS", labels)
    xfail_platforms_block = _get_string_list_arg_block(
        "XFAIL_PLATFORMS", xfail_platforms
    )
    return _convert_block_to_string(
        _build_call_rule(
            rule_name="iree_benchmark_suite_module_test",
            parameter_blocks=[
                name_block,
                driver_block,
                expected_output_block,
                timeout_block,
                modules_block,
                runner_args_block,
                labels_block,
                xfail_platforms_block,
            ],
        )
    )


def build_add_dependencies(target: str, deps: List[str]) -> str:
    if len(deps) == 0:
        raise ValueError("Target dependencies can't be empty.")
    deps_list = _get_string_list(deps, quote=False)
    return _convert_block_to_string(
        [f"add_dependencies({target}"] + _get_block_body(deps_list) + [")"]
    )


def build_set(variable_name: str, value: str) -> str:
    return _convert_block_to_string(
        [f"set({variable_name}"] + _get_block_body([value]) + [")"]
    )
