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

"""Parses snipMate files."""

import os
import glob

from UltiSnips import _vim
from UltiSnips.snippet.definition import SnipMateSnippetDefinition
from UltiSnips.snippet.source.file._base import SnippetFileSource
from UltiSnips.snippet.source.file._common import handle_extends
from UltiSnips.text import LineIterator, head_tail


def _splitall(path):
    """Split 'path' into all its components."""
    # From http://my.safaribooksonline.com/book/programming/
    # python/0596001673/files/pythoncook-chp-4-sect-16
    allparts = []
    while True:
        parts = os.path.split(path)
        if parts[0] == path:  # sentinel for absolute paths
            allparts.insert(0, parts[0])
            break
        elif parts[1] == path:  # sentinel for relative paths
            allparts.insert(0, parts[1])
            break
        else:
            path = parts[0]
            allparts.insert(0, parts[1])
    return allparts


def snipmate_files_for(ft):
    """Returns all snipMate files we need to look at for 'ft'."""
    if ft == 'all':
        ft = '_'
    patterns = [
        '%s.snippets' % ft,
        os.path.join(ft, '*.snippets'),
        os.path.join(ft, '*.snippet'),
        os.path.join(ft, '*/*.snippet'),
    ]
    ret = set()
    for rtp in _vim.eval('&runtimepath').split(','):
        path = os.path.realpath(os.path.expanduser(
            os.path.join(rtp, 'snippets')))
        for pattern in patterns:
            for fn in glob.glob(os.path.join(path, pattern)):
                ret.add(fn)
    return ret


def _parse_snippet_file(content, full_filename):
    """Parses 'content' assuming it is a .snippet file and yields events."""
    filename = full_filename[:-len('.snippet')]  # strip extension
    segments = _splitall(filename)
    segments = segments[segments.index('snippets') + 1:]
    assert len(segments) in (2, 3)

    trigger = segments[1]
    description = segments[2] if 2 < len(segments) else ''

    # Chomp \n if any.
    if content and content.endswith(os.linesep):
        content = content[:-len(os.linesep)]
    yield 'snippet', (SnipMateSnippetDefinition(trigger, content,
                                                description, full_filename),)


def _parse_snippet(line, lines, filename):
    """Parse a snippet defintions."""
    start_line_index = lines.line_index
    trigger, description = head_tail(line[len('snippet'):].lstrip())
    content = ''
    while True:
        next_line = lines.peek()
        if next_line is None:
            break
        if next_line.strip() and not next_line.startswith('\t'):
            break
        line = next(lines)
        if line[0] == '\t':
            line = line[1:]
        content += line
    content = content[:-1]  # Chomp the last newline
    return 'snippet', (SnipMateSnippetDefinition(
        trigger, content, description, '%s:%i' % (filename, start_line_index)),)


def _parse_snippets_file(data, filename):
    """Parse 'data' assuming it is a .snippets file.

    Yields events in the file.

    """
    lines = LineIterator(data)
    for line in lines:
        if not line.strip():
            continue

        head, tail = head_tail(line)
        if head == 'extends':
            yield handle_extends(tail, lines.line_index)
        elif head in 'snippet':
            snippet = _parse_snippet(line, lines, filename)
            if snippet is not None:
                yield snippet
        elif head and not head.startswith('#'):
            yield 'error', ('Invalid line %r' % line.rstrip(), lines.line_index)


class SnipMateFileSource(SnippetFileSource):

    """Manages all snipMate snippet definitions found in rtp."""

    def _get_all_snippet_files_for(self, ft):
        return snipmate_files_for(ft)

    def _parse_snippet_file(self, filedata, filename):
        if filename.lower().endswith('snippet'):
            for event, data in _parse_snippet_file(filedata, filename):
                yield event, data
        else:
            for event, data in _parse_snippets_file(filedata, filename):
                yield event, data
