# Copyright (C) 2023 The Android Open Source Project
#
# Licensed 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.

load("@bazel_skylib//lib:paths.bzl", "paths")
load(":cc_library_common.bzl", "create_ccinfo_for_includes")
load(":cc_library_static.bzl", "cc_library_static")

# Return the relative directory where the cpp files should be generated
def _cpp_gen_dir(ctx):
    return ctx.attr.name

# Return the relative directory where the h files should be generated
def _h_gen_dir(ctx):
    return paths.join(
        _cpp_gen_dir(ctx),
        "include",
    )

def _cc_xsdc_args(ctx):
    args = ctx.actions.args()
    args.add(ctx.file.src)
    args.add("-p", ctx.attr.package_name)
    args.add("-c")  # Generate cpp sources

    if ctx.attr.gen_writer:
        args.add("-w")

    if ctx.attr.enums_only:
        args.add("-e")

    if ctx.attr.parser_only:
        # parser includes the enum.h file so generate both
        # in the wrapper cc_static_library, we will only include the .o file of the parser
        pass

    if ctx.attr.boolean_getter:
        args.add("-b")

    if ctx.attr.tinyxml:
        args.add("-t")

    for root_element in ctx.attr.root_elements:
        args.add("-r", root_element)

    return args

# Returns a tuple of cpp and h filenames that should be generated
def _cc_xsdc_outputs(ctx):
    filename_stem = ctx.attr.package_name.replace(".", "_")
    parser_cpp = filename_stem + ".cpp"
    parser_h = filename_stem + ".h"
    enums_cpp = filename_stem + "_enums.cpp"
    enums_h = filename_stem + "_enums.h"
    if ctx.attr.parser_only:
        # parser_cpp includes enums_h, so we need to return both .h files
        return [parser_cpp], [parser_h, enums_h]
    elif ctx.attr.enums_only:
        return [enums_cpp], [enums_h]

    # Default: Generate both parser and enums
    return [parser_cpp, enums_cpp], [parser_h, enums_h]

def _cc_xsd_codegen_impl(ctx):
    outputs_cpp, outputs_h = [], []
    cpp_filenames, h_filenames = _cc_xsdc_outputs(ctx)

    # Declare the cpp files
    for cpp_filename in cpp_filenames:
        outputs_cpp.append(
            ctx.actions.declare_file(
                paths.join(
                    _cpp_gen_dir(ctx),
                    cpp_filename,
                ),
            ),
        )

    # Declare the h files
    for h_filename in h_filenames:
        outputs_h.append(
            ctx.actions.declare_file(
                paths.join(
                    _h_gen_dir(ctx),
                    h_filename,
                ),
            ),
        )

    args = _cc_xsdc_args(ctx)

    # Pass the output directory
    args.add("-o", outputs_cpp[0].dirname)

    ctx.actions.run(
        executable = ctx.executable._xsdc,
        inputs = [ctx.file.src] + ctx.files.include_files,
        outputs = outputs_cpp + outputs_h,
        arguments = [args],
        mnemonic = "XsdcCppCompile",
    )

    return [
        DefaultInfo(
            # Return the CPP files.
            # Skip headers since rdep does not compile headers.
            files = depset(outputs_cpp),
        ),
        create_ccinfo_for_includes(
            ctx,
            hdrs = outputs_h,
            includes = [_h_gen_dir(ctx)],
        ),
    ]

_cc_xsd_codegen = rule(
    implementation = _cc_xsd_codegen_impl,
    doc = "This rule generates .cpp/.h files from an xsd file using xsdc",
    attrs = {
        "src": attr.label(
            allow_single_file = [".xsd"],
            doc = "The main xsd file",
            mandatory = True,
        ),
        "include_files": attr.label_list(
            allow_files = [".xsd"],
            doc = "The (transitive) xsd files included by `src` using xs:include",
        ),
        "package_name": attr.string(
            doc = "Namespace to use in the generated .cpp file",
            mandatory = True,
        ),
        "gen_writer": attr.bool(
            doc = "Add xml writer to the generated .cpp file",
        ),
        "enums_only": attr.bool(),
        "parser_only": attr.bool(),
        "boolean_getter": attr.bool(
            doc = "Whether getter name of boolean element or attribute is getX or isX. If true, getter name is isX",
            default = False,
        ),
        "tinyxml": attr.bool(
            doc = "Generate code that uses libtinyxml2 instead of libxml2",
            default = False,
        ),
        "root_elements": attr.string_list(
            doc = "If set, xsdc will generate parser code only for the specified root elements",
        ),
        "_xsdc": attr.label(
            executable = True,
            cfg = "exec",
            default = Label("//system/tools/xsdc"),
        ),
    },
    provides = [
        CcInfo,
    ],
)

def cc_xsd_config_library(
        name,
        src,
        include_files = [],
        package_name = "",
        gen_writer = False,
        enums_only = False,
        parser_only = False,
        boolean_getter = False,
        tinyxml = False,
        root_elements = [],
        deps = [],
        implementation_dynamic_deps = [],
        **kwargs):
    """
    Generate .cpp/.h sources from .xsd file using xsdc and wrap it in a cc_static_library.

    """
    _gen = name + "_gen"

    _cc_xsd_codegen(
        name = _gen,
        src = src,
        include_files = include_files,
        package_name = package_name,
        gen_writer = gen_writer,
        enums_only = enums_only,
        parser_only = parser_only,
        boolean_getter = boolean_getter,
        tinyxml = tinyxml,
        root_elements = root_elements,
        **kwargs
    )

    cc_library_static(
        name = name,
        srcs = [_gen],
        deps = deps + [_gen],  # Generated hdrs
        implementation_dynamic_deps = implementation_dynamic_deps,
        **kwargs
    )