# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=invalid-name, no-member

"""Executable object for TVM Runtime"""

from collections.abc import Callable
from typing import Any

from tvm_ffi import Function

import tvm
from tvm.contrib import utils as _utils

from . import Module


class Executable:
    """The executable object generated by `tvm.compile`."""

    def __init__(self, mod: Module):
        """Initialize the Executable object."""
        self.mod: Module = mod
        self._jitted_mod: Module | None = None

    def __getitem__(self, name: str) -> Function:
        """Get the Function from the jitted module."""
        return self.jit().get_function(name, query_imports=True)

    def __call__(self, *args, **kwargs) -> Any:
        """Call the executable."""
        return self.jit().main(*args, **kwargs)

    def jit(
        self,
        *,
        fcompile: Callable[[str, list[str], dict[str, Any]], None] | None = None,
        addons: list[str] | None = None,
        force_recompile: bool = False,
        **kwargs,
    ) -> Module:
        """Just-in-time compile and link the modules.

        The Executable returned by tvm.compile may not be directly
        runnable as they may contain CUDA source files and objects that
        are yet to be compiled and linked.
        This function helps to create a runtime.Module for these cases.

        Parameters
        ----------
        fcompile : function(target, file_list, kwargs), optional
            The compilation function to use create the final library object during

        addons : list of str, optional
            Additional object files to link against.

        force_recompile : bool, optional
            If True, force a recompile of the module.

        kwargs : dict, optional
            Additional arguments passed to fcompile

        Returns
        -------
        rt_mod: tvm.runtime.Module
            A runnable runtime module that can be passed to VirtualMachine.

        Examples
        --------
        .. code:: python

            ex = tvm.compile(mod, target)
            rt_mod = ex.jit()

        """

        # If the module is already jitted and we don't want to force a recompile,
        # return the cached module
        if self._jitted_mod is not None and not force_recompile:
            return self._jitted_mod

        # TODO(tvm-team): Update runtime.Module interface
        # to query these properties as bitmask.
        def _not_runnable(x):
            return x.kind in ("c", "static_library")

        # pylint:disable = protected-access
        not_runnable_list = self.mod._collect_from_import_tree(_not_runnable)

        # everything is runnable, directly return mod.
        if len(not_runnable_list) == 0:
            return self.mod

        # found source module, or other not runnable modules need to be export and load
        # TODO(tvm-team): Support runnable but not exportable module.
        # by collecting the link and allow export_library skip those modules.
        workspace_dir = _utils.tempdir()
        dso_path = workspace_dir.relpath("exported.so")
        self.export_library(dso_path, fcompile=fcompile, addons=addons, **kwargs)
        self._jitted_mod = tvm.runtime.load_module(dso_path)
        return self._jitted_mod

    def export_library(
        self,
        file_name,
        *,
        fcompile=None,
        addons=None,
        workspace_dir=None,
        **kwargs,
    ):
        """
        Export the module and all imported modules into a single device library.

        This function only works on host LLVM modules, other runtime::Module
        subclasses will work with this API but they must support implement
        the save and load mechanisms of modules completely including saving
        from streams and files. This will pack your non-shared library module
        into a single shared library which can later be loaded by TVM.

        Parameters
        ----------
        file_name : str
            The name of the shared library.

        fcompile : function(target, file_list, kwargs), optional
            The compilation function to use create the final library object during
            export.

            For example, when fcompile=_cc.create_shared, or when it is not supplied but
            module is "llvm," this is used to link all produced artifacts
            into a final dynamic library.

            This behavior is controlled by the type of object exported.
            If fcompile has attribute object_format, will compile host library
            to that format. Otherwise, will use default format "o".

        addons : list of str, optional
            Additional object files to link against.

        workspace_dir : str, optional
            The path of the directory used to create the intermediate
            artifacts when exporting the module.
            If this is not provided a temporary dir will be created.

        kwargs : dict, optional
            Additional arguments passed to fcompile

        Returns
        -------
        result of fcompile()  : unknown, optional
            If the compilation function returns an artifact it would be returned via
            export_library, if any.
        """
        return self.mod.export_library(
            file_name,
            fcompile=fcompile,
            addons=addons,
            workspace_dir=workspace_dir,
            **kwargs,
        )
