from itertools import chain, repeat
import sublime
from sublime import Region
from sublime_plugin import EventListener

# String that must be found in the syntax setting of the current view
# to active this plugin.
SYNTAX = 'Asciidoc'

# Selector that specifies the scope in which completions may be activated.
ADOC_SCOPE = 'text.asciidoc - source'

# Selector that specifies the scope in which attribute completions may
# be activated.
ATTR_SCOPE = 'variable.other'

# Scope of the attribute name in the attribute entry.
ATTR_ENTRY_SCOPE = 'support.variable.attribute.asciidoc'

# Selector that specifies the scope in which cross reference completions may
# be activated.
XREF_SCOPE = 'meta.xref.asciidoc'

# Scope of the anchor ID.
ANCHOR_SCOPE = 'markup.underline.blockid.id.asciidoc'

# Scope of the section titles.
SEC_TITLE_SCOPE = 'entity.name.section.asciidoc'

# Name of the plugin's settings file.
SETTINGS_NAME = 'Asciidoctor.sublime-settings'

# Global list of built-in attributes to display in the completion list.
builtin_attrs = []


def plugin_loaded():
    """ Called by SublimeText when the plugin is loaded. """
    global builtin_attrs
    settings = sublime.load_settings(SETTINGS_NAME)
    builtin_attrs = [(item, 'built-in')
                     for item in sorted(settings.get('built_in_attributes'))]


class AsciidocAttributeCompletions(EventListener):

    def on_query_completions(self, view, prefix, locations):
        """ Called by SublimeText when auto-complete pop-up box appears. """

        if SYNTAX not in view.settings().get('syntax'):
            return None
        if not all(self.should_trigger(view, loc) for loc in locations):
            return None

        local_attrs = (
            (attr, 'local') for attr, lno in self.declared_attrs(view)
            if attr not in builtin_attrs and min(cursors_line_num(view)) > lno)

        return (filter_completions(prefix, local_attrs, builtin_attrs),
                sublime.INHIBIT_WORD_COMPLETIONS)

    def should_trigger(self, view, point):
        """ Return True if completions should be triggered at the given point. """
        return (view.match_selector(point, ATTR_SCOPE) or
                view.match_selector(point, ADOC_SCOPE) and lsubstr(view, point) in [':', '{'])

    def declared_attrs(self, view):
        """ Get attributes declared in the document.

        Yields:
            Tuple of attribute name and line number where it's first declared.
        """
        return sorted(
            {view.substr(region): view.rowcol(region.end())[0]
                for region in reversed(view.find_by_selector(ATTR_ENTRY_SCOPE))}
            .items())


class AsciidocCrossReferenceCompletions(EventListener):

    def on_query_completions(self, view, prefix, locations):
        """ Called by SublimeText when auto-complete pop-up box appears. """

        if SYNTAX not in view.settings().get('syntax'):
            return None
        if not all(self.should_trigger(view, loc) for loc in locations):
            return None

        anchors = zip(find_by_scope(view, ANCHOR_SCOPE), repeat('anchor'))
        titles = zip(find_by_scope(view, SEC_TITLE_SCOPE), repeat('title'))

        return sorted(filter_completions(prefix, anchors, titles),
                      key=lambda t: t[0].lower())

    def should_trigger(self, view, point):
        """ Return True if completions should be triggered at the given point. """
        return (view.match_selector(point, XREF_SCOPE) or
                view.match_selector(point, ADOC_SCOPE) and lsubstr(view, point, 2) == '<<')


def filter_completions(prefix, *data):
    """ Filter completions that starts with the given prefix and format them
    for the completions list.

    Arguments:
        prefix (str):
        *data: An iterable with tuples of a trigger (content) and a hint (text
            showed on the right side of the trigger).
    """
    return (("%s\t%s" % (content, hint), content)
            for content, hint in chain(*data) if content.startswith(prefix))


def cursors_line_num(view):
    """ Return list of 0-based line numbers of the cursor(s). """
    return [view.rowcol(region.b)[0] for region in view.sel()]


def find_by_scope(view, selector):
    """ Find all substrings in the file matching the given scope selector. """
    return map(view.substr, view.find_by_selector(selector))


def lsubstr(view, point, length=1):
    """ Return the character(s) to the left of the point on the same line. """
    col = view.rowcol(point)[1]
    region = Region(point - min(length, col), point)
    return view.substr(region)