"""
Combined NFS exports
====================

The NFS exports files are normally available to rules from both a single
NFSExports object and zero or more NFSExportsD objects.  This combiner turns
those into one set of data.

Examples:
    >>> type(all_nfs)
    <class 'insights.combiners.nfs_exports.AllNFSExports'>
    >>> all_nfs.files  # List of files exporting NFS shares
    ['/etc/exports', '/etc/exports.d/mnt.exports']
    >>> '/home/example' in all_nfs.exports  # All exports stored by path
    True
    >>> sorted(all_nfs.exports['/home/example'].keys())  # Each path is a dictionary of host specs and flags.
    ['@group', 'ins1.example.com', 'ins2.example.com']
    >>> all_nfs.exports['/home/example']['ins2.example.com']  # Each host contains a list of flags.
    ['rw', 'sync', 'no_root_squash']
    >>> '/home/example' in all_nfs.ignored_exports  # Ignored exports are remembered within one file
    True
    >>> list(all_nfs.ignored_exports['/home/example'].keys())  # Each ignored export is then stored by source file...
    ['/etc/exports']
    >>> list(all_nfs.ignored_exports['/home/example']['/etc/exports'].keys())  # ... and then by host spec...
    ['ins2.example.com']
    >>> all_nfs.ignored_exports['/home/example']['/etc/exports']['ins2.example.com']  # ... holding the values that were duplicated
    ['rw', 'sync', 'no_root_squash']
    >>> '/home/insights/shared/rw'  in all_nfs.ignored_exports  # Ignored exports are remembered across files
    True

"""
from insights.core.plugins import combiner
from insights.parsers.nfs_exports import NFSExports, NFSExportsD

try:
    from six.moves import collections_abc
except ImportError:
    import collections as collections_abc


@combiner(NFSExports, optional=[NFSExportsD])
class AllNFSExports(object):
    """
    Combiner for accessing all the NFS export configuration files.

    Exports are allowed to be listed multiple times, with all duplicate
    host after the first causing ``exportfs`` to emit a warning and ignore
    the host.  So we combine the raw lines and ignored exports into structures
    listing the source file for each

    Attributes:
        files (list): the list of source files that contained NFS export
            definitions.

        exports (dict of dicts): the NFS exports stored by export path, with
            each path storing a dictionary of host flag lists.

        ignored_exports (dict): A dictionary of exported paths that have
            host definitions that conflicted with a previous definition,
            stored by export path and then path of the file that defined it.

        raw_lines (dict of dicts): A dictionary of raw lines that define each
            exported path, with the lines stored by defining file.

    """
    def __init__(self, nfsexports, nfsexportsd):
        self.files = []
        self.exports = {}
        self.ignored_exports = {}
        self.raw_lines = {}

        sources = [nfsexports]
        # Make sure exports are stored in the order they're parsed -
        # alphabetically by file name.  Ignore it if nfsexportsd isn't valid.
        if isinstance(nfsexportsd, collections_abc.Iterable):
            sources.extend(sorted(nfsexportsd, key=lambda f: f.file_path))

        def add_paths_to_dict(src_path, src_dict, dest_dict):
            # Because ignored_exports and raw_lines are stored by path, we
            # keep that and add the paths in them piecewise, stored by the
            # path of this source file.
            for path, value in src_dict.items():
                if path not in dest_dict:
                    dest_dict[path] = {src_path: value}
                else:
                    dest_dict[path][src_path] = value

        for source in sources:
            self.files.append(source.file_path)
            # Add all raw lines from the source to raw lines.
            add_paths_to_dict(source.file_path, source.raw_lines, self.raw_lines)
            # Likewise all the ignored exports from this file.
            add_paths_to_dict(source.file_path, source.ignored_exports, self.ignored_exports)
            # For exports though we have to preserve existing host definitions
            # and ignore repeated host specs.
            for path, hosts in source.data.items():
                if path in self.exports:
                    # Check whether the host specification has been listed
                    # more than once across different files:
                    for host, flags in hosts.items():
                        if host in self.exports[path]:
                            # Add this to the ignored_exports list.  But
                            # because we know that this path-host tuple isn't
                            # duplicated in this file (it's already in
                            # ignored_exports if it does), then we can assume
                            # that the file isn't in the common ignored_exports
                            # already.
                            if path in self.ignored_exports:
                                self.ignored_exports[path][host] = flags
                            else:
                                self.ignored_exports[path] = {host: flags}
                        else:
                            # A new host spec - add as is.
                            self.exports[path][host] = flags
                else:
                    # No exports for this path so far, add them all
                    self.exports[path] = hosts

        super(AllNFSExports, self).__init__()
