# !/usr/bin/python
# CHIPSEC: Platform Security Assessment Framework
# Copyright (c) 2021, Intel Corporation
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; Version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA  02110-1301, USA.

# Contact information:
# chipsec@intel.com

"""
>>> chipsec_util config show [config] <name>

Examples:

>>> chipsec_util config show ALL
>>> chipsec_util config show [CONFIG_PCI|MEMORY_RANGES|MM_MSGBUS|MSGBUS|IO|MSR]
>>> chipsec_util config show SCOPE
"""

from argparse import ArgumentParser

from chipsec.command import BaseCommand, toLoad


class CONFIGCommand(BaseCommand):
    """Command to display configuration information."""

    # Initialize command
    def __init__(self, argv, cs):
        """Initialize the command with base class and set options."""
        super(CONFIGCommand, self).__init__(argv, cs)
        self.skip_list = ["LOCKS", "CONTROLS", "ALL"]
        try:
            # Get parent keys from scope manager
            self.all_options = (
                self.cs.Cfg.scope_manager.parent_keys +
                self.skip_list + ["SCOPE"]
            )
        except (AttributeError, TypeError):
            # Fallback if scope_manager is not available
            self.all_options = [
                "CONFIG_PCI_RAW", "CONFIG_PCI", "MEMORY_RANGES", "MM_MSGBUS",
                "MSGBUS", "IO", "MSR", "MMIO_BARS", "IO_BARS", "LOCKS",
                "CONTROLS", "ALL", "SCOPE"
            ]
        self.child_details = False  # Default to not showing child details

    # Command setup and infrastructure methods
    def requirements(self):
        """Specify resource requirements for this command."""
        return toLoad.All

    def parse_arguments(self):
        """Parse command line arguments."""
        parser = ArgumentParser(usage='chipsec_util config')
        subparsers = parser.add_subparsers()

        # Show command
        parser_show = subparsers.add_parser('show')
        parser_show.add_argument(
            'config',
            choices=self.all_options,
            default="ALL"
        )
        parser_show.add_argument(
            '-c', '--child-details',
            action='store_true',
            help='Include child details'
        )
        parser_show.set_defaults(func=self.show, config="ALL")

        parser.parse_args(self.argv, namespace=self)
        return True

    # Main display methods
    def show(self):
        """Show configuration information based on selected option."""
        if self.config == "SCOPE":
            self.scope_details()
            return

        if self.config == "ALL":
            # Exclude SCOPE from all_options when processing ALL
            config = [x for x in self.all_options if x != "SCOPE"]
            # Also show scope information when 'ALL' is selected
            self.scope_details()
        else:
            config = [self.config]

        for mconfig in config:
            if mconfig in self.skip_list:
                continue

            try:
                cfg = getattr(self.cs.Cfg, mconfig)
            except AttributeError:
                self.logger.log(f"\n{mconfig} - Not available")
                continue

            self.logger.log(f"\n{mconfig}")
            for vid in cfg.keys():
                self.logger.log(f"{vid}:")
                for name in cfg[vid].keys():
                    if mconfig in ["CONFIG_PCI_RAW"]:
                        cfg_data = cfg[vid][name]
                        self.logger.log(
                            f'\t{name} - {self.pci_details(cfg_data)}'
                        )
                    elif mconfig in ["CONFIG_PCI"]:
                        cfg_data = cfg[vid][name]
                        self.logger.log(
                            f'\t{name} - {self.pci_details(cfg_data)}'
                        )
                    elif mconfig == "MEMORY_RANGES":
                        self.logger.log(
                            f'\t{name} - {self.memory_details(cfg[vid][name])}'
                        )
                    elif mconfig in ["MM_MSGBUS", "MSGBUS", "IO"]:
                        port_details = self.get_port_details(cfg[vid][name])
                        self.logger.log(f'\t{name} - {port_details}')
                    elif mconfig in ["MMIO_BARS", "IO_BARS"]:
                        for cfg_data in cfg[vid][name]:
                            details = self.mmio_details(
                                cfg[vid][name][cfg_data]
                            )
                            self.logger.log(f'\t{cfg_data} - {details}')
                    elif mconfig == "MSR":
                        self.logger.log(
                            f'\t{name} - {self.msr_details(cfg[vid][name])}'
                        )

                    if (self.child_details and
                            mconfig not in ["CONFIG_PCI_RAW"]):
                        if mconfig not in ["MMIO_BARS", "IO_BARS"]:
                            self.get_child_details(vid, name)
                        else:
                            for cfg_data in cfg[vid][name]:
                                self.get_child_details(
                                    vid, cfg[vid][name][cfg_data].name
                                )

        if set(config).intersection({"LOCKS", "ALL"}):
            self.lock_details()
        if set(config).intersection({"CONTROLS", "ALL"}):
            self.control_details()
        return

    # Detail formatting methods for different types of registers/configurations
    def msr_details(self, regi):
        """Format MSR register details."""
        ret = f'config: {regi.config}'
        return ret

    def memory_details(self, regi):
        """Format memory range details."""
        try:
            # Try to access as dictionary first
            if isinstance(regi, dict):
                # Basic attributes always displayed
                ret = (f"access: {regi.get('access', 'N/A')}, "
                       f"address: {regi.get('address', 'N/A')}, "
                       f"size: {regi.get('size', 'N/A')}")

                # Add configuration if available
                if 'config' in regi:
                    ret += f", config: {regi['config']}"

                # BEGIN: Extended MMIO Functionality
                # Display extended MMIO attributes if available
                if 'base_ref' in regi:
                    ret += f", base_ref: {regi['base_ref']}"
                    if 'base_value' in regi:
                        ret += f" (0x{regi['base_value']:x})"

                if 'ip_ref' in regi:
                    ret += f", ip_ref: {regi['ip_ref']}"
                    if 'ip_value' in regi:
                        ret += f" (0x{regi['ip_value']:x})"

                if 'scope' in regi:
                    ret += f", scope: {regi['scope']}"
                # END: Extended MMIO Functionality

            else:
                # Fall back to attribute access if it's an object
                ret = (f"access: {getattr(regi, 'access', 'N/A')}, "
                       f"address: {getattr(regi, 'address', 'N/A')}, "
                       f"size: {getattr(regi, 'size', 'N/A')}")

                # Add configuration if available
                if hasattr(regi, 'config'):
                    ret += f", config: {regi.config}"

                # BEGIN: Extended MMIO Functionality
                # Display extended MMIO attributes if available
                if hasattr(regi, 'base_ref') and regi.base_ref:
                    ret += f", base_ref: {regi.base_ref}"
                    if hasattr(regi, 'base_value') and regi.base_value:
                        ret += f" (0x{regi.base_value:x})"

                if hasattr(regi, 'ip_ref') and regi.ip_ref:
                    ret += f", ip_ref: {regi.ip_ref}"
                    if hasattr(regi, 'ip_value') and regi.ip_value:
                        ret += f" (0x{regi.ip_value:x})"

                if hasattr(regi, 'scope') and regi.scope:
                    ret += f", scope: {regi.scope}"
                # END: Extended MMIO Functionality

        except (KeyError, AttributeError):
            # In case of any error, just return the string representation
            ret = str(regi)
        return ret

    def get_port_details(self, regi):
        """Format port details."""
        ret = ''
        if regi:
            ret = f'port: {regi.port}, config: {regi.config}'
        return ret

    def ima_details(self, regi):
        """Format IMA register details."""
        base_value = regi['base'] if 'base' in regi.keys() else None
        ret = f"index: {regi['index']}, data: {regi['data']}, " \
              f"base: {base_value}"
        return ret

    def register_details(self, regi):
        """Format register details based on type."""
        ret = ""
        if regi['type'] == 'pcicfg' or regi['type'] == 'mmcfg':
            ret = f"device: {regi['device']}, offset: {regi['offset']}, " \
                  f"size: {regi['size']}"
        elif regi['type'] == 'mmio':
            ret = f"bar: {regi['bar']}, offset: {regi['offset']}, " \
                  f"size: {regi['size']}"
        elif regi['type'] == 'mm_msgbus':
            ret = f"offset: {regi['offset']}, size: {regi['size']}"
        elif regi['type'] == 'io':
            ret = f"size: {regi['size']}"
        elif regi['type'] == 'iobar':
            ret = f"bar: {regi['bar']}, offset: {regi['offset']}, " \
                  f"size: {regi['size']}"
        elif regi['type'] == 'msr':
            if 'size' in regi:
                ret = f"msr: {regi['msr']}, size: {regi['size']}"
            else:
                ret = f"msr: {regi['msr']}"
        elif regi['type'] == 'R Byte':
            if 'size' in regi:
                ret = f"offset: {regi['offset']}, size: {regi['size']}"
            else:
                ret = f"offset: {regi['offset']}"
        elif regi['type'] == 'memory':
            ret = f"access: {regi['access']}, offset: {regi['offset']}, " \
                  f"size: {regi['size']}"

        # Add field information if available
        try:
            if isinstance(regi, (list, tuple)) and len(regi) > 0:
                reg_obj = regi[0]
                if hasattr(reg_obj, 'fields') and reg_obj.fields:
                    for key in reg_obj.fields:
                        bit = reg_obj.fields[key]['bit']
                        size = reg_obj.fields[key]['size']
                        end_bit = bit + size - 1
                        ret += f'\n\t\t\t{key} - bit {bit}:{end_bit}'
        except (IndexError, AttributeError, TypeError):
            # Silently ignore errors when accessing fields
            pass

        return ret

    def pci_details(self, regi):
        """Format PCI configuration details."""
        return str(regi)

    def mmio_details(self, regi):
        """Format MMIO details."""
        return str(regi)

    def io_details(self, regi):
        """Format IO details."""
        if 'register' in regi:
            fixed_addr = (
                regi['fixed_address'] if 'fixed_address' in regi else None
            )
            ret = (f"register: {regi['register']}, "
                   f"base_field: {regi['base_field']}, "
                   f"size: {regi['size']}, "
                   f"fixed_addr: {fixed_addr}")
        else:
            size = regi['size'] if 'size' in regi else None
            fixed_addr = (
                regi['fixed_address'] if 'fixed_address' in regi else None
            )
            ret = (f"bus: {regi['bus']}, "
                   f"dev: {regi['dev']}, "
                   f"func: {regi['fun']}, "
                   f"reg: {regi['reg']}, "
                   f"mask: {regi['mask']}, "
                   f"size: {size}, "
                   f"fixed_addr: {fixed_addr}")
        return ret

    # Data display methods for child details, controls, and locks
    def get_child_details(self, vid, dev):
        """Get and display child configuration details."""
        self.logger.log(f'get_child details {dev}')
        config = self.cs.Cfg.child_keys
        for mconfig in config:
            if mconfig in ["CONTROLS", "LOCKS", "LOCKEDBY"]:
                continue

            cfg = getattr(self.cs.Cfg, mconfig)
            self.logger.log(f"\t{mconfig}:")

            if mconfig == "IMA_REGISTERS":
                if vid in cfg and dev in cfg[vid]:
                    for name in cfg[vid][dev].keys():
                        details = self.ima_details(cfg[vid][dev][name])
                        self.logger.log(f'\t\t{name} - {details}')
            elif mconfig == "REGISTERS":
                try:
                    if vid in cfg and dev in cfg[vid]:
                        for name in cfg[vid][dev].keys():
                            details = self.register_details(
                                cfg[vid][dev][name]
                            )
                            self.logger.log(f'\t\t{name} - {details}')
                except KeyError:
                    pass
        self.logger.log("")

    def control_details(self):
        """Display control details."""
        self.logger.log("\nCONTROLS")
        try:
            cfg = getattr(self.cs.Cfg, "CONTROLS")
            for regi in cfg.keys():
                self.logger.log(f'\t{regi}')
        except AttributeError as e:
            self.logger.log(f"\tError retrieving controls: {e}")
        return

    def lock_details(self):
        """Display lock details."""
        self.logger.log("\nLOCKS")
        try:
            locks = self.cs.Cfg.get_lock_list()
            for lock in locks:
                lock_cfg = self.cs.Cfg.get_lock_obj(lock)
                self.logger.log(f'\t{lock_cfg}')
        except AttributeError as e:
            self.logger.log(f"\tError retrieving locks: {e}")
        return

    def scope_details(self):
        """Display the contents of the scope manager."""
        self.logger.log("\nSCOPE MANAGER")

        # Display parent keys
        self.logger.log("\nParent Keys:")
        for key in self.cs.Cfg.scope_manager.parent_keys:
            self.logger.log(f"\t{key}")

        # Display child keys
        self.logger.log("\nChild Keys:")
        for key in self.cs.Cfg.scope_manager.child_keys:
            self.logger.log(f"\t{key}")

        # Display current scope
        self.logger.log("\nCurrent Scope:")
        for scope_key, scope_value in self.cs.Cfg.scope.items():
            key_name = scope_key if scope_key is not None else "DEFAULT"
            self.logger.log(f"\t{key_name}: {scope_value}")

        # BEGIN: Extended MMIO Functionality
        # Display memory bases if available
        if hasattr(self.cs.Cfg, 'memory_bases') and self.cs.Cfg.memory_bases:
            self.logger.log("\nMemory Bases:")
            for base_name, base_value in self.cs.Cfg.memory_bases.items():
                self.logger.log(f"\t{base_name}: 0x{base_value:x}")

        # Display IP addresses if available
        if hasattr(self.cs.Cfg, 'ip_addresses') and self.cs.Cfg.ip_addresses:
            self.logger.log("\nIP Addresses:")
            for ip_name, ip_value in self.cs.Cfg.ip_addresses.items():
                self.logger.log(f"\t{ip_name}: 0x{ip_value:x}")
        # END: Extended MMIO Functionality

        # Display available scopes in the configuration
        self.logger.log("\nAvailable Configuration Items:")

        # Go through parent keys and display available items
        for parent_key in self.cs.Cfg.scope_manager.parent_keys:
            if hasattr(self.cs.Cfg, parent_key):
                cfg_data = getattr(self.cs.Cfg, parent_key)
                if cfg_data:
                    self.logger.log(f"\n{parent_key}:")
                    for vid in cfg_data.keys():
                        self.logger.log(f"\t{vid}:")
                        if isinstance(cfg_data[vid], dict):
                            # Limit to first 10 items for readability
                            items = list(cfg_data[vid].keys())
                            for name in items[:10]:
                                self.logger.log(f"\t\t{name}")
                            if len(items) > 10:
                                remaining = len(items) - 10
                                self.logger.log(
                                    f"\t\t... ({remaining} more items)"
                                )
        return


commands = {'config': CONFIGCommand}
