# coding: utf8
"""
    cssselect2
    ----------

    CSS selectors for ElementTree.

    :copyright: (c) 2012 by Simon Sapin, 2017 by Guillaume Ayoub.
    :license: BSD, see LICENSE for more details.

"""

from __future__ import unicode_literals

import operator

from webencodings import ascii_lower

# Classes are imported here to expose them at the top level of the module
from .compiler import compile_selector_list  # noqa
from .parser import SelectorError  # noqa
from .tree import ElementWrapper  # noqa

VERSION = '0.2.2'


class Matcher(object):
    """A CSS selectors storage that can match against HTML elements."""
    def __init__(self):
        self.id_selectors = {}
        self.class_selectors = {}
        self.lower_local_name_selectors = {}
        self.namespace_selectors = {}
        self.lang_attr_selectors = []
        self.other_selectors = []
        self.order = 0

    def add_selector(self, selector, payload):
        """
        Add a selector and its payload to the matcher.

        :param selector:
            A :class:`CompiledSelector` object.
        :param payload:
            Some data associated to the selector,
            such as :class:`declarations <~tinycss2.ast.Declaration>`
            parsed from the :attr:`~tinycss2.ast.QualifiedRule.content`
            of a style rule.
            It can be any Python object,
            and will be returned as-is by :meth:`match`.

        """
        self.order += 1

        if selector.never_matches:
            return

        entry = (
            selector.test, selector.specificity, self.order,
            selector.pseudo_element, payload)
        if selector.id is not None:
            self.id_selectors.setdefault(selector.id, []).append(entry)
        elif selector.class_name is not None:
            self.class_selectors.setdefault(selector.class_name, []) \
                .append(entry)
        elif selector.local_name is not None:
            self.lower_local_name_selectors.setdefault(
                selector.lower_local_name, []).append(entry)
        elif selector.namespace is not None:
            self.namespace_selectors.setdefault(selector.namespace, []) \
                .append(entry)
        elif selector.requires_lang_attr:
            self.lang_attr_selectors.append(entry)
        else:
            self.other_selectors.append(entry)

    def match(self, element):
        """
        Match selectors against the given element.

        :param element:
            An :class:`ElementWrapper`.
        :returns:
            A list of the :obj:`payload` objects associated
            to selectors that match element,
            in order of lowest to highest :attr:`~CompiledSelector.specificity`
            and in order of addition with :meth:`add_selector`
            among selectors of equal specificity.

        """
        relevant_selectors = []

        if element.id is not None:
            relevant_selectors.append(self.id_selectors.get(element.id, []))

        for class_name in element.classes:
            relevant_selectors.append(self.class_selectors.get(class_name, []))

        relevant_selectors.append(
            self.lower_local_name_selectors.get(
                ascii_lower(element.local_name), []))
        relevant_selectors.append(
            self.namespace_selectors.get(element.namespace_url, []))

        if 'lang' in element.etree_element.attrib:
            relevant_selectors.append(self.lang_attr_selectors)

        relevant_selectors.append(self.other_selectors)

        results = [
            (specificity, order, pseudo, payload)
            for selector_list in relevant_selectors
            for test, specificity, order, pseudo, payload in selector_list
            if test(element)
        ]
        results.sort(key=SORT_KEY)
        return results


SORT_KEY = operator.itemgetter(0, 1)
