#
# Copyright (c) 2023-2025, Arm Limited. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#

from anytree import RenderTree
from anytree.importer import DictImporter
from prettytable import PrettyTable


class TfaPrettyPrinter:
    """A class for printing the memory layout of ELF files.

    This class provides interfaces for printing various memory layout views of
    ELF files in a TF-A build. It can be used to understand how the memory is
    structured and consumed.
    """

    def __init__(self, columns: int = None, as_decimal: bool = False):
        self.term_size = columns if columns and columns > 120 else 120
        self._tree = None
        self._footprint = None
        self._symbol_map = None
        self.as_decimal = as_decimal

    def format_args(self, *args, width=10, fmt=None):
        if not fmt and type(args[0]) is int:
            fmt = f">{width}x" if not self.as_decimal else f">{width}"
        return [f"{arg:{fmt}}" if fmt else arg for arg in args]

    def format_row(self, leading, *args, width=10, fmt=None):
        formatted_args = self.format_args(*args, width=width, fmt=fmt)
        return leading + " ".join(formatted_args)

    @staticmethod
    def map_elf_symbol(
        leading: str,
        section_name: str,
        rel_pos: int,
        columns: int,
        width: int = None,
        is_edge: bool = False,
    ):
        empty_col = "{:{}{}}"

        # Some symbols are longer than the column width, truncate them until
        # we find a more elegant way to display them!
        len_over = len(section_name) - width
        if len_over > 0:
            section_name = section_name[len_over:-len_over]

        sec_row = f"+{section_name:-^{width-1}}+"
        sep, fill = ("+", "-") if is_edge else ("|", "")

        sec_row_l = empty_col.format(sep, fill + "<", width) * rel_pos
        sec_row_r = empty_col.format(sep, fill + ">", width) * (
            columns - rel_pos - 1
        )

        return leading + sec_row_l + sec_row + sec_row_r

    def print_footprint(
        self, app_mem_usage: dict, sort_key: str = None, fields: list = None
    ):
        assert len(app_mem_usage), "Empty memory layout dictionary!"
        if not fields:
            fields = ["Component", "Start", "Limit", "Size", "Free", "Total"]

        sort_key = fields[0] if not sort_key else sort_key

        # Iterate through all the memory types, create a table for each
        # type, rows represent a single module.
        for mem in sorted(set(k for _, v in app_mem_usage.items() for k in v)):
            table = PrettyTable(
                sortby=sort_key,
                title=f"Memory Usage (bytes) [{mem.upper()}]",
                field_names=fields,
            )

            for mod, vals in app_mem_usage.items():
                if mem in vals.keys():
                    val = vals[mem]
                    table.add_row(
                        [
                            mod.upper(),
                            *self.format_args(
                                *[val[k.lower()] for k in fields[1:]]
                            ),
                        ]
                    )
            print(table, "\n")

    def print_symbol_table(
        self,
        symbols: list,
        modules: list,
        start: int = 12,
    ):
        assert len(symbols), "Empty symbol list!"
        modules = sorted(modules)
        col_width = int((self.term_size - start) / len(modules))
        address_fixed_width = 11

        num_fmt = (
            f"0=#0{address_fixed_width}x" if not self.as_decimal else ">10"
        )

        _symbol_map = [
            " " * start
            + "".join(self.format_args(*modules, fmt=f"^{col_width}"))
        ]
        last_addr = None

        for i, (name, addr, mod) in enumerate(symbols):
            # Do not print out an address twice if two symbols overlap,
            # for example, at the end of one region and start of another.
            leading = (
                f"{addr:{num_fmt}}" + " " if addr != last_addr else " " * start
            )

            _symbol_map.append(
                self.map_elf_symbol(
                    leading,
                    name,
                    modules.index(mod),
                    len(modules),
                    width=col_width,
                    is_edge=(not i or i == len(symbols) - 1),
                )
            )

            last_addr = addr

        self._symbol_map = ["Memory Layout:"]
        self._symbol_map += list(reversed(_symbol_map))
        print("\n".join(self._symbol_map))

    def print_mem_tree(
        self, mem_map_dict, modules, depth=1, min_pad=12, node_right_pad=12
    ):
        # Start column should have some padding between itself and its data
        # values.
        anchor = min_pad + node_right_pad * (depth - 1)
        headers = ["start", "end", "size"]

        self._tree = [
            (f"{'name':<{anchor}}" + " ".join(f"{arg:>10}" for arg in headers))
        ]

        for mod in sorted(modules):
            root = DictImporter().import_(mem_map_dict[mod])
            for pre, fill, node in RenderTree(root, maxlevel=depth):
                leading = f"{pre}{node.name}".ljust(anchor)
                self._tree.append(
                    self.format_row(
                        leading,
                        node.start,
                        node.end,
                        node.size,
                    )
                )
        print("\n".join(self._tree), "\n")
