# Copyright lowRISC contributors (OpenTitan project).
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

"""Class describing lint configuration object."""

from pathlib import Path

from tabulate import tabulate

from dvsim.flow.one_shot import OneShotCfg
from dvsim.logging import log
from dvsim.msg_buckets import MsgBuckets
from dvsim.utils import check_bool, subst_wildcards


class LintCfg(OneShotCfg):
    """Derivative class for linting purposes."""

    flow = "lint"

    def __init__(self, flow_cfg_file, hjson_data, args, mk_config) -> None:
        # TODO: check whether this can be replaced with the subflow concept.
        # This determines whether the flow is for a style lint run.
        # Format: bool
        self.is_style_lint = ""
        # Determines which message severities to print into report summaries
        # Format: [str, ...]
        self.report_severities = []
        # Determines which message severities lead to a pass/fail
        # Format: [str, ...]
        self.fail_severities = []
        # Message bucket format configuration
        # Format: [{category: str, severity: str,  label: str}, ...]
        self.message_buckets = []
        # This key is used as a wildcard to place an additional
        # fusesoc argument before the name of the core to invoke.
        # Format: str
        self.additional_fusesoc_argument = ""

        super().__init__(flow_cfg_file, hjson_data, args, mk_config)

        if self.is_style_lint == "":
            self.is_style_lint = False
        else:
            self.is_style_lint = check_bool(self.is_style_lint)

        # Set the title for lint results.
        if self.is_style_lint:
            self.results_title = f"{self.name.upper()} Style Lint Results"
        else:
            self.results_title = f"{self.name.upper()} Lint Results"

    def gen_results_summary(self):
        """Gathers the aggregated results from all sub configs."""
        # Generate results table for runs.
        log.info("Create summary of lint results")

        results_str = f"## {self.results_title} (Summary)\n\n"
        results_str += f"### {self.timestamp_long}\n"
        if self.revision:
            results_str += f"### {self.revision}\n"
        results_str += f"### Branch: {self.branch}\n"
        results_str += "\n"

        # Aggregate with all summaries
        self.totals = MsgBuckets(self.message_buckets)
        for cfg in self.cfgs:
            self.totals += cfg.result_summary

        # Construct Header
        labels = self.totals.get_labels(self.report_severities)
        header = ["Name", *labels]
        colalign = ("center",) * len(header)
        table = [header]

        keys = self.totals.get_keys(self.report_severities)
        for cfg in self.cfgs:
            name_with_link = cfg._get_results_page_link(self.results_dir)

            row = [name_with_link]
            row += cfg.result_summary.get_counts_md(keys)
            table.append(row)

        if len(table) > 1:
            self.results_summary_md = (
                results_str
                + tabulate(table, headers="firstrow", tablefmt="pipe", colalign=colalign)
                + "\n"
            )
        else:
            self.results_summary_md = f"{results_str}\nNo results to display.\n"

        # Return only the tables
        return self.results_summary_md

    # TODO(#9079): This way of parsing out messages into an intermediate
    # results.hjson file will be replaced by a native parser mechanism.
    def _gen_results(self, results):
        # '''
        # The function is called after the regression has completed. It looks
        # for a results.hjson file with aggregated results from the lint run.
        # The hjson needs to have the following format:
        #
        # {
        #     bucket_key: [str],
        #     // other buckets according to message_buckets configuration
        # }
        #
        # Each bucket key points to a list of signatures (strings).
        # The bucket categories and severities are defined in the
        # message_buckets class variable, and can be set via Hjson Dvsim
        # config files.
        #
        # Note that if this is a primary config, the results will
        # be generated using the _gen_results_summary function
        # '''

        # Generate results table for runs.
        results_str = f"## {self.results_title}\n\n"
        results_str += f"### {self.timestamp_long}\n"
        if self.revision:
            results_str += f"### {self.revision}\n"
        results_str += f"### Branch: {self.branch}\n"
        results_str += f"### Tool: {self.tool.upper()}\n\n"

        # Load all result files from all build modes and convert them to
        # message buckets.
        self.result = []
        self.result_summary = MsgBuckets(self.message_buckets)
        for mode in self.build_modes:
            result_path = Path(
                subst_wildcards(self.build_dir, {"build_mode": mode.name}) + "/results.hjson",
            )
            log.info("[results:hjson]: [%s]: [%s]", self.name, result_path)
            # TODO(#9079): replace this with native log parser
            msgs = MsgBuckets(self.message_buckets)
            msgs.load_hjson(result_path)
            self.result.append(msgs)
            # Aggregate with summary results
            self.result_summary += msgs

        # Construct Header
        labels = self.result_summary.get_labels(self.report_severities)
        header = ["Build Mode", *labels]
        colalign = ("center",) * len(header)
        table = [header]
        fail_msgs = ""
        self.errors_seen = 0
        keys = self.result_summary.get_keys(self.report_severities)
        for mode, res in zip(self.build_modes, self.result, strict=False):
            row = [mode.name, *res.get_counts_md(keys)]
            table.append(row)
            self.errors_seen += res.has_signatures(self.fail_severities)
            fail_msgs += f"\n### Messages for Build Mode `'{mode.name}'`\n"
            fail_msgs += res.print_signatures_md(self.report_severities, self.max_msg_count)

        if len(table) > 1:
            self.results_md = (
                results_str
                + tabulate(table, headers="firstrow", tablefmt="pipe", colalign=colalign)
                + "\n"
            )

            # Th published report will default to self.results_md if they are
            # empty. In case it needs need to be sanitized, override it and do
            # not append detailed messages.
            if self.sanitize_publish_results:
                self.publish_results_md = self.results_md
            # Locally generated result always contains all details.
            self.results_md += fail_msgs
        else:
            self.results_md = f"{results_str}\nNo results to display.\n"
            self.publish_results_md = self.results_md

        return self.results_md
