# coding: utf8
import os
import re

import sublime
import sublime_plugin
import difflib
import tempfile

from fnmatch import fnmatch
import codecs

SETTINGS = sublime.load_settings('FileDiffs.sublime-settings')

CLIPBOARD = u'Diff file with Clipboard'
SELECTIONS = u'Diff Selections'
SAVED = u'Diff file with Saved'
FILE = u'Diff file with File in Project…'
TAB = u'Diff file with Open Tab…'

FILE_DIFFS = [CLIPBOARD, SAVED, FILE, TAB]


class FileDiffMenuCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        menu_items = FILE_DIFFS[:]
        saved = SAVED
        non_empty_regions = [region for region in self.view.sel() if not region.empty()]
        if len(non_empty_regions) == 2:
            menu_items.insert(1, SELECTIONS)
        elif len(non_empty_regions):
            menu_items = [f.replace(u'Diff file', u'Diff selection') for f in menu_items]
            saved = saved.replace(u'Diff file', u'Diff selection')

        if not (self.view.file_name() and self.view.is_dirty()):
            menu_items.remove(saved)

        def on_done(index):
            restored_menu_items = [f.replace(u'Diff selection', u'Diff file') for f in menu_items]
            if index == -1:
                return
            elif restored_menu_items[index] == CLIPBOARD:
                self.view.run_command('file_diff_clipboard')
            elif restored_menu_items[index] == SELECTIONS:
                self.view.run_command('file_diff_selections')
            elif restored_menu_items[index] == SAVED:
                self.view.run_command('file_diff_saved')
            elif restored_menu_items[index] == FILE:
                self.view.run_command('file_diff_file')
            elif restored_menu_items[index] == TAB:
                self.view.run_command('file_diff_tab')
        self.view.window().show_quick_panel(menu_items, on_done)


class FileDiffCommand(sublime_plugin.TextCommand):
    def diff_content(self):
        content = ''

        regions = [region for region in self.view.sel()]
        for region in regions:
            if region.empty():
                continue
            content += self.view.substr(region)

        if not content:
            content = self.view.substr(sublime.Region(0, self.view.size()))
        return content

    def run_diff(self, a, b, from_file=None, to_file=None):
        from_content = a
        to_content = b

        if os.path.exists(a):
            if from_file is None:
                from_file = a
            with codecs.open(from_file, mode='U', encoding='utf-8') as f:
                from_content = f.readlines()
        else:
            from_content = a.splitlines(True)
            if from_file is None:
                from_file = 'from_file'

        if os.path.exists(b):
            if to_file is None:
                to_file = b
            with codecs.open(to_file, mode='U', encoding='utf-8') as f:
                to_content = f.readlines()
        else:
            to_content = b.splitlines(True)
            if to_file is None:
                to_file = 'to_file'

        diffs = list(difflib.unified_diff(from_content, to_content, from_file, to_file))

        open_in_sublime = SETTINGS.get('open_in_sublime', True)
        external_command = SETTINGS.get('cmd')
        if not diffs:
            sublime.status_message('No Difference')
        else:
            if external_command:
                self.diff_with_external(a, b, from_file, to_file)

            if open_in_sublime:
                # fix diffs
                diffs = map(lambda line: (line and line[-1] == "\n") and line or line + "\n", diffs)
                self.diff_in_sublime(diffs)

    def diff_with_external(self, a, b, from_file=None, to_file=None):
        try:
            if not os.path.exists(from_file):
                tmp_file = tempfile.NamedTemporaryFile(delete=False)
                from_file = tmp_file.name
                tmp_file.close()

                with codecs.open(from_file, encoding='utf-8', mode='w+') as tmp_file:
                    tmp_file.write(a)

            if not os.path.exists(to_file):
                tmp_file = tempfile.NamedTemporaryFile(delete=False)
                to_file = tmp_file.name
                tmp_file.close()

                with codecs.open(to_file, encoding='utf-8', mode='w+') as tmp_file:
                    tmp_file.write(b)

            if os.path.exists(from_file):
                command = SETTINGS.get('cmd')
                if command is not None:
                    command = [c.replace(u'$file1', from_file) for c in command]
                    command = [c.replace(u'$file2', to_file) for c in command]
                    self.view.window().run_command("exec", {"cmd": command})
        except Exception as e:
            # some basic logging here, since we are cluttering the /tmp folder
            print repr(e)
            sublime.status_message(str(e))

    def diff_in_sublime(self, diffs):
        scratch = self.view.window().new_file()
        scratch.set_scratch(True)
        scratch.set_syntax_file('Packages/Lang Diff/Diff.tmLanguage')
        scratch_edit = scratch.begin_edit('file_diffs')
        scratch.insert(scratch_edit, 0, ''.join(diffs))
        scratch.end_edit(scratch_edit)


class FileDiffClipboardCommand(FileDiffCommand):
    def run(self, edit, **kwargs):
        current = sublime.get_clipboard()
        self.run_diff(self.diff_content(), current,
            from_file=self.view.file_name(),
            to_file='(clipboard)')


class FileDiffSelectionsCommand(FileDiffCommand):
    def run(self, edit, **kwargs):
        regions = self.view.sel()
        current = self.view.substr(regions[0])
        diff = self.view.substr(regions[1])

        # trim off indent
        indent = None
        for line in current.splitlines():
            new_indent = re.match('[ \t]*', line).group(0)
            if new_indent == '':
                continue

            if indent is None:
                indent = new_indent
            elif len(new_indent) < len(indent):
                indent = new_indent

            if not indent:
                break

        if indent:
            current = u"\n".join(line[len(indent):] for line in current.splitlines())

        # trim off indent
        indent = None
        for line in diff.splitlines():
            new_indent = re.match('[ \t]*', line).group(0)
            if new_indent == '':
                continue

            if indent is None:
                indent = new_indent
            elif len(new_indent) < len(indent):
                indent = new_indent

        if indent:
            diff = u"\n".join(line[len(indent):] for line in diff.splitlines())

        self.run_diff(current, diff,
            from_file='first selection',
            to_file='second selection')


class FileDiffSavedCommand(FileDiffCommand):
    def run(self, edit, **kwargs):
        content = ''
        regions = [region for region in self.view.sel()]
        for region in regions:
            if region.empty():
                continue
            content += self.view.substr(region)
        if not content:
            content = self.view.substr(sublime.Region(0, self.view.size()))

        self.run_diff(self.view.file_name(), content,
            from_file=self.view.file_name(),
            to_file=self.view.file_name() + u' (Unsaved)')


class FileDiffFileCommand(FileDiffCommand):
    def run(self, edit, **kwargs):
        common = None
        folders = self.view.window().folders()
        files = self.find_files(folders)
        for folder in folders:
            if common == None:
                common = folder
            else:
                common_len = len(common)
                while folder[0:common_len] != common[0:common_len]:
                    common_len -= 1
                    common = common[0:common_len]

        my_file = self.view.file_name()
        # filter out my_file
        files = [file for file in files if file != my_file]
        # shorten names using common length
        file_picker = [file[len(common):] for file in files]

        def on_done(index):
            if index > -1:
                self.run_diff(self.diff_content(), files[index],
                    from_file=self.view.file_name())
        self.view.window().show_quick_panel(file_picker, on_done)

    def find_files(self, folders):
        # Cannot access these settings!!  WHY!?
        # folder_exclude_patterns = self.view.settings().get('folder_exclude_patterns')
        # file_exclude_patterns = self.view.settings().get('file_exclude_patterns')
        folder_exclude_patterns = [".svn", ".git", ".hg", "CVS"]
        file_exclude_patterns = ["*.pyc", "*.pyo", "*.exe", "*.dll", "*.obj", "*.o", "*.a", "*.lib", "*.so", "*.dylib", "*.ncb", "*.sdf", "*.suo", "*.pdb", "*.idb", ".DS_Store", "*.class", "*.psd", "*.db"]

        ret = []
        for folder in folders:
            if not os.path.isdir(folder):
                continue

            for file in os.listdir(folder):
                fullpath = os.path.join(folder, file)
                if os.path.isdir(fullpath):
                    # excluded folder?
                    if not len([True for pattern in folder_exclude_patterns if fnmatch(file, pattern)]):
                        ret += self.find_files([fullpath])
                else:
                    # excluded file?
                    if not len([True for pattern in file_exclude_patterns if fnmatch(file, pattern)]):
                        ret.append(fullpath)
        return ret


class FileDiffTabCommand(FileDiffCommand):
    def run(self, edit, **kwargs):
        my_id = self.view.id()
        files = []
        contents = []
        untitled_count = 1
        for v in self.view.window().views():
            if v.id() != my_id:
                this_content = v.substr(sublime.Region(0, v.size()))
                if v.file_name():
                    files.append(v.file_name())
                elif v.name():
                    files.append(v.name())
                else:
                    files.append('untitled %d' % untitled_count)
                    untitled_count += 1

                contents.append(this_content)

        def on_done(index):
            if index > -1:
                self.run_diff(self.diff_content(), contents[index],
                    from_file=self.view.file_name(),
                    to_file=files[index])

        if len(files) == 1:
            on_done(0)
        else:
            menu_items = [os.path.basename(f) for f in files]
            self.view.window().show_quick_panel(menu_items, on_done)
