#!/usr/bin/env python
# Copyright 2015-2016, Tresys Technology, LLC
#
# This file is part of SETools.
#
# SETools 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, either version 2 of the License, or
# (at your option) any later version.
#
# SETools 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 SETools.  If not, see <http://www.gnu.org/licenses/>.
#

from __future__ import print_function
import setools
import argparse
import sys
import logging
from itertools import chain

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("--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()

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

if args.debug:
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s|%(levelname)s|%(name)s|%(message)s')
elif args.verbose:
    logging.basicConfig(level=logging.INFO, format='%(message)s')
else:
    logging.basicConfig(level=logging.WARNING, format='%(message)s')

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

    if all_differences or args.property:
        print("Policy Properties ({0} Modified)".format(len(diff.modified_properties)))

        if diff.modified_properties and not args.stats:
            for name, added, removed in sorted(diff.modified_properties, key=lambda x: x.property):
                print("      * {0} +{1} -{2}".format(name, added, removed))

        print()

    if all_differences or args.common:
        if diff.added_commons or diff.removed_commons or diff.modified_commons or args.common:
            print("Commons ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_commons), len(diff.removed_commons), len(diff.modified_commons)))
            if diff.added_commons and not args.stats:
                print("   Added Commons: {0}".format(len(diff.added_commons)))
                for c in sorted(diff.added_commons):
                    print("      + {0}".format(c))
            if diff.removed_commons and not args.stats:
                print("   Removed Commons: {0}".format(len(diff.removed_commons)))
                for c in sorted(diff.removed_commons):
                    print("      - {0}".format(c))
            if diff.modified_commons and not args.stats:
                print("   Modified Commons: {0}".format(len(diff.modified_commons)))
                for name, mod in sorted(diff.modified_commons.items()):
                    change = []
                    if mod.added_perms:
                        change.append("{0} Added permissions".format(len(mod.added_perms)))
                    if mod.removed_perms:
                        change.append("{0} Removed permissions".format(len(mod.removed_perms)))

                    print("      * {0} ({1})".format(name, ", ".join(change)))
                    for p in sorted(mod.added_perms):
                        print("          + {0}".format(p))
                    for p in sorted(mod.removed_perms):
                        print("          - {0}".format(p))
            print()

    if all_differences or args.class_:
        if diff.added_classes or diff.removed_classes or diff.modified_classes or args.class_:
            print("Classes ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_classes), len(diff.removed_classes), len(diff.modified_classes)))
            if diff.added_classes and not args.stats:
                print("   Added Classes: {0}".format(len(diff.added_classes)))
                for c in sorted(diff.added_classes):
                    print("      + {0}".format(c))
            if diff.removed_classes and not args.stats:
                print("   Removed Classes: {0}".format(len(diff.removed_classes)))
                for c in sorted(diff.removed_classes):
                    print("      - {0}".format(c))
            if diff.modified_classes and not args.stats:
                print("   Modified Classes: {0}".format(len(diff.modified_classes)))
                for name, mod in sorted(diff.modified_classes.items()):
                    change = []
                    if mod.added_perms:
                        change.append("{0} Added permissions".format(len(mod.added_perms)))
                    if mod.removed_perms:
                        change.append("{0} Removed permissions".format(len(mod.removed_perms)))

                    print("      * {0} ({1})".format(name, ", ".join(change)))
                    for p in sorted(mod.added_perms):
                        print("          + {0}".format(p))
                    for p in sorted(mod.removed_perms):
                        print("          - {0}".format(p))
            print()

    if all_differences or args.bool_:
        if diff.added_booleans or diff.removed_booleans or \
                diff.modified_booleans or args.bool_:
            print("Booleans ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_booleans), len(diff.removed_booleans),
                len(diff.modified_booleans)))
            if diff.added_booleans and not args.stats:
                print("   Added Booleans: {0}".format(len(diff.added_booleans)))
                for a in sorted(diff.added_booleans):
                    print("      + {0}".format(a))
            if diff.removed_booleans and not args.stats:
                print("   Removed Booleans: {0}".format(len(diff.removed_booleans)))
                for a in sorted(diff.removed_booleans):
                    print("      - {0}".format(a))
            if diff.modified_booleans and not args.stats:
                print("   Modified Booleans: {0}".format(len(diff.modified_booleans)))
                for name, mod in sorted(diff.modified_booleans.items()):
                    print("      * {0} (Modified default state)".format(name))
                    print("          + {0}".format(mod.added_state))
                    print("          - {0}".format(mod.removed_state))

            print()

    if all_differences or args.role:
        if diff.added_roles or diff.removed_roles or diff.modified_roles or args.role:
            print("Roles ({0} Added, {1} Removed, {2} Modified)".format(len(diff.added_roles),
                                                                        len(diff.removed_roles),
                                                                        len(diff.modified_roles)))
            if diff.added_roles and not args.stats:
                print("   Added Roles: {0}".format(len(diff.added_roles)))
                for r in sorted(diff.added_roles):
                    print("      + {0}".format(r))
            if diff.removed_roles and not args.stats:
                print("   Removed Roles: {0}".format(len(diff.removed_roles)))
                for r in sorted(diff.removed_roles):
                    print("      - {0}".format(r))
            if diff.modified_roles and not args.stats:
                print("   Modified Roles: {0}".format(len(diff.modified_roles)))
                for name, mod in sorted(diff.modified_roles.items()):
                    change = []
                    if mod.added_types:
                        change.append("{0} Added types".format(len(mod.added_types)))
                    if mod.removed_types:
                        change.append("{0} Removed types".format(len(mod.removed_types)))

                    print("      * {0} ({1})".format(name, ", ".join(change)))
                    for t in sorted(mod.added_types):
                        print("          + {0}".format(t))
                    for t in sorted(mod.removed_types):
                        print("          - {0}".format(t))
            print()

    if all_differences or args.type_:
        if diff.added_types or diff.removed_types or diff.modified_types or args.type_:
            print("Types ({0} Added, {1} Removed, {2} Modified)".format(len(diff.added_types),
                                                                        len(diff.removed_types),
                                                                        len(diff.modified_types)))
            if diff.added_types and not args.stats:
                print("   Added Types: {0}".format(len(diff.added_types)))
                for r in sorted(diff.added_types):
                    print("      + {0}".format(r))
            if diff.removed_types and not args.stats:
                print("   Removed Types: {0}".format(len(diff.removed_types)))
                for r in sorted(diff.removed_types):
                    print("      - {0}".format(r))
            if diff.modified_types and not args.stats:
                print("   Modified Types: {0}".format(len(diff.modified_types)))
                for name, mod in sorted(diff.modified_types.items()):
                    change = []
                    if mod.added_attributes:
                        change.append("{0} Added attributes".format(len(mod.added_attributes)))
                    if mod.removed_attributes:
                        change.append("{0} Removed attributes".format(len(mod.removed_attributes)))
                    if mod.added_aliases:
                        change.append("{0} Added aliases".format(len(mod.added_aliases)))
                    if mod.removed_aliases:
                        change.append("{0} Removed aliases".format(len(mod.removed_aliases)))
                    if mod.modified_permissive:
                        if mod.permissive:
                            change.append("Removed permissive")
                        else:
                            change.append("Added permissive")

                    print("      * {0} ({1})".format(name, ", ".join(change)))
                    if mod.added_attributes or mod.removed_attributes:
                        print("          Attributes:")
                    for t in sorted(mod.added_attributes):
                        print("          + {0}".format(t))
                    for t in sorted(mod.removed_attributes):
                        print("          - {0}".format(t))

                    if mod.added_aliases or mod.removed_aliases:
                        print("          Aliases:")
                    for t in sorted(mod.added_aliases):
                        print("          + {0}".format(t))
                    for t in sorted(mod.removed_aliases):
                        print("          - {0}".format(t))

            print()

    if all_differences or args.attribute:
        if diff.added_type_attributes or diff.removed_type_attributes or \
                diff.modified_type_attributes or args.attribute:
            print("Type Attributes ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_type_attributes), len(diff.removed_type_attributes),
                len(diff.modified_type_attributes)))
            if diff.added_type_attributes and not args.stats:
                print("   Added Type Attributes: {0}".format(len(diff.added_type_attributes)))
                for a in sorted(diff.added_type_attributes):
                    print("      + {0}".format(a))
            if diff.removed_type_attributes and not args.stats:
                print("   Removed Type Attributes: {0}".format(len(diff.removed_type_attributes)))
                for a in sorted(diff.removed_type_attributes):
                    print("      - {0}".format(a))
            if diff.modified_type_attributes and not args.stats:
                print("   Modified Type Attributes: {0}".format(len(diff.modified_type_attributes)))
                for name, mod in sorted(diff.modified_type_attributes.items()):
                    change = []
                    if mod.added_types:
                        change.append("{0} Added types".format(len(mod.added_types)))
                    if mod.removed_types:
                        change.append("{0} Removed types".format(len(mod.removed_types)))

                    print("      * {0} ({1})".format(name, ", ".join(change)))
                    for t in sorted(mod.added_types):
                        print("          + {0}".format(t))
                    for t in sorted(mod.removed_types):
                        print("          - {0}".format(t))
            print()

    if all_differences or args.user:
        if diff.added_users or diff.removed_users or diff.modified_users or args.user:
            print("Users ({0} Added, {1} Removed, {2} Modified)".format(len(diff.added_users),
                                                                        len(diff.removed_users),
                                                                        len(diff.modified_users)))
            if diff.added_users and not args.stats:
                print("   Added Users: {0}".format(len(diff.added_users)))
                for u in sorted(diff.added_users):
                    print("      + {0}".format(u))
            if diff.removed_users and not args.stats:
                print("   Removed Users: {0}".format(len(diff.removed_users)))
                for u in sorted(diff.removed_users):
                    print("      - {0}".format(u))
            if diff.modified_users and not args.stats:
                print("   Modified Users: {0}".format(len(diff.modified_users)))
                for name, mod in sorted(diff.modified_users.items()):
                    change = []
                    if mod.added_roles:
                        change.append("{0} Added roles".format(len(mod.added_roles)))
                    if mod.removed_roles:
                        change.append("{0} Removed roles".format(len(mod.removed_roles)))
                    if mod.removed_level:
                        change.append("Modified default level")
                    if mod.removed_range:
                        change.append("Modified range")

                    print("      * {0} ({1})".format(name, ", ".join(change)))
                    if mod.added_roles or mod.removed_roles:
                        print("          Roles:")
                    for t in sorted(mod.added_roles):
                        print("          + {0}".format(t))
                    for t in sorted(mod.removed_roles):
                        print("          - {0}".format(t))

                    if mod.removed_level:
                        print("          Default level:")
                        print("          + {0}".format(mod.added_level))
                        print("          - {0}".format(mod.removed_level))

                    if mod.removed_range:
                        print("          Range:")
                        print("          + {0}".format(mod.added_range))
                        print("          - {0}".format(mod.removed_range))
            print()

    if all_differences or args.category:
        if diff.added_categories or diff.removed_categories or diff.modified_categories \
                or args.category:
            print("Categories ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_categories), len(diff.removed_categories),
                len(diff.modified_categories)))
            if diff.added_categories and not args.stats:
                print("   Added Categories: {0}".format(len(diff.added_categories)))
                for c in sorted(diff.added_categories):
                    print("      + {0}".format(c))
            if diff.removed_categories and not args.stats:
                print("   Removed Categories: {0}".format(len(diff.removed_categories)))
                for c in sorted(diff.removed_categories):
                    print("      - {0}".format(c))
            if diff.modified_categories and not args.stats:
                print("   Modified Categories: {0}".format(len(diff.modified_categories)))
                for name, mod in sorted(diff.modified_categories.items()):
                    change = []
                    if mod.added_aliases:
                        change.append("{0} Added Aliases".format(len(mod.added_aliases)))
                    if mod.removed_aliases:
                        change.append("{0} Removed Aliases".format(len(mod.removed_aliases)))

                    print("      * {0} ({1})".format(name, ", ".join(change)))
                    print("          Aliases:")
                    for a in sorted(mod.added_aliases):
                        print("          + {0}".format(a))
                    for a in sorted(mod.removed_aliases):
                        print("          - {0}".format(a))

            print()

    if all_differences or args.sensitivity:
        if diff.added_sensitivities or diff.removed_sensitivities or diff.modified_sensitivities \
                or args.sensitivity:
            print("Sensitivities ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_sensitivities), len(diff.removed_sensitivities),
                len(diff.modified_sensitivities)))
            if diff.added_sensitivities and not args.stats:
                print("   Added Sensitivites: {0}".format(len(diff.added_sensitivities)))
                for s in sorted(diff.added_sensitivities):
                    print("      + {0}".format(s))
            if diff.removed_sensitivities and not args.stats:
                print("   Removed Sensitivities: {0}".format(len(diff.removed_sensitivities)))
                for s in sorted(diff.removed_sensitivities):
                    print("      - {0}".format(s))
            if diff.modified_sensitivities and not args.stats:
                print("   Modified Sensitivities: {0}".format(len(diff.modified_sensitivities)))
                for name, mod in sorted(diff.modified_sensitivities.items()):
                    change = []
                    if mod.added_aliases:
                        change.append("{0} Added Aliases".format(len(mod.added_aliases)))
                    if mod.removed_aliases:
                        change.append("{0} Removed Aliases".format(len(mod.removed_aliases)))

                    print("      * {0} ({1})".format(name, ", ".join(change)))
                    print("          Aliases:")
                    for a in sorted(mod.added_aliases):
                        print("          + {0}".format(a))
                    for a in sorted(mod.removed_aliases):
                        print("          - {0}".format(a))

            print()

    if all_differences or args.level:
        if diff.added_levels or diff.removed_levels or \
                diff.modified_levels or args.level:
            print("Levels ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_levels), len(diff.removed_levels),
                len(diff.modified_levels)))
            if diff.added_levels and not args.stats:
                print("   Added Levels: {0}".format(len(diff.added_levels)))
                for l in sorted(diff.added_levels):
                    print("      + {0}".format(l))
            if diff.removed_levels and not args.stats:
                print("   Removed Levels: {0}".format(len(diff.removed_levels)))
                for l in sorted(diff.removed_levels):
                    print("      - {0}".format(l))
            if diff.modified_levels and not args.stats:
                print("   Modified Levels: {0}".format(len(diff.modified_levels)))
                for level, added_categories, removed_categories, _ in sorted(diff.modified_levels,
                                                                             key=lambda x: x.level):
                    change = []
                    if added_categories:
                        change.append("{0} Added Categories".format(len(added_categories)))
                    if removed_categories:
                        change.append("{0} Removed Categories".format(len(removed_categories)))

                    print("      * {0} ({1})".format(level.sensitivity, ", ".join(change)))
                    for c in sorted(added_categories):
                        print("          + {0}".format(c))
                    for c in sorted(removed_categories):
                        print("          - {0}".format(c))
            print()

    if all_differences or args.allow:
        if diff.added_allows or diff.removed_allows or diff.modified_allows or args.allow:
            print("Allow Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_allows), len(diff.removed_allows), len(diff.modified_allows)))

            if diff.added_allows and not args.stats:
                print("   Added Allow Rules: {0}".format(len(diff.added_allows)))
                for r in sorted(diff.added_allows):
                    print("      + {0}".format(r))

            if diff.removed_allows and not args.stats:
                print("   Removed Allow Rules: {0}".format(len(diff.removed_allows)))
                for r in sorted(diff.removed_allows):
                    print("      - {0}".format(r))

            if diff.modified_allows and not args.stats:
                print("   Modified Allow Rules: {0}".format(len(diff.modified_allows)))

                for rule, added_perms, removed_perms, matched_perms in sorted(diff.modified_allows,
                                                                              key=lambda x: x.rule):
                    perms = " ".join(chain((p for p in matched_perms),
                                           ("+" + p for p in added_perms),
                                           ("-" + p for p in removed_perms)))
                    rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} {{ {1} }};".format(
                        rule, perms)

                    try:
                        rule_string += " [ {0} ]".format(rule.conditional)
                    except AttributeError:
                        pass
                    print("      * {0}".format(rule_string))

            print()

    if all_differences or args.allowxperm:
        if diff.added_allowxperms or diff.removed_allowxperms or diff.modified_allowxperms \
                or args.allowxperm:

            print("Allowxperm Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_allowxperms), len(diff.removed_allowxperms),
                len(diff.modified_allowxperms)))

            if diff.added_allowxperms and not args.stats:
                print("   Added Allowxperm Rules: {0}".format(len(diff.added_allowxperms)))
                for r in sorted(diff.added_allowxperms):
                    print("      + {0}".format(r))

            if diff.removed_allowxperms and not args.stats:
                print("   Removed Allowxperm Rules: {0}".format(len(diff.removed_allowxperms)))
                for r in sorted(diff.removed_allowxperms):
                    print("      - {0}".format(r))

            if diff.modified_allowxperms and not args.stats:
                print("   Modified Allowxperm Rules: {0}".format(len(diff.modified_allowxperms)))

                for rule, added_perms, removed_perms, matched_perms in sorted(
                        diff.modified_allowxperms, key=lambda x: x.rule):

                    # 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 matched_perms:
                        for p in str(matched_perms).split(" "):
                            perms.append(p)
                    if added_perms:
                        for p in str(added_perms).split(" "):
                            if '-' in p:
                                perms.append("+[{0}]".format(p))
                            else:
                                perms.append("+{0}".format(p))
                    if removed_perms:
                        for p in str(removed_perms).split(" "):
                            if '-' in p:
                                perms.append("-[{0}]".format(p))
                            else:
                                perms.append("-{0}".format(p))

                    rule_string = \
                        "{0.ruletype} {0.source} {0.target}:{0.tclass} {0.xperm_type} {{ {1} }};". \
                        format(rule, perms)

                    print("      * {0.ruletype} {0.source} {0.target}:{0.tclass} {0.xperm_type} "
                          "{{ {1} }};".format(rule, " ".join(perms)))

            print()

    if all_differences or args.neverallow:
        if diff.added_neverallows or diff.removed_neverallows or diff.modified_neverallows or \
                args.neverallow:
            print("Neverallow Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_neverallows), len(diff.removed_neverallows),
                len(diff.modified_neverallows)))

            if diff.added_neverallows and not args.stats:
                print("   Added Neverallow Rules: {0}".format(len(diff.added_neverallows)))
                for r in sorted(diff.added_neverallows):
                    print("      + {0}".format(r))

            if diff.removed_neverallows and not args.stats:
                print("   Removed Neverallow Rules: {0}".format(len(diff.removed_neverallows)))
                for r in sorted(diff.removed_neverallows):
                    print("      - {0}".format(r))

            if diff.modified_neverallows and not args.stats:
                print("   Modified Neverallow Rules: {0}".format(len(diff.modified_neverallows)))

                for rule, added_perms, removed_perms, matched_perms in sorted(
                        diff.modified_neverallows, key=lambda x: x.rule):
                    perms = " ".join(chain((p for p in matched_perms),
                                           ("+" + p for p in added_perms),
                                           ("-" + p for p in removed_perms)))
                    rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} {{ {1} }};".format(
                        rule, perms)

                    try:
                        rule_string += " [ {0} ]".format(rule.conditional)
                    except AttributeError:
                        pass
                    print("      * {0}".format(rule_string))

            print()

    if all_differences or args.neverallowxperm:
        if diff.added_neverallowxperms or diff.removed_neverallowxperms or \
                diff.modified_neverallowxperms or args.neverallowxperm:

            print("Neverallowxperm Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_neverallowxperms), len(diff.removed_neverallowxperms),
                len(diff.modified_neverallowxperms)))

            if diff.added_neverallowxperms and not args.stats:
                print("   Added Neverallowxperm Rules: {0}".format(
                      len(diff.added_neverallowxperms)))
                for r in sorted(diff.added_neverallowxperms):
                    print("      + {0}".format(r))

            if diff.removed_neverallowxperms and not args.stats:
                print("   Removed Neverallowxperm Rules: {0}".format(
                      len(diff.removed_neverallowxperms)))
                for r in sorted(diff.removed_neverallowxperms):
                    print("      - {0}".format(r))

            if diff.modified_neverallowxperms and not args.stats:
                print("   Modified Neverallowxperm Rules: {0}".format(
                      len(diff.modified_neverallowxperms)))

                for rule, added_perms, removed_perms, matched_perms in sorted(
                        diff.modified_neverallowxperms, key=lambda x: x.rule):

                    # 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 matched_perms:
                        for p in str(matched_perms).split(" "):
                            perms.append(p)
                    if added_perms:
                        for p in str(added_perms).split(" "):
                            if '-' in p:
                                perms.append("+[{0}]".format(p))
                            else:
                                perms.append("+{0}".format(p))
                    if removed_perms:
                        for p in str(removed_perms).split(" "):
                            if '-' in p:
                                perms.append("-[{0}]".format(p))
                            else:
                                perms.append("-{0}".format(p))

                    rule_string = \
                        "{0.ruletype} {0.source} {0.target}:{0.tclass} {0.xperm_type} {{ {1} }};". \
                        format(rule, perms)

                    print("      * {0.ruletype} {0.source} {0.target}:{0.tclass} {0.xperm_type} "
                          "{{ {1} }};".format(rule, " ".join(perms)))

            print()

    if all_differences or args.auditallow:
        if diff.added_auditallows or diff.removed_auditallows or diff.modified_auditallows or \
                args.auditallow:
            print("Auditallow Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_auditallows), len(diff.removed_auditallows),
                len(diff.modified_auditallows)))

            if diff.added_auditallows and not args.stats:
                print("   Added Auditallow Rules: {0}".format(len(diff.added_auditallows)))
                for r in sorted(diff.added_auditallows):
                    print("      + {0}".format(r))

            if diff.removed_auditallows and not args.stats:
                print("   Removed Auditallow Rules: {0}".format(len(diff.removed_auditallows)))
                for r in sorted(diff.removed_auditallows):
                    print("      - {0}".format(r))

            if diff.modified_auditallows and not args.stats:
                print("   Modified Auditallow Rules: {0}".format(len(diff.modified_auditallows)))

                for rule, added_perms, removed_perms, matched_perms in sorted(
                        diff.modified_auditallows, key=lambda x: x.rule):
                    perms = " ".join(chain((p for p in matched_perms),
                                           ("+" + p for p in added_perms),
                                           ("-" + p for p in removed_perms)))
                    rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} {{ {1} }};".format(
                        rule, perms)

                    try:
                        rule_string += " [ {0} ]".format(rule.conditional)
                    except AttributeError:
                        pass
                    print("      * {0}".format(rule_string))

            print()

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

            print("Auditallowxperm Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_auditallowxperms), len(diff.removed_auditallowxperms),
                len(diff.modified_auditallowxperms)))

            if diff.added_auditallowxperms and not args.stats:
                print("   Added Auditallowxperm Rules: {0}".format(
                      len(diff.added_auditallowxperms)))
                for r in sorted(diff.added_auditallowxperms):
                    print("      + {0}".format(r))

            if diff.removed_auditallowxperms and not args.stats:
                print("   Removed Auditallowxperm Rules: {0}".format(
                      len(diff.removed_auditallowxperms)))
                for r in sorted(diff.removed_auditallowxperms):
                    print("      - {0}".format(r))

            if diff.modified_auditallowxperms and not args.stats:
                print("   Modified Auditallowxperm Rules: {0}".format(
                      len(diff.modified_auditallowxperms)))

                for rule, added_perms, removed_perms, matched_perms in sorted(
                        diff.modified_auditallowxperms, key=lambda x: x.rule):

                    # 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 matched_perms:
                        for p in str(matched_perms).split(" "):
                            perms.append(p)
                    if added_perms:
                        for p in str(added_perms).split(" "):
                            if '-' in p:
                                perms.append("+[{0}]".format(p))
                            else:
                                perms.append("+{0}".format(p))
                    if removed_perms:
                        for p in str(removed_perms).split(" "):
                            if '-' in p:
                                perms.append("-[{0}]".format(p))
                            else:
                                perms.append("-{0}".format(p))

                    rule_string = \
                        "{0.ruletype} {0.source} {0.target}:{0.tclass} {0.xperm_type} {{ {1} }};". \
                        format(rule, perms)

                    print("      * {0.ruletype} {0.source} {0.target}:{0.tclass} {0.xperm_type} "
                          "{{ {1} }};".format(rule, " ".join(perms)))

            print()

    if all_differences or args.dontaudit:
        if diff.added_dontaudits or diff.removed_dontaudits or diff.modified_dontaudits or \
                args.dontaudit:
            print("Dontaudit Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_dontaudits), len(diff.removed_dontaudits),
                len(diff.modified_dontaudits)))

            if diff.added_dontaudits and not args.stats:
                print("   Added Dontaudit Rules: {0}".format(len(diff.added_dontaudits)))
                for r in sorted(diff.added_dontaudits):
                    print("      + {0}".format(r))

            if diff.removed_dontaudits and not args.stats:
                print("   Removed Dontaudit Rules: {0}".format(len(diff.removed_dontaudits)))
                for r in sorted(diff.removed_dontaudits):
                    print("      - {0}".format(r))

            if diff.modified_dontaudits and not args.stats:
                print("   Modified Dontaudit Rules: {0}".format(len(diff.modified_dontaudits)))

                for rule, added_perms, removed_perms, matched_perms in sorted(
                        diff.modified_dontaudits, key=lambda x: x.rule):
                    perms = " ".join(chain((p for p in matched_perms),
                                           ("+" + p for p in added_perms),
                                           ("-" + p for p in removed_perms)))
                    rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} {{ {1} }};".format(
                        rule, perms)

                    try:
                        rule_string += " [ {0} ]".format(rule.conditional)
                    except AttributeError:
                        pass
                    print("      * {0}".format(rule_string))

            print()

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

            print("Dontauditxperm Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_dontauditxperms), len(diff.removed_dontauditxperms),
                len(diff.modified_dontauditxperms)))

            if diff.added_dontauditxperms and not args.stats:
                print("   Added Dontauditxperm Rules: {0}".format(
                      len(diff.added_dontauditxperms)))
                for r in sorted(diff.added_dontauditxperms):
                    print("      + {0}".format(r))

            if diff.removed_dontauditxperms and not args.stats:
                print("   Removed Dontauditxperm Rules: {0}".format(
                      len(diff.removed_dontauditxperms)))
                for r in sorted(diff.removed_dontauditxperms):
                    print("      - {0}".format(r))

            if diff.modified_dontauditxperms and not args.stats:
                print("   Modified Dontauditxperm Rules: {0}".format(
                      len(diff.modified_dontauditxperms)))

                for rule, added_perms, removed_perms, matched_perms in sorted(
                        diff.modified_dontauditxperms, key=lambda x: x.rule):

                    # 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 matched_perms:
                        for p in str(matched_perms).split(" "):
                            perms.append(p)
                    if added_perms:
                        for p in str(added_perms).split(" "):
                            if '-' in p:
                                perms.append("+[{0}]".format(p))
                            else:
                                perms.append("+{0}".format(p))
                    if removed_perms:
                        for p in str(removed_perms).split(" "):
                            if '-' in p:
                                perms.append("-[{0}]".format(p))
                            else:
                                perms.append("-{0}".format(p))

                    rule_string = \
                        "{0.ruletype} {0.source} {0.target}:{0.tclass} {0.xperm_type} {{ {1} }};". \
                        format(rule, perms)

                    print("      * {0.ruletype} {0.source} {0.target}:{0.tclass} {0.xperm_type} "
                          "{{ {1} }};".format(rule, " ".join(perms)))

            print()

    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:
            print("Type_transition Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_type_transitions), len(diff.removed_type_transitions),
                len(diff.modified_type_transitions)))

            if diff.added_type_transitions and not args.stats:
                print("   Added Type_transition Rules: {0}".format(
                    len(diff.added_type_transitions)))
                for r in sorted(diff.added_type_transitions):
                    print("      + {0}".format(r))

            if diff.removed_type_transitions and not args.stats:
                print("   Removed Type_transition Rules: {0}".format(
                    len(diff.removed_type_transitions)))
                for r in sorted(diff.removed_type_transitions):
                    print("      - {0}".format(r))

            if diff.modified_type_transitions and not args.stats:
                print("   Modified Type_transition Rules: {0}".format(
                    len(diff.modified_type_transitions)))

                for rule, added_default, removed_default in sorted(diff.modified_type_transitions,
                                                                   key=lambda x: x.rule):
                    rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} +{1} -{2}".format(
                        rule, added_default, removed_default)

                    try:
                        rule_string += " {0}".format(rule.filename)
                    except AttributeError:
                        pass

                    rule_string += ";"

                    try:
                        rule_string += " [ {0} ]".format(rule.conditional)
                    except AttributeError:
                        pass

                    print("      * {0}".format(rule_string))

            print()

    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:
            print("Type_change Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_type_changes), len(diff.removed_type_changes),
                len(diff.modified_type_changes)))

            if diff.added_type_changes and not args.stats:
                print("   Added Type_change Rules: {0}".format(len(diff.added_type_changes)))
                for r in sorted(diff.added_type_changes):
                    print("      + {0}".format(r))

            if diff.removed_type_changes and not args.stats:
                print("   Removed Type_change Rules: {0}".format(len(diff.removed_type_changes)))
                for r in sorted(diff.removed_type_changes):
                    print("      - {0}".format(r))

            if diff.modified_type_changes and not args.stats:
                print("   Modified Type_change Rules: {0}".format(len(diff.modified_type_changes)))

                for rule, added_default, removed_default in sorted(diff.modified_type_changes,
                                                                   key=lambda x: x.rule):
                    rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} +{1} -{2}".format(
                        rule, added_default, removed_default)

                    try:
                        rule_string += " {0}".format(rule.filename)
                    except AttributeError:
                        pass

                    rule_string += ";"

                    try:
                        rule_string += " [ {0} ]".format(rule.conditional)
                    except AttributeError:
                        pass

                    print("      * {0}".format(rule_string))

            print()

    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:
            print("Type_member Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_type_members), len(diff.removed_type_members),
                len(diff.modified_type_members)))

            if diff.added_type_members and not args.stats:
                print("   Added Type_member Rules: {0}".format(len(diff.added_type_members)))
                for r in sorted(diff.added_type_members):
                    print("      + {0}".format(r))

            if diff.removed_type_members and not args.stats:
                print("   Removed Type_member Rules: {0}".format(len(diff.removed_type_members)))
                for r in sorted(diff.removed_type_members):
                    print("      - {0}".format(r))

            if diff.modified_type_members and not args.stats:
                print("   Modified Type_member Rules: {0}".format(len(diff.modified_type_members)))

                for rule, added_default, removed_default in sorted(diff.modified_type_members,
                                                                   key=lambda x: x.rule):
                    rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} +{1} -{2}".format(
                        rule, added_default, removed_default)

                    try:
                        rule_string += " {0}".format(rule.filename)
                    except AttributeError:
                        pass

                    rule_string += ";"

                    try:
                        rule_string += " [ {0} ]".format(rule.conditional)
                    except AttributeError:
                        pass

                    print("      * {0}".format(rule_string))

            print()

    if all_differences or args.role_allow:
        if diff.added_role_allows or diff.removed_role_allows or args.role_allow:
            print("Role allow Rules ({0} Added, {1} Removed)".format(
                len(diff.added_role_allows), len(diff.removed_role_allows)))

            if diff.added_role_allows and not args.stats:
                print("   Added Role Allow Rules: {0}".format(
                    len(diff.added_role_allows)))
                for r in sorted(diff.added_role_allows):
                    print("      + {0}".format(r))

            if diff.removed_role_allows and not args.stats:
                print("   Removed Role Allow Rules: {0}".format(
                    len(diff.removed_role_allows)))
                for r in sorted(diff.removed_role_allows):
                    print("      - {0}".format(r))

            print()

    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:
            print("Role_transition Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_role_transitions), len(diff.removed_role_transitions),
                len(diff.modified_role_transitions)))

            if diff.added_role_transitions and not args.stats:
                print("   Added Role_transition Rules: {0}".format(
                    len(diff.added_role_transitions)))
                for r in sorted(diff.added_role_transitions):
                    print("      + {0}".format(r))

            if diff.removed_role_transitions and not args.stats:
                print("   Removed Role_transition Rules: {0}".format(
                    len(diff.removed_role_transitions)))
                for r in sorted(diff.removed_role_transitions):
                    print("      - {0}".format(r))

            if diff.modified_role_transitions and not args.stats:
                print("   Modified Role_transition Rules: {0}".format(
                    len(diff.modified_role_transitions)))

                for rule, added_default, removed_default in sorted(diff.modified_role_transitions,
                                                                   key=lambda x: x.rule):
                    rule_string = \
                        "{0.ruletype} {0.source} {0.target}:{0.tclass} +{1} -{2}".format(
                            rule, added_default, removed_default)

                    print("      * {0}".format(rule_string))

            print()

    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:
            print("Range_transition Rules ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_range_transitions), len(diff.removed_range_transitions),
                len(diff.modified_range_transitions)))

            if diff.added_range_transitions and not args.stats:
                print("   Added Range_transition Rules: {0}".format(
                    len(diff.added_range_transitions)))
                for r in sorted(diff.added_range_transitions):
                    print("      + {0}".format(r))

            if diff.removed_range_transitions and not args.stats:
                print("   Removed Range_transition Rules: {0}".format(
                    len(diff.removed_range_transitions)))
                for r in sorted(diff.removed_range_transitions):
                    print("      - {0}".format(r))

            if diff.modified_range_transitions and not args.stats:
                print("   Modified Range_transition Rules: {0}".format(
                    len(diff.modified_range_transitions)))

                for rule, added_default, removed_default in sorted(diff.modified_range_transitions,
                                                                   key=lambda x: x.rule):
                    # added brackets around range change for clarity since ranges
                    # can have '-' and spaces.
                    rule_string = \
                        "{0.ruletype} {0.source} {0.target}:{0.tclass} +[{1}] -[{2}]".format(
                            rule, added_default, removed_default)

                    print("      * {0}".format(rule_string))

            print()

    if all_differences or args.constrain:
        if diff.added_constrains or diff.removed_constrains or args.constrain:
            print("Constraints ({0} Added, {1} Removed)".format(
                len(diff.added_constrains), len(diff.removed_constrains)))

            if diff.added_constrains and not args.stats:
                print("   Added Constraints: {0}".format(
                    len(diff.added_constrains)))
                for r in sorted(diff.added_constrains):
                    print("      + {0}".format(r))

            if diff.removed_constrains and not args.stats:
                print("   Removed Constraints: {0}".format(
                    len(diff.removed_constrains)))
                for r in sorted(diff.removed_constrains):
                    print("      - {0}".format(r))

            print()

    if all_differences or args.mlsconstrain:
        if diff.added_mlsconstrains or diff.removed_mlsconstrains or args.mlsconstrain:
            print("MLS Constraints ({0} Added, {1} Removed)".format(
                len(diff.added_mlsconstrains), len(diff.removed_mlsconstrains)))

            if diff.added_mlsconstrains and not args.stats:
                print("   Added MLS Constraints: {0}".format(
                    len(diff.added_mlsconstrains)))
                for r in sorted(diff.added_mlsconstrains):
                    print("      + {0}".format(r))

            if diff.removed_mlsconstrains and not args.stats:
                print("   Removed MLS Constraints: {0}".format(
                    len(diff.removed_mlsconstrains)))
                for r in sorted(diff.removed_mlsconstrains):
                    print("      - {0}".format(r))

            print()

    if all_differences or args.validatetrans:
        if diff.added_validatetrans or diff.removed_validatetrans or args.validatetrans:
            print("Validatetrans ({0} Added, {1} Removed)".format(
                len(diff.added_validatetrans), len(diff.removed_validatetrans)))

            if diff.added_validatetrans and not args.stats:
                print("   Added Validatetrans: {0}".format(
                    len(diff.added_validatetrans)))
                for r in sorted(diff.added_validatetrans):
                    print("      + {0}".format(r))

            if diff.removed_validatetrans and not args.stats:
                print("   Removed Validatetrans: {0}".format(
                    len(diff.removed_validatetrans)))
                for r in sorted(diff.removed_validatetrans):
                    print("      - {0}".format(r))

            print()

    if all_differences or args.mlsvalidatetrans:
        if diff.added_mlsvalidatetrans or diff.removed_mlsvalidatetrans or args.mlsvalidatetrans:
            print("MLS Validatetrans ({0} Added, {1} Removed)".format(
                len(diff.added_mlsvalidatetrans), len(diff.removed_mlsvalidatetrans)))

            if diff.added_mlsvalidatetrans and not args.stats:
                print("   Added MLS Validatetrans: {0}".format(
                    len(diff.added_mlsvalidatetrans)))
                for r in sorted(diff.added_mlsvalidatetrans):
                    print("      + {0}".format(r))

            if diff.removed_mlsvalidatetrans and not args.stats:
                print("   Removed MLS Validatetrans: {0}".format(
                    len(diff.removed_mlsvalidatetrans)))
                for r in sorted(diff.removed_mlsvalidatetrans):
                    print("      - {0}".format(r))

            print()

    if all_differences or args.initialsid:
        if diff.added_initialsids or diff.removed_initialsids or diff.modified_initialsids \
                or args.initialsid:
            print("Initial SIDs ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_initialsids), len(diff.removed_initialsids),
                len(diff.modified_initialsids)))
            if diff.added_initialsids and not args.stats:
                print("   Added Initial SIDs: {0}".format(len(diff.added_initialsids)))
                for s in sorted(diff.added_initialsids):
                    print("      + {0}".format(s.statement()))
            if diff.removed_initialsids and not args.stats:
                print("   Removed Initial SIDs: {0}".format(len(diff.removed_initialsids)))
                for s in sorted(diff.removed_initialsids):
                    print("      - {0}".format(s.statement()))
            if diff.modified_initialsids and not args.stats:
                print("   Modified Initial SIDs: {0}".format(len(diff.modified_initialsids)))
                for name, mod in sorted(diff.modified_initialsids.items()):
                    print("      * {0} +[{1.added_context}] -[{1.removed_context}];".format(
                          name, mod))

            print()

    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:
            print("Fs_use ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_fs_uses), len(diff.removed_fs_uses),
                len(diff.modified_fs_uses)))
            if diff.added_fs_uses and not args.stats:
                print("   Added Fs_use: {0}".format(len(diff.added_fs_uses)))
                for s in sorted(diff.added_fs_uses):
                    print("      + {0}".format(s))
            if diff.removed_fs_uses and not args.stats:
                print("   Removed Fs_use: {0}".format(len(diff.removed_fs_uses)))
                for s in sorted(diff.removed_fs_uses):
                    print("      - {0}".format(s))
            if diff.modified_fs_uses and not args.stats:
                print("   Modified Fs_use: {0}".format(len(diff.modified_fs_uses)))
                for entry in sorted(diff.modified_fs_uses, key=lambda x: x.rule):
                    print("      * {0.ruletype} {0.fs} +[{1}] -[{2}];".format(
                          entry.rule, entry.added_context, entry.removed_context))

            print()

    if all_differences or args.genfscon:
        if diff.added_genfscons or diff.removed_genfscons or diff.modified_genfscons \
                or args.genfscon:
            print("Genfscons ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_genfscons), len(diff.removed_genfscons),
                len(diff.modified_genfscons)))
            if diff.added_genfscons and not args.stats:
                print("   Added Genfscons: {0}".format(len(diff.added_genfscons)))
                for s in sorted(diff.added_genfscons):
                    print("      + {0}".format(s))
            if diff.removed_genfscons and not args.stats:
                print("   Removed Genfscons: {0}".format(len(diff.removed_genfscons)))
                for s in sorted(diff.removed_genfscons):
                    print("      - {0}".format(s))
            if diff.modified_genfscons and not args.stats:
                print("   Modified Genfscons: {0}".format(len(diff.modified_genfscons)))
                for entry in sorted(diff.modified_genfscons, key=lambda x: x.rule):
                    print("      * genfscon {0.fs} {0.path} {0.filetype} +[{1}] -[{2}];".format(
                          entry.rule, entry.added_context, entry.removed_context))

            print()

    if all_differences or args.netifcon:
        if diff.added_netifcons or diff.removed_netifcons or \
                diff.modified_netifcons or args.netifcon:
            print("Netifcons ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_netifcons), len(diff.removed_netifcons),
                len(diff.modified_netifcons)))
            if diff.added_netifcons and not args.stats:
                print("   Added Netifcons: {0}".format(len(diff.added_netifcons)))
                for n in sorted(diff.added_netifcons):
                    print("      + {0}".format(n))
            if diff.removed_netifcons and not args.stats:
                print("   Removed Netifcons: {0}".format(len(diff.removed_netifcons)))
                for n in sorted(diff.removed_netifcons):
                    print("      - {0}".format(n))
            if diff.modified_netifcons and not args.stats:
                print("   Modified Netifcons: {0}".format(len(diff.modified_netifcons)))
                for entry in sorted(diff.modified_netifcons, key=lambda x: x.rule):
                    # 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 entry.removed_context:
                        change.append("Modified Context")
                    if entry.removed_packet:
                        change.append("Modified Packet Context")

                    print("      * {0.netif} ({1})".format(entry.rule, ", ".join(change)))

                    if entry.removed_context:
                        print("          Context:")
                        print("             + {0}".format(entry.added_context))
                        print("             - {0}".format(entry.removed_context))
                    if entry.removed_packet:
                        print("          Packet Context:")
                        print("             + {0}".format(entry.added_packet))
                        print("             - {0}".format(entry.removed_packet))

            print()

    if all_differences or args.nodecon:
        if diff.added_nodecons or diff.removed_nodecons or diff.modified_nodecons \
                or args.nodecon:
            print("Nodecons ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_nodecons), len(diff.removed_nodecons),
                len(diff.modified_nodecons)))
            if diff.added_nodecons and not args.stats:
                print("   Added Nodecons: {0}".format(len(diff.added_nodecons)))
                for n in sorted(diff.added_nodecons):
                    print("      + {0}".format(n))
            if diff.removed_nodecons and not args.stats:
                print("   Removed Nodecons: {0}".format(len(diff.removed_nodecons)))
                for n in sorted(diff.removed_nodecons):
                    print("      - {0}".format(n))
            if diff.modified_nodecons and not args.stats:
                print("   Modified Nodecons: {0}".format(len(diff.modified_nodecons)))
                for con, added_context, removed_context in sorted(diff.modified_nodecons,
                                                                  key=lambda x: x.rule):
                    print("      * nodecon {0.address} {0.netmask} +[{1}] -[{2}];".format(
                          con, added_context, removed_context))

            print()

    if all_differences or args.portcon:
        if diff.added_portcons or diff.removed_portcons or diff.modified_portcons \
                or args.portcon:
            print("Portcons ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_portcons), len(diff.removed_portcons),
                len(diff.modified_portcons)))
            if diff.added_portcons and not args.stats:
                print("   Added Portcons: {0}".format(len(diff.added_portcons)))
                for n in sorted(diff.added_portcons):
                    print("      + {0}".format(n))
            if diff.removed_portcons and not args.stats:
                print("   Removed Portcons: {0}".format(len(diff.removed_portcons)))
                for n in sorted(diff.removed_portcons):
                    print("      - {0}".format(n))
            if diff.modified_portcons and not args.stats:
                print("   Modified Portcons: {0}".format(len(diff.modified_portcons)))
                for con, added_context, removed_context in sorted(diff.modified_portcons,
                                                                  key=lambda x: x.rule):
                    low, high = con.ports
                    if low == high:
                        print("      * portcon {0.protocol} {1} +[{2}] -[{3}];".format(
                              con, low, added_context, removed_context))
                    else:
                        print("      * portcon {0.protocol} {1}-{2} +[{3}] -[{4}];".format(
                              con, low, high, added_context, removed_context))

            print()

    if all_differences or args.polcap:
        if diff.added_polcaps or diff.removed_polcaps or args.polcap:
            print("Policy Capabilities ({0} Added, {1} Removed)".format(
                len(diff.added_polcaps), len(diff.removed_polcaps)))
            if diff.added_polcaps and not args.stats:
                print("   Added Policy Capabilities: {0}".format(len(diff.added_polcaps)))
                for n in sorted(diff.added_polcaps):
                    print("      + {0}".format(n))
            if diff.removed_polcaps and not args.stats:
                print("   Removed Policy Capabilities: {0}".format(len(diff.removed_polcaps)))
                for n in sorted(diff.removed_polcaps):
                    print("      - {0}".format(n))

            print()

    if all_differences or args.default:
        if diff.added_defaults or diff.removed_defaults or args.default:
            print("Defaults ({0} Added, {1} Removed, {2} Modified)".format(
                len(diff.added_defaults), len(diff.removed_defaults), len(diff.modified_defaults)))
            if diff.added_defaults and not args.stats:
                print("   Added Defaults: {0}".format(len(diff.added_defaults)))
                for d in sorted(diff.added_defaults):
                    print("      + {0}".format(d))
            if diff.removed_defaults and not args.stats:
                print("   Removed Defaults: {0}".format(len(diff.removed_defaults)))
                for d in sorted(diff.removed_defaults):
                    print("      - {0}".format(d))
            if diff.modified_defaults and not args.stats:
                print("   Modified Defaults: {0}".format(len(diff.modified_defaults)))
                for default, added_default, removed_default, added_range, removed_range in sorted(
                        diff.modified_defaults, key=lambda x: x.rule):
                    line = "      * {0.ruletype} {0.tclass} ".format(default)
                    if removed_default:
                        line += "+{0} -{1}".format(added_default, removed_default)
                    else:
                        line += default.default

                    if removed_range:
                        line += " +{0} -{1};".format(added_range, removed_range)
                    else:
                        try:
                            line += " {0};".format(default.default_range)
                        except AttributeError:
                            line += ";"
                    print(line)

            print()

    if all_differences or args.typebounds:
        if diff.added_typebounds or diff.removed_typebounds or args.typebounds:
            print("Typebounds ({0} Added, {1} Removed, {2} Modified)".format(
                  len(diff.added_typebounds), len(diff.removed_typebounds),
                  len(diff.modified_typebounds)))
            if diff.added_typebounds and not args.stats:
                print("   Added Typebounds: {0}".format(len(diff.added_typebounds)))
                for d in sorted(diff.added_typebounds):
                    print("      + {0}".format(d))
            if diff.removed_typebounds and not args.stats:
                print("   Removed Typebounds: {0}".format(len(diff.removed_typebounds)))
                for d in sorted(diff.removed_typebounds):
                    print("      - {0}".format(d))
            if diff.modified_typebounds and not args.stats:
                print("   Modified Typebounds: {0}".format(len(diff.modified_typebounds)))
                for bound, added_bound, removed_bound in sorted(
                        diff.modified_typebounds, key=lambda x: x.rule):
                    print("      * {0.ruletype} +{1} -{2} {0.child};".format(
                          bound, added_bound, removed_bound))

            print()

except Exception as err:
    if args.debug:
        logging.exception(str(err))
    else:
        print(err)

    sys.exit(1)
