#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2023-2024 Arm Limited.
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Generates build files for either bazel or cmake experimental builds using filelist.json
Usage
    python scripts/generate_build_files.py --bazel
    python scripts/generate_build_files.py --cmake

Writes generated file to the bazel BUILD file located under src/ if using --bazel flag.
Writes generated file to the CMake CMakeLists.txt file located under src/ if using --cmake flag.
"""

import argparse
import json
import glob


def get_operator_backend_files(filelist, operators, backend='', techs=[], attrs=[]):
    files = {"common": []}

    # Early return if filelist is empty
    if backend not in filelist:
        return files

    # Iterate over operators and create the file lists to compiler
    for operator in operators:
        if operator in filelist[backend]['operators']:
            files['common'] += filelist[backend]['operators'][operator]["files"]["common"]
            for tech in techs:
                if tech in filelist[backend]['operators'][operator]["files"]:
                    # Add tech as a key to dictionary if not there
                    if tech not in files:
                        files[tech] = []

                    # Add tech files to the tech file list
                    tech_files = filelist[backend]['operators'][operator]["files"][tech]
                    files[tech] += tech_files.get('common', [])
                    for attr in attrs:
                        files[tech] += tech_files.get(attr, [])

    # Remove duplicates if they exist
    return {k: list(set(v)) for k, v in files.items()}


def collect_operators(filelist, operators, backend=''):
    ops = set()
    for operator in operators:
        if operator in filelist[backend]['operators']:
            ops.add(operator)
            if 'deps' in filelist[backend]['operators'][operator]:
                ops.update(filelist[backend]['operators'][operator]['deps'])
        else:
            print("Operator {0} is unsupported on {1} backend!".format(
                operator, backend))

    return ops


def resolve_operator_dependencies(filelist, operators, backend=''):
    resolved_operators = collect_operators(filelist, operators, backend)

    are_ops_resolved = False
    while not are_ops_resolved:
        resolution_pass = collect_operators(
            filelist, resolved_operators, backend)
        if len(resolution_pass) != len(resolved_operators):
            resolved_operators.update(resolution_pass)
        else:
            are_ops_resolved = True

    return resolved_operators

def get_template_header():
    return """# Copyright (c) 2023-2024 Arm Limited.
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE."""

def build_from_template_bazel(srcs_graph, srcs_sve, srcs_sve2, srcs_core):

    line_separator = '",\n\t"'

    template = f"""{get_template_header()}

filegroup(
        name = "arm_compute_graph_srcs",
        srcs = ["{line_separator.join(srcs_graph)}"]  +
    glob(["**/*.h",
    "**/*.hpp",
    "**/*.inl"]),
		visibility = ["//visibility:public"]
)

filegroup(
        name = "arm_compute_sve2_srcs",
        srcs = ["{line_separator.join(srcs_sve2)}"]  +
    glob(["**/*.h",
    "**/*.hpp",
    "**/*.inl"]),
		visibility = ["//visibility:public"]
)

filegroup(
        name = "arm_compute_sve_srcs",
        srcs = ["{line_separator.join(srcs_sve)}"]  +
    glob(["**/*.h",
    "**/*.hpp",
    "**/*.inl"]),
		visibility = ["//visibility:public"]
)

filegroup(
        name = "arm_compute_srcs",
        srcs = ["{line_separator.join(srcs_core)}"]  +
    glob(["**/*.h",
    "**/*.hpp",
    "**/*.inl"]),
		visibility = ["//visibility:public"]
)
"""

    return template


def build_from_template_cmake(srcs_graph, srcs_sve, srcs_sve2, srcs_core):

    line_separator = '\n\t'

    template = f"""{get_template_header()}

target_sources(
    arm_compute_graph
    PRIVATE
    {line_separator.join(srcs_graph)}
)

target_sources(
    arm_compute_sve
    PRIVATE
    {line_separator.join(srcs_sve)}
)

target_sources(
    arm_compute_sve2
    PRIVATE
    {line_separator.join(srcs_sve2)}
)

target_sources(
    arm_compute
    PRIVATE
    {line_separator.join(srcs_core)}
)"""
    return template


def gather_sources():

    # Source file list
    with open("filelist.json") as fp:
        filelist = json.load(fp)

    # Common backend files
    lib_files = filelist['common']

    # Logging files
    lib_files += filelist['logging']

    # C API files
    lib_files += filelist['c_api']['common']
    lib_files += filelist['c_api']['operators']

    # Scheduler infrastructure
    lib_files += filelist['scheduler']['single']
    # Add both cppthreads and omp sources for now
    lib_files += filelist['scheduler']['threads']
    lib_files += filelist['scheduler']['omp']

    # Graph files
    graph_files = glob.glob('src/graph/*.cpp')
    graph_files += glob.glob('src/graph/*/*.cpp')

    lib_files_sve = []
    lib_files_sve2 = []

    # -------------------------------------
    # NEON files
    lib_files += filelist['cpu']['common']
    simd = ['neon', 'sve', 'sve2']

    # Get attributes
    data_types = ["qasymm8", "qasymm8_signed", "qsymm16",
                  "fp16", "fp32", "integer"]
    data_layouts = ["nhwc", "nchw"]
    fixed_format_kernels = ["fixed_format_kernels"]
    attrs = data_types + data_layouts + \
        fixed_format_kernels + ["estate64"]

    # Setup data-type and data-layout files to include
    cpu_operators = filelist['cpu']['operators'].keys()
    cpu_ops_to_build = resolve_operator_dependencies(
        filelist, cpu_operators, 'cpu')
    cpu_files = get_operator_backend_files(
        filelist, cpu_ops_to_build, 'cpu', simd, attrs)

    # Shared among ALL CPU files
    lib_files += cpu_files.get('common', [])

    # Arm® Neon™ specific files
    lib_files += cpu_files.get('neon', [])

    # SVE files only
    lib_files_sve = cpu_files.get('sve', [])

    # SVE2 files only
    lib_files_sve2 = cpu_files.get('sve2', [])

    graph_files += glob.glob('src/graph/backends/NEON/*.cpp')

    # -------------------------------------

    graph_files = sorted([path.replace("src/", "") for path in graph_files])
    lib_files_sve = sorted([path.replace("src/", "") for path in lib_files_sve])
    lib_files_sve2 = sorted([path.replace("src/", "") for path in lib_files_sve2])
    lib_files = sorted([path.replace("src/", "") for path in lib_files])

    return graph_files, lib_files_sve, lib_files_sve2, lib_files


if "__main__" in __name__:

    parser = argparse.ArgumentParser()
    parser.add_argument("--bazel", action="store_true")
    parser.add_argument("--cmake", action="store_true")
    args = parser.parse_args()

    graph_files, lib_files_sve, lib_files_sve2, lib_files = gather_sources()

    if args.bazel:
        # 8562a4ec: Remove CommonGraphOptions from Utils target and warnings
        graph_files += ["//utils:CommonGraphOptions.cpp"]

        bazel_build_string = build_from_template_bazel(
            graph_files, lib_files_sve, lib_files_sve2, lib_files)
        with open("src/BUILD.bazel", "w") as fp:
            fp.write(bazel_build_string)

    if args.cmake:
        cmake_build_string = build_from_template_cmake(
            graph_files, lib_files_sve, lib_files_sve2, lib_files)
        with open("src/CMakeLists.txt", "w") as fp:
            fp.write(cmake_build_string)

    if not args.cmake and not args.bazel:
        print("Supply either --bazel or --cmake flag to generate build files for corresponding build")
