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

from elementmaker import E

from book_list.theme import get_color
from dom import set_css, ensure_id, clear, build_rule, add_extra_css

MODAL_Z_INDEX = 1000
POPUP_Z_INDEX = MODAL_Z_INDEX + 1
popup_count = 0

shown_popups = set()
associated_widgets = {}


def element_contains_click_event(element, event):
    r = element.getBoundingClientRect()
    return r.left <= event.clientX <= r.right and r.top <= event.clientY <= r.bottom


def click_in_popup(event):
    for popup_id in shown_popups:
        popup = document.getElementById(popup_id)
        if popup and element_contains_click_event(popup, event):
            return True
        w = associated_widgets[popup_id]
        if w and w.length:
            for wid in w:
                widget = document.getElementById(wid)
                if widget and element_contains_click_event(widget, event):
                    return True
    return False


def filter_clicks(event):
    if shown_popups.length:
        if not click_in_popup(event):
            for popup_id in shown_popups:
                hide_popup(popup_id)
            shown_popups.clear()
            event.stopPropagation(), event.preventDefault()


def install_event_filters():
    window.addEventListener('click', filter_clicks, True)


def create_popup(parent, idprefix):
    nonlocal popup_count
    popup_count += 1
    pid = (idprefix or 'popup') + '-' + popup_count
    # Position has to be fixed so that setting style.top/style.bottom works in
    # viewport coordinates
    div = E.div(id=pid, style='display: none; position: fixed; z-index: {}'.format(POPUP_Z_INDEX))
    parent = parent or document.body
    parent.appendChild(div)
    return div


def show_popup(popup_id, associated_widget_ids=None):
    elem = document.getElementById(popup_id)
    elem.style.display = 'block'
    shown_popups.add(popup_id)
    associated_widgets[popup_id] = associated_widget_ids


def hide_popup(popup_id):
    elem = document.getElementById(popup_id)
    if elem:
        elem.style.display = 'none'
    v'delete associated_widgets[popup_id]'


class CompletionPopup:
    CLASS = 'popup-completion-items'
    CURRENT_ITEM_CLASS = 'popup-completion-current-item'

    def __init__(self, parent=None, max_items=25, onselect=None):
        self.max_items = max_items
        c = create_popup(parent)
        set_css(c, user_select='none')
        self.container_id = c.getAttribute('id')
        self.onselect = onselect
        self.items = []
        self.matches = []
        c.appendChild(E.div(class_=self.CLASS))
        self.associated_widget_ids = set()
        self.current_query, self.is_upwards = '', False
        self.applied_query = self.current_query

    @property
    def container(self):
        return document.getElementById(self.container_id)

    @property
    def is_visible(self):
        return self.container.style.display is not 'none'

    def set_all_items(self, items):
        self.items = list(items)
        self.matches = []
        self.applied_query = None

    def add_associated_widget(self, widget_or_id):
        if jstype(widget_or_id) is not 'string':
            widget_or_id = ensure_id(widget_or_id)
        self.associated_widget_ids.add(widget_or_id)

    def popup(self, widget):
        if not self.is_visible:
            if self.applied_query is not self.current_query:
                self._apply_query()
            if self.matches.length:
                self.show_at_widget(widget)

    def show_at_widget(self, w):
        br = w.getBoundingClientRect()
        if br.top > window.innerHeight - br.bottom:
            y, upwards = br.top, True
        else:
            y, upwards = br.bottom, False
        self._show_at(br.left, y, br.width, upwards)

    def set_query(self, query):
        self.current_query = query
        if self.is_visible and self.applied_query is not self.current_query:
            self._apply_query()
            if not self.matches.length:
                self.hide()

    def hide(self):
        self.container.style.display = 'none'
        c = self.current_item
        if c:
            c.classList.remove(self.CURRENT_ITEM_CLASS)

    def handle_keydown(self, event):
        key = event.key
        if key is 'Escape' or key is 'Esc':
            self.hide()
            return True
        if key is 'ArrowUp':
            self.move_highlight(True)
            return True
        if key is 'ArrowDown':
            self.move_highlight(False)
            return True
        return False

    @property
    def current_item(self):
        c = self.container
        return c.querySelector('div.{} > div.{}'.format(self.CLASS, self.CURRENT_ITEM_CLASS))

    @property
    def current_text(self):
        return self.current_item?.textContent

    def move_highlight(self, up=None):
        if up is None:
            up = self.is_upwards
        ans = None
        div = self.current_item
        if div:
            div.classList.remove(self.CURRENT_ITEM_CLASS)
            ans = div.previousSibling if up else div.nextSibling
        if not ans:
            c = self.container.firstChild
            ans = c.lastChild if up else c.firstChild
        if ans:
            ans.classList.add(self.CURRENT_ITEM_CLASS)

    def _show_at(self, x, y, width, upwards):
        self.is_upwards = upwards
        c = self.container
        cs = c.style
        cs.left = x + 'px'
        cs.top = 'auto' if upwards else y + 'px'
        cs.bottom = (window.innerHeight - y) + 'px' if upwards else 'auto'
        cs.width = width + 'px'
        cs.maxHeight = ((y if upwards else window.innerHeight - y) - 10) + 'px'
        show_popup(self.container_id, self.associated_widget_ids)

    def _apply_query(self):
        q = self.current_query.toLowerCase()
        self.matches.clear()
        self.applied_query = self.current_query
        if not q:
            self.matches = list(self.items[:self.max_items + 1])
        else:
            i = 0
            while self.matches.length < self.max_items and i < self.items.length:
                if self.items[i].toLowerCase().startswith(q):
                    self.matches.push(self.items[i])
                i += 1
        self._render_matches()

    def _render_matches(self):
        c = self.container
        clear(c.firstChild)
        items = self.matches
        if self.is_upwards:
            items = reversed(items)
        for m in items:
            c.firstChild.appendChild(E.div(m, onmouseenter=self.onmouseenter, onclick=self.onclick))

    def onmouseenter(self, event):
        div = self.current_item
        if div:
            div.classList.remove(self.CURRENT_ITEM_CLASS)
        event.currentTarget.classList.add(self.CURRENT_ITEM_CLASS)

    def onclick(self, event):
        self.onmouseenter(event)
        try:
            if self.onselect:
                self.onselect(self.current_text)
        finally:
            self.hide()

add_extra_css(def ():
    sel = 'div.' + CompletionPopup.CLASS
    style = build_rule(
        sel, overflow='hidden', text_align='left',
        background_color=get_color('window-background'), border='solid 1px ' + get_color('window-foreground')
    )
    sel += ' > div'
    style += build_rule(sel, cursor='pointer', padding='1ex 1rem', white_space='nowrap', text_overflow='ellipsis', overflow='hidden')
    sel += '.' + CompletionPopup.CURRENT_ITEM_CLASS
    style += build_rule(sel, color=get_color('list-hover-foreground'), background_color=get_color('list-hover-background'))
    return style
)
