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

"""A Snippet instance is an instance of a Snippet Definition.

That is, when the user expands a snippet, a SnippetInstance is created
to keep track of the corresponding TextObjects. The Snippet itself is
also a TextObject.

"""

from UltiSnips import _vim
from UltiSnips.position import Position
from UltiSnips.text_objects._base import EditableTextObject, \
    NoneditableTextObject
from UltiSnips.text_objects._tabstop import TabStop


class SnippetInstance(EditableTextObject):

    """See module docstring."""
    # pylint:disable=protected-access

    def __init__(self, snippet, parent, initial_text,
                 start, end, visual_content, last_re, globals, context):
        if start is None:
            start = Position(0, 0)
        if end is None:
            end = Position(0, 0)
        self.snippet = snippet
        self._cts = 0

        self.context = context
        self.locals = {'match': last_re, 'context': context}
        self.globals = globals
        self.visual_content = visual_content

        EditableTextObject.__init__(self, parent, start, end, initial_text)

    def replace_initial_text(self):
        """Puts the initial text of all text elements into Vim."""
        def _place_initial_text(obj):
            """recurses on the children to do the work."""
            obj.overwrite()
            if isinstance(obj, EditableTextObject):
                for child in obj._children:
                    _place_initial_text(child)
        _place_initial_text(self)

    def replay_user_edits(self, cmds, ctab=None):
        """Replay the edits the user has done to keep endings of our Text
        objects in sync with reality."""
        for cmd in cmds:
            self._do_edit(cmd, ctab)

    def update_textobjects(self):
        """Update the text objects that should change automagically after the
        users edits have been replayed.

        This might also move the Cursor

        """
        vc = _VimCursor(self)
        done = set()
        not_done = set()

        def _find_recursive(obj):
            """Finds all text objects and puts them into 'not_done'."""
            if isinstance(obj, EditableTextObject):
                for child in obj._children:
                    _find_recursive(child)
            not_done.add(obj)
        _find_recursive(self)

        counter = 10
        while (done != not_done) and counter:
            # Order matters for python locals!
            for obj in sorted(not_done - done):
                if obj._update(done):
                    done.add(obj)
            counter -= 1
        if not counter:
            raise RuntimeError(
                'The snippets content did not converge: Check for Cyclic '
                'dependencies or random strings in your snippet. You can use '
                "'if not snip.c' to make sure to only expand random output "
                'once.')
        vc.to_vim()
        self._del_child(vc)

    def select_next_tab(self, backwards=False):
        """Selects the next tabstop or the previous if 'backwards' is True."""
        if self._cts is None:
            return

        if backwards:
            cts_bf = self._cts

            res = self._get_prev_tab(self._cts)
            if res is None:
                self._cts = cts_bf
                return self._tabstops.get(self._cts, None)
            self._cts, ts = res
            return ts
        else:
            res = self._get_next_tab(self._cts)
            if res is None:
                self._cts = None

                ts = self._get_tabstop(self, 0)
                if ts:
                    return ts

                # TabStop 0 was deleted. It was probably killed through some
                # edit action. Recreate it at the end of us.
                start = Position(self.end.line, self.end.col)
                end = Position(self.end.line, self.end.col)
                return TabStop(self, 0, start, end)
            else:
                self._cts, ts = res
                return ts

        return self._tabstops[self._cts]

    def _get_tabstop(self, requester, no):
        # SnippetInstances are completely self contained, therefore, we do not
        # need to ask our parent for Tabstops
        cached_parent = self._parent
        self._parent = None
        rv = EditableTextObject._get_tabstop(self, requester, no)
        self._parent = cached_parent
        return rv

    def get_tabstops(self):
        return self._tabstops


class _VimCursor(NoneditableTextObject):

    """Helper class to keep track of the Vim Cursor when text objects expand
    and move."""

    def __init__(self, parent):
        NoneditableTextObject.__init__(
            self, parent, _vim.buf.cursor, _vim.buf.cursor,
            tiebreaker=Position(-1, -1))

    def to_vim(self):
        """Moves the cursor in the Vim to our position."""
        assert self._start == self._end
        _vim.buf.cursor = self._start
