#!/usr/bin/env python
# encoding: utf-8

"""Parsing of snippet files."""

from collections import defaultdict

class _LineIterator(object):
    """Convenience class that keeps track of line numbers."""

    def __init__(self, text):
        self._line_index = None
        self._lines = enumerate(text.splitlines(True), 1)

    def __iter__(self):
        return self

    def __next__(self):
        """Returns the next line."""
        self._line_index, line = next(self._lines)
        return line
    next = __next__  # for python2

    @property
    def line_index(self):
        """The 1 based line index in the current file."""
        return self._line_index

def _handle_snippet_or_global(line, lines, globals):
    """Parses the snippet that begins at the current line."""

    descr = ""
    opts = ""

    # Ensure this is a snippet
    snip = line.split()[0]

    # Get and strip options if they exist
    remain = line[len(snip):].strip()
    words = remain.split()
    if len(words) > 2:
        # second to last word ends with a quote
        if '"' not in words[-1] and words[-2][-1] == '"':
            opts = words[-1]
            remain = remain[:-len(opts) - 1].rstrip()

    # Get and strip description if it exists
    remain = remain.strip()
    if len(remain.split()) > 1 and remain[-1] == '"':
        left = remain[:-1].rfind('"')
        if left != -1 and left != 0:
            descr, remain = remain[left:], remain[:left]

    # The rest is the trigger
    trig = remain.strip()
    if len(trig.split()) > 1 or "r" in opts:
        if trig[0] != trig[-1]:
            return "error", ("Invalid multiword trigger: '%s'" % trig,
                    lines.line_index)
        trig = trig[1:-1]
    end = "end" + snip
    content = ""

    found_end = False
    for line in lines:
        if line.rstrip() == end:
            content = content[:-1]  # Chomp the last newline
            found_end = True
            break
        content += line

    if not found_end:
        return "error", ("Missing 'endsnippet' for %r" % trig, lines.line_index)

    if snip == "global":
        globals[trig].append(content)
    elif snip == "snippet":
        return "snippet", (trig, content, descr, opts, globals)
    else:
        return "error", ("Invalid snippet type: '%s'" % snip, lines.line_index)

def _head_tail(line):
    """Returns the first word in 'line' and the rest of 'line' or None if the
    line is too short."""
    generator = (t.strip() for t in line.split(None, 1))
    head = next(generator).strip()
    tail = ''
    try:
        tail = next(generator).strip()
    except StopIteration:
        pass
    return head, tail


def parse_snippets_file(data):
    """Parse 'data' assuming it is a snippet file. Yields events in the
    file."""

    globals = defaultdict(list)
    lines = _LineIterator(data)
    for line in lines:
        if not line.strip():
            continue

        head, tail = _head_tail(line)
        if head == "extends":
            if tail:
                yield "extends", ([p.strip() for p in tail.split(',')],)
            else:
                yield "error", ("'extends' without file types",
                        lines.line_index)
        elif head in ("snippet", "global"):
            snippet = _handle_snippet_or_global(line, lines, globals)
            if snippet is not None:
                yield snippet
        elif head == "clearsnippets":
            yield "clearsnippets", (tail.split(),)
        elif head == "priority":
            try:
                priority = int(tail.split()[0])
                yield "priority", (priority,)
            except (ValueError, IndexError):
                yield "error", ("Invalid priority %r" % tail, lines.line_index)
        elif head and not head.startswith('#'):
            yield "error", ("Invalid line %r" % line.rstrip(), lines.line_index)
