# Created By: Virgil Dupras
# Created On: 2006/05/02
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html

from xml.etree import ElementTree as ET

from hscommon.util import FileOrPath


class IgnoreList:
    """An ignore list implementation that is iterable, filterable and exportable to XML.

    Call Ignore to add an ignore list entry, and AreIgnore to check if 2 items are in the list.
    When iterated, 2 sized tuples will be returned, the tuples containing 2 items ignored together.
    """

    # ---Override
    def __init__(self):
        self.clear()

    def __iter__(self):
        for first, seconds in self._ignored.items():
            for second in seconds:
                yield (first, second)

    def __len__(self):
        return self._count

    # ---Public
    def are_ignored(self, first, second):
        def do_check(first, second):
            try:
                matches = self._ignored[first]
                return second in matches
            except KeyError:
                return False

        return do_check(first, second) or do_check(second, first)

    def clear(self):
        self._ignored = {}
        self._count = 0

    def filter(self, func):
        """Applies a filter on all ignored items, and remove all matches where func(first,second)
        doesn't return True.
        """
        filtered = IgnoreList()
        for first, second in self:
            if func(first, second):
                filtered.ignore(first, second)
        self._ignored = filtered._ignored
        self._count = filtered._count

    def ignore(self, first, second):
        if self.are_ignored(first, second):
            return
        try:
            matches = self._ignored[first]
            matches.add(second)
        except KeyError:
            try:
                matches = self._ignored[second]
                matches.add(first)
            except KeyError:
                matches = set()
                matches.add(second)
                self._ignored[first] = matches
        self._count += 1

    def remove(self, first, second):
        def inner(first, second):
            try:
                matches = self._ignored[first]
                if second in matches:
                    matches.discard(second)
                    if not matches:
                        del self._ignored[first]
                    self._count -= 1
                    return True
                else:
                    return False
            except KeyError:
                return False

        if not inner(first, second) and not inner(second, first):
            raise ValueError()

    def load_from_xml(self, infile):
        """Loads the ignore list from a XML created with save_to_xml.

        infile can be a file object or a filename.
        """
        try:
            root = ET.parse(infile).getroot()
        except Exception:
            return
        file_elems = (e for e in root if e.tag == "file")
        for fn in file_elems:
            file_path = fn.get("path")
            if not file_path:
                continue
            subfile_elems = (e for e in fn if e.tag == "file")
            for sfn in subfile_elems:
                subfile_path = sfn.get("path")
                if subfile_path:
                    self.ignore(file_path, subfile_path)

    def save_to_xml(self, outfile):
        """Create a XML file that can be used by load_from_xml.

        outfile can be a file object or a filename.
        """
        root = ET.Element("ignore_list")
        for filename, subfiles in self._ignored.items():
            file_node = ET.SubElement(root, "file")
            file_node.set("path", filename)
            for subfilename in subfiles:
                subfile_node = ET.SubElement(file_node, "file")
                subfile_node.set("path", subfilename)
        tree = ET.ElementTree(root)
        with FileOrPath(outfile, "wb") as fp:
            tree.write(fp, encoding="utf-8")
