load("@rules_cc//cc:defs.bzl", "cc_binary")
load("@rules_python//python:defs.bzl", "py_library")

# Rules for building Everest modules

def cc_everest_module(
    name,
    srcs = [],
    deps = [],
    impls = [],
    data = [],
    includes = [],
    **cc_binary_kwargs
):
    """
    Define C++ Everest module.

    Args:
        name: Name of the module.
        srcs: List of source files, required files such as implemntations of 
            interfaces and the module main file are automatically included.
        deps: List of dependencies. Libraries that are required to build the 
            module.
        impls: List of implementations that the module has. It should match the
            content of mainifest.yaml file.
        data: List of data files that should be available at runtime.
    """
    impl_srcs = []
    for impl in impls:
        impl_srcs += native.glob(["{}/*.cpp".format(impl), "{}/*.hpp".format(impl)])

    module_srcs = [
        name + ".cpp",
        name + ".hpp",
    ]

    binary = name + "__binary"
    manifest = native.glob(["manifest.y*ml"], allow_empty = False)[0]

    prefix = native.package_name().removeprefix("modules/")

    native.genrule(
        name = "ld-ev",
        outs = [
            "generated/modules/{}/ld-ev.hpp".format(name),
            "generated/modules/{}/ld-ev.cpp".format(name),
        ],
        srcs = [
            manifest,
            "//lib/everest/framework/schemas:schemas",
            "//lib/everest/framework:dependencies.yaml",
            "@everest-core//types:types",
            "@everest-core//:MODULE.bazel",
            "@everest-core//interfaces:interfaces",
        ],
        tools = [
            "//applications/utils/ev-dev-tools:ev-cli",
        ],
        cmd = """
    $(location //applications/utils/ev-dev-tools:ev-cli) module generate-loader \
        --work-dir `dirname $(location @everest-core//:MODULE.bazel)` \
        --schemas-dir `dirname $(location //lib/everest/framework:dependencies.yaml)`/schemas \
        --disable-clang-format \
        --output-dir `dirname $(location generated/modules/{module_name}/ld-ev.hpp)`/.. \
        {prefix}
    """.format(module_name = name, prefix = prefix)
    )

    cc_binary(
        name = binary,
        srcs = depset(srcs + impl_srcs + module_srcs + [
            ":ld-ev",
        ]).to_list(),
        deps = deps + [
            "@everest-core//interfaces:interfaces_lib",
            "//lib/everest/framework:framework",
        ],
        data = data,  # Pass through data files to the binary
        copts = ["-std=c++17"],
        includes = [
            ".",
            "generated/modules/" + name,
        ] + includes,
        visibility = ["//visibility:public"],
        # See https://github.com/HowardHinnant/date/issues/324
        local_defines = [
            "BUILD_TZ_LIB=ON",
            "USE_SYSTEM_TZ_DB=ON",
            "USE_OS_TZDB=1",
            "USE_AUTOLOAD=0",
            "HAS_REMOTE_API=0",
        ],
        **cc_binary_kwargs,
    )

    native.genrule(
        name = "copy_to_subdir",
        srcs = [":" + binary, manifest],
        outs = [
            "{}/manifest.yaml".format(name),
            "{}/{}".format(name, name),
        ],
        cmd = "mkdir -p $(RULEDIR)/{} && ".format(name) +
              "cp $(location {}) $(RULEDIR)/{}/{} && ".format(binary, name, name) +
              "cp $(location {}) $(RULEDIR)/{}/".format(manifest, name),
    )

    native.filegroup(
        name = name,
        srcs = [
            ":copy_to_subdir",
        ] + data,  # Include data files in the filegroup
        data = [":" + binary],  # Include the binary to get its runfiles
        visibility = ["//visibility:public"],
    )

def py_everest_module(
        name,
        deps = [],
        interfaces_deps = [],
        types_deps = [],
        visibility = None):
    """Creates a Python-based Module for EVerest.

    Package where this macro is used should contain a directory with the same name as the module.
    All Python Files from that folder would be included in the module.
    The `manifest.yaml` file from that module would be used as manifest for the module.
    """
    py_library(
        name = name,
        srcs = native.glob(["**/*.py"]),
        data = ["manifest.yaml"] + interfaces_deps + types_deps,
        deps = deps + ["@everest-core//lib/everest/framework/everestpy/src:framework"],
        visibility = ["//visibility:public"],
    )
    native.filegroup(
        name = "{}_bundle".format(name),
        srcs = [
            ":{}".format(name),
            "manifest.yaml".format(name)
        ],
        visibility = ["//visibility:public"],
    )