# Copyright (C) 2018 Kai Willadsen <kai.willadsen@gmail.com>
#
# 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 2 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 gi.repository import Gdk
from gi.repository import Gtk

from meld.const import MODE_DELETE, MODE_INSERT, MODE_REPLACE
from meld.misc import get_common_theme
from meld.settings import meldsettings
from meld.ui.gtkcompat import draw_style_common, get_style

# Fixed size of the renderer. Ideally this would be font-dependent and
# would adjust to other textview attributes, but that's both quite difficult
# and not necessarily desirable.
PIXBUF_HEIGHT = 16


def load(icon_name):
    icon_theme = Gtk.IconTheme.get_default()
    return icon_theme.load_icon(icon_name, PIXBUF_HEIGHT, 0)


class ActionGutter(Gtk.DrawingArea):

    __gtype_name__ = "ActionGutter"

    ACTION_MAP = {
        'LTR': {
            MODE_REPLACE: load("meld-change-apply-right"),
            MODE_DELETE: load("meld-change-delete"),
            MODE_INSERT: load("meld-change-copy"),
        },
        'RTL': {
            MODE_REPLACE: load("meld-change-apply-left"),
            MODE_DELETE: load("meld-change-delete"),
            MODE_INSERT: load("meld-change-copy"),
        }
    }

    def setup(self, from_pane, to_pane, views, filediff, linediffer):
        self.from_pane = from_pane
        self.to_pane = to_pane

        # FIXME: Views are needed only for editable checking; connect to this
        # in Filediff instead?
        self.views = views
        # FIXME: Don't pass in the linediffer; pass a generator like elsewhere
        self.linediffer = linediffer
        self.mode = MODE_REPLACE

        direction = 'LTR' if self.from_pane < self.to_pane else 'RTL'
        if self.views[0].get_direction() == Gtk.TextDirection.RTL:
            direction = 'LTR' if direction == 'RTL' else 'RTL'

        self.action_map = self.ACTION_MAP[direction]
        self.filediff = filediff
        self.filediff.connect(
            "action-mode-changed", self.on_container_mode_changed)
        meldsettings.connect('changed', self.on_setting_changed)
        self.on_setting_changed(meldsettings, 'style-scheme')

    def on_setting_changed(self, meldsettings, key):
        if key == 'style-scheme':
            self.fill_colors, self.line_colors = get_common_theme()
            alpha = self.fill_colors['current-chunk-highlight'].alpha
            self.chunk_highlights = {
                state: Gdk.RGBA(*[alpha + c * (1.0 - alpha) for c in colour])
                for state, colour in self.fill_colors.items()
            }

    def on_container_mode_changed(self, container, mode):
        self.mode = mode
        self.queue_draw()

    def set_view(self, view):
        self.view = view
        # FIXME: Get the view's scroll adjustment and assign it as ours

    def set_buffer(self, buffer):
        self.buffer = buffer

    def do_draw(self, context):
        if not self.views:
            return

        # FIXME: better place? cache this?
        self.views_editable = [v.get_editable() for v in self.views]

        pix_start = [t.get_visible_rect().y for t in self.views]
        y_offset = [
            t.translate_coordinates(self, 0, 0)[1] for t in self.views]

        clip_y = min(y_offset) - 1
        clip_height = max(t.get_visible_rect().height for t in self.views) + 2
        allocation = self.get_allocation()

        stylecontext = self.get_style_context()
        Gtk.render_background(
            stylecontext, context, 0, clip_y, allocation.width, clip_height)
        context.set_line_width(1.0)

        height = allocation.height
        visible = [
            self.views[0].get_line_num_for_y(pix_start[0]),
            self.views[0].get_line_num_for_y(pix_start[0] + height),
            self.views[1].get_line_num_for_y(pix_start[1]),
            self.views[1].get_line_num_for_y(pix_start[1] + height),
        ]

        # FIXME: RTL?
        left, right = self.from_pane, self.to_pane

        def view_offset_line(view_idx, line_num):
            line_start = self.views[view_idx].get_y_for_line_num(line_num)
            return line_start - pix_start[view_idx] + y_offset[view_idx]

        context.save()
        context.set_line_width(1.0)

        x = -0.5
        width = allocation.width + 1

        # Paint chunk backgrounds and outlines
        for change in self.filediff.linediffer.pair_changes(left, right, visible):
            view = self.views[self.from_pane] # FIXME: is this... right? maybe?
            y = view_offset_line(0, change[1])
            height = max(0, view_offset_line(0, change[2]) - y - 1)

            context.rectangle(x, y + 0.5, width, height)
            if change[1] != change[2]:
                context.set_source_rgba(*self.fill_colors[change[0]])
                context.fill_preserve()
                if self.views[self.from_pane].current_chunk_check(change):
                    highlight = self.fill_colors['current-chunk-highlight']
                    context.set_source_rgba(*highlight)
                    context.fill_preserve()

            context.set_source_rgba(*self.line_colors[change[0]])
            context.stroke()

            # FIXME
            action = self._classify_change_actions(change)

            pixbuf = self.action_map.get(action)
            if not pixbuf:
                continue

            style_context = get_style(None, "button.flat.image-button")
            # FIXME: need to get the state based on cursor position I guess?
            # Ideally, handle:
            # Gtk.StateFlags.NORMAL, Gtk.StateFlags.FOCUSED, Gtk.StateFlags.PRELIGHT, Gtk.StateFlags.SELECTED
            # style_context.set_state(Gtk.StateFlags.PRELIGHT)

            # TODO: Fix padding and min-height in CSS (or elsewhere) so that
            # draw_style_common works right
            draw_style_common(style_context, context, x, y, width, height)

            # Gtk.render_background(style_context, context, x, y, width, height)
            # Gtk.render_frame(style_context, context, x, y, width, height)

            pix_width, pix_height = pixbuf.props.width, pixbuf.props.height

            # We're using the iter location height here. If we used the
            # line height instead, then wrapped lines would have
            # misaligned buttons.
            # FIXME: this `change[1]` is getting a bit wild and opaque
            line_iter = view.get_buffer().get_iter_at_line(change[1])
            loc = view.get_iter_location(line_iter)

            icon_x = x + (width - pix_width) // 2
            icon_y = y + (loc.height - pix_height) // 2

            Gtk.render_icon(style_context, context, pixbuf, icon_x, icon_y)

        context.restore()

    def do_get_preferred_height(self):
        # FIXME: why do I need this monstrousity
        return 100, 1000

    def _classify_change_actions(self, change):
        """Classify possible actions for the given change

        Returns the action that can be performed given the content and
        context of the change.
        """
        editable, other_editable = self.views_editable

        if not editable and not other_editable:
            return None

        # Reclassify conflict changes, since we treat them the same as a
        # normal two-way change as far as actions are concerned
        change_type = change[0]
        if change_type == "conflict":
            if change[1] == change[2]:
                change_type = "insert"
            elif change[3] == change[4]:
                change_type = "delete"
            else:
                change_type = "replace"

        if change_type == 'insert':
            return None

        action = self.mode
        if action == MODE_DELETE and not editable:
            action = None
        elif action == MODE_INSERT and change_type == 'delete':
            action = MODE_REPLACE
        if not other_editable:
            action = MODE_DELETE
        return action


ActionGutter.set_css_name("action-gutter")


# class GutterRendererChunkAction(
#         GtkSource.GutterRendererPixbuf, MeldGutterRenderer):

#     def do_activate(self, start, area, event):
#         line = start.get_line()
#         chunk_index = self.linediffer.locate_chunk(self.from_pane, line)[0]
#         if chunk_index is None:
#             return

#         chunk = self.linediffer.get_chunk(
#             chunk_index, self.from_pane, self.to_pane)
#         if chunk[1] != line:
#             return

#         action = self._classify_change_actions(chunk)
#         if action == MODE_DELETE:
#             self.filediff.delete_chunk(self.from_pane, chunk)
#         elif action == MODE_INSERT:
#             copy_menu = self._make_copy_menu(chunk)
#             # TODO: Need a custom GtkMenuPositionFunc to position this next to
#             # the clicked gutter, not where the cursor is
#             copy_menu.popup(None, None, None, None, 0, event.time)
#         elif action == MODE_REPLACE:
#             self.filediff.replace_chunk(self.from_pane, self.to_pane, chunk)

#     def _make_copy_menu(self, chunk):
#         copy_menu = Gtk.Menu()
#         copy_up = Gtk.MenuItem.new_with_mnemonic(_("Copy _up"))
#         copy_down = Gtk.MenuItem.new_with_mnemonic(_("Copy _down"))
#         copy_menu.append(copy_up)
#         copy_menu.append(copy_down)
#         copy_menu.show_all()

#         # FIXME: This is horrible
#         widget = self.filediff.widget
#         copy_menu.attach_to_widget(widget, None)

#         def copy_chunk(widget, chunk, copy_up):
#             self.filediff.copy_chunk(self.from_pane, self.to_pane, chunk,
#                                      copy_up)

#         copy_up.connect('activate', copy_chunk, chunk, True)
#         copy_down.connect('activate', copy_chunk, chunk, False)
#         return copy_menu

#     def do_query_activatable(self, start, area, event):
#         line = start.get_line()
#         chunk_index = self.linediffer.locate_chunk(self.from_pane, line)[0]
#         if chunk_index is not None:
#             # FIXME: This is all chunks, not just those shared with to_pane
#             chunk = self.linediffer.get_chunk(chunk_index, self.from_pane)
#             if chunk[1] == line:
#                 return True
#         return False
