# Forked from: https://github.com/optilude/SublimeTextMisc/blob/master/navigationHistory.py
import sublime, sublime_plugin
from collections import deque

MAX_SIZE = 64
LINE_THRESHOLD = 2

class Location(object):
    """A location in the history
    """

    def __init__(self, path, line, col):
        self.path = path
        self.line = line
        self.col = col
    
    def __eq__(self, other):
        return self.path == other.path and self.line == other.line
    
    def __ne__(self, other):
        return not self.__eq__(other)
    
    def __nonzero__(self):
        return (self.path is not None and self.line is not None)

    def near(self, other):
        return self.path == other.path and abs(self.line - other.line) <= LINE_THRESHOLD

    def copy(self):
        return Location(self.path, self.line, self.col)

class History(object):
    """Keep track of the history for a single window
    """

    def __init__(self, max_size=MAX_SIZE):
        self._current = None                # current location as far as the
                                            # history is concerned
        self._back = deque([], max_size)    # items before self._current
        self._forward = deque([], max_size) # items after self._current
        
        self._last_movement = None          # last recorded movement
    
    def record_movement(self, location):
        """Record movement to the given location, pushing history if
        applicable
        """

        if location:
            if self.has_changed(location):
                #Push the current location if it hasn't been added already, so when going back after using goto it goes to the correct location
                if self._back and self._last_movement is not self._back[-1]:
                    self.push(self._last_movement)                
                self.push(location)
            self.mark_location(location)

    def mark_location(self, location):
        """Remember the current location, for the purposes of being able
        to do a has_changed() check.
        """
        self._last_movement = location.copy()
    
    def has_changed(self, location):
        """Determine if the given location combination represents a
        significant enough change to warrant pushing history.
        """

        return self._last_movement is None or not self._last_movement.near(location)
    
    def push(self, location):
        """Push the given location to the back history. Clear the forward
        history.
        """

        if self._current is not None:
            self._back.append(self._current.copy())
        self._current = location.copy()
        self._forward.clear()

    def back(self):
        """Move backward in history, returning the location to jump to.
        Returns None if no history.
        """

        if not self._back:
            return None
        
        self._forward.appendleft(self._current)
        self._current = self._back.pop()
        self._last_movement = self._current # preempt, so we don't re-push
        return self._current

    def forward(self):
        """Move forward in history, returning the location to jump to.
        Returns None if no history.
        """

        if not self._forward:
            return None
        
        self._back.append(self._current)
        self._current = self._forward.popleft()
        self._last_movement = self._current # preempt, so we don't re-push
        return self._current

_histories = {} # window id -> History

def get_history():
    """Get a History object for the current window,
    creating a new one if required
    """

    window = sublime.active_window()
    if window is None:
        return None

    window_id = window.id()
    history = _histories.get(window_id, None)
    if history is None:
        _histories[window_id] = history = History()
    return history

class NavigationHistoryRecorder(sublime_plugin.EventListener):
    """Keep track of history
    """

    def on_selection_modified(self, view):
        """When the selection is changed, possibly record movement in the
        history
        """
        history = get_history()
        if history is None:
            return

        path = view.file_name()
        row, col = view.rowcol(view.sel()[0].a)
        history.record_movement(Location(path, row + 1, col + 1))
    
    # def on_close(self, view):
    #     """When a view is closed, check to see if the window was closed too
    #     and clean up orphan histories
    #     """
    #
    #     # XXX: This doesn't work - event runs before window is removed
    #     # from sublime.windows()
    #
    #     windows_with_history = set(_histories.keys())
    #     window_ids = set([w.id() for w in sublime.windows()])
    #     closed_windows = windows_with_history.difference(window_ids)
    #     for window_id in closed_windows:
    #         del _histories[window_id]

class NavigationHistoryBack(sublime_plugin.TextCommand):
    """Go back in history
    """

    def run(self, edit):
        history = get_history()
        if history is None:
            return

        location = history.back()
        if location:
            window = sublime.active_window()
            window.open_file("{0}:{1}:{2}".format(location.path, location.line, location.col), sublime.ENCODED_POSITION)

class NavigationHistoryForward(sublime_plugin.TextCommand):
    """Go forward in history
    """

    def run(self, edit):
        history = get_history()
        if history is None:
            return

        location = history.forward()
        if location:
            window = sublime.active_window()
            window.open_file("{0}:{1}:{2}".format(location.path, location.line, location.col), sublime.ENCODED_POSITION)