"""
Implements a special view to visualize and stage pieces of a project's
current diff.
"""

import os
import re
import bisect

import sublime
from sublime_plugin import WindowCommand, TextCommand, EventListener

from .navigate import GsNavigate
from ..git_command import GitCommand
from ..exceptions import GitSavvyError
from ...common import util


DIFF_TITLE = "DIFF: {}"
DIFF_CACHED_TITLE = "DIFF (cached): {}"


diff_views = {}


class GsDiffCommand(WindowCommand, GitCommand):

    """
    Create a new view to display the difference of `target_commit`
    against `base_commit`. If `target_commit` is None, compare
    working directory with `base_commit`.  If `in_cached_mode` is set,
    display a diff of the Git index. Set `disable_stage` to True to
    disable Ctrl-Enter in the diff view.
    """

    def run(self, **kwargs):
        sublime.set_timeout_async(lambda: self.run_async(**kwargs), 0)

    def run_async(self, in_cached_mode=False, file_path=None, current_file=False, base_commit=None,
                  target_commit=None, disable_stage=False, title=None):
        repo_path = self.repo_path
        if current_file:
            file_path = self.file_path or file_path

        view_key = "{0}{1}+{2}".format(
            in_cached_mode,
            "-" if base_commit is None else "--" + base_commit,
            file_path or repo_path
        )
        savvy_settings = sublime.load_settings("GitSavvy.sublime-settings")

        if view_key in diff_views and diff_views[view_key] in sublime.active_window().views():
            diff_view = diff_views[view_key]
        else:
            diff_view = util.view.get_scratch_view(self, "diff", read_only=True)
            if not title:
                title = (DIFF_CACHED_TITLE if in_cached_mode else DIFF_TITLE).format(os.path.basename(repo_path))
            diff_view.set_name(title)
            diff_view.set_syntax_file("Packages/GitSavvy/syntax/diff.sublime-syntax")
            diff_view.settings().set("git_savvy.repo_path", repo_path)
            diff_view.settings().set("git_savvy.file_path", file_path)
            diff_view.settings().set("git_savvy.diff_view.in_cached_mode", in_cached_mode)
            diff_view.settings().set("git_savvy.diff_view.ignore_whitespace", False)
            diff_view.settings().set("git_savvy.diff_view.show_word_diff", False)
            diff_view.settings().set("git_savvy.diff_view.base_commit", base_commit)
            diff_view.settings().set("git_savvy.diff_view.target_commit", target_commit)
            diff_view.settings().set("git_savvy.diff_view.show_diffstat", savvy_settings.get("show_diffstat", True))
            diff_view.settings().set("git_savvy.diff_view.disable_stage", disable_stage)
            diff_views[view_key] = diff_view

        self.window.focus_view(diff_view)
        diff_view.sel().clear()
        diff_view.run_command("gs_diff_refresh")
        diff_view.run_command("gs_diff_navigate")
        diff_view.run_command("gs_handle_vintageous")


class GsDiffRefreshCommand(TextCommand, GitCommand):

    """
    Refresh the diff view with the latest repo state.
    """

    def run(self, edit, cursors=None):
        if self.view.settings().get("git_savvy.disable_diff"):
            return
        in_cached_mode = self.view.settings().get("git_savvy.diff_view.in_cached_mode")
        ignore_whitespace = self.view.settings().get("git_savvy.diff_view.ignore_whitespace")
        show_word_diff = self.view.settings().get("git_savvy.diff_view.show_word_diff")
        base_commit = self.view.settings().get("git_savvy.diff_view.base_commit")
        target_commit = self.view.settings().get("git_savvy.diff_view.target_commit")
        show_diffstat = self.view.settings().get("git_savvy.diff_view.show_diffstat")

        try:
            stdout = self.git(
                "diff",
                "--ignore-all-space" if ignore_whitespace else None,
                "--word-diff" if show_word_diff else None,
                "--stat" if show_diffstat else None,
                "--patch",
                "--no-color",
                "--cached" if in_cached_mode else None,
                base_commit,
                target_commit,
                "--", self.file_path)
        except GitSavvyError as err:
            # When the output of the above Git command fails to correctly parse,
            # the expected notification will be displayed to the user.  However,
            # once the userpresses OK, a new refresh event will be triggered on
            # the view.
            #
            # This causes an infinite loop of increasingly frustrating error
            # messages, ultimately resulting in psychosis and serious medical
            # bills.  This is a better, though somewhat cludgy, alternative.
            #
            if err.args and type(err.args[0]) == UnicodeDecodeError: