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

# Copyright © 2012-2014, 2016-2017 marmuta <marmvta@gmail.com>
#
# This file is part of Onboard.
#
# Onboard 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.
#
# Onboard 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/>.

""" Enlarged drag handles for resizing or moving """

from __future__ import division, print_function, unicode_literals

from math import pi, sqrt
import cairo

from Onboard.utils       import Rect, drop_shadow
from Onboard.definitions import Handle

### Logging ###
import logging
_logger = logging.getLogger("TouchHandles")
###############


class TouchHandle(object):
    """ Enlarged drag handle for resizing or moving """

    id = None
    prelight = False
    pressed = False
    corner_radius = 0     # radius of the outer corners (window edges)

    _size = (40, 40)
    _fallback_size = (40, 40)
    _hit_proximity_factor = 1.5
    _rect = None
    _scale = 1.0   # scale of handle relative to resize handles
    _handle_alpha = 0.45
    _shadow_alpha = 0.04
    _shadow_size = 8
    _shadow_offset = (0.0, 2.0)
    _screen_dpi = 96

    _handle_angles = {}  # dictionary at class scope!

    lock_x_axis = False
    lock_y_axis = False

    def __init__(self, id):
        self.id = id

        # initialize angles
        if not self._handle_angles:
            for i, h in enumerate(Handle.EDGES):
                self._handle_angles[h] = i * pi / 2.0
            for i, h in enumerate(Handle.CORNERS):
                self._handle_angles[h] = i * pi / 2.0 + pi / 4.0
            self._handle_angles[Handle.MOVE] = 0.0

    def get_rect(self):
        rect = self._rect
        if not rect is None and \
           self.pressed:
            rect = rect.offset(1.0, 1.0)
        return rect

    def get_radius(self):
        w, h = self.get_rect().get_size()
        return min(w, h) / 2.0

    def get_shadow_rect(self):
        rect = self.get_rect()
        if rect:
            rect = rect.inflate(self._shadow_size+1)
            rect.w += self._shadow_offset[0]
            rect.h += self._shadow_offset[1]
        return rect

    def get_arrow_angle(self):
        return self._handle_angles[self.id]

    def is_edge_handle(self):
        return self.id in Handle.EDGES

    def is_corner_handle(self):
        return self.id in Handle.CORNERS

    def update_position(self, canvas_rect):
        w, h = self._size
        w = min(w, canvas_rect.w / 3.0)
        w = min(w, canvas_rect.h / 3.0)
        h = w
        self._scale = 1.0

        xc, yc = canvas_rect.get_center()
        if self.id is Handle.MOVE:
            d = min(canvas_rect.w - 2.0 * w, canvas_rect.h - 2.0 * h)
            self._scale = 1.4
            w = min(w * self._scale, d)
            h = min(h * self._scale, d)

        if self.id in [Handle.WEST,
                       Handle.NORTH_WEST,
                       Handle.SOUTH_WEST]:
            x = canvas_rect.left()
        if self.id in [Handle.NORTH,
                       Handle.NORTH_WEST,
                       Handle.NORTH_EAST]:
            y = canvas_rect.top()
        if self.id in [Handle.EAST,
                       Handle.NORTH_EAST,
                       Handle.SOUTH_EAST]:
            x = canvas_rect.right() - w
        if self.id in [Handle.SOUTH,
                       Handle.SOUTH_WEST,
                       Handle.SOUTH_EAST]:
            y = canvas_rect.bottom() - h

        if self.id in [Handle.MOVE, Handle.EAST, Handle.WEST]:
            y = yc - h / 2.0
        if self.id in [Handle.MOVE, Handle.NORTH, Handle.SOUTH]:
            x = xc - w / 2.0

        self._rect = Rect(x, y, w, h)

    def hit_test(self, point):
        rect   = self.get_rect().grow(self._hit_proximity_factor)
        radius = self.get_radius() * self._hit_proximity_factor

        if rect and rect.is_point_within(point):
            _win = self._window.get_window()
            if _win:
                context = _win.cairo_create()
                self._build_handle_path(context, rect, radius)
                return context.in_fill(*point)
        return False

        xc, yc = rect.get_center()
        dx = xc - point[0]
        dy = yc - point[1]
        d = sqrt(dx*dx + dy*dy)
        return d <= radius

    def draw(self, context):
        if self.pressed:
            alpha_factor = 1.5
        else:
            alpha_factor = 1.0

        context.new_path()

        self._draw_handle_shadow(context, alpha_factor)
        self._draw_handle(context, alpha_factor)
        self._draw_arrows(context)

    def _draw_handle(self, context, alpha_factor):
        radius = self.get_radius()
        line_width = radius / 15.0

        alpha = self._handle_alpha * alpha_factor
        if self.pressed:
            context.set_source_rgba(0.78, 0.33, 0.17, alpha)
        elif self.prelight:
            context.set_source_rgba(0.98, 0.53, 0.37, alpha)
        else:
            context.set_source_rgba(0.78, 0.33, 0.17, alpha)

        self._build_handle_path(context)
        context.fill_preserve()
        context.set_line_width(line_width)
        context.stroke()

    def _draw_handle_shadow(self, context, alpha_factor):
        rect = self.get_rect()

        context.save()

        # There is a massive performance boost for groups when clipping is used.
        # Integer limits are again dramatically faster (x4) then using floats.
        # for 1000x draw_drop_shadow:
        #     with clipping: ~300ms, without: ~11000ms
        context.rectangle(*self.get_shadow_rect().int())
        context.clip()

        context.push_group()

        # draw the shadow
        context.push_group_with_content(cairo.CONTENT_ALPHA)
        self._build_handle_path(context)
        context.set_source_rgba(0.0, 0.0, 0.0, 1.0)
        context.fill()
        group = context.pop_group()
        drop_shadow(context, group, rect,
                    self._shadow_size,
                    self._shadow_offset,
                    self._shadow_alpha,
                    5)

        # cut out the handle area, because the handle is transparent
        context.save()
        context.set_operator(cairo.OPERATOR_CLEAR)
        context.set_source_rgba(0.0, 0.0, 0.0, 1.0)
        self._build_handle_path(context)
        context.fill()
        context.restore()

        context.pop_group_to_source()
        context.paint()

        context.restore()


    def _draw_arrows(self, context):
        radius = self.get_radius()
        xc, yc = self.get_rect().get_center()
        scale = radius / 2.0 / self._scale * 1.2

        angle = self.get_arrow_angle()
        if self.id == Handle.MOVE:
            num_arrows = 4
            if self.lock_x_axis:
                num_arrows -= 2
                angle += pi * 0.5
            if self.lock_y_axis:
                num_arrows -= 2
        else:
            num_arrows = 2
        angle_step = 2.0 * pi / num_arrows

        for i in range(num_arrows):
            context.save()

            context.translate(xc, yc)
            context.rotate(angle + i * angle_step)
            context.scale(scale, scale)

            # arrow distance from center
            if self.id is Handle.MOVE:
                context.translate(0.9, 0)
            else:
                context.translate(0.30, 0)

            self._draw_arrow(context)

            context.restore()

    def _draw_arrow(self, context):
        context.move_to( 0.0, -0.5)
        context.line_to( 0.5,  0.0)
        context.line_to( 0.0,  0.5)
        context.close_path()

        context.set_source_rgba(1.0, 1.0, 1.0, 0.8)
        context.fill_preserve()

        context.set_source_rgba(0.0, 0.0, 0.0, 0.8)
        context.set_line_width(0)
        context.stroke()

    def _build_handle_path(self, context, rect = None, radius = None):
        if rect is None:
            rect = self.get_rect()
        if radius is None:
            radius = self.get_radius()
        xc, yc = rect.get_center()
        corner_radius = self.corner_radius

        angle = self.get_arrow_angle()
        m = cairo.Matrix()
        m.translate(xc, yc)
        m.rotate(angle)

        if self.is_edge_handle():
            p0 = m.transform_point(radius, -radius)
            p1 = m.transform_point(radius, radius)
            context.arc(xc, yc, radius, angle + pi / 2.0, angle + pi / 2.0 + pi)
            context.line_to(*p0)
            context.line_to(*p1)
            context.close_path()
        elif self.is_corner_handle():
            m.rotate(-pi / 4.0)  # rotate to SOUTH_EAST

            context.arc(xc, yc, radius, angle + 3 * pi / 4.0,
                                        angle + 5 * pi / 4.0)
            pt = m.transform_point(radius, -radius)
            context.line_to(*pt)

            if corner_radius:
                # outer corner, following the rounded window corner
                pt  = m.transform_point(radius,  radius - corner_radius)
                ptc = m.transform_point(radius - corner_radius,
                                        radius - corner_radius)
                context.line_to(*pt)
                context.arc(ptc[0], ptc[1], corner_radius,
                            angle - pi / 4.0,  angle + pi / 4.0)
            else:
                pt = m.transform_point(radius,  radius)
                context.line_to(*pt)

            pt = m.transform_point(-radius,  radius)
            context.line_to(*pt)
            context.close_path()
        else:
            context.arc(xc, yc, radius, 0, 2.0 * pi)

    def redraw(self):
        rect = self.get_shadow_rect()
        if rect:
            self._window.queue_draw_area(*rect)


class TouchHandles(object):
    """ Full set of resize and move handles """
    active = False
    opacity = 1.0
    rect = None

    def __init__(self):
        self.handles = []
        self._handle_pool = [TouchHandle(Handle.MOVE),
                             TouchHandle(Handle.NORTH_WEST),
                             TouchHandle(Handle.NORTH),
                             TouchHandle(Handle.NORTH_EAST),
                             TouchHandle(Handle.EAST),
                             TouchHandle(Handle.SOUTH_EAST),
                             TouchHandle(Handle.SOUTH),
                             TouchHandle(Handle.SOUTH_WEST),
                             TouchHandle(Handle.WEST)]

    def set_active_handles(self, handle_ids):
        self.handles = []
        for handle in self._handle_pool:
            if handle.id in handle_ids:
                self.handles.append(handle)

    def set_window(self, window):
        for handle in self._handle_pool:
            handle._window = window

    def update_positions(self, canvas_rect):
        self.rect = canvas_rect
        for handle in self.handles:
            handle.update_position(canvas_rect)

    def draw(self, context):
        if self.opacity:
            clip_rect = Rect.from_extents(*context.clip_extents())
            for handle in self.handles:
                rect = handle.get_shadow_rect()
                if rect.intersects(clip_rect):
                    context.save()
                    context.rectangle(*rect.int())
                    context.clip()
                    context.push_group()

                    handle.draw(context)

                    context.pop_group_to_source()
                    context.paint_with_alpha(self.opacity);
                    context.restore()

    def redraw(self):
        if self.rect:
            for handle in self.handles:
                handle.redraw()

    def hit_test(self, point):
        if self.active:
            for handle in self.handles:
                if handle.hit_test(point):
                    return handle.id

    def set_prelight(self, handle_id):
        for handle in self.handles:
            prelight = handle.id == handle_id and not handle.pressed
            if handle.prelight != prelight:
                handle.prelight = prelight
                handle.redraw()

    def set_pressed(self, handle_id):
        for handle in self.handles:
            pressed = handle.id == handle_id
            if handle.pressed != pressed:
                handle.pressed = pressed
                handle.redraw()

    def set_corner_radius(self, corner_radius):
        for handle in self.handles:
            handle.corner_radius = corner_radius

    def set_monitor_dimensions(self, size_px, size_mm):
        min_monitor_mm = 50
        target_size_mm = (5, 5)
        min_size = TouchHandle._fallback_size

        if size_mm[0] < min_monitor_mm or \
           size_mm[1] < min_monitor_mm:
            w = 0
            h = 0
        else:
            w = size_px[0] / size_mm[0] * target_size_mm[0]
            h = size_px[0] / size_mm[0] * target_size_mm[0]
        size = max(w, min_size[0]), max(h, min_size[1])
        TouchHandle._size = size

    def lock_x_axis(self, lock):
        """ Set to False to constraint movement in x. """
        for handle in self.handles:
            handle.lock_x_axis = lock

    def lock_y_axis(self, lock):
        """ Set to True to constraint movement in y. """
        for handle in self.handles:
            handle.lock_y_axis = lock


