# 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 = v'[]'
    for elem in document.querySelectorAll(f'[data-lnum="{sourceline}"]'):
        tags.push(elem.tagName.toLowerCase())
        if elem is node:
            break
    return v'[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 = v'[]'
    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(v'[property, val, style.getPropertyPriority(property), get_color(property, val)]')
            if not all_properties[property]:
                cval = node_style.getPropertyValue(property)
                cval
                all_properties[property] = v'[cval, get_color(property, cval)]'
        i += 1
    return properties


def get_sheet_rules(sheet):
    if sheet.disabled or not sheet.cssRules:
        return v'[]'
    sheet_media = sheet.media and sheet.media.mediaText
    if sheet_media and sheet_media.length and not window.matchMedia(sheet_media).matches:
        return v'[]'
    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(v'[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 = v'[]'
    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), v'[]', 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()
