# Copyright lowRISC contributors (OpenTitan project).
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
r'''
Class describing lint configuration object
'''

import logging as log
from pathlib import Path

from tabulate import tabulate

from OneShotCfg import OneShotCfg
from utils import subst_wildcards, check_bool
from MsgBuckets import MsgBuckets


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

    flow = 'lint'

    def __init__(self, flow_cfg_file, hjson_data, args, mk_config):
        # 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'

        print(self.results_summary_md)

        # 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):
            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
