# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg
#
# This file is part of Timeline.
#
# Timeline 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.
#
# Timeline 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 Timeline.  If not, see <http://www.gnu.org/licenses/>.


import os

import wx

from timelinelib.canvas.drawing.utils import black_solid_pen, black_solid_brush, get_colour, darken_color
from timelinelib.config.paths import EVENT_ICONS_DIR
from timelinelib.features.experimental.experimentalfeatures import EXTENDED_CONTAINER_HEIGHT
from timelinelib.canvas.eventboxdrawers.defaultmilestonedrawer import DefaultMilestoneDrawer
from timelinelib.canvas.eventboxdrawers.handlerect import HandleRect, MIDDLE_HANDLE, LEFT_HANDLE, RIGHT_HANDLE
from timelinelib.wxgui.utils import load_bitmap


HANDLE_SIZE = 4
HALF_HANDLE_SIZE = HANDLE_SIZE // 2
DATA_INDICATOR_SIZE = 10
INNER_PADDING = 3  # Space inside event box to text (pixels)
GRAY = (200, 200, 200)
BITMAPS = dict();


class DefaultEventBoxDrawer:

    def draw(self, dc, scene, rect, event, view_properties):
        self.scene = scene
        self.view_properties = view_properties
        selected = view_properties.is_selected(event)
        self.center_text = scene.center_text()
        if event.is_milestone():
            DefaultMilestoneDrawer(rect, event, selected, view_properties).draw(dc)
        elif scene.never_show_period_events_as_point_events() and rect.y < scene.divider_y and event.is_period():
            self._draw_period_event_as_symbol_below_divider_line(dc, scene, event)
        else:
            self._draw_event_box(dc, rect, event, selected)

    def _draw_period_event_as_symbol_below_divider_line(self, dc, scene, event):
        dc.DestroyClippingRegion()
        x = scene.x_pos_for_time(event.mean_time())
        y0 = scene.divider_y
        y1 = y0 + 10
        dc.SetBrush(black_solid_brush())
        dc.SetPen(black_solid_pen(1))
        dc.DrawLine(x, y0, x, y1)
        dc.DrawCircle(x, y1, 2)
        event.symbol_rect = wx.Rect(x - 5, y1 - 5, 10, 10)

    def _draw_event_box(self, dc, rect, event, selected):
        self._draw_background(dc, rect, event)
        self._draw_fuzzy_edges(dc, rect, event)
        self._draw_locked_edges(dc, rect, event)
        self._draw_progress_box(dc, rect, event)
        self._draw_text(dc, rect, event)
        self._draw_contents_indicator(dc, event, rect)
        self._draw_locked_edges(dc, rect, event)
        self._draw_selection_handles(dc, event, rect, selected)
        self._draw_hyperlink(dc, rect, event)

    def _draw_background(self, dc, rect, event):
        dc.SetBrush(wx.Brush(event.get_color(), wx.BRUSHSTYLE_SOLID))
        dc.SetPen(self._get_pen(dc, event))
        dc.DrawRectangle(rect)

    def _get_pen(self, dc, event):
        pen = self._get_thin_border_pen(event)
        if self.view_properties.is_highlighted(event):
            if self.view_properties.get_highlight_count(event) % 2 == 0:
                dc.DestroyClippingRegion()
                pen = self._get_thick_border_pen(event)
        return pen

    def _draw_fuzzy_edges(self, dc, rect, event):
        if event.fuzzy_start:
            self._draw_fuzzy_start(dc, rect)
        if event.fuzzy_end:
            if not event.get_ends_today():
                self._draw_fuzzy_end(dc, rect)

    def _draw_locked_edges(self, dc, rect, event):
        if event.get_ends_today():
            self._draw_locked_end(dc, rect)
        if event.get_locked():
            self._draw_locked_start(dc, rect)
            self._draw_locked_end(dc, rect)

    def _draw_contents_indicator(self, dc, event, rect):
        if event.has_balloon_data():
            self._draw_balloon_indicator(dc, event, rect)

    def _draw_selection_handles(self, dc, event, rect, selected):
        if not event.locked and selected:
            self._draw_handles(dc, event, rect)

    def _get_thin_border_pen(self, event):
        return self._get_border_pen(event)

    def _get_thick_border_pen(self, event):
        return self._get_border_pen(event, thickness=8)

    def _get_border_pen(self, event, thickness=1):
        return wx.Pen(event.get_border_color(), thickness, wx.PENSTYLE_SOLID)

    def _get_balloon_indicator_brush(self, event):
        base_color = event.get_color()
        darker_color = darken_color(base_color, 0.6)
        brush = wx.Brush(darker_color, wx.BRUSHSTYLE_SOLID)
        return brush

    def _draw_fuzzy_start(self, dc, rect):
        self._inflate_clipping_region(dc, rect)
        dc.DrawBitmap(self._get_fuzzy_bitmap(), rect.x - 4, rect.y + 4, True)

    def _draw_fuzzy_end(self, dc, rect):
        self._inflate_clipping_region(dc, rect)
        dc.DrawBitmap(self._get_fuzzy_bitmap(), rect.x + rect.width - 8, rect.y + 4, True)

    def _draw_locked_start(self, dc, rect):
        self._inflate_clipping_region(dc, rect)
        dc.DrawBitmap(self._get_lock_bitmap(), rect.x - 7, rect.y + 3, True)

    def _draw_locked_end(self, dc, rect):
        self._inflate_clipping_region(dc, rect)
        dc.DrawBitmap(self._get_lock_bitmap(), rect.x + rect.width - 8, rect.y + 3, True)

    def _draw_progress_box(self, dc, rect, event):
        if event.get_data("progress"):
            self._set_progress_color(dc, event)
            progress_rect = self._get_progress_rect(rect, event)
            dc.DrawRectangle(progress_rect)

    def _set_progress_color(self, dc, event):
        progress_color = event.get_progress_color()
        dc.SetBrush(wx.Brush(wx.Colour(progress_color[0], progress_color[1], progress_color[2])))

    def _get_progress_rect(self, event_rect, event):
        HEIGHT_FACTOR = 0.35
        h = event_rect.height * HEIGHT_FACTOR
        y = event_rect.y + (event_rect.height - h)
        if event.is_period():
            rw = self.scene._calc_width_for_period_event(event)
            rx = self.scene._calc_x_pos_for_period_event(event)
        else:
            rw = self.scene._calc_width_for_non_period_event(event)
            rx = self.scene._calc_x_pos_for_non_period_event(event, rw)
        w = rw * event.get_data("progress") / 100.0
        # Avoid overflow values in args to wx.Rect()
        #  which can happen when rx has a very large negative or positive value.
        if rx < 0:
            w += rx
            rx = 0
        if w > self.scene.width:
            w = self.scene.width
        return wx.Rect(int(rx), int(y), int(w), int(h))

    def _draw_balloon_indicator(self, dc, event, rect):
        """
        The data contents indicator is a small triangle drawn in the upper
        right corner of the event rectangle.
        """
        corner_x = rect.X + rect.Width
        points = (
            wx.Point(corner_x - DATA_INDICATOR_SIZE, rect.Y),
            wx.Point(corner_x, rect.Y),
            wx.Point(corner_x, rect.Y + DATA_INDICATOR_SIZE),
        )
        dc.SetBrush(self._get_balloon_indicator_brush(event))
        dc.SetPen(wx.TRANSPARENT_PEN)
        dc.DrawPolygon(points)

    def _draw_text(self, dc, rect, event):
        # Ensure that we can't draw content outside inner rectangle
        if self._there_is_room_for_the_text(rect):
            self._draw_the_text(dc, rect, event)

    def _there_is_room_for_the_text(self, rect):
        return deflate_rect(rect).Width > 0

    def _draw_the_text(self, dc, rect, event):
        self._set_text_foreground_color(dc, event)
        if event.is_container() and EXTENDED_CONTAINER_HEIGHT.enabled():
            EXTENDED_CONTAINER_HEIGHT.draw_container_text_top_adjusted(event.get_text(), dc, rect)
        else:
            self._draw_normal_text(dc, rect, event)
        dc.DestroyClippingRegion()

    def _draw_normal_text(self, dc, rect, event):
        self._set_clipping_rect(dc, rect)
        dc.DrawText(self._get_text(event), self._calc_x_pos(dc, rect, event), self._calc_y_pos(rect))

    def _get_text(self, event):
        if event.get_progress() == 100 and self.view_properties.get_display_checkmark_on_events_done():
            return "\u2714" + event.get_text()
        else:
            return event.get_text()

    def _set_clipping_rect(self, dc, rect):
        dc.SetClippingRegion(deflate_rect(rect))

    def _calc_x_pos(self, dc, rect, event):
        inner_rect = deflate_rect(rect)
        text_x = inner_rect.X
        text_x = self._adjust_x_for_edge_icons(event, rect, text_x)
        text_x = self._adjust_x_for_centered_text(dc, event, inner_rect, text_x)
        return text_x

    def _adjust_x_for_edge_icons(self, event, rect, text_x):
        if event.has_edge_icons():
            text_x += rect.Height // 2
        return text_x

    def _adjust_x_for_centered_text(self, dc, event, inner_rect, text_x):
        if self.center_text:
            text_x = self._center_text(dc, event, inner_rect, text_x)
        return text_x

    def _calc_y_pos(self, rect):
        return deflate_rect(rect).Y

    def _center_text(self, dc, event, inner_rect, text_x):
        width, _ = dc.GetTextExtent(self._get_text(event))
        return max(text_x, text_x + (inner_rect.width - width) // 2)

    def _set_text_foreground_color(self, dc, event):
        try:
            dc.SetTextForeground(get_colour(event.get_category().font_color))
        except AttributeError:
            dc.SetTextForeground(wx.BLACK)

    def _draw_handles(self, dc, event, rect):

        def draw_frame_around_event():
            small_rect = wx.Rect(*rect)
            small_rect.Deflate(1, 1)
            border_color = event.get_border_color()
            border_color = darken_color(border_color)
            pen = wx.Pen(border_color, 1, wx.PENSTYLE_SOLID)
            dc.SetBrush(wx.TRANSPARENT_BRUSH)
            dc.SetPen(pen)
            dc.DrawRectangle(small_rect)

        dc.SetClippingRegion(rect)
        draw_frame_around_event()
        self._draw_all_handles(dc, rect)
        dc.DestroyClippingRegion()

    @staticmethod
    def _draw_all_handles(dc, rect):
        dc.DestroyClippingRegion()
        HandleRect(rect, LEFT_HANDLE).draw(dc)
        HandleRect(rect, MIDDLE_HANDLE).draw(dc)
        HandleRect(rect, RIGHT_HANDLE).draw(dc)

    def _draw_hyperlink(self, dc, rect, event):
        if event.get_hyperlink():
            bitmap = self._get_hyperlink_bitmap()
            if not bitmap.IsOk():
                bitmap = wx.ArtProvider.GetBitmap(wx.ART_MISSING_IMAGE, wx.ART_MENU)
            dc.DrawBitmap(bitmap, rect.x + rect.width - 14, rect.y + 4, True)

    @staticmethod
    def _inflate_clipping_region(dc, rect):
        copy = wx.Rect(*rect)
        copy.Inflate(10, 0)
        dc.DestroyClippingRegion()
        dc.SetClippingRegion(copy)

    def _get_hyperlink_bitmap(self):
        return get_bitmap(self.view_properties.get_hyperlink_icon())

    def _get_lock_bitmap(self):
        return get_bitmap(self.view_properties.get_locked_icon())

    def _get_fuzzy_bitmap(self):
        return get_bitmap(self.view_properties.get_fuzzy_icon())


def deflate_rect(rect, dx=INNER_PADDING, dy=INNER_PADDING):
    return wx.Rect(*rect).Deflate(dx, dy)


def center_point_with_offset(rect, dx=0, dy=0):
    y = rect.Y + rect.Height // 2 - dy
    x = rect.X + rect.Width // 2 - dx
    return wx.Point(x, y)


def draw_centered_text(dc, rect, label):
    size = dc.GetTextExtent(label)
    point = center_point_with_offset(rect, size.width // 2, size.height // 2)
    dc.DrawText(label, point)


def get_bitmap(name):
    if name not in BITMAPS:
        path = os.path.join(EVENT_ICONS_DIR, name)
        BITMAPS[name] = load_bitmap(path)
    return BITMAPS[name]
