# -*- coding:utf-8 -*-

#  A gedit plugin that provides a panel for Bazaar
#  Copyright © 2010-2013  B. Clausius <barcc@gmx.de>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import print_function, division

import os

from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Gtk
from gi.repository import GdkPixbuf
from gi.repository import GtkSource
from gi.repository import Gedit

from . import bzrpanel
from .bzrpanel import str2or3
from .bzr_const import NULL_REVISION
from .helper import ConnectHelper
from .task import TaskManager

DATA_DIR = os.path.dirname(__file__)


class AppHelper:
    def __init__(self):
        assert apphelper is None
        self.current_line_pixbuf = GdkPixbuf.Pixbuf.new_from_file(os.path.join(DATA_DIR, 'current.png'))
        self.changed_line_pixbuf = GdkPixbuf.Pixbuf.new_from_file(os.path.join(DATA_DIR, 'changed.png'))
        self.treedata_dict = {}  # key: directory of the workingtree, value: TreeData instance
        
    @classmethod
    def activate(cls):
        global apphelper
        if apphelper is None:
            apphelper = cls()
            
    def deactivate(self):
        TaskManager.stop()
        global apphelper
        apphelper = None
        for treedata in self.treedata_dict.values():
            treedata.reset()
        self.treedata_dict.clear()
        
    def get_treedata(self, wt_basedir):
        try:
            treedata = self.treedata_dict[wt_basedir]
        except KeyError:
            treedata = bzrpanel.TreeData(wt_basedir)
            self.treedata_dict[wt_basedir] = treedata
        return treedata
        
apphelper = None


class WindowHelper(GObject.Object, Gedit.WindowActivatable, ConnectHelper):
    __gtype_name__ = "BazaarPanelWindowHelper"
    window = GObject.property(type=Gedit.Window)
    
    def __init__(self):
        GObject.Object.__init__(self)
        ConnectHelper.__init__(self)
        self.panel = None
        self.tabhelpers = {}
        
    def do_activate(self):
        AppHelper.activate()
        self.panel = bzrpanel.PanelHelper(self.window)
        
        # create empty panel
        bzr_icon_path = os.path.join(os.path.dirname(__file__), "bzr-panel.svg")
        image = Gtk.Image.new_from_file(bzr_icon_path)
        iconset = Gtk.IconSet.new_from_pixbuf(image.props.pixbuf)
        image = Gtk.Image.new_from_icon_set(iconset, Gtk.IconSize.MENU)
        panel = self.window.get_bottom_panel()
        panel.add_item(self.panel.box, "BazaarPanel", "Bazaar", image)
        
        self._connect(self.panel, 'diff-position-changed', self.on_diff_position_changed)
        self._connect(self.panel, 'wt-basedir-changed', self.on_panel_wt_basedir_changed)
        self._connect(self.window, 'active-tab-changed', self.on_active_tab_changed)
        self._connect(self.window, 'tab-added', self.on_tab_added)
        self._connect(self.window, 'tab-removed', self.on_tab_removed)
        
        # If the plugin is enabled with the preferences dialog box then tabs are open,
        # that were not set up by the "active-tab-changed" and "tab-added" signals
        for doc in self.window.get_documents():
            tab = Gedit.Tab.get_from_document(doc)
            self.on_tab_added(self.window, tab)
            if tab == self.window.get_active_tab():
                self.on_active_tab_changed(self.window, tab)
        
    def do_deactivate(self):
        for tabhelper in self.tabhelpers.values():
            tabhelper.do_deactivate()
        self.tabhelpers.clear()
        
        self.disconnect_all()
        
        panel = self.window.get_bottom_panel()
        panel.remove_item(self.panel.box)
        self.panel.deactivate()
        self.panel = None
        
        # AppActivatable could do this, unfortunately, AppActivatable.do_deactivate
        # is called when the plugin is deactivated, but not when the application exits.
        if len(Gedit.App.get_default().get_windows()) <= 1:
            apphelper.deactivate()
            
    def on_active_tab_changed(self, unused_window, tab):
        if tab is None:
            return
        location = tab.get_document().get_location()
        path = location and location.get_path()
        try:
            tabhelper = self.tabhelpers[tab]
        except KeyError:
            TaskManager.task(self, 'get_wt_basedir', path)
        else:
            self.panel.set_widget(path, tabhelper.treedata)
        
    def on_tab_added(self, unused_window, tab):
        doc = tab.get_document()
        self._connect(doc, 'load', self.on_document_load, tab)
        
    def on_tab_removed(self, unused_window, tab):
        doc = tab.get_document()
        self._disconnect(doc)
        tabhelper = self.tabhelpers.get(tab, None)
        if tabhelper is not None:
            tabhelper.do_deactivate()
            del self.tabhelpers[tab]
        if self.window.get_active_tab() is None:
            self.panel.remove_widget()
        
        #TODO: unref WidgetHelper
        
    def on_document_load(self, doc, location, encoding, line_pos, column_pos, tab):
        # then load signal is emitted when GeditDocument.load is called.
        # 1. The active tab is a new unmodified document and a file is opened
        # 2. A opened file is externally modified and the user activates the reload button
        # 3. The reload menu item is selected
        # 4. The load function is called by e.g. a plugin
        if tab != self.window.get_active_tab():
            return
        path = location and location.get_path()
        TaskManager.task(self, 'get_wt_basedir', path)
        
    def on_diff_position_changed(self, unused_panel, filename, lineno):
        tab = self.window.get_active_tab()
        tabhelper = self.tabhelpers.get(tab, None)
        if tabhelper is not None:
            tabhelper.set_diff_position(filename, lineno)
            
    def on_panel_wt_basedir_changed(self, unused_panel):
        tab = self.window.get_active_tab()
        if tab is None:
            return
        location = tab.get_document().get_location()
        path = location and location.get_path()
        TaskManager.task(self, 'get_wt_basedir', path)
        
    def set_wt_basedir(self, path, wt_basedir, wt_path):
        tab = self.window.get_active_tab()
        if tab is None:
            return
        location = tab.get_document().get_location()
        active_path = location and location.get_path()
        if path != active_path:
            return
        treedata = apphelper.get_treedata(wt_basedir)
        self.panel.set_widget(path, treedata)
        try:
            tabhelper = self.tabhelpers[tab]
        except KeyError:
            tabhelper = TabHelper(tab)
            self.tabhelpers[tab] = tabhelper
        tabhelper.set_treedata(treedata, wt_path)
            
        
class TabHelper(GObject.Object, ConnectHelper):
    def __init__(self, tab):
        GObject.Object.__init__(self)
        ConnectHelper.__init__(self)
        self.tab = tab
        self.treedata = None
        self.wt_path = None
        
        view = self.tab.get_view()
        view.set_show_line_marks(True)
        doc = view.get_buffer()
        self._connect(doc, 'saved', self.on_document_saved)
        
    def set_treedata(self, treedata, wt_path):
        self._disconnect(self.treedata)
        self.treedata = treedata
        self.wt_path = wt_path
        self._connect(self.treedata, 'refresh', self.on_treedata_refresh)
        self._connect(self.treedata, 'diff-changed', self.on_diff_changed)
        unused_diff_text, diff_map = self.treedata.get_wt_diff(wt_path)
        GLib.idle_add(self.set_marks_to_doc, diff_map)
        
    def do_deactivate(self):
        self.disconnect_all()
        self.treedata = None
        self.clear_marks()
        view = self.tab.get_view()
        view.set_show_line_marks(False)
        
    def on_treedata_refresh(self, treedata):
        unused_diff_text, diff_map = self.treedata.get_wt_diff(self.wt_path)
        self.set_marks_to_doc(diff_map)
        
    def on_diff_changed(self, treedata, revid_old, revid_new, wt_path, unused_diff_text, lineno_map):
        '''Set source marks in the active document'''
        if self.wt_path != wt_path:
            return
        if revid_old not in [treedata.last_revid, NULL_REVISION] or revid_new != NULL_REVISION:
            return
        self.set_marks_to_doc(lineno_map)
            
    def set_marks_to_doc(self, lineno_map):
        view = self.tab.get_view()
        buf = view.get_buffer()
        start, end = buf.get_bounds()
        buf.remove_source_marks(start, end, 'modified-lines')
        
        if lineno_map is None or self.wt_path is None:
            return
            
        attributes = GtkSource.MarkAttributes()
        attributes.set_pixbuf(apphelper.changed_line_pixbuf)
        view.set_mark_attributes('modified-lines', attributes, 0)
        for filename, lineno, modify in lineno_map.values():
            if filename == self.wt_path and modify == '+':
                source_iter = buf.get_iter_at_line(lineno-1)
                buf.create_source_mark(None, 'modified-lines', source_iter)
                
    def clear_marks(self):
        buf = self.tab.get_view().get_buffer()
        start, end = buf.get_bounds()
        buf.remove_source_marks(start, end, 'modified-lines')
        buf.remove_source_marks(start, end, 'current-diff-line')
        
    def set_diff_position(self, filename, lineno):
        view = self.tab.get_view()
        buf = view.get_buffer()
        mark = buf.get_mark('current-diff-line')
        
        if filename is None:
            if mark:
                buf.delete_mark(mark)
            return
        if filename != buf.get_location().get_path():
            return
            
        source_iter = buf.get_iter_at_line(lineno-1)
        if mark is None:
            attributes = GtkSource.MarkAttributes()
            attributes.set_pixbuf(apphelper.current_line_pixbuf)
            view.set_mark_attributes('current-diff-line', attributes, 1)
            buf.create_source_mark('current-diff-line', 'current-diff-line', source_iter)
        else:
            buf.move_mark(mark, source_iter)
        view.scroll_to_iter(source_iter, 0.2, False, 0.5, 0.5)
        
    def on_document_saved(self, doc, unused_arg):
        location = doc.get_location()
        path = location and location.get_path()
        self.treedata.request_files(None, NULL_REVISION)
        # FIXME: Need some delay, so that diff is not calculated from the old state
        GLib.timeout_add(500, self.delayed_request_diff, path)
        
    def delayed_request_diff(self, path):
        if self.treedata.wt_basedir:
            rel_path = os.path.relpath(path, self.treedata.wt_basedir)
        else:
            rel_path = None
        self.treedata.request_diff(None, NULL_REVISION, rel_path)
        
        
