"""
Copyright 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
This file is part of GNU Radio

GNU Radio Companion 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.

GNU Radio Companion 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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
"""

from __future__ import absolute_import

from gi.repository import Gtk, Gdk

from .canvas.colors import FLOWGRAPH_BACKGROUND_COLOR
from . import Constants
from . import Actions


class DrawingArea(Gtk.DrawingArea):
    """
    DrawingArea is the gtk pixel map that graphical elements may draw themselves on.
    The drawing area also responds to mouse and key events.
    """

    def __init__(self, flow_graph):
        """
        DrawingArea constructor.
        Connect event handlers.

        Args:
            main_window: the main_window containing all flow graphs
        """
        Gtk.DrawingArea.__init__(self)

        self._flow_graph = flow_graph
        self.set_property('can_focus', True)

        self.zoom_factor = 1.0
        self._update_after_zoom = False
        self.ctrl_mask = False
        self.mod1_mask = False
        self.button_state = [False] * 10

        # self.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
        self.connect('realize', self._handle_window_realize)
        self.connect('draw', self.draw)
        self.connect('motion-notify-event', self._handle_mouse_motion)
        self.connect('button-press-event', self._handle_mouse_button_press)
        self.connect('button-release-event', self._handle_mouse_button_release)
        self.connect('scroll-event', self._handle_mouse_scroll)
        self.add_events(
            Gdk.EventMask.BUTTON_PRESS_MASK |
            Gdk.EventMask.POINTER_MOTION_MASK |
            Gdk.EventMask.BUTTON_RELEASE_MASK |
            Gdk.EventMask.SCROLL_MASK |
            Gdk.EventMask.LEAVE_NOTIFY_MASK |
            Gdk.EventMask.ENTER_NOTIFY_MASK
            # Gdk.EventMask.FOCUS_CHANGE_MASK
        )

        # This may not be the correct place to be handling the user events
        # Should this be in the page instead?
        # Or should more of the page functionality move here?
        self.connect('key_press_event', self._handle_key_press)

        # setup drag and drop
        self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
        self.connect('drag-data-received', self._handle_drag_data_received)
        self.drag_dest_set_target_list(None)
        self.drag_dest_add_text_targets()

        # setup the focus flag
        self._focus_flag = False
        self.get_focus_flag = lambda: self._focus_flag

        def _handle_notify_event(widget, event, focus_flag):
            self._focus_flag = focus_flag

        self.connect('leave-notify-event', _handle_notify_event, False)
        self.connect('enter-notify-event', _handle_notify_event, True)
        # todo: fix
#        self.set_flags(Gtk.CAN_FOCUS)  # self.set_can_focus(True)
#        self.connect('focus-out-event', self._handle_focus_lost_event)

        # Setup a map of the accelerator keys to the action to trigger
        self.accels = {
            Gtk.accelerator_parse('d'): Actions.BLOCK_DISABLE,
            Gtk.accelerator_parse('e'): Actions.BLOCK_ENABLE,
            Gtk.accelerator_parse('b'): Actions.BLOCK_BYPASS,
            Gtk.accelerator_parse('c'): Actions.BLOCK_CREATE_HIER,
            Gtk.accelerator_parse('Up'): Actions.BLOCK_DEC_TYPE,
            Gtk.accelerator_parse('Down'): Actions.BLOCK_INC_TYPE,
            Gtk.accelerator_parse('Left'): Actions.BLOCK_ROTATE_CCW,
            Gtk.accelerator_parse('Right'): Actions.BLOCK_ROTATE_CW,
            Gtk.accelerator_parse('minus'): Actions.PORT_CONTROLLER_DEC,
            Gtk.accelerator_parse('plus'): Actions.PORT_CONTROLLER_INC,
            Gtk.accelerator_parse('Add'): Actions.PORT_CONTROLLER_INC,
            Gtk.accelerator_parse('Subtract'): Actions.PORT_CONTROLLER_DEC,
            Gtk.accelerator_parse('Return'): Actions.BLOCK_PARAM_MODIFY,
            Gtk.accelerator_parse('<Shift>t'): Actions.BLOCK_VALIGN_TOP,
            Gtk.accelerator_parse('<Shift>m'): Actions.BLOCK_VALIGN_MIDDLE,
            Gtk.accelerator_parse('<Shift>b'): Actions.BLOCK_VALIGN_BOTTOM,
            Gtk.accelerator_parse('<Shift>l'): Actions.BLOCK_HALIGN_LEFT,
            Gtk.accelerator_parse('<Shift>c'): Actions.BLOCK_HALIGN_CENTER,
            Gtk.accelerator_parse('<Shift>r'): Actions.BLOCK_HALIGN_RIGHT,
        }


    ##########################################################################
    # Handlers
    ##########################################################################
    def _handle_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time):
        """
        Handle a drag and drop by adding a block at the given coordinate.
        """
        coords = x / self.zoom_factor, y / self.zoom_factor
        self._flow_graph.add_new_block(selection_data.get_text(), coords)

    def _handle_mouse_scroll(self, widget, event):
        if event.get_state() & Gdk.ModifierType.CONTROL_MASK:
            change = 1.2 if event.direction == Gdk.ScrollDirection.UP else 1/1.2
            zoom_factor = min(max(self.zoom_factor * change, 0.1), 5.0)

            if zoom_factor != self.zoom_factor:
                self.zoom_factor = zoom_factor
                self._update_after_zoom = True
                self.queue_draw()
            return True

        return False

    def _handle_mouse_button_press(self, widget, event):
        """
        Forward button click information to the flow graph.
        """
        self.grab_focus()
        self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK
        self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK
        self.button_state[event.button] = True

        if event.button == 1:
            double_click = (event.type == Gdk.EventType._2BUTTON_PRESS)
            self.button_state[1] = not double_click
            self._flow_graph.handle_mouse_selector_press(
                double_click=double_click,
                coordinate=self._translate_event_coords(event),
            )
        elif event.button == 3:
            self._flow_graph.handle_mouse_context_press(
                coordinate=self._translate_event_coords(event),
                event=event,
            )

    def _handle_mouse_button_release(self, widget, event):
        """
        Forward button release information to the flow graph.
        """
        self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK
        self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK
        self.button_state[event.button] = False
        if event.button == 1:
            self._flow_graph.handle_mouse_selector_release(
                coordinate=self._translate_event_coords(event),
            )

    def _handle_mouse_motion(self, widget, event):
        """
        Forward mouse motion information to the flow graph.
        """
        self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK
        self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK

        if self.button_state[1]:
            self._auto_scroll(event)

        self._flow_graph.handle_mouse_motion(
            coordinate=self._translate_event_coords(event),
        )

    def _handle_key_press(self, widget, event):
        """
        Handle specific keypresses when the drawing area has focus that
        triggers actions by the user.
        """
        key = event.keyval
        mod = event.state

        try:
            action = self.accels[(key, mod)]
            action()
            return True
        except KeyError:
            return False

    def _update_size(self):
        w, h = self._flow_graph.get_extents()[2:]
        self.set_size_request(w * self.zoom_factor + 100, h * self.zoom_factor + 100)

    def _auto_scroll(self, event):
        x, y = event.x, event.y
        scrollbox = self.get_parent().get_parent()

        self._update_size()

        def scroll(pos, adj):
            """scroll if we moved near the border"""
            adj_val = adj.get_value()
            adj_len = adj.get_page_size()
            if pos - adj_val > adj_len - Constants.SCROLL_PROXIMITY_SENSITIVITY:
                adj.set_value(adj_val + Constants.SCROLL_DISTANCE)
                adj.emit('changed')
            elif pos - adj_val < Constants.SCROLL_PROXIMITY_SENSITIVITY:
                adj.set_value(adj_val - Constants.SCROLL_DISTANCE)
                adj.emit('changed')

        scroll(x, scrollbox.get_hadjustment())
        scroll(y, scrollbox.get_vadjustment())

    def _handle_window_realize(self, widget):
        """
        Called when the window is realized.
        Update the flowgraph, which calls new pixmap.
        """
        self._flow_graph.update()
        self._update_size()

    def draw(self, widget, cr):
        width = widget.get_allocated_width()
        height = widget.get_allocated_height()

        cr.set_source_rgba(*FLOWGRAPH_BACKGROUND_COLOR)
        cr.rectangle(0, 0, width, height)
        cr.fill()

        cr.scale(self.zoom_factor, self.zoom_factor)
        cr.set_line_width(2.0 / self.zoom_factor)

        if self._update_after_zoom:
            self._flow_graph.create_labels(cr)
            self._flow_graph.create_shapes()
            self._update_size()
            self._update_after_zoom = False

        self._flow_graph.draw(cr)

    def _translate_event_coords(self, event):
        return event.x / self.zoom_factor, event.y / self.zoom_factor

    def _handle_focus_lost_event(self, widget, event):
        # don't clear selection while context menu is active
        if not self._flow_graph.context_menu.get_take_focus():
            self._flow_graph.unselect()
            self._flow_graph.update_selected()
            self._flow_graph.queue_draw()