#!/usr/bin/env python3
# Copyright 2015-2016, Tresys Technology, LLC
#
# SPDX-License-Identifier: GPL-2.0-only
#

import setools
import argparse
import sys
import logging
import signal
import warnings
from itertools import chain
from contextlib import suppress


signal.signal(signal.SIGPIPE, signal.SIG_DFL)

parser = argparse.ArgumentParser(
    description="SELinux policy semantic difference tool.",
    epilog="If no differences are selected, all differences will be printed.")
parser.add_argument("POLICY1", help="Path to the first SELinux policy to diff.", nargs=1)
parser.add_argument("POLICY2", help="Path to the second SELinux policy to diff.", nargs=1)
parser.add_argument("--version", action="version", version=setools.__version__)
parser.add_argument("--stats", action="store_true", help="Display only statistics.")
parser.add_argument("-v", "--verbose", action="store_true",
                    help="Print extra informational messages")
parser.add_argument("--debug", action="store_true", dest="debug", help="Enable debugging.")

comp = parser.add_argument_group("component differences")
comp.add_argument("--common", action="store_true", help="Print common differences")
comp.add_argument("-c", "--class", action="store_true", help="Print class differences",
                  dest="class_")
comp.add_argument("-t", "--type", action="store_true", help="Print type differences",
                  dest="type_")
comp.add_argument("-a", "--attribute", action="store_true", help="Print type attribute differences")
comp.add_argument("-r", "--role", action="store_true", help="Print role differences")
comp.add_argument("-u", "--user", action="store_true", help="Print user differences")
comp.add_argument("-b", "--bool", action="store_true", help="Print Boolean differences",
                  dest="bool_")
comp.add_argument("--sensitivity", action="store_true", help="Print MLS sensitivity differences")
comp.add_argument("--category", action="store_true", help="Print MLS category differences")
comp.add_argument("--level", action="store_true", help="Print MLS level definition differences")

terule = parser.add_argument_group("type enforcement rule differences")
terule.add_argument("-A", action="store_true", help="Print allow and allowxperm rule differences")
terule.add_argument("--allow", action="store_true", help="Print allow rule differences")
# terule.add_argument("--neverallow", action="store_true", help="Print neverallow rule differences")
terule.add_argument("--auditallow", action="store_true", help="Print auditallow rule differences")
terule.add_argument("--dontaudit", action="store_true", help="Print dontaudit rule differences")
terule.add_argument("--allowxperm", action="store_true", help="Print allowxperm rule differences")
# terule.add_argument("--neverallowxperm", action="store_true",
#                     help="Print neverallowxperm rule differences")
terule.add_argument("--auditallowxperm", action="store_true",
                    help="Print auditallowxperm rule differences")
terule.add_argument("--dontauditxperm", action="store_true",
                    help="Print dontauditxperm rule differences")
terule.add_argument("-T", "--type_trans", action="store_true",
                    help="Print type_transition rule differences")
terule.add_argument("--type_change", action="store_true", help="Print type_change rule differences")
terule.add_argument("--type_member", action="store_true",
                    help="Print type_member rule differences")

rbacrule = parser.add_argument_group("RBAC rule differences")
rbacrule.add_argument("--role_allow", action="store_true", help="Print role allow rule differences")
rbacrule.add_argument("--role_trans", action="store_true",
                      help="Print role_transition rule differences")

mlsrule = parser.add_argument_group("MLS rule differences")
mlsrule.add_argument("--range_trans", action="store_true",
                     help="Print range_transition rule differences")

constrain = parser.add_argument_group("Constraint differences")
constrain.add_argument("--constrain", action="store_true", help="Print constrain differences")
constrain.add_argument("--mlsconstrain", action="store_true", help="Print mlsconstrain differences")
constrain.add_argument("--validatetrans", action="store_true",
                       help="Print validatetrans differences")
constrain.add_argument("--mlsvalidatetrans", action="store_true",
                       help="Print mlsvalidatetrans differences")

labeling = parser.add_argument_group("labeling statement differences")
labeling.add_argument("--ibendportcon", action="store_true", help="Print ibendportcon differences")
labeling.add_argument("--ibpkeycon", action="store_true", help="Print ibkeycon differences")
labeling.add_argument("--initialsid", action="store_true", help="Print initial SID differences")
labeling.add_argument("--fs_use", action="store_true", help="Print fs_use_* differences")
labeling.add_argument("--genfscon", action="store_true", help="Print genfscon differences")
labeling.add_argument("--netifcon", action="store_true", help="Print netifcon differences")
labeling.add_argument("--nodecon", action="store_true", help="Print nodecon differences")
labeling.add_argument("--portcon", action="store_true", help="Print portcon differences")

other = parser.add_argument_group("other differences")
other.add_argument("--default", action="store_true", help="Print default_* differences")
other.add_argument("--property", action="store_true",
                   help="Print policy property differences (handle_unknown, version, MLS)")
other.add_argument("--polcap", action="store_true", help="Print policy capability differences")
other.add_argument("--typebounds", action="store_true", help="Print typebounds differences")

args = parser.parse_args()

# neverallow and neverallowxperm options are disabled
args.neverallow = False
args.neverallowxperm = False

if args.A:
    args.allow = True
    args.allowxperm = True

all_differences = not any((args.class_, args.common, args.type_, args.attribute, args.role,
                           args.user, args.bool_, args.sensitivity, args.category, args.level,
                           args.allow, args.neverallow, args.auditallow, args.dontaudit,
                           args.type_trans, args.type_change, args.type_member, args.role_allow,
                           args.role_trans, args.range_trans, args.initialsid, args.genfscon,
                           args.netifcon, args.nodecon, args.portcon, args.fs_use, args.polcap,
                           args.property, args.default, args.constrain, args.mlsconstrain,
                           args.validatetrans, args.mlsvalidatetrans, args.typebounds,
                           args.allowxperm, args.neverallowxperm, args.auditallowxperm,
                           args.dontauditxperm, args.ibendportcon, args.ibpkeycon))

if args.debug:
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s|%(levelname)s|%(name)s|%(message)s')
    if not sys.warnoptions:
        warnings.simplefilter("default")
elif args.verbose:
    logging.basicConfig(level=logging.INFO, format='%(message)s')
    if not sys.warnoptions:
        warnings.simplefilter("default")
else:
    logging.basicConfig(level=logging.WARNING, format='%(message)s')
    if not sys.warnoptions:
        warnings.simplefilter("ignore")

try:
    p1 = setools.SELinuxPolicy(args.POLICY1[0])
    p2 = setools.SELinuxPolicy(args.POLICY2[0])
    diff = setools.PolicyDifference(p1, p2)

    perms: list[str]

    if all_differences or args.property:
        if diff.modified_properties or args.property:
            print(f"Policy Properties ({len(diff.modified_properties)} Modified)")
            if not args.stats:
                for prop in sorted(diff.modified_properties):
                    print(f"      * {prop.property} +{prop.added} -{prop.removed}")
            print()
            del diff.modified_properties

    if all_differences or args.polcap:
        if diff.added_polcaps or diff.removed_polcaps or args.polcap:
            na = len(diff.added_polcaps)
            nr = len(diff.removed_polcaps)
            print(f"Policy Capabilities ({na} Added, {nr} Removed)")
            if diff.added_polcaps and not args.stats:
                print(f"   Added Policy Capabilities: {na}")
                for cap in sorted(diff.added_polcaps):
                    print(f"      + {cap}")
            if diff.removed_polcaps and not args.stats:
                print(f"   Removed Policy Capabilities: {nr}")
                for cap in sorted(diff.removed_polcaps):
                    print(f"      - {cap}")

            print()
            del diff.added_polcaps
            del diff.removed_polcaps

    if all_differences or args.common:
        if diff.added_commons or diff.removed_commons or diff.modified_commons or args.common:
            na = len(diff.added_commons)
            nr = len(diff.removed_commons)
            nm = len(diff.modified_commons)
            print(f"Commons ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_commons and not args.stats:
                print(f"   Added Commons: {na}")
                for c in sorted(diff.added_commons):
                    print(f"      + {c}")
            if diff.removed_commons and not args.stats:
                print(f"   Removed Commons: {nr}")
                for c in sorted(diff.removed_commons):
                    print(f"      - {c}")
            if diff.modified_commons and not args.stats:
                print(f"   Modified Commons: {nm}")
                for com in sorted(diff.modified_commons):
                    change = []
                    if com.added_perms:
                        change.append(f"{len(com.added_perms)} Added permissions")
                    if com.removed_perms:
                        change.append(f"{len(com.removed_perms)} Removed permissions")

                    print(f"      * {com.common.name} ({', '.join(change)})")
                    for p in sorted(com.added_perms):
                        print(f"          + {p}")
                    for p in sorted(com.removed_perms):
                        print(f"          - {p}")
            print()
            del diff.added_commons
            del diff.removed_commons
            del diff.modified_commons

    if all_differences or args.class_:
        if diff.added_classes or diff.removed_classes or diff.modified_classes or args.class_:
            na = len(diff.added_classes)
            nr = len(diff.removed_classes)
            nm = len(diff.modified_classes)
            print(f"Classes ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_classes and not args.stats:
                print(f"   Added Classes: {na}")
                for cls in sorted(diff.added_classes):
                    print(f"      + {cls}")
            if diff.removed_classes and not args.stats:
                print(f"   Removed Classes: {nr}")
                for cls in sorted(diff.removed_classes):
                    print(f"      - {cls}")
            if diff.modified_classes and not args.stats:
                print(f"   Modified Classes: {nm}")
                for mcls in sorted(diff.modified_classes):
                    change = []
                    if mcls.added_perms:
                        change.append(f"{len(mcls.added_perms)} Added permissions")
                    if mcls.removed_perms:
                        change.append(f"{len(mcls.removed_perms)} Removed permissions")

                    print(f"      * {mcls.class_.name} ({', '.join(change)})")
                    for p in sorted(mcls.added_perms):
                        print(f"          + {p}")
                    for p in sorted(mcls.removed_perms):
                        print(f"          - {p}")
            print()
            del diff.added_classes
            del diff.removed_classes
            del diff.modified_classes

    if all_differences or args.default:
        if diff.added_defaults or diff.removed_defaults or diff.modified_defaults or args.default:
            na = len(diff.added_defaults)
            nr = len(diff.removed_defaults)
            nm = len(diff.modified_defaults)
            print(f"Defaults ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_defaults and not args.stats:
                print(f"   Added Defaults: {na}")
                for dflt in sorted(diff.added_defaults):
                    print(f"      + {dflt}")
            if diff.removed_defaults and not args.stats:
                print(f"   Removed Defaults: {nr}")
                for dflt in sorted(diff.removed_defaults):
                    print(f"      - {dflt}")
            if diff.modified_defaults and not args.stats:
                print(f"   Modified Defaults: {nm}")
                for mdflt in sorted(diff.modified_defaults):
                    line = f"      * {mdflt.rule.ruletype} {mdflt.rule.tclass} "
                    if mdflt.removed_default:
                        line += f"+{mdflt.added_default} -{mdflt.removed_default}"
                    else:
                        line += mdflt.rule.default.name

                    if mdflt.removed_default_range:
                        line += f" +{mdflt.added_default_range} -{mdflt.removed_default_range};"
                    else:
                        try:
                            line += f" {mdflt.rule.default_range};"  # type: ignore
                        except AttributeError:
                            line += ";"
                    print(line)

            print()
            del diff.added_defaults
            del diff.removed_defaults
            del diff.modified_defaults

    if all_differences or args.bool_:
        if diff.added_booleans or diff.removed_booleans or \
                diff.modified_booleans or args.bool_:
            na = len(diff.added_booleans)
            nr = len(diff.removed_booleans)
            nm = len(diff.modified_booleans)
            print(f"Booleans ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_booleans and not args.stats:
                print(f"   Added Booleans: {na}")
                for a in sorted(diff.added_booleans):
                    print(f"      + {a}")
            if diff.removed_booleans and not args.stats:
                print(f"   Removed Booleans: {nr}")
                for a in sorted(diff.removed_booleans):
                    print(f"      - {a}")
            if diff.modified_booleans and not args.stats:
                print(f"   Modified Booleans: {nm}")
                for bool_ in sorted(diff.modified_booleans):
                    print(f"      * {bool_.boolean.name} (Modified default state)")
                    print(f"          + {bool_.added_state}")
                    print(f"          - {bool_.removed_state}")

            print()
            del diff.added_booleans
            del diff.removed_booleans
            del diff.modified_booleans

    if all_differences or args.role:
        if diff.added_roles or diff.removed_roles or diff.modified_roles or args.role:
            na = len(diff.added_roles)
            nr = len(diff.removed_roles)
            nm = len(diff.modified_roles)
            print(f"Roles ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_roles and not args.stats:
                print(f"   Added Roles: {na}")
                for role in sorted(diff.added_roles):
                    print(f"      + {role}")
            if diff.removed_roles and not args.stats:
                print(f"   Removed Roles: {nr}")
                for role in sorted(diff.removed_roles):
                    print(f"      - {role}")
            if diff.modified_roles and not args.stats:
                print(f"   Modified Roles: {nm}")
                for mrole in sorted(diff.modified_roles):
                    change = []
                    if mrole.added_types:
                        change.append(f"{len(mrole.added_types)} Added types")
                    if mrole.removed_types:
                        change.append(f"{len(mrole.removed_types)} Removed types")

                    print(f"      * {mrole.role.name} ({', '.join(change)})")
                    for type_ in sorted(mrole.added_types):
                        print(f"          + {type_}")
                    for type_ in sorted(mrole.removed_types):
                        print(f"          - {type_}")
            print()
            del diff.added_roles
            del diff.removed_roles
            del diff.modified_roles

    if all_differences or args.type_:
        if diff.added_types or diff.removed_types or diff.modified_types or args.type_:
            na = len(diff.added_types)
            nr = len(diff.removed_types)
            nm = len(diff.modified_types)
            print(f"Types ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_types and not args.stats:
                print(f"   Added Types: {na}")
                for type_ in sorted(diff.added_types):
                    print(f"      + {type_}")
            if diff.removed_types and not args.stats:
                print(f"   Removed Types: {nr}")
                for type_ in sorted(diff.removed_types):
                    print(f"      - {type_}")
            if diff.modified_types and not args.stats:
                print(f"   Modified Types: {nm}")
                for mtype in sorted(diff.modified_types):
                    change = []
                    if mtype.added_attributes:
                        change.append(f"{len(mtype.added_attributes)} Added attributes")
                    if mtype.removed_attributes:
                        change.append(f"{len(mtype.removed_attributes)} Removed attributes")
                    if mtype.added_aliases:
                        change.append(f"{len(mtype.added_aliases)} Added aliases")
                    if mtype.removed_aliases:
                        change.append(f"{len(mtype.removed_aliases)} Removed aliases")
                    if mtype.modified_permissive:
                        if mtype.permissive:
                            change.append("Removed permissive")
                        else:
                            change.append("Added permissive")

                    print(f"      * {mtype.type_.name} ({','.join(change)})")
                    if mtype.added_attributes or mtype.removed_attributes:
                        print("          Attributes:")
                    for attr in sorted(mtype.added_attributes):
                        print(f"          + {attr}")
                    for attr in sorted(mtype.removed_attributes):
                        print(f"          - {attr}")

                    if mtype.added_aliases or mtype.removed_aliases:
                        print("          Aliases:")
                    for alias in sorted(mtype.added_aliases):
                        print(f"          + {alias}")
                    for alias in sorted(mtype.removed_aliases):
                        print(f"          - {alias}")

            print()
            del diff.added_types
            del diff.removed_types
            del diff.modified_types

    if all_differences or args.typebounds:
        if diff.added_typebounds or diff.removed_typebounds or diff.modified_typebounds \
                or args.typebounds:
            na = len(diff.added_typebounds)
            nr = len(diff.removed_typebounds)
            nm = len(diff.modified_typebounds)
            print(f"Typebounds ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_typebounds and not args.stats:
                print(f"   Added Typebounds: {na}")
                for tb in sorted(diff.added_typebounds):
                    print(f"      + {tb}")
            if diff.removed_typebounds and not args.stats:
                print(f"   Removed Typebounds: {nr}")
                for tb in sorted(diff.removed_typebounds):
                    print(f"      - {tb}")
            if diff.modified_typebounds and not args.stats:
                print(f"   Modified Typebounds: {nm}")
                for mtb in sorted(diff.modified_typebounds):
                    print(
                        f"      * {mtb.rule.ruletype} +{mtb.added_bound} -{mtb.removed_bound} "
                        f"{mtb.rule.child};")

            print()
            del diff.added_typebounds
            del diff.removed_typebounds

    if all_differences or args.attribute:
        if diff.added_type_attributes or diff.removed_type_attributes or \
                diff.modified_type_attributes or args.attribute:
            na = len(diff.added_type_attributes)
            nr = len(diff.removed_type_attributes)
            nm = len(diff.modified_type_attributes)
            print(f"Type Attributes ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_type_attributes and not args.stats:
                print(f"   Added Type Attributes: {na}")
                for attr in sorted(diff.added_type_attributes):
                    print(f"      + {attr}")
            if diff.removed_type_attributes and not args.stats:
                print(f"   Removed Type Attributes: {nr}")
                for attr in sorted(diff.removed_type_attributes):
                    print(f"      - {attr}")
            if diff.modified_type_attributes and not args.stats:
                print(f"   Modified Type Attributes: {nm}")
                for mattr in sorted(diff.modified_type_attributes):
                    change = []
                    if mattr.added_types:
                        change.append(f"{len(mattr.added_types)} Added types")
                    if mattr.removed_types:
                        change.append(f"{len(mattr.removed_types)} Removed types")

                    print(f"      * {mattr.attr.name} ({', '.join(change)})")
                    for type_ in sorted(mattr.added_types):
                        print(f"          + {type_}")
                    for type_ in sorted(mattr.removed_types):
                        print(f"          - {type_}")
            print()
            del diff.added_type_attributes
            del diff.removed_type_attributes
            del diff.modified_type_attributes

    if all_differences or args.user:
        if diff.added_users or diff.removed_users or diff.modified_users or args.user:
            na = len(diff.added_users)
            nr = len(diff.removed_users)
            nm = len(diff.modified_users)
            print(f"Users ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_users and not args.stats:
                print(f"   Added Users: {na}")
                for user in sorted(diff.added_users):
                    print(f"      + {user}")
            if diff.removed_users and not args.stats:
                print(f"   Removed Users: {nr}")
                for user in sorted(diff.removed_users):
                    print(f"      - {user}")
            if diff.modified_users and not args.stats:
                print(f"   Modified Users: {nm}")
                for muser in sorted(diff.modified_users):
                    change = []
                    if muser.added_roles:
                        change.append(f"{len(muser.added_roles)} Added roles")
                    if muser.removed_roles:
                        change.append(f"{len(muser.removed_roles)} Removed roles")
                    if muser.removed_level:
                        change.append("Modified default level")
                    if muser.removed_range:
                        change.append("Modified range")

                    print(f"      * {muser.user.name} ({', '.join(change)})")
                    if muser.added_roles or muser.removed_roles:
                        print("          Roles:")
                    for role in sorted(muser.added_roles):
                        print(f"          + {role}")
                    for role in sorted(muser.removed_roles):
                        print(f"          - {role}")

                    if muser.removed_level:
                        print("          Default level:")
                        print(f"          + {muser.added_level}")
                        print(f"          - {muser.removed_level}")

                    if muser.removed_range:
                        print("          Range:")
                        print(f"          + {muser.added_range}")
                        print(f"          - {muser.removed_range}")
            print()
            del diff.added_users
            del diff.removed_users
            del diff.modified_users

    if all_differences or args.category:
        if diff.added_categories or diff.removed_categories or diff.modified_categories \
                or args.category:
            na = len(diff.added_categories)
            nr = len(diff.removed_categories)
            nm = len(diff.modified_categories)
            print(f"Categories ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_categories and not args.stats:
                print(f"   Added Categories: {na}")
                for cat in sorted(diff.added_categories):
                    print(f"      + {cat}")
            if diff.removed_categories and not args.stats:
                print(f"   Removed Categories: {nr}")
                for cat in sorted(diff.removed_categories):
                    print(f"      - {cat}")
            if diff.modified_categories and not args.stats:
                print(f"   Modified Categories: {nm}")
                for mcat in sorted(diff.modified_categories):
                    change = []
                    if mcat.added_aliases:
                        change.append(f"{len(mcat.added_aliases)} Added Aliases")
                    if mcat.removed_aliases:
                        change.append(f"{len(mcat.removed_aliases)} Removed Aliases")

                    print(f"      * {mcat.category.name} ({', '.join(change)})")
                    print("          Aliases:")
                    for alias in sorted(mcat.added_aliases):
                        print(f"          + {alias}")
                    for alias in sorted(mcat.removed_aliases):
                        print(f"          - {alias}")

            print()
            del diff.added_categories
            del diff.removed_categories
            del diff.modified_categories

    if all_differences or args.sensitivity:
        if diff.added_sensitivities or diff.removed_sensitivities or diff.modified_sensitivities \
                or args.sensitivity:
            na = len(diff.added_sensitivities)
            nr = len(diff.removed_sensitivities)
            nm = len(diff.modified_sensitivities)
            print(f"Sensitivities ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_sensitivities and not args.stats:
                print(f"   Added Sensitivities: {na}")
                for sens in sorted(diff.added_sensitivities):
                    print(f"      + {sens}")
            if diff.removed_sensitivities and not args.stats:
                print(f"   Removed Sensitivities: {nr}")
                for sens in sorted(diff.removed_sensitivities):
                    print(f"      - {sens}")
            if diff.modified_sensitivities and not args.stats:
                print(f"   Modified Sensitivities: {nm}")
                for msens in sorted(diff.modified_sensitivities):
                    change = []
                    if msens.added_aliases:
                        change.append(f"{len(msens.added_aliases)} Added Aliases")
                    if msens.removed_aliases:
                        change.append(f"{len(msens.removed_aliases)} Removed Aliases")

                    print(f"      * {msens.sensitivity.name} ({', '.join(change)})")
                    print("          Aliases:")
                    for alias in sorted(msens.added_aliases):
                        print(f"          + {alias}")
                    for alias in sorted(msens.removed_aliases):
                        print(f"          - {alias}")

            print()
            del diff.added_sensitivities
            del diff.removed_sensitivities
            del diff.modified_sensitivities

    if all_differences or args.level:
        if diff.added_levels or diff.removed_levels or \
                diff.modified_levels or args.level:
            na = len(diff.added_levels)
            nr = len(diff.removed_levels)
            nm = len(diff.modified_levels)
            print(f"Levels ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_levels and not args.stats:
                print(f"   Added Levels: {na}")
                for level in sorted(diff.added_levels):
                    print(f"      + {level}")
            if diff.removed_levels and not args.stats:
                print(f"   Removed Levels: {len(diff.removed_levels)}")
                for level in sorted(diff.removed_levels):
                    print(f"      - {level}")
            if diff.modified_levels and not args.stats:
                print(f"   Modified Levels: {len(diff.modified_levels)}")
                for mlevel in sorted(diff.modified_levels):
                    change = []
                    if mlevel.added_categories:
                        change.append(f"{len(mlevel.added_categories)} Added Categories")
                    if mlevel.removed_categories:
                        change.append(f"{len(mlevel.removed_categories)} Removed Categories")

                    print(f"      * level {mlevel.level.sensitivity} ({', '.join(change)})")
                    for cat in sorted(mlevel.added_categories):
                        print(f"          + {cat}")
                    for cat in sorted(mlevel.removed_categories):
                        print(f"          - {cat}")
            print()
            del diff.added_levels
            del diff.removed_levels
            del diff.modified_levels

    if all_differences or args.allow:
        if diff.added_allows or diff.removed_allows or diff.modified_allows or args.allow:
            na = len(diff.added_allows)
            nr = len(diff.removed_allows)
            nm = len(diff.modified_allows)
            print(f"Allow Rules ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_allows and not args.stats:
                print(f"   Added Allow Rules: {na}")
                for avr in sorted(diff.added_allows):
                    print(f"      + {avr}")

            if diff.removed_allows and not args.stats:
                print(f"   Removed Allow Rules: {nr}")
                for avr in sorted(diff.removed_allows):
                    print(f"      - {avr}")

            if diff.modified_allows and not args.stats:
                print(f"   Modified Allow Rules: {nm}")

                for mavr in sorted(diff.modified_allows):
                    perm_str = " ".join(chain((p for p in mavr.matched_perms),
                                              (f"+{p}" for p in mavr.added_perms),
                                              (f"-{p}" for p in mavr.removed_perms)))
                    rule_string = f"{mavr.rule.ruletype} {mavr.rule.source} " \
                        f"{mavr.rule.target}:{mavr.rule.tclass} {{ {perm_str} }};"

                    with suppress(AttributeError):
                        rule_string += f" [ {mavr.rule.conditional} ]"

                    print(f"      * {rule_string}")

            print()
            del diff.added_allows
            del diff.removed_allows
            del diff.modified_allows

    if all_differences or args.allowxperm:
        if diff.added_allowxperms or diff.removed_allowxperms or diff.modified_allowxperms \
                or args.allowxperm:
            na = len(diff.added_allowxperms)
            nr = len(diff.removed_allowxperms)
            nm = len(diff.modified_allowxperms)
            print(f"Allowxperm Rules ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_allowxperms and not args.stats:
                print(f"   Added Allowxperm Rules: {na}")
                for avxr in sorted(diff.added_allowxperms):
                    print(f"      + {avxr}")

            if diff.removed_allowxperms and not args.stats:
                print(f"   Removed Allowxperm Rules: {nr}")
                for avxr in sorted(diff.removed_allowxperms):
                    print(f"      - {avxr}")

            if diff.modified_allowxperms and not args.stats:
                print(f"   Modified Allowxperm Rules: {nm}")

                for mavxr in sorted(diff.modified_allowxperms):
                    # Process the string representation of the sets
                    # so hex representation and ranges are preserved.
                    # Check if the perm sets have contents, otherwise
                    # split on empty string will be an empty string.
                    # Add brackets to added and removed permissions
                    # in case there is a range of permissions.
                    perms = []
                    if mavxr.matched_perms:
                        for p in str(mavxr.matched_perms).split(" "):
                            perms.append(p)
                    if mavxr.added_perms:
                        for p in str(mavxr.added_perms).split(" "):
                            if '-' in p:
                                perms.append(f"+[{p}]")
                            else:
                                perms.append(f"+{p}")
                    if mavxr.removed_perms:
                        for p in str(mavxr.removed_perms).split(" "):
                            if '-' in p:
                                perms.append(f"-[{p}]")
                            else:
                                perms.append(f"-{p}")

                    print(f"      * {mavxr.rule.ruletype} {mavxr.rule.source} "
                          f"{mavxr.rule.target}:{mavxr.rule.tclass} {mavxr.rule.xperm_type} "
                          f"{{ {' '.join(perms)} }};")

            print()
            del diff.added_allowxperms
            del diff.removed_allowxperms
            del diff.modified_allowxperms

    if all_differences or args.neverallow:
        if diff.added_neverallows or diff.removed_neverallows or diff.modified_neverallows or \
                args.neverallow:
            na = len(diff.added_neverallows)
            nr = len(diff.removed_neverallows)
            nm = len(diff.modified_neverallows)
            print(f"Neverallow Rules ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_neverallows and not args.stats:
                print(f"   Added Neverallow Rules: {na}")
                for avr in sorted(diff.added_neverallows):
                    print(f"      + {avr}")

            if diff.removed_neverallows and not args.stats:
                print(f"   Removed Neverallow Rules: {nr}")
                for avr in sorted(diff.removed_neverallows):
                    print(f"      - {avr}")

            if diff.modified_neverallows and not args.stats:
                print(f"   Modified Neverallow Rules: {nm}")

                for mavr in sorted(diff.modified_neverallows):
                    perm_str = " ".join(chain((p for p in mavr.matched_perms),
                                              ("+" + p for p in mavr.added_perms),
                                              ("-" + p for p in mavr.removed_perms)))
                    rule_string = f"{mavr.rule.ruletype} {mavr.rule.source} " \
                        f"{mavr.rule.target}:{mavr.rule.tclass} {{ {perm_str} }};"

                    with suppress(AttributeError):
                        rule_string += f" [ {mavr.rule.conditional} ]"

                    print(f"      * {rule_string}")

            print()
            del diff.added_neverallows
            del diff.removed_neverallows
            del diff.modified_neverallows

    if all_differences or args.neverallowxperm:
        if diff.added_neverallowxperms or diff.removed_neverallowxperms or \
                diff.modified_neverallowxperms or args.neverallowxperm:
            na = len(diff.added_neverallowxperms)
            nr = len(diff.removed_neverallowxperms)
            nm = len(diff.modified_neverallowxperms)
            print(f"Neverallowxperm Rules ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_neverallowxperms and not args.stats:
                print(f"   Added Neverallowxperm Rules: {na}")
                for avxr in sorted(diff.added_neverallowxperms):
                    print(f"      + {avxr}")

            if diff.removed_neverallowxperms and not args.stats:
                print(f"   Removed Neverallowxperm Rules: {nr}")
                for avxr in sorted(diff.removed_neverallowxperms):
                    print(f"      - {avxr}")

            if diff.modified_neverallowxperms and not args.stats:
                print(f"   Modified Neverallowxperm Rules: {nm}")

                for mavxr in sorted(diff.modified_neverallowxperms):

                    # Process the string representation of the sets
                    # so hex representation and ranges are preserved.
                    # Check if the perm sets have contents, otherwise
                    # split on empty string will be an empty string.
                    # Add brackets to added and removed permissions
                    # in case there is a range of permissions.
                    perms = []
                    if mavxr.matched_perms:
                        for p in str(mavxr.matched_perms).split(" "):
                            perms.append(p)
                    if mavxr.added_perms:
                        for p in str(mavxr.added_perms).split(" "):
                            if '-' in p:
                                perms.append(f"+[{p}]")
                            else:
                                perms.append(f"+{p}")
                    if mavxr.removed_perms:
                        for p in str(mavxr.removed_perms).split(" "):
                            if '-' in p:
                                perms.append(f"-[{p}]")
                            else:
                                perms.append(f"-{p}")

                    print(f"      * {mavxr.rule.ruletype} {mavxr.rule.source} "
                          f"{mavxr.rule.target}:{mavxr.rule.tclass} "
                          f"{mavxr.rule.xperm_type} {{ {' '.join(perms)} }};")

            print()
            del diff.added_neverallowxperms
            del diff.removed_neverallowxperms
            del diff.modified_neverallowxperms

    if all_differences or args.auditallow:
        if diff.added_auditallows or diff.removed_auditallows or diff.modified_auditallows or \
                args.auditallow:
            na = len(diff.added_auditallows)
            nr = len(diff.removed_auditallows)
            nm = len(diff.modified_auditallows)
            print(f"Auditallow Rules ({na} Added, {nr} Removed, {nm} Modified)")

            if diff.added_auditallows and not args.stats:
                print(f"   Added Auditallow Rules: {na}")
                for avr in sorted(diff.added_auditallows):
                    print(f"      + {avr}")

            if diff.removed_auditallows and not args.stats:
                print(f"   Removed Auditallow Rules: {nr}")
                for avr in sorted(diff.removed_auditallows):
                    print(f"      - {avr}")

            if diff.modified_auditallows and not args.stats:
                print(f"   Modified Auditallow Rules: {nm}")

                for mavr in sorted(diff.modified_auditallows):
                    perm_str = " ".join(chain((p for p in mavr.matched_perms),
                                              ("+" + p for p in mavr.added_perms),
                                              ("-" + p for p in mavr.removed_perms)))
                    rule_string = f"{mavr.rule.ruletype} {mavr.rule.source} " \
                        f"{mavr.rule.target}:{mavr.rule.tclass} {{ {perm_str} }};"

                    with suppress(AttributeError):
                        rule_string += f" [ {mavr.rule.conditional} ]"

                    print(f"      * {rule_string}")

            print()
            del diff.added_auditallows
            del diff.removed_auditallows
            del diff.modified_auditallows

    if all_differences or args.auditallowxperm:
        if diff.added_auditallowxperms or diff.removed_auditallowxperms or \
                diff.modified_auditallowxperms or args.auditallowxperm:

            na = len(diff.added_auditallowxperms)
            nr = len(diff.removed_auditallowxperms)
            nm = len(diff.modified_auditallowxperms)
            print(f"Auditallowxperm Rules ({na} Added, {nr} Removed, {nm} Modified)")

            if diff.added_auditallowxperms and not args.stats:
                print(f"   Added Auditallowxperm Rules: {na}")
                for avxr in sorted(diff.added_auditallowxperms):
                    print(f"      + {avxr}")

            if diff.removed_auditallowxperms and not args.stats:
                print(f"   Removed Auditallowxperm Rules: {nr}")
                for avxr in sorted(diff.removed_auditallowxperms):
                    print(f"      - {avxr}")

            if diff.modified_auditallowxperms and not args.stats:
                print(f"   Modified Auditallowxperm Rules: {nm}")

                for mavxr in sorted(diff.modified_auditallowxperms):

                    # Process the string representation of the sets
                    # so hex representation and ranges are preserved.
                    # Check if the perm sets have contents, otherwise
                    # split on empty string will be an empty string.
                    # Add brackets to added and removed permissions
                    # in case there is a range of permissions.
                    perms = []
                    if mavxr.matched_perms:
                        for p in str(mavxr.matched_perms).split(" "):
                            perms.append(p)
                    if mavxr.added_perms:
                        for p in str(mavxr.added_perms).split(" "):
                            if '-' in p:
                                perms.append(f"+[{p}]")
                            else:
                                perms.append(f"+{p}")
                    if mavxr.removed_perms:
                        for p in str(mavxr.removed_perms).split(" "):
                            if '-' in p:
                                perms.append(f"-[{p}]")
                            else:
                                perms.append(f"-{p}")

                    print(f"      * {mavxr.rule.ruletype} {mavxr.rule.source} "
                          f"{mavxr.rule.target}:{mavxr.rule.tclass} "
                          f"{mavxr.rule.xperm_type} {{ {' '.join(perms)} }};")

            print()
            del diff.added_auditallowxperms
            del diff.removed_auditallowxperms
            del diff.modified_auditallowxperms

    if all_differences or args.dontaudit:
        if diff.added_dontaudits or diff.removed_dontaudits or diff.modified_dontaudits or \
                args.dontaudit:

            na = len(diff.added_dontaudits)
            nr = len(diff.removed_dontaudits)
            nm = len(diff.modified_dontaudits)
            print(f"Dontaudit Rules ({na} Added, {nr} Removed, {nm} Modified)")

            if diff.added_dontaudits and not args.stats:
                print(f"   Added Dontaudit Rules: {na}")
                for avr in sorted(diff.added_dontaudits):
                    print(f"      + {avr}")

            if diff.removed_dontaudits and not args.stats:
                print(f"   Removed Dontaudit Rules: {nr}")
                for avr in sorted(diff.removed_dontaudits):
                    print(f"      - {avr}")

            if diff.modified_dontaudits and not args.stats:
                print(f"   Modified Dontaudit Rules: {nm}")

                for mavr in sorted(diff.modified_dontaudits):
                    perm_str = " ".join(chain((p for p in mavr.matched_perms),
                                              ("+" + p for p in mavr.added_perms),
                                              ("-" + p for p in mavr.removed_perms)))
                    rule_string = f"{mavr.rule.ruletype} {mavr.rule.source} " \
                        f"{mavr.rule.target}:{mavr.rule.tclass} {{ {perm_str} }};"

                    with suppress(AttributeError):
                        rule_string += f" [ {mavr.rule.conditional} ]"

                    print(f"      * {rule_string}")

            print()
            del diff.added_dontaudits
            del diff.removed_dontaudits
            del diff.modified_dontaudits

    if all_differences or args.dontauditxperm:
        if diff.added_dontauditxperms or diff.removed_dontauditxperms or \
                diff.modified_dontauditxperms or args.dontauditxperm:

            na = len(diff.added_dontauditxperms)
            nr = len(diff.removed_dontauditxperms)
            nm = len(diff.modified_dontauditxperms)
            print(f"Dontauditxperm Rules ({na} Added, {nr} Removed, {nm} Modified)")

            if diff.added_dontauditxperms and not args.stats:
                print(f"   Added Dontauditxperm Rules: {na}")
                for avxr in sorted(diff.added_dontauditxperms):
                    print(f"      + {avxr}")

            if diff.removed_dontauditxperms and not args.stats:
                print(f"   Removed Dontauditxperm Rules: {nr}")
                for avxr in sorted(diff.removed_dontauditxperms):
                    print(f"      - {avxr}")

            if diff.modified_dontauditxperms and not args.stats:
                print(f"   Modified Dontauditxperm Rules: {nm}")

                for mavxr in sorted(diff.modified_dontauditxperms):

                    # Process the string representation of the sets
                    # so hex representation and ranges are preserved.
                    # Check if the perm sets have contents, otherwise
                    # split on empty string will be an empty string.
                    # Add brackets to added and removed permissions
                    # in case there is a range of permissions.
                    perms = []
                    if mavxr.matched_perms:
                        for p in str(mavxr.matched_perms).split(" "):
                            perms.append(p)
                    if mavxr.added_perms:
                        for p in str(mavxr.added_perms).split(" "):
                            if '-' in p:
                                perms.append(f"+[{p}]")
                            else:
                                perms.append(f"+{p}")
                    if mavxr.removed_perms:
                        for p in str(mavxr.removed_perms).split(" "):
                            if '-' in p:
                                perms.append(f"-[{p}]")
                            else:
                                perms.append(f"-{p}")

                    print(f"      * {mavxr.rule.ruletype} {mavxr.rule.source} "
                          f"{mavxr.rule.target}:{mavxr.rule.tclass} "
                          f"{mavxr.rule.xperm_type} {{ {' '.join(perms)} }};")

            print()
            del diff.added_dontauditxperms
            del diff.removed_dontauditxperms
            del diff.modified_dontauditxperms

    if all_differences or args.type_trans:
        if diff.added_type_transitions or diff.removed_type_transitions or \
                diff.modified_type_transitions or args.type_trans:

            na = len(diff.added_type_transitions)
            nr = len(diff.removed_type_transitions)
            nm = len(diff.modified_type_transitions)
            print(f"Type_transition Rules ({na} Added, {nr} Removed, {nm} Modified)")

            if diff.added_type_transitions and not args.stats:
                print(f"   Added Type_transition Rules: {na}")
                for ter in sorted(diff.added_type_transitions):
                    print(f"      + {ter}")

            if diff.removed_type_transitions and not args.stats:
                print(f"   Removed Type_transition Rules: {nr}")
                for ter in sorted(diff.removed_type_transitions):
                    print(f"      - {ter}")

            if diff.modified_type_transitions and not args.stats:
                print(f"   Modified Type_transition Rules: {nm}")

                for mter in sorted(diff.modified_type_transitions):
                    rule_string = \
                        f"{mter.rule.ruletype} {mter.rule.source} " \
                        f"{mter.rule.target}:{mter.rule.tclass} " \
                        f"+{mter.added_default} -{mter.removed_default}"

                    with suppress(AttributeError):
                        rule_string += f" {mter.rule.filename}"

                    rule_string += ";"

                    with suppress(AttributeError):
                        rule_string += f" [ {mter.rule.conditional} ]"

                    print(f"      * {rule_string}")

            print()
            del diff.added_type_transitions
            del diff.removed_type_transitions
            del diff.modified_type_transitions

    if all_differences or args.type_change:
        if diff.added_type_changes or diff.removed_type_changes or \
                diff.modified_type_changes or args.type_change:

            na = len(diff.added_type_changes)
            nr = len(diff.removed_type_changes)
            nm = len(diff.modified_type_changes)
            print(f"Type_change Rules ({na} Added, {nr} Removed, {nm} Modified)")

            if diff.added_type_changes and not args.stats:
                print(f"   Added Type_change Rules: {na}")
                for ter in sorted(diff.added_type_changes):
                    print(f"      + {ter}")

            if diff.removed_type_changes and not args.stats:
                print(f"   Removed Type_change Rules: {nr}")
                for ter in sorted(diff.removed_type_changes):
                    print(f"      - {ter}")

            if diff.modified_type_changes and not args.stats:
                print(f"   Modified Type_change Rules: {nm}")

                for mter in sorted(diff.modified_type_changes):
                    rule_string = \
                        f"{mter.rule.ruletype} {mter.rule.source} " \
                        f"{mter.rule.target}:{mter.rule.tclass} " \
                        f"+{mter.added_default} -{mter.removed_default};"

                    with suppress(AttributeError):
                        rule_string += f" [ {mter.rule.conditional} ]"

                    print(f"      * {rule_string}")

            print()
            del diff.added_type_changes
            del diff.removed_type_changes
            del diff.modified_type_changes

    if all_differences or args.type_member:
        if diff.added_type_members or diff.removed_type_members or \
                diff.modified_type_members or args.type_member:

            na = len(diff.added_type_members)
            nr = len(diff.removed_type_members)
            nm = len(diff.modified_type_members)
            print(f"Type_member Rules ({na} Added, {nr} Removed, {nm} Modified)")

            if diff.added_type_members and not args.stats:
                print(f"   Added Type_member Rules: {na}")
                for ter in sorted(diff.added_type_members):
                    print(f"      + {ter}")

            if diff.removed_type_members and not args.stats:
                print(f"   Removed Type_member Rules: {nr}")
                for ter in sorted(diff.removed_type_members):
                    print(f"      - {ter}")

            if diff.modified_type_members and not args.stats:
                print(f"   Modified Type_member Rules: {nm}")

                for mter in sorted(diff.modified_type_members):
                    rule_string = \
                        f"{mter.rule.ruletype} {mter.rule.source} " \
                        f"{mter.rule.target}:{mter.rule.tclass} " \
                        f"+{mter.added_default} -{mter.removed_default};"

                    with suppress(AttributeError):
                        rule_string += f" [ {mter.rule.conditional} ]"

                    print(f"      * {rule_string}")

            print()
            del diff.added_type_members
            del diff.removed_type_members
            del diff.modified_type_members

    if all_differences or args.role_allow:
        if diff.added_role_allows or diff.removed_role_allows or args.role_allow:

            na = len(diff.added_role_allows)
            nr = len(diff.removed_role_allows)
            print(f"Role allow Rules ({na} Added, {nr} Removed)")

            if diff.added_role_allows and not args.stats:
                print(f"   Added Role Allow Rules: {na}")
                for ra in sorted(diff.added_role_allows):
                    print(f"      + {ra}")

            if diff.removed_role_allows and not args.stats:
                print(f"   Removed Role Allow Rules: {nr}")
                for ra in sorted(diff.removed_role_allows):
                    print(f"      - {ra}")

            print()
            del diff.added_role_allows
            del diff.removed_role_allows

    if all_differences or args.role_trans:
        if diff.added_role_transitions or diff.removed_role_transitions or \
                diff.modified_role_transitions or args.role_trans:

            na = len(diff.added_role_transitions)
            nr = len(diff.removed_role_transitions)
            nm = len(diff.modified_role_transitions)
            print(f"Role_transition Rules ({na} Added, {nr} Removed, {nm} Modified)")

            if diff.added_role_transitions and not args.stats:
                print(f"   Added Role_transition Rules: {na}")
                for rotr in sorted(diff.added_role_transitions):
                    print(f"      + {rotr}")

            if diff.removed_role_transitions and not args.stats:
                print(f"   Removed Role_transition Rules: {nr}")
                for rotr in sorted(diff.removed_role_transitions):
                    print(f"      - {rotr}")

            if diff.modified_role_transitions and not args.stats:
                print(f"   Modified Role_transition Rules: {nm}")

                for mrotr in sorted(diff.modified_role_transitions):
                    rule_string = \
                        f"{mrotr.rule.ruletype} {mrotr.rule.source} " \
                        f"{mrotr.rule.target}:{mrotr.rule.tclass} " \
                        f"+{mrotr.added_default} -{mrotr.removed_default};"

                    print(f"      * {rule_string}")

            print()
            del diff.added_role_transitions
            del diff.removed_role_transitions
            del diff.modified_role_transitions

    if all_differences or args.range_trans:
        if diff.added_range_transitions or diff.removed_range_transitions or \
                diff.modified_range_transitions or args.range_trans:

            na = len(diff.added_range_transitions)
            nr = len(diff.removed_range_transitions)
            nm = len(diff.modified_range_transitions)
            print(f"Range_transition Rules ({na} Added, {nr} Removed, {nm} Modified)")

            if diff.added_range_transitions and not args.stats:
                print(f"   Added Range_transition Rules: {na}")
                for ratr in sorted(diff.added_range_transitions):
                    print(f"      + {ratr}")

            if diff.removed_range_transitions and not args.stats:
                print(f"   Removed Range_transition Rules: {nr}")
                for ratr in sorted(diff.removed_range_transitions):
                    print(f"      - {ratr}")

            if diff.modified_range_transitions and not args.stats:
                print(f"   Modified Range_transition Rules: {nm}")

                for mratr in sorted(diff.modified_range_transitions):
                    # added brackets around range change for clarity since ranges
                    # can have '-' and spaces.
                    rule_string = \
                        f"{mratr.rule.ruletype} {mratr.rule.source} " \
                        f"{mratr.rule.target}:{mratr.rule.tclass} " \
                        f"+[{mratr.added_default}] -[{mratr.removed_default}];"

                    print(f"      * {rule_string}")

            print()
            del diff.added_range_transitions
            del diff.removed_range_transitions
            del diff.modified_range_transitions

    if all_differences or args.constrain:
        if diff.added_constrains or diff.removed_constrains or args.constrain:
            na = len(diff.added_constrains)
            nr = len(diff.removed_constrains)
            print(f"Constraints ({na} Added, {nr} Removed)")

            if diff.added_constrains and not args.stats:
                print(f"   Added Constraints: {na}")
                for constraint in sorted(diff.added_constrains):
                    print(f"      + {constraint}")

            if diff.removed_constrains and not args.stats:
                print(f"   Removed Constraints: {nr}")
                for constraint in sorted(diff.removed_constrains):
                    print(f"      - {constraint}")

            print()
            del diff.added_constrains
            del diff.removed_constrains

    if all_differences or args.mlsconstrain:
        if diff.added_mlsconstrains or diff.removed_mlsconstrains or args.mlsconstrain:
            na = len(diff.added_mlsconstrains)
            nr = len(diff.removed_mlsconstrains)
            print(f"MLS Constraints ({na} Added, {nr} Removed)")

            if diff.added_mlsconstrains and not args.stats:
                print(f"   Added MLS Constraints: {na}")
                for constraint in sorted(diff.added_mlsconstrains):
                    print(f"      + {constraint}")

            if diff.removed_mlsconstrains and not args.stats:
                print(f"   Removed MLS Constraints: {nr}")
                for constraint in sorted(diff.removed_mlsconstrains):
                    print(f"      - {constraint}")

            print()
            del diff.added_mlsconstrains
            del diff.removed_mlsconstrains

    if all_differences or args.validatetrans:
        if diff.added_validatetrans or diff.removed_validatetrans or args.validatetrans:
            na = len(diff.added_validatetrans)
            nr = len(diff.removed_validatetrans)
            print(f"Validatetrans ({na} Added, {nr} Removed)")

            if diff.added_validatetrans and not args.stats:
                print(f"   Added Validatetrans: {na}")
                for validatetrans in sorted(diff.added_validatetrans):
                    print(f"      + {validatetrans}")

            if diff.removed_validatetrans and not args.stats:
                print(f"   Removed Validatetrans: {nr}")
                for validatetrans in sorted(diff.removed_validatetrans):
                    print(f"      - {validatetrans}")

            print()
            del diff.added_validatetrans
            del diff.removed_validatetrans

    if all_differences or args.mlsvalidatetrans:
        if diff.added_mlsvalidatetrans or diff.removed_mlsvalidatetrans or args.mlsvalidatetrans:
            na = len(diff.added_mlsvalidatetrans)
            nr = len(diff.removed_mlsvalidatetrans)
            print(f"MLS Validatetrans ({na} Added, {nr} Removed)")

            if diff.added_mlsvalidatetrans and not args.stats:
                print(f"   Added MLS Validatetrans: {na}")
                for validatetrans in sorted(diff.added_mlsvalidatetrans):
                    print(f"      + {validatetrans}")

            if diff.removed_mlsvalidatetrans and not args.stats:
                print(f"   Removed MLS Validatetrans: {nr}")
                for validatetrans in sorted(diff.removed_mlsvalidatetrans):
                    print(f"      - {validatetrans}")

            print()
            del diff.added_mlsvalidatetrans
            del diff.removed_mlsvalidatetrans

    if all_differences or args.initialsid:
        if diff.added_initialsids or diff.removed_initialsids or diff.modified_initialsids \
                or args.initialsid:
            na = len(diff.added_initialsids)
            nr = len(diff.removed_initialsids)
            nm = len(diff.modified_initialsids)
            print(f"Initial SIDs ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_initialsids and not args.stats:
                print(f"   Added Initial SIDs: {na}")
                for isid in sorted(diff.added_initialsids):
                    print(f"      + {isid.statement()}")
            if diff.removed_initialsids and not args.stats:
                print(f"   Removed Initial SIDs: {nr}")
                for isid in sorted(diff.removed_initialsids):
                    print(f"      - {isid.statement()}")
            if diff.modified_initialsids and not args.stats:
                print(f"   Modified Initial SIDs: {nm}")
                for misid in sorted(diff.modified_initialsids):
                    print(f"      * sid {misid.isid.name} +[{misid.added_context}] "
                          f"-[{misid.removed_context}];")

            print()
            del diff.added_initialsids
            del diff.removed_initialsids
            del diff.modified_initialsids

    if all_differences or args.ibendportcon:
        if diff.added_ibendportcons or diff.removed_ibendportcons or diff.modified_ibendportcons \
                or args.ibendportcon:
            na = len(diff.added_ibendportcons)
            nr = len(diff.removed_ibendportcons)
            nm = len(diff.modified_ibendportcons)
            print(f"Ibendportcons ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_ibendportcons and not args.stats:
                print(f"   Added Ibendportcons: {na}")
                for ibp in sorted(diff.added_ibendportcons):
                    print(f"      + {ibp}")
            if diff.removed_ibendportcons and not args.stats:
                print(f"   Removed Ibendportcons: {nr}")
                for ibp in sorted(diff.removed_ibendportcons):
                    print(f"      - {ibp}")
            if diff.modified_ibendportcons and not args.stats:
                print(f"   Modified Ibendportcons: {nm}")
                for mibp in sorted(diff.modified_ibendportcons):
                    print(f"      * ibendportcon {mibp.rule.name} {mibp.rule.port} "
                          f"+[{mibp.added_context}] -[{mibp.removed_context}]")

            print()
            del diff.added_ibendportcons
            del diff.removed_ibendportcons
            del diff.modified_ibendportcons

    if all_differences or args.ibpkeycon:
        if diff.added_ibpkeycons or diff.removed_ibpkeycons or diff.modified_ibpkeycons \
                or args.ibpkeycon:
            na = len(diff.added_ibpkeycons)
            nr = len(diff.removed_ibpkeycons)
            nm = len(diff.modified_ibpkeycons)
            print(f"Ibpkeycons ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_ibpkeycons and not args.stats:
                print(f"   Added Ibpkeycons: {na}")
                for ibpk in sorted(diff.added_ibpkeycons):
                    print(f"      + {ibpk}")
            if diff.removed_ibpkeycons and not args.stats:
                print(f"   Removed Ibpkeycons: {nr}")
                for ibpk in sorted(diff.removed_ibpkeycons):
                    print(f"      - {ibpk}")
            if diff.modified_ibpkeycons and not args.stats:
                print(f"   Modified Ibpkeycons: {nm}")
                for mibpk in sorted(diff.modified_ibpkeycons):
                    if mibpk.rule.pkeys.low == mibpk.rule.pkeys.high:
                        print(f"      * ibpkeycon {mibpk.rule.subnet_prefix} "
                              f"{mibpk.rule.pkeys.low:#x} +[{mibpk.added_context}] "
                              f"-[{mibpk.removed_context}]")
                    else:
                        print(f"      * ibpkeycon {mibpk.rule.subnet_prefix} "
                              f"{mibpk.rule.pkeys.low:#x}-{mibpk.rule.pkeys.high:#x} "
                              f"+[{mibpk.added_context}] -[{mibpk.removed_context}]")

            print()
            del diff.added_ibpkeycons
            del diff.removed_ibpkeycons
            del diff.modified_ibpkeycons

    if all_differences or args.fs_use:
        if diff.added_fs_uses or diff.removed_fs_uses or diff.modified_fs_uses \
                or args.fs_use:
            na = len(diff.added_fs_uses)
            nr = len(diff.removed_fs_uses)
            nm = len(diff.modified_fs_uses)
            print(f"Fs_use ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_fs_uses and not args.stats:
                print(f"   Added Fs_use: {na}")
                for fsu in sorted(diff.added_fs_uses):
                    print(f"      + {fsu}")
            if diff.removed_fs_uses and not args.stats:
                print(f"   Removed Fs_use: {nr}")
                for fsu in sorted(diff.removed_fs_uses):
                    print(f"      - {fsu}")
            if diff.modified_fs_uses and not args.stats:
                print(f"   Modified Fs_use: {nm}")
                for mfsu in sorted(diff.modified_fs_uses):
                    print(f"      * {mfsu.rule.ruletype} {mfsu.rule.fs} "
                          f"+[{mfsu.added_context}] -[{mfsu.removed_context}];")

            print()
            del diff.added_fs_uses
            del diff.removed_fs_uses
            del diff.modified_fs_uses

    if all_differences or args.genfscon:
        if diff.added_genfscons or diff.removed_genfscons or diff.modified_genfscons \
                or args.genfscon:
            na = len(diff.added_genfscons)
            nr = len(diff.removed_genfscons)
            nm = len(diff.modified_genfscons)
            print(f"Genfscons ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_genfscons and not args.stats:
                print(f"   Added Genfscons: {na}")
                for genfs in sorted(diff.added_genfscons):
                    print(f"      + {genfs}")
            if diff.removed_genfscons and not args.stats:
                print(f"   Removed Genfscons: {nr}")
                for genfs in sorted(diff.removed_genfscons):
                    print(f"      - {genfs}")
            if diff.modified_genfscons and not args.stats:
                print(f"   Modified Genfscons: {nm}")
                for mgenfs in sorted(diff.modified_genfscons):
                    print(f"      * genfscon {mgenfs.rule.fs} {mgenfs.rule.path} "
                          f"{mgenfs.rule.filetype} +[{mgenfs.added_context}] "
                          f"-[{mgenfs.removed_context}];")

            print()
            del diff.added_genfscons
            del diff.removed_genfscons
            del diff.modified_genfscons

    if all_differences or args.netifcon:
        if diff.added_netifcons or diff.removed_netifcons or \
                diff.modified_netifcons or args.netifcon:
            na = len(diff.added_netifcons)
            nr = len(diff.removed_netifcons)
            nm = len(diff.modified_netifcons)
            print(f"Netifcons ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_netifcons and not args.stats:
                print(f"   Added Netifcons: {na}")
                for netif in sorted(diff.added_netifcons):
                    print(f"      + {netif}")
            if diff.removed_netifcons and not args.stats:
                print(f"   Removed Netifcons: {nr}")
                for netif in sorted(diff.removed_netifcons):
                    print(f"      - {netif}")
            if diff.modified_netifcons and not args.stats:
                print(f"   Modified Netifcons: {nm}")
                for mnetif in sorted(diff.modified_netifcons):
                    # This output is different than other statements because
                    # it becomes difficult to read if this was condensed
                    # into a single line, especially if both contexts
                    # are modified.
                    change = []
                    if mnetif.removed_context:
                        change.append("Modified Context")
                    if mnetif.removed_packet:
                        change.append("Modified Packet Context")

                    print(f"      * netif {mnetif.rule.netif} ({', '.join(change)})")

                    if mnetif.removed_context:
                        print("          Context:")
                        print(f"             + {mnetif.added_context}")
                        print(f"             - {mnetif.removed_context}")
                    if mnetif.removed_packet:
                        print("          Packet Context:")
                        print(f"             + {mnetif.added_packet}")
                        print(f"             - {mnetif.removed_packet}")

            print()
            del diff.added_netifcons
            del diff.removed_netifcons
            del diff.modified_netifcons

    if all_differences or args.nodecon:
        if diff.added_nodecons or diff.removed_nodecons or diff.modified_nodecons \
                or args.nodecon:
            na = len(diff.added_nodecons)
            nr = len(diff.removed_nodecons)
            nm = len(diff.modified_nodecons)
            print(f"Nodecons ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_nodecons and not args.stats:
                print(f"   Added Nodecons: {na}")
                for node in sorted(diff.added_nodecons):
                    print(f"      + {node}")
            if diff.removed_nodecons and not args.stats:
                print(f"   Removed Nodecons: {nr}")
                for node in sorted(diff.removed_nodecons):
                    print(f"      - {node}")
            if diff.modified_nodecons and not args.stats:
                print(f"   Modified Nodecons: {nm}")
                for mnode in sorted(diff.modified_nodecons):
                    print(f"      * nodecon {mnode.rule.network.with_netmask.replace('/', ' ')} "
                          f"+[{mnode.added_context}] -[{mnode.removed_context}];")

            print()
            del diff.added_nodecons
            del diff.removed_nodecons
            del diff.modified_nodecons

    if all_differences or args.portcon:
        if diff.added_portcons or diff.removed_portcons or diff.modified_portcons \
                or args.portcon:
            na = len(diff.added_portcons)
            nr = len(diff.removed_portcons)
            nm = len(diff.modified_portcons)
            print(f"Portcons ({na} Added, {nr} Removed, {nm} Modified)")
            if diff.added_portcons and not args.stats:
                print(f"   Added Portcons: {na}")
                for port in sorted(diff.added_portcons):
                    print(f"      + {port}")
            if diff.removed_portcons and not args.stats:
                print(f"   Removed Portcons: {nr}")
                for port in sorted(diff.removed_portcons):
                    print(f"      - {port}")
            if diff.modified_portcons and not args.stats:
                print(f"   Modified Portcons: {nm}")
                for mport in sorted(diff.modified_portcons):
                    low, high = mport.rule.ports.low, mport.rule.ports.high
                    if low == high:
                        print(f"      * portcon {mport.rule.protocol} {low} "
                              f"+[{mport.added_context}] -[{mport.removed_context}];")
                    else:
                        print(f"      * portcon {mport.rule.protocol} {low}-{high} "
                              f"+[{mport.added_context}] -[{mport.removed_context}];")

            print()
            del diff.added_portcons
            del diff.removed_portcons
            del diff.modified_portcons

except AssertionError:
    # Always provide a traceback for assertion errors
    raise

except Exception as err:
    if args.debug:
        raise
    else:
        print(err)

    sys.exit(1)
