# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals

from elementmaker import E

from fs_images import fix_fullscreen_svg_images
from live_css import get_matched_css, get_sourceline_address
from qt import from_python, to_python


FAKE_HOST = '__FAKE_HOST__'
FAKE_PROTOCOL = '__FAKE_PROTOCOL__'


def is_hidden(elem):
    while elem:
        if (elem.style and (elem.style.visibility is 'hidden' or elem.style.display is 'none')):
            return True
        elem = elem.parentNode
    return False


def is_block(elem):
    style = window.getComputedStyle(elem)
    return style.display is 'block' or style.display is 'flex-box' or style.display is 'box'


def in_table(elem):
    while elem:
        tn = elem.tagName.toLowerCase() if elem.tagName else ''
        if tn is 'table':
            return True
        elem = elem.parentNode
    return False


def find_containing_block(elem):
    while elem and elem.dataset.isBlock is not '1':
        elem = elem.parentNode
    return elem


def line_height():
    ans = line_height.ans
    if not ans:
        ds = window.getComputedStyle(document.body)
        try:
            # will fail if line-height = "normal"
            lh = float(ds.lineHeight)
        except:
            try:
                lh = 1.2 * float(ds.fontSize)
            except:
                lh = 15
        ans = line_height.ans = max(5, lh)
    return ans


def scroll_to_node(node, sync_context):
    if node is document.body:
        window.scrollTo(0, 0)
    else:
        node.scrollIntoView()
        if sync_context:
            window.scrollBy(0, -sync_context * line_height())


state = {'blocks_found': False, 'in_split_mode': False}


def go_to_line(lnum, sync_context):
    for node in document.querySelectorAll(f'[data-lnum="{lnum}"]'):
        if is_hidden(node):
            continue
        scroll_to_node(node, sync_context)
        break


@from_python
@no_prune
def go_to_sourceline_address(sourceline, tags, sync_context):
    nodes = document.querySelectorAll(f'[data-lnum="{sourceline}"]')
    for index in range(nodes.length):
        node = nodes[index]
        if index >= tags.length or node.tagName.toLowerCase() is not tags[index]:
            break
        if index == tags.length - 1 and not is_hidden(node):
            return scroll_to_node(node, sync_context)
    go_to_line(sourceline, sync_context)


def line_numbers():
    found_body = False
    ans = []
    for node in document.getElementsByTagName('*'):
        if not found_body and node.tagName.toLowerCase() is 'body':
            found_body = True
        if found_body:
            ans.push(node.dataset.lnum)
    return ans


def find_blocks():
    if state.blocks_found:
        return
    for elem in document.body.getElementsByTagName('*'):
        if is_block(elem) and not in_table(elem):
            elem.setAttribute('data-is-block', '1')
    state.blocks_found = True


@from_python
@no_prune
def set_split_mode(enabled):
    state.in_split_mode = enabled
    document.body.dataset.inSplitMode = '1' if enabled else '0'
    if enabled:
        find_blocks()


def report_split(node):
    loc = []
    totals = []
    parent = find_containing_block(node)
    while parent and parent.tagName.toLowerCase() is not 'body':
        totals.push(parent.parentNode.children.length)
        num = 0
        sibling = parent.previousElementSibling
        while sibling:
            num += 1
            sibling = sibling.previousElementSibling
        loc.push(num)
        parent = parent.parentNode
    loc.reverse()
    totals.reverse()
    to_python.request_split(loc, totals)


def onclick(event):
    event.preventDefault()
    if state.in_split_mode:
        report_split(event.target)
    else:
        e = event.target
        address = get_sourceline_address(e)
        # Find the closest containing link, if any
        href = tn = ''
        while e and e is not document.body and e is not document and e is not document.documentElement and (tn is not 'a' or not href):
            tn = e.tagName.toLowerCase() if e.tagName else ''
            href = e.getAttribute('href')
            e = e.parentNode
        if to_python.request_sync:
            to_python.request_sync(tn, href, address)
    return False


@from_python
@no_prune
def go_to_anchor(anchor):
    elem = document.getElementById(anchor)
    if not elem:
        elem = document.querySelector(f'[name="{anchor}"]')
    if elem:
        elem.scrollIntoView()
        address = get_sourceline_address(elem)
        if to_python.request_sync:
            to_python.request_sync('', '', address)


@from_python
@no_prune
def live_css(editor_name, sourceline, tags):
    all_properties = {}
    ans = {'nodes': [], 'computed_css': all_properties, 'editor_name': editor_name, 'sourceline': sourceline, 'tags': tags}
    target = None
    i = 0
    for node in document.querySelectorAll(f'[data-lnum="{sourceline}"]'):
        tn = node.tagName.toLowerCase() if node.tagName else ''
        if tn is not tags[i]:
            if to_python.live_css_data:
                to_python.live_css_data(ans)
            return
        i += 1
        target = node
        if i >= tags.length:
            break
    ancestor_specificity = 0
    while target and target.ownerDocument:
        css = get_matched_css(target, ancestor_specificity is not 0, all_properties)
        # We want to show the Matched CSS rules header even if no rules matched
        if css.length > 0 or ancestor_specificity is 0:
            tn = target.tagName.toLowerCase() if target.tagName else ''
            ans.nodes.push({
                'name': tn,
                'css': css, 'ancestor_specificity': -ancestor_specificity,
                'sourceline': target.getAttribute('data-lnum')
            })
        target = target.parentNode
        ancestor_specificity += 1
    if to_python.live_css_data:
        to_python.live_css_data(ans)


def check_for_maths():
    if document.body.getElementsByTagNameNS('http://www.w3.org/1998/Math/MathML', 'math').length > 0:
        return True
    for s in document.scripts:
        if s.type is 'text/x-mathjax-config':
            return True
    return False


def load_mathjax():
    script = E.script(type='text/javascript')
    script.async = True
    script.src = f'{FAKE_PROTOCOL}://{FAKE_HOST}/calibre_internal-mathjax/startup.js'
    document.head.appendChild(script)


if document.body:
    settings = JSON.parse(window.navigator.userAgent.split('|')[1])
    css = '[data-in-split-mode="1"] [data-is-block="1"]:hover { cursor: pointer !important; border-top: solid 5px green !important }'
    if settings.os is 'macos':
        # See settings.pyj for reason for webkit-hyphenate-character
        css += '\n* { -webkit-hyphenate-character: "-" !important }\n'
    document.body.addEventListener('click', onclick, True)
    document.documentElement.appendChild(E.style(type='text/css', css))

    fix_fullscreen_svg_images()
    if check_for_maths():
        load_mathjax()
