#
# Copyright (c) 2004-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# LicenseRef-NvidiaProprietary
#
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
# property and proprietary rights in and to this material, related
# documentation and any modifications thereto. Any use, reproduction,
# disclosure or distribution of this material and related documentation
# without an express license agreement from NVIDIA CORPORATION or
# its affiliates is strictly prohibited
#

from mlxlink_multiplane_classes import *

INVALID_PORT_STR = 'Invalid port number!'
BM_ASIC_NUM = 4


def inject_str_into_colored_print(print_statement, new_word):
    col_index = print_statement.find(':')
    color_indexes = [i for i in range(col_index + 1, len(print_statement)) if print_statement[i] == 'm']
    escape_indexes = [i for i in range(col_index + 1, len(print_statement)) if print_statement[i] == '\x1b']
    if len(color_indexes) != 2 or len(escape_indexes) != 2:
        return None
    return print_statement[:color_indexes[0] + 1] + new_word + print_statement[escape_indexes[1]:]


def color_change(print_statement, new_color):
    if print_statement:
        current_color = None
        for key, value in COLOR_DICT.items():
            if value in print_statement:
                current_color = key
        return print_statement.replace(COLOR_DICT.get(current_color), COLOR_DICT.get(new_color)) if current_color is not None else add_color_to_str(print_statement, new_color)
    return None


def extract_value_from_row(print_row):
    index = print_row.find(':')
    return print_row[index + 2:]


def strip_string_from_color(colored_str):
    return colored_str[colored_str.find('m') + 1: colored_str.find('\x1b[0m')]


def check_all_values_available(values_lst):
    for val in values_lst:
        if 'N/A' in val:
            return False
    return True


def print_show_module_per_asic(module_info):
    output = []
    for attr in module_info.get_all_attrs_names():
        attr_info = module_info.get_attr(attr)
        if attr_info:
            output.append(attr_info)
    return '\n'.join(output)


class AggragetedParser():

    def __init__(self, mlxlink_output):
        self.output = mlxlink_output
        self.parsed_output = {}
        self.unique_commands_lst = []

    def get_operational_info(self):
        sections = [OperationalInfo(), SupportedInfo(), TroubleshootingInfo(), ToolInfo()]

        for section in sections:
            if section.NAME in self.output:
                section.parse(self.output)
        return sections

    def get_section(self, section):
        if section.NAME in self.output:
            section.parse(self.output)
        return section

    def get_unique_cmd_lst(self):
        self.parse_unique_commands()  # TODO: find a better place for this calling
        return self.unique_commands_lst

    def parse_unique_commands(self):
        u_cmd = UniqueCommands()
        for attr in u_cmd.get_all_attrs_names():
            line = u_cmd.find_attr_line(attr, self.output)
            if line:
                self.unique_commands_lst.append(line)

    def parse_mlxlink_output(self):
        self.parsed_output['Operational info'] = self.get_operational_info()
        self.parsed_output['Module info'] = self.get_section(ModuleInfo())
        self.parsed_output['Counters info'] = self.get_section(CountersInfo())
        self.parsed_output['Eye opening info'] = self.get_section(EyeOpeningInfo())
        self.parsed_output['Serdes tx info'] = self.get_section(SerdesTxInfo())
        self.parsed_output['General device info'] = self.get_section(GeneralDeviceInfo())
        self.parsed_output['Fec histogram info'] = self.get_section(ShowFecHistogram())
        self.parsed_output['Fec capabilities info'] = self.get_section(FecCapabilitiesInfo())
        self.parsed_output['Test Mode Info'] = self.get_section(TestModeInfo())
        self.parsed_output['Cable Read Output'] = self.get_section(CableRead())
        self.parsed_output['PLR Info'] = self.get_section(ShowPlr())
        self.parsed_output['KR Info'] = self.get_section(ShowKr())
        self.parsed_output['Unique Commands'] = self.get_unique_cmd_lst()

        return self.parsed_output


class AggragetedPrinter():

    def __init__(self, mlxlink_output, system_type):
        self.output = mlxlink_output
        self.warnings = WarningsHandler('yellow')
        self.special_errors = ErrorHandler('red')
        self.asics_down = []
        self.asics_up = []
        self.system_type = system_type

    def print_operational_info_section(self, oper_info_lst):
        relevance_lst = [sec.is_relevant_section() for sec in oper_info_lst]
        if all(False == rl for rl in relevance_lst):
            return

        asic_num = len(oper_info_lst)
        if asic_num == 0:
            raise Exception("E - Operational info section was not found!")

        oper_info_lst[0].print_title()

        # State section
        state_lst = [section.get_attr('State') for section in oper_info_lst]
        if all(state_lst[0] == st for st in state_lst):
            print(state_lst[0])
        else:
            if any('Active' in st for st in state_lst):
                for i in range(len(state_lst)):
                    if 'Active' not in state_lst[i]:
                        self.asics_down.append(str(i))
                    else:
                        self.asics_up.append(i)
                print(color_change(state_lst[self.asics_up[0]].replace('Active', 'Partly Active, ASIC(s) {} is down.'.format(','.join(self.asics_down))), 'yellow'))
            else:
                print((state_lst[0] + ' , ' + ', '.join([extract_value_from_row(st) for st in state_lst[1:]])).replace('N/A', color_change('N/A', 'red')))

        # Physical state section
        phy_state_lst = [section.get_attr('Physical state') for section in oper_info_lst]
        if all(phy_state_lst[0] == st for st in phy_state_lst):
            print(phy_state_lst[0])
        else:
            print(phy_state_lst[0][:phy_state_lst[0].find(':') + 1], add_color_to_str('Link Down. ASIC(s) {} is down.'.format(', '.join(self.asics_down)), 'red'))

        # Speed section
        speed_lst = [section.get_attr('Speed') for section in oper_info_lst]

        if all(speed_lst[0] == st for st in speed_lst):
            if 'N/A' in speed_lst[0]:
                print(speed_lst[0])
            else:
                print(speed_lst[0][:-1] + add_color_to_str('_X{}'.format(asic_num), 'green'))
        else:
            self.warnings.add_msg("Speed is not the same across all ASICs!")
            print((speed_lst[0] + ' , ' + ', '.join([extract_value_from_row(st) for st in speed_lst[1:]])).replace('N/A', color_change('N/A', 'red')))

        # Width section
        width_lst = [section.get_attr('Width') for section in oper_info_lst]
        if all(width_lst[0] == sec for sec in width_lst[1:]):
            print(width_lst[0].replace('1x', str(asic_num) + 'x'))
        else:
            self.warnings.add_msg("Width is not the same across all ASICs!")
            print((width_lst[0] + ' , ' + ', '.join([extract_value_from_row(st) for st in width_lst[1:]])).replace('N/A', color_change('N/A', 'red')))

        # FEC section
        fec_lst = [section.get_attr('FEC') for section in oper_info_lst]
        if all(fec_lst[0] == sec for sec in fec_lst[1:]):
            print(fec_lst[0])
        else:
            self.warnings.add_msg("FEC is not the same across all ASICs!")
            print((fec_lst[0] + ' , ' + ', '.join([extract_value_from_row(st) for st in fec_lst[1:]])).replace('N/A', color_change('N/A', 'red')))

        # Loopback Mode section
        Loopback_lst = [section.get_attr('Loopback Mode') for section in oper_info_lst]
        if all(Loopback_lst[0] == sec for sec in Loopback_lst[1:]):
            print(Loopback_lst[0])
        else:
            self.warnings.add_msg("Loopback Mode is not the same across all ASICs!")
            print(Loopback_lst[0][:-1] + ' , ' + ', '.join([extract_value_from_row(st) for st in Loopback_lst[1:]]))

        # Auto Negotiation section
        an_lst = [section.get_attr('Auto Negotiation') for section in oper_info_lst]
        if all(an_lst[0] == sec for sec in an_lst[1:]):
            print(an_lst[0])
        else:
            self.warnings.add_msg("Auto Negotiation is not the same across all ASICs!")
            print(an_lst[0][:-1] + ' , ' + ', '.join([extract_value_from_row(st) for st in an_lst[1:]]))

        print()  # print a new line at the end of each section

    def print_supported_info_section(self, support_info_lst):
        relevance_lst = [sec.is_relevant_section() for sec in support_info_lst]
        if all(False == rl for rl in relevance_lst):
            return

        support_info_lst[0].print_title()

        # Enabled Link Speed section
        en_link_speed_lst = [section.get_attr('Enabled Link Speed') for section in support_info_lst]
        if all(en_link_speed_lst[0] == sec for sec in en_link_speed_lst[1:]):
            print(en_link_speed_lst[0])
        else:
            print(en_link_speed_lst[0][:-1] + ' , ' + ', '.join([extract_value_from_row(st) for st in en_link_speed_lst[1:]]))
            self.warnings.add_msg("Enabled Link Speed is not the same across all ASICs!")

        # Supported Cable Speed section
        cable_speed_lst = [section.get_attr('Supported Cable Speed') for section in support_info_lst]
        if all(cable_speed_lst[0] == sec for sec in cable_speed_lst[1:]):
            print(cable_speed_lst[0])
        else:
            print(cable_speed_lst[0][:-1] + ' , ' + ', '.join([extract_value_from_row(st) for st in cable_speed_lst[1:]]))
            self.warnings.add_msg("Supported Cable Speed is not the same across all ASICs!")

        print()  # print a new line at the end of each section

    def print_troubleshooting_info_section(self, troubleshooting_Info):
        relevance_lst = [sec.is_relevant_section() for sec in troubleshooting_Info]
        if all(False == rl for rl in relevance_lst):
            return

        troubleshooting_Info[0].print_title()

        # Status Opcode section
        status_opcode_lst = [section.get_attr('Status Opcode') for section in troubleshooting_Info]
        print(status_opcode_lst[0][:-1], ', ', ','.join([extract_value_from_row(st) for st in status_opcode_lst[1:]]), sep='')

        # Group Opcode section
        group_opcode_lst = [section.get_attr('Group Opcode') for section in troubleshooting_Info]
        if all('N/A' in sec for sec in group_opcode_lst):
            print(group_opcode_lst[0])
        else:
            print(group_opcode_lst[0][:-1], ', ', ','.join([extract_value_from_row(st) for st in group_opcode_lst[1:]]), sep='')

        # Recommendation section
        recommendation_lst = [section.get_attr('Recommendation') for section in troubleshooting_Info]
        if all('No issue was observed' in rec for rec in recommendation_lst):
            print(recommendation_lst[0])
        else:
            for rec in recommendation_lst:
                if 'No issue was observed' not in rec:
                    print(rec)
                    break

        # Time to Link Up section
        time_lst = [section.get_attr('Time to Link Up') for section in troubleshooting_Info]
        offline_asic = not check_all_values_available(time_lst)
        if offline_asic or '' in time_lst:
            for t in time_lst:
                if 'N/A' in t:
                    print(t)
                    break
        else:
            time_num_lst = [float(strip_string_from_color(extract_value_from_row(t).replace('sec', '')).strip()) for t in time_lst]
            print(inject_str_into_colored_print(time_lst[0], str(max(time_num_lst)) + ' sec'))

        print()  # print a new line at the end of each section

    def print_tool_info_section(self, tool_info):
        relevance_lst = [sec.is_relevant_section() for sec in tool_info]
        if all(False == rl for rl in relevance_lst):
            return

        tool_info[0].print_title()

        # Firmware Version section
        fw_ver_lst = [section.get_attr('Firmware Version') for section in tool_info]
        if all(fw_ver_lst[0] == ver for ver in fw_ver_lst[1:]):
            print(fw_ver_lst[0])
        else:
            print(fw_ver_lst[0][:-1], ', ', ','.join([extract_value_from_row(st) for st in fw_ver_lst[1:]]), sep='')

        # amBER Version section
        print(tool_info[0].get_attr('amBER Version'))

        # MFT Version section
        print(tool_info[0].get_attr('MFT Version'))

        print()  # print a new line at the end of each section

    def print_operational_info(self, oper_info_gen_lst):
        if len(oper_info_gen_lst) != BM_ASIC_NUM:
            raise Exception('E - Operational info is missing (should be {} ASICs)'.format(BM_ASIC_NUM))

        self.print_operational_info_section([obj[0] for obj in oper_info_gen_lst])
        self.print_supported_info_section([obj[1] for obj in oper_info_gen_lst])
        self.print_troubleshooting_info_section([obj[2] for obj in oper_info_gen_lst])
        self.print_tool_info_section([obj[3] for obj in oper_info_gen_lst])

    def print_show_counters(self, counters_info):
        if len(counters_info) == 0:
            raise Exception("E - Show Counters section was not found!")

        relevance_lst = [sec.is_relevant_section() for sec in counters_info]
        if all(False == rl for rl in relevance_lst):
            return

        counters_info[0].print_title()

        # Time Since Last Clear section
        last_clear = [section.get_attr('Time Since Last Clear [Min]') for section in counters_info]
        values_are_missing = not check_all_values_available(last_clear)
        if values_are_missing:
            print(last_clear[0], ', ', ', '.join([extract_value_from_row(st) for st in last_clear[1:]]), sep='')
        else:
            last_clear_val = [float(strip_string_from_color(extract_value_from_row(l)).strip()) for l in last_clear]
            if all(lc > 60 for lc in last_clear_val):
                print(last_clear[0][:last_clear[0].find(':') + 2] + str(max(last_clear_val)))
            else:
                clear_differences = []
                for i in range(len(counters_info)):
                    clear_differences.append([abs(float(extract_value_from_row(last_clear[i])) - float(extract_value_from_row(last_clear[j]))) for j in range(len(last_clear)) if i != j])
                present_diff = False
                for dif_lst in clear_differences:
                    if not all(dl < 2 for dl in dif_lst):
                        present_diff = True
                        break
                if present_diff:
                    self.warnings.add_msg("Time since last clear difference between ASICs is greater than 2 min!")
                    print(last_clear[0], ', ', ', '.join([extract_value_from_row(st) for st in last_clear[1:]]), sep='')
                else:
                    print(last_clear[0][:last_clear[0].find(':') + 1], str(max(last_clear_val)))

        # Symbol Errors section
        sym_err = [section.get_attr('Symbol Errors') for section in counters_info]
        print(sym_err[0], ', ', ', '.join([extract_value_from_row(st) for st in sym_err[1:]]), sep='')

        # Symbol BER section
        sym_ber = [section.get_attr('Symbol BER') for section in counters_info]
        print(sym_ber[0], ', ', ', '.join([extract_value_from_row(st) for st in sym_ber[1:]]), sep='')

        # Effective Physical Errors
        eff_phy_err = [section.get_attr('Effective Physical Errors') for section in counters_info]
        print(eff_phy_err[0], ', ', ', '.join([extract_value_from_row(st) for st in eff_phy_err[1:]]), sep='')

        # Effective Physical BER
        eff_phy_ber = [section.get_attr('Effective Physical BER') for section in counters_info]
        print(eff_phy_ber[0], ', ', ', '.join([extract_value_from_row(st) for st in eff_phy_ber[1:]]), sep='')

        # Raw Physical Errors Per Lane
        raw_phy_err = [section.get_attr('Raw Physical Errors Per Lane') for section in counters_info]
        print(raw_phy_err[0], ', ', ', '.join([extract_value_from_row(st) for st in raw_phy_err[1:]]), sep='')

        # Raw Physical BER Per Lane
        raw_phy_ber = [section.get_attr('Raw Physical BER Per Lane') for section in counters_info]
        if not all(rfb == '' for rfb in raw_phy_ber):
            print(raw_phy_ber[0], ', ', ', '.join([extract_value_from_row(st) for st in raw_phy_ber[1:]]), sep='')

        # Raw Physical BER
        raw_phy_ber_sum = [section.get_attr('Raw Physical BER') for section in counters_info]
        print(raw_phy_ber_sum[0], ', ', ', '.join([extract_value_from_row(st) for st in raw_phy_ber_sum[1:]]), sep='')

        # Link Down Counter
        down_counter = [section.get_attr('Link Down Counter') for section in counters_info]
        if values_are_missing:
            for counter in down_counter:
                if 'N/A' in counter:
                    print(counter)
                    break
        else:
            down_counter_val = [int(str(extract_value_from_row(l)).strip()) for l in down_counter]
            print(down_counter[0][:down_counter[0].find(':') + 2] + str(sum(down_counter_val)))

        # Link Error Recovery Counter
        rec_counter = [section.get_attr('Link Error Recovery Counter') for section in counters_info]
        if values_are_missing:
            for counter in rec_counter:
                if 'N/A' in counter:
                    print(counter)
                    break
        else:
            rec_counter_val = [int(str(extract_value_from_row(l)).strip()) for l in rec_counter]
            print(rec_counter[0][:rec_counter[0].find(':') + 2] + str(sum(rec_counter_val)))

        print()  # print a new line at the end of each section

    def print_general_device_info(self, gen_device):
        if len(gen_device) == 0:
            raise Exception("E - Show general device info section was not found!")

        relevance_lst = [sec.is_relevant_section() for sec in gen_device]
        if all(False == rl for rl in relevance_lst):
            return

        gen_device[0].print_title()

        # Part Number section
        part_num = [section.get_attr('Part Number') for section in gen_device]
        if all(part_num[0] == p for p in part_num[1:]):
            print(part_num[0])
        else:  # a different ASICs with different part numbers
            print(part_num[0], ',', ','.join([extract_value_from_row(st) for st in part_num[1:]]), sep='')

        # Part Name section
        part_name = [section.get_attr('Part Name') for section in gen_device]
        if all(part_name[0] == p for p in part_name[1:]):
            print(part_name[0])
        else:  # a different ASICs with different part names
            print(part_name[0], ',', ','.join([extract_value_from_row(st) for st in part_name[1:]]), sep='')

        # Serial Number section
        serial_num = [section.get_attr('Serial Number') for section in gen_device]
        if all(serial_num[0] == p for p in serial_num[1:]):
            print(serial_num[0])
        else:
            print(serial_num[0], ',', ','.join([extract_value_from_row(st) for st in serial_num[1:]]), sep='')

        # Revision section
        rev_num = [section.get_attr('Revision') for section in gen_device]
        if all(rev_num[0] == p for p in rev_num[1:]):
            print(rev_num[0])
        else:
            print(rev_num[0], ',', ','.join([extract_value_from_row(st) for st in rev_num[1:]]), sep='')

        # FW Version section
        fw_ver = [section.get_attr('FW Version') for section in gen_device]
        if all(fw_ver[0] == p for p in fw_ver[1:]):
            print(fw_ver[0])
        else:  # a different ASICs with different fw versions
            self.warnings.add_msg("FW Versions is not the same across all ASICs!")
            print(fw_ver[0][:-1] + ' , ' + ', '.join([extract_value_from_row(st) for st in fw_ver[1:]]))

        print()  # print a new line at the end of each section

    def print_show_module(self, module_info):
        if len(module_info) == 0:
            raise Exception("E - Show module info section was not found!")

        relevance_lst = [sec.is_relevant_section() for sec in module_info]
        if all(False == rl for rl in relevance_lst):
            return

        for i in range(len(module_info)):
            to_print = print_show_module_per_asic(module_info[i])
            if not self.system_type:
                module_info[i].print_title()
                print(to_print)
                break
            module_info[i].print_title(' ASIC {}'.format(i))
            print(to_print)

            print()  # print a new line between each ASIC's data

    def print_show_eye_opening(self, eye_opening_info):
        if len(eye_opening_info) == 0:
            raise Exception("E - Show eye opening info section was not found!")

        relevance_lst = [sec.is_relevant_section() for sec in eye_opening_info]
        if all(False == rl for rl in relevance_lst):
            return
        values_to_print_lst = [eye_opening_info[0].get_attr('FOM Mode'), eye_opening_info[0].get_attr('Lane')]

        # print(eye_opening_info[0].get_attr('FOM Mode'))  # print the title for the eye values

        attr_names_lst = eye_opening_info[0].get_all_attrs_names()
        values_are_missing = False
        for i in range(2, len(attr_names_lst)):  # TODO: add try except for case the values are not numbers
            attr_lst = [section.get_attr(attr_names_lst[i]) for section in eye_opening_info]
            if not values_are_missing:
                values_are_missing = not check_all_values_available(attr_lst)

            if values_are_missing:
                break
            attr_val_lst = [int(str(extract_value_from_row(a)).strip()) for a in attr_lst]
            values_to_print_lst.append(attr_lst[0][:attr_lst[0].find(':') + 2] + str(sum(attr_val_lst)))

        if values_are_missing:
            self.special_errors.add_msg("Show eye could not be displayed. At least one ASIC is down!")
        else:
            eye_opening_info[0].print_title()
            print('\n'.join(values_to_print_lst))

        print()  # print a new line at the end of each section

    def print_show_serdes_tx(self, serdes_tx):
        if len(serdes_tx) == 0:
            raise Exception("E - Show serdes tx info section was not found!")

        relevance_lst = [sec.is_relevant_section() for sec in serdes_tx]
        if all(False == rl for rl in relevance_lst):
            return

        serdes_tx[0].print_title()

        print(serdes_tx[0].get_attr('Serdes TX parameters'))  # print the title for the serdes tx values

        for asic_num in range(BM_ASIC_NUM):
            print(serdes_tx[asic_num].get_attr('Lane 0').replace('Lane 0', 'Lane {}'.format(asic_num)))
        # attr_names_lst = serdes_tx[0].get_all_attrs_names()
        # go over all the lanes
        # for i in range(1, len(attr_names_lst)):
        #     attr_lst = [section.get_attr(attr_names_lst[i]) for section in serdes_tx]
        #     attr_val_lst = [str(extract_value_from_row(a)).strip() for a in attr_lst]
        #     attr_val_lst = [sec.split(',') for sec in attr_val_lst]

        #     sum_lst = []
        #     for i in range(len(serdes_tx[0].get_attr('Serdes TX parameters').split(','))):
        #         sum_lst.append(sum(int(attr_val_lst[j][i].strip()) for j in range(BM_ASIC_NUM)))

        #     print(attr_names_lst)
        #     exit(1)
        #     print(attr_names_lst[i][:attr_names_lst[i].find(':')] + ' ' + ', '.join(sum_lst))

        # TODO: TBD how to print the fields in this section.

        print()  # print a new line at the end of each section

    def print_show_fec_info(self, show_fec):
        relevance_lst = [sec.is_relevant_section() for sec in show_fec]
        if all(False == rl for rl in relevance_lst):
            return

        show_fec[0].print_title()

        print(show_fec[0].get_attr('Header'))  # print the Header for the fec histogram values
        alignment_index = show_fec[0].get_attr('Header').find('O')

        for hist_bin in [elm for elm in show_fec[0].get_all_attrs_names() if 'Bin ' in elm]:
            prefix = show_fec[0].get_attr(hist_bin) + ' , '
            suffix = []
            for i in range(1, BM_ASIC_NUM):
                bin_tmp = show_fec[i].get_attr(hist_bin)
                suffix.append(bin_tmp[alignment_index:].strip())

            print(prefix + ', '.join(suffix))

        print()  # print a new line at the end of each section

    def print_test_mode_info(self, test_mode_sec):

        relevance_lst = [sec.is_relevant_section() for sec in test_mode_sec]
        if all(False == rl for rl in relevance_lst):
            return

        test_mode_sec[0].print_title()

        # RX PRBS Mode section
        rx_mode = [section.get_attr('RX PRBS Mode') for section in test_mode_sec]
        print((rx_mode[0] + ' , ' + ', '.join([extract_value_from_row(st) for st in rx_mode[1:]])).replace('N/A', color_change('N/A', 'red')))

        # TX PRBS Mode section
        tx_mode = [section.get_attr('TX PRBS Mode') for section in test_mode_sec]
        print((tx_mode[0] + ' , ' + ', '.join([extract_value_from_row(st) for st in tx_mode[1:]])).replace('N/A', color_change('N/A', 'red')))

        # RX Lane Rate section
        rx_lane_rate = [section.get_attr('RX Lane Rate') for section in test_mode_sec]
        print((rx_lane_rate[0] + ' , ' + ', '.join([extract_value_from_row(st) for st in rx_lane_rate[1:]])).replace('N/A', color_change('N/A', 'red')))

        # TX Lane Rate section
        tx_lane_rate = [section.get_attr('TX Lane Rate') for section in test_mode_sec]
        print((tx_lane_rate[0] + ' , ' + ', '.join([extract_value_from_row(st) for st in tx_lane_rate[1:]])).replace('N/A', color_change('N/A', 'red')))

        # Tuning Status section
        tuning_status = [section.get_attr('Tuning Status') for section in test_mode_sec]
        print((tuning_status[0] + ' , ' + ', '.join([extract_value_from_row(st) for st in tuning_status[1:]])).replace('N/A', color_change('N/A', 'red')))

        # Lock Status section
        lock_status = [section.get_attr('Lock Status') for section in test_mode_sec]
        print((lock_status[0] + ' / ' + '/ '.join([extract_value_from_row(st) for st in lock_status[1:]])).replace('N/A', color_change('N/A', 'red')))

        print()  # print a new line at the end of each section

    def print_fec_cap(self, fec_caps, error_lst):
        if len(fec_caps) == 0:
            raise Exception("E - Show fec section was not found!")

        relevance_lst = [sec.is_relevant_section() for sec in fec_caps]
        if all(False == rl for rl in relevance_lst):
            fec_error_str = 'FEC information is not available for InfiniBand protocol'
            if fec_error_str in error_lst:
                self.warnings.add_msg(fec_error_str)
            return

        fec_caps[0].print_title()

        for attr in fec_caps[0].get_all_attrs_names():
            attr_lst = [section.get_attr(attr) for section in fec_caps if section.get_attr(attr)]
            if attr_lst:
                if all(attr_lst[0] == at for at in attr_lst):
                    print(attr_lst[0])
                else:
                    self.warnings.add_msg("Fec capabilities is not the same across all ASICs!")

        print()  # print a new line at the end of each section

    def print_show_plr(self, plr_lst):
        if len(plr_lst) == 0:
            raise Exception("E - Show plr section was not found!")

        relevance_lst = [sec.is_relevant_section() for sec in plr_lst]
        if all(False == rl for rl in relevance_lst):
            return

        plr_lst[0].print_title()

        # PLR Reject Mode section
        plr_reject_mode = [section.get_attr('PLR Reject Mode') for section in plr_lst]
        print(plr_reject_mode[0], ', ', ', '.join([extract_value_from_row(prm) for prm in plr_reject_mode[1:]]), sep='')

        # PLR Margin Threshold section
        plr_margin_threshold = [section.get_attr('PLR Margin Threshold') for section in plr_lst]
        print(plr_margin_threshold[0], ', ', ', '.join([extract_value_from_row(pmt) for pmt in plr_margin_threshold[1:]]), sep='')

        print()  # print a new line at the end of each section

    def print_show_kr(self, kr_lst):
        if len(kr_lst) == 0:
            raise Exception("E - Show kr section was not found!")

        relevance_lst = [sec.is_relevant_section() for sec in kr_lst]
        if all(False == rl for rl in relevance_lst):
            return

        kr_lst[0].print_title()

        # Support Non-Standard Training Flow section
        support_non_standard_training_flow = [section.get_attr('Support Non-Standard Training Flow') for section in kr_lst]
        print(support_non_standard_training_flow[0], ', ', ', '.join([extract_value_from_row(snstf) for snstf in support_non_standard_training_flow[1:]]), sep='')

        # Number of Iterations section
        number_of_iterations = [section.get_attr('Number of Iterations') for section in kr_lst]
        print(number_of_iterations[0], ', ', ', '.join([extract_value_from_row(noi) for noi in number_of_iterations[1:]]), sep='')
        # Time for Iteration section
        time_for_iteration = [section.get_attr('Time for Iteration') for section in kr_lst]
        print(time_for_iteration[0], ', ', ', '.join([extract_value_from_row(tfi) for tfi in time_for_iteration[1:]]), sep='')

        # BER Target section
        ber_target = [section.get_attr('BER Target') for section in kr_lst]
        print(ber_target[0], ', ', ', '.join([extract_value_from_row(bt) for bt in ber_target[1:]]), sep='')

        print()  # print a new line at the end of each section

    def print_cable_operations(self, cable_opers):
        if len(cable_opers) == 0:
            raise Exception("E - Cable read section was not found!")

        relevance_lst = [sec.is_relevant_section() for sec in cable_opers]
        if all(False == rl for rl in relevance_lst):
            return

        for i in range(len(cable_opers)):
            cable_opers[i].print_title(' ASIC {}'.format(i))
            print('\n'.join(cable_opers[i].get_data_lst()))

            print()  # print a new line between each ASIC's data

        print()  # print a new line at the end of each section

    def print_unique_cmd(self, unique_cmd_lst):
        if unique_cmd_lst:
            print('\n'.join(unique_cmd_lst))

    def print_parsed_output(self, error_lst):
        self.print_operational_info([obj.get('Operational info') for obj in self.output])
        self.print_show_module([obj.get('Module info') for obj in self.output])
        self.print_show_counters([obj.get('Counters info') for obj in self.output])
        self.print_show_eye_opening([obj.get('Eye opening info') for obj in self.output])
        self.print_show_serdes_tx([obj.get('Serdes tx info') for obj in self.output])
        self.print_general_device_info([obj.get('General device info') for obj in self.output])
        self.print_show_fec_info([obj.get('Fec histogram info') for obj in self.output])
        self.print_test_mode_info([obj.get('Test Mode Info') for obj in self.output])
        self.print_fec_cap([obj.get('Fec capabilities info') for obj in self.output], error_lst)
        self.print_cable_operations([obj.get('Cable Read Output') for obj in self.output])
        self.print_show_plr([obj.get('PLR Info') for obj in self.output])
        self.print_show_kr([obj.get('KR Info') for obj in self.output])
        self.print_unique_cmd(self.output[0].get('Unique Commands'))
        self.special_errors.display_msg()
        self.warnings.display_msg()


def check_unauthorized_flags(arguments_lst):
    """ Add unauthorized flags in here.
    """
    return any("--show_tx_group_map" in arg for arg in arguments_lst)

# mlxlink_arguments_lst: arguments list
# devices_lst: [(ASIC#, split#)]
# rcs_lst: rc per ASIC (same ASIC order as devices)
# mlxlink_parsed_output_lst: parsed output for all ASICs
# error_lst: error msg per ASIC
# system_type: system type (BM \ CPO system)


def multiplane_aggregator(mlxlink_arguments_lst, devices_lst, rcs_lst, mlxlink_parsed_output_lst, error_lst, system_type):
    if mlxlink_arguments_lst is None or devices_lst is None or mlxlink_parsed_output_lst is None:
        raise Exception("E - missing parameters!")

    # screen unauthorized commands
    if check_unauthorized_flags(mlxlink_arguments_lst):
        raise Exception(color_change("\n-E- failed to parse arguments. Bad input parameter\n", "red"))

    # check if the command was successfully excuted on all ASICs
    aggregated_rc = all(rc == 0 for rc in rcs_lst)
    if aggregated_rc:
        printer = AggragetedPrinter(mlxlink_parsed_output_lst, system_type)
        printer.print_parsed_output(error_lst)
        return

    # at least one command failed, check why it failed
    # check invalid port status
    invalid_port_status = [INVALID_PORT_STR in asic_out for asic_out in mlxlink_parsed_output_lst]
    failed_num = invalid_port_status.count(True)
    if failed_num == len(devices_lst) - 1:
        # TODO: present the only ASIC that wasn't failed because of invalid port as the only output (no need to parse)
        return

    # more than one of the ASICs failed with non-invalid port error, which means it wasn't a command unique to an ASIC
    # TODO: call the aggrageted parser.
    pass
