from __future__ import absolute_import, unicode_literals, print_function, division

import codecs
import functools
import tempfile
import os

import sublime
import sublime_plugin
from . import GitTextCommand, GitWindowCommand, plugin_file, view_contents, _make_text_safeish
from .add import GitAddSelectedHunkCommand

history = []


class GitQuickCommitCommand(GitTextCommand):
    def run(self, edit, target=None):
        if target is None:
            # 'target' might also be False, in which case we just don't provide an add argument
            target = self.get_file_name()
        self.get_window().show_input_panel("Message", "",
            functools.partial(self.on_input, target), None, None)

    def on_input(self, target, message):
        if message.strip() == "":
            self.panel("No commit message provided")
            return

        if target:
            command = ['git', 'add']
            if target == '*':
                command.append('--all')
            else:
                command.extend(('--', target))
            self.run_command(command, functools.partial(self.add_done, message))
        else:
            self.add_done(message, "")

    def add_done(self, message, result):
        if result.strip():
            sublime.error_message("Error adding file:\n" + result)
            return
        self.run_command(['git', 'commit', '-m', message])


# Commit is complicated. It'd be easy if I just wanted to let it run
# on OSX, and assume that subl was in the $PATH. However... I can't do
# that. Second choice was to set $GIT_EDITOR to sublime text for the call
#  to commit, and let that Just Work. However, on Windows you can't pass
# -w to sublime, which means the editor won't wait, and so the commit will fail
# with an empty message.
# Thus this flow:
# 1. `status --porcelain --untracked-files=no` to know whether files need
#    to be committed
# 2. `status` to get a template commit message (not the exact one git uses; I
#    can't see a way to ask it to output that, which is not quite ideal)
# 3. Create a scratch buffer containing the template
# 4. When this buffer is closed, get its contents with an event handler and
#    pass execution back to the original command. (I feel that the way this
#    is done is  a total hack. Unfortunately, I cannot see a better way right
#    now.)
# 5. Strip lines beginning with # from the message, and save in a temporary
#    file
# 6. `commit -F [tempfile]`
class GitCommitCommand(GitWindowCommand):
    active_message = False
    extra_options = ""
    quit_when_nothing_staged = True

    def run(self):
        self.lines = []
        self.working_dir = self.get_working_dir()
        self.run_command(
            ['git', 'status', '--untracked-files=no', '--porcelain'],
            self.porcelain_status_done
        )

    def porcelain_status_done(self, result):
        # todo: split out these status-parsing things... asdf
        has_staged_files = False
        result_lines = result.rstrip().split('\n')
        for line in result_lines:
            if line and not line[0].isspace():
                has_staged_files = True
                break
        if not has_staged_files and self.quit_when_nothing_staged:
            self.panel("Nothing to commit")
            return
        # Okay, get the template!
        s = sublime.load_settings("Git.sublime-settings")
        if s.get("verbose_commits"):
            self.run_command(['git', 'diff', '--staged', '--no-color'], self.diff_done)
        else:
            self.run_command(['git', 'status'], self.diff_done)

    def diff_done(self, result):
        settings = sublime.load_settings("Git.sublime-settings")
        historySize = settings.get('history_size')
        rulers = settings.get('commit_rulers')

        def format(line):
            return '# ' + line.replace("\n", " ")

        if not len(self.lines):
            self.lines = ["", ""]

        self.lines.extend(map(format, history[:historySize]))
        self.lines.extend([
            "# --------------",
            "# Please enter the commit message for your changes. Everything below",
            "# this paragraph is ignored, and an empty message aborts the commit.",
            "# Just close the window to accept your message.",
            result.strip()
        ])
        template = "\n".join(self.lines)
        msg = self.window.new_file()
        msg.set_scratch(True)
        msg.set_name("COMMIT_EDITMSG")

        if rulers:
            msg.settings().set('rulers', rulers)

        self._output_to_view(msg, template, syntax=plugin_file("syntax/Git Commit Message.tmLanguage"))
        msg.sel().clear()
        msg.sel().add(sublime.Region(0, 0))
        GitCommitCommand.active_message = self

    def message_done(self, message):
        # filter out the comments (git commit doesn't do this automatically)
        settings = sublime.load_settings("Git.sublime-settings")
        historySize = settings.get('history_size')
        lines = [line for line in message.split("\n# --------------")[0].split("\n")
            if not line.lstrip().startswith('#')]
        message = '\n'.join(lines).strip()

        if len(message) and historySize:
            history.insert(0, message)
        # write the temp file
        message_file = tempfile.NamedTemporaryFile(delete=False)
        message_file.write(_make_text_safeish(message, self.fallback_encoding, 'encode'))
        message_file.close()
        self.message_file = message_file
        # and actually commit
        with codecs.open(message_file.name, mode = 'r', encoding = 'utf-8') as fp:
            self.run_command(['git', 'commit', '-F', '-', self.extra_options],
                self.commit_done, working_dir=self.working_dir, stdin=fp.read())

    def commit_done(self, result, **kwargs):
        os.remove(self.message_file.name)
        self.panel(result)


class GitCommitAmendCommand(GitCommitCommand):
    extra_options = "--amend"
    quit_when_nothing_staged = False

    def diff_done(self, result):
        self.after_show = result
        self.run_command(['git', 'log', '-n', '1', '--format=format:%B'], self.amend_diff_done)

    def amend_diff_done(self, result):
        self.lines = result.split("\n")
        super(GitCommitAmendCommand, self).diff_done(self.after_show)


class GitCommitMessageListener(sublime_plugin.EventListener):
    def on_close(self, view):
        if view.name() != "COMMIT_EDITMSG":
            return
        command = GitCommitCommand.active_message
        if not command:
            return
        message = view_contents(view)
        command.message_done(message)


class GitCommitHistoryCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        self.edit = edit
        if history:
            self.view.window().show_quick_panel(history, self.panel_done, sublime.MONOSPACE_FONT)
        else:
            sublime.message_dialog("You have no commit history.\n\nCommit history is just a quick list of messages you've used in this session.")

    def panel_done(self, index):
        if index > -1:
            self.view.replace(self.edit, self.view.sel()[0], history[index] + '\n')


class GitCommitSelectedHunk(GitAddSelectedHunkCommand):
    def cull_diff(self, result):
        super(GitCommitSelectedHunk, self).cull_diff(result)
        self.get_window().run_command('git_commit')
