# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
# globals: CSSRule

from __python__ import bound_methods, hash_literals


INHERITED_PROPS = {  # {{{
		'azimuth': '2',
		'border-collapse': '2',
		'border-spacing': '2',
		'caption-side': '2',
		'color': '2',
		'cursor': '2',
		'direction': '2',
		'elevation': '2',
		'empty-cells': '2',
		'fit': '3',
		'fit-position': '3',
		'font': '2',
		'font-family': '2',
		'font-size': '2',
		'font-size-adjust': '2',
		'font-stretch': '2',
		'font-style': '2',
		'font-variant': '2',
		'font-weight': '2',
		'hanging-punctuation': '3',
		'hyphenate-after': '3',
		'hyphenate-before': '3',
		'hyphenate-character': '3',
		'hyphenate-lines': '3',
		'hyphenate-resource': '3',
		'hyphens': '3',
		'image-resolution': '3',
		'letter-spacing': '2',
		'line-height': '2',
		'line-stacking': '3',
		'line-stacking-ruby': '3',
		'line-stacking-shift': '3',
		'line-stacking-strategy': '3',
		'list-style': '2',
		'list-style-image': '2',
		'list-style-position': '2',
		'list-style-type': '2',
		'marquee-direction': '3',
		'orphans': '2',
		'overflow-style': '3',
		'page': '2',
		'page-break-inside': '2',
		'pitch': '2',
		'pitch-range': '2',
		'presentation-level': '3',
		'punctuation-trim': '3',
		'quotes': '2',
		'richness': '2',
		'ruby-align': '3',
		'ruby-overhang': '3',
		'ruby-position': '3',
		'speak': '2',
		'speak-header': '2',
		'speak-numeral': '2',
		'speak-punctuation': '2',
		'speech-rate': '2',
		'stress': '2',
		'text-align': '2',
		'text-align-last': '3',
		'text-emphasis': '3',
		'text-height': '3',
		'text-indent': '2',
		'text-justify': '3',
		'text-outline': '3',
		'text-replace': '?',
		'text-shadow': '3',
		'text-transform': '2',
		'text-wrap': '3',
		'visibility': '2',
		'voice-balance': '3',
		'voice-family': '2',
		'voice-rate': '3',
		'voice-pitch': '3',
		'voice-pitch-range': '3',
		'voice-stress': '3',
		'voice-volume': '3',
		'volume': '2',
		'white-space': '2',
		'white-space-collapse': '3',
		'widows': '2',
		'word-break': '3',
		'word-spacing': '2',
		'word-wrap': '3',
        # the mozilla extensions are all proprietary properties
		'-moz-force-broken-image-icon': 'm',
		'-moz-image-region': 'm',
		'-moz-stack-sizing': 'm',
		'-moz-user-input': 'm',
		'-x-system-font': 'm',
        # the opera extensions are all draft implementations of CSS3 properties
		'-xv-voice-balance': 'o',
		'-xv-voice-pitch': 'o',
		'-xv-voice-pitch-range': 'o',
		'-xv-voice-rate': 'o',
		'-xv-voice-stress': 'o',
		'-xv-voice-volume': 'o',
        # the explorer extensions are all draft implementations of CSS3 properties
		'-ms-text-align-last': 'e',
		'-ms-text-justify': 'e',
		'-ms-word-break': 'e',
		'-ms-word-wrap': 'e'
}  # }}}


def get_sourceline_address(node):
    sourceline = parseInt(node.dataset.lnum)
    tags = []
    for elem in document.querySelectorAll(f'[data-lnum="{sourceline}"]'):
        tags.push(elem.tagName.toLowerCase())
        if elem is node:
            break
    return [sourceline, tags]


def get_color(property, val):
    color = None
    if property.indexOf('color') > -1:
        try:
            color = window.parseCSSColor(val)  # Use the csscolor library to get an rgba 4-tuple
        except:
            pass
    return color


def get_style_properties(style, all_properties, node_style, is_ancestor):
    i = 0
    properties = []
    while i < style.length:
        property = style.item(i)
        if property:
            property = property.toLowerCase()
            val = style.getPropertyValue(property)
        else:
            val = None
        if property and val and (not is_ancestor or INHERITED_PROPS[property]):
            properties.push([property, val, style.getPropertyPriority(property), get_color(property, val)])
            if not all_properties[property]:
                cval = node_style.getPropertyValue(property)
                cval
                all_properties[property] = [cval, get_color(property, cval)]
        i += 1
    return properties


def get_sheet_rules(sheet):
    if sheet.disabled or not sheet.cssRules:
        return []
    sheet_media = sheet.media and sheet.media.mediaText
    if sheet_media and sheet_media.length and not window.matchMedia(sheet_media).matches:
        return []
    return sheet.cssRules


def selector_matches(node, selector):
    try:
        return node.matches(selector)
    except:
        # happens if the selector uses epub|type
        if 'epub|' in selector:
            sanitized_sel = str.replace(selector, 'epub|', '*|')
            try:
                # TODO: Actually parse the selector and extract the attribute
                # and check it manually
                return node.matches(sanitized_sel)
            except:
                return False
        return False


def process_rules(node, rules, address, sheet, sheet_index, all_properties, node_style, is_ancestor, ans):
    offset = 0
    for rule_index in range(rules.length):
        rule = rules[rule_index]
        rule_address = address.concat([rule_index - offset])
        if rule.type is CSSRule.MEDIA_RULE:
            process_rules(node, rule.cssRules, rule_address, sheet, sheet_index, all_properties, node_style, is_ancestor, ans)
            continue
        if rule.type is not CSSRule.STYLE_RULE:
            if rule.type is CSSRule.NAMESPACE_RULE:
                offset += 1
            continue
        st = rule.selectorText
        if not selector_matches(node, st):
            continue
        type = 'sheet'
        href = sheet.href
        if not href:
            href = get_sourceline_address(sheet.ownerNode)
            type = 'elem'
        # Technically, we should check for the highest specificity
        # matching selector, but we cheat and use the first one
        parts = st.split(',')
        if parts.length > 1:
            for q in parts:
                if selector_matches(node, q):
                    st = q
                    break
        properties = get_style_properties(rule.style, all_properties, node_style, is_ancestor)
        if properties.length > 0:
            data = {'selector': st, 'type': type, 'href': href, 'properties': properties, 'rule_address': rule_address, 'sheet_index': sheet_index}
            ans.push(data)


def get_matched_css(node, is_ancestor, all_properties):
    ans = []
    node_style = window.getComputedStyle(node)

    sheets = document.styleSheets
    for sheet_index in range(sheets.length):
        sheet = sheets[sheet_index]
        process_rules(node, get_sheet_rules(sheet), [], sheet, sheet_index, all_properties, node_style, is_ancestor, ans)

    if node.getAttribute('style'):
        properties = get_style_properties(node.style, all_properties, node_style, is_ancestor)
        if properties.length > 0:
            data = {
                'selector': None,
                'type': 'inline',
                'href': get_sourceline_address(node),
                'properties': properties,
                'rule_address': None,
                'sheet_index': None,
            }
            ans.push(data)

    return ans.reverse()
