#    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 <https://www.gnu.org/licenses/>.
"""Notification window for text"""
import logging
import time
import math

import cairo
import gi
from .image_getter import get_surface, draw_img_to_rect
from .overlay import OverlayWindow
gi.require_version('PangoCairo', '1.0')
# pylint: disable=wrong-import-position,wrong-import-order
from gi.repository import Pango, PangoCairo  # nopep8

log = logging.getLogger(__name__)


class NotificationOverlayWindow(OverlayWindow):
    """Overlay window for notifications"""

    def __init__(self, discover, piggyback=None):
        OverlayWindow.__init__(self, discover, piggyback)
        self.text_spacing = 4
        self.content = []
        self.test_content = [
            {
                "icon": (
                    "https://cdn.discordapp.com/"
                    "icons/951077080769114172/991abffc0d2a5c040444be4d1a4085f4.webp?size=96"
                ),
                "title": "Title1"
            },
            {
                "title": "Title2",
                "body": "Body",
                "icon": None
            },
            {
                "icon": ("https://cdn.discordapp.com/"
                         "icons/951077080769114172/991abffc0d2a5c040444be4d1a4085f4.webp?size=96"
                         ),
                "title": "Title 3",
                "body": ("Lorem ipsum dolor sit amet, consectetur adipiscing elit,"
                         " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
                         "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris "
                         "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in "
                         "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla "
                         "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa "
                         "qui officia deserunt mollit anim id est laborum."
                         )
            },
            {
                "icon": None,
                "title": "Title 3",
                "body": ("Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
                         "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
                         "Ut enim ad minim veniam, quis nostrud exercitation ullamco "
                         "laboris nisi ut aliquip ex ea commodo consequat. Duis aute "
                         "irure dolor in reprehenderit in voluptate velit esse cillum "
                         "dolore eu fugiat nulla pariatur. Excepteur sint occaecat "
                         "cupidatat non proident, sunt in culpa qui officia deserunt "
                         "mollit anim id est laborum."
                         )
            },
            {
                "icon": ("https://cdn.discordapp.com/"
                         "avatars/147077941317206016/6a6935192076489fa6dc1eb5dafbf6e7.webp?size=128"
                         ),
                "title": "PM",
                "body": "Birdy test"
            }
        ]
        self.text_font = None
        self.text_size = 13
        self.text_time = None
        self.show_icon = None
        self.pango_rect = Pango.Rectangle()
        self.pango_rect.width = self.text_size * Pango.SCALE
        self.pango_rect.height = self.text_size * Pango.SCALE

        self.connected = True
        self.bg_col = [0.0, 0.6, 0.0, 0.1]
        self.fg_col = [1.0, 1.0, 1.0, 1.0]
        self.icons = []
        self.reverse_order = False
        self.padding = 10
        self.border_radius = 5
        self.limit_width = 100
        self.testing = False
        self.icon_size = 64
        self.icon_pad = 16
        self.icon_left = True

        self.image_list = {}
        self.warned_filetypes = []
        self.set_title("Discover Notifications")
        self.redraw()

    def set_blank(self):
        """Set to no data and redraw"""
        self.content = []
        self.set_needs_redraw()

    def tick(self):
        """Remove old messages from dataset"""
        now = time.time()
        newlist = []
        oldsize = len(self.content)
        # Iterate over and remove messages older than 30s
        for message in self.content:
            if message['time'] + self.text_time > now:
                newlist.append(message)
        self.content = newlist
        # If there is still content to remove
        if len(newlist) > 0 or oldsize != len(newlist):
            self.set_needs_redraw()

    def add_notification_message(self, data):
        """Add new message to dataset"""
        noti = None
        data = data['data']
        message_id = data['message']['id']
        for message in self.content:
            if message['id'] == message_id:
                return
        if 'body' in data and 'title' in data:
            if 'icon_url' in data:
                noti = {"icon": data['icon_url'],
                        "title": data['title'],
                        "body": data['body'], "time": time.time(),
                        "id": message_id}
            else:
                noti = {"title": data['title'],
                        "body": data['body'], "time": time.time(),
                        "id": message_id}

        if noti:
            self.content.append(noti)
            self.set_needs_redraw()
            self.get_all_images()

    def set_padding(self, padding):
        """Config option: Padding between notifications, in window-space pixels"""
        if self.padding != padding:
            self.padding = padding
            self.set_needs_redraw()

    def set_border_radius(self, radius):
        """Config option: Radius of the border, in window-space pixels"""
        if self.border_radius != radius:
            self.border_radius = radius
            self.set_needs_redraw()

    def set_icon_size(self, size):
        """Config option: Size of icons, in window-space pixels"""
        if self.icon_size != size:
            self.icon_size = size
            self.image_list = {}
            self.get_all_images()

    def set_icon_pad(self, pad):
        """Config option: Padding between icon and message, in window-space pixels"""
        if self.icon_pad != pad:
            self.icon_pad = pad
            self.set_needs_redraw()

    def set_icon_left(self, left):
        """Config option: Icon on left or right of text"""
        if self.icon_left != left:
            self.icon_left = left
            self.set_needs_redraw()

    def set_text_time(self, timer):
        """Config option: Duration that a message will be visible for, in seconds"""
        self.text_time = timer
        self.timer_after_draw = timer

    def set_limit_width(self, limit):
        """Config option: Word wrap limit, in window-space pixels
        """
        if self.limit_width != limit:
            self.limit_width = limit
            self.set_needs_redraw()

    def get_all_images(self):
        """Return a list of all downloaded images"""
        the_list = self.content
        if self.testing:
            the_list = self.test_content
        for line in the_list:
            icon = line["icon"]

            if icon and icon not in self.image_list:
                get_surface(self.recv_icon, icon, icon,
                            self.icon_size)

    def recv_icon(self, identifier, pix, _mask):
        """Callback from image_getter for icons"""
        self.image_list[identifier] = pix
        self.set_needs_redraw()

    def set_fg(self, fg_col):
        """Config option: Set default text colour"""
        if self.fg_col != fg_col:
            self.fg_col = fg_col
            self.set_needs_redraw()

    def set_bg(self, bg_col):
        """Config option: Set background colour"""
        if self.bg_col != bg_col:
            self.bg_col = bg_col
            self.set_needs_redraw()

    def set_show_icon(self, icon):
        """Config option: Set if icons should be shown inline"""
        if self.show_icon != icon:
            self.show_icon = icon
            self.set_needs_redraw()
            self.get_all_images()

    def set_reverse_order(self, rev):
        """Config option: Reverse order of messages"""
        if self.reverse_order != rev:
            self.reverse_order = rev
            self.set_needs_redraw()

    def set_font(self, font):
        """Config option: Font used to render text"""
        if self.text_font != font:
            self.text_font = font

            self.pango_rect = Pango.Rectangle()
            font = Pango.FontDescription(self.text_font)
            self.pango_rect.width = font.get_size() * Pango.SCALE
            self.pango_rect.height = font.get_size() * Pango.SCALE
            self.set_needs_redraw()

    def recv_attach(self, identifier, pix):
        """Callback from image_getter for attachments"""
        self.icons[identifier] = pix
        self.set_needs_redraw()

    def calc_all_height(self):
        """Return the height in window-space pixels required
           to draw this overlay with current dataset"""
        h = 0
        my_list = self.content
        if self.testing:
            my_list = self.test_content
        for line in my_list:
            h += self.calc_height(line)
        if h > 0:
            h -= self.padding  # Remove one unneeded padding
        return h

    def calc_height(self, line):
        """Return height in window-space pixels required to draw individual notification"""
        icon_width = 0
        icon_pad = 0
        icon = line['icon']
        if self.show_icon and icon and icon in self.image_list and self.image_list[icon]:
            icon_width = self.icon_size
            icon_pad = self.icon_pad
        message = ""
        if 'body' in line and len(line['body']) > 0:
            m_no_body = "<span>%s</span>\n%s"
            message = m_no_body % (self.sanitize_string(line["title"]),
                                   self.sanitize_string(line['body']))
        else:
            m_with_body = "<span>%s</span>"
            message = m_with_body % (self.sanitize_string(line["title"]))
        layout = self.create_pango_layout(message)
        layout.set_auto_dir(True)
        layout.set_markup(message, -1)
        (_floating_x, _floating_y, floating_width,
         _floating_height) = self.get_floating_coords()
        width = self.limit_width if floating_width > self.limit_width else floating_width
        layout.set_width((Pango.SCALE * (width -
                         (self.border_radius * 4 + icon_width + icon_pad))))
        layout.set_spacing(Pango.SCALE * 3)
        if self.text_font:
            font = Pango.FontDescription(self.text_font)
            layout.set_font_description(font)
        _text_width, text_height = layout.get_pixel_size()
        if text_height < icon_width:
            text_height = icon_width
        return text_height + (self.border_radius*4) + self.padding

    def has_content(self):
        """Return true if this overlay has meaningful content to show"""
        if not self.enabled:
            return False
        if self.hidden:
            return False
        if self.testing:
            return self.test_content
        return self.content

    def overlay_draw(self, w, context, data=None):
        """Draw the overlay"""
        if self.piggyback:
            self.piggyback.overlay_draw(w, context, data)
        if not self.enabled:
            return
        self.context = context
        (_width, height) = self.get_size()
        if not self.piggyback_parent:
            context.set_antialias(cairo.ANTIALIAS_GOOD)

            # Make background transparent
            context.set_source_rgba(0.0, 0.0, 0.0, 0.0)
            context.set_operator(cairo.OPERATOR_SOURCE)
            context.paint()

        self.tick()
        context.save()
        if self.is_wayland or self.piggyback_parent or self.discover.steamos:
            # Special case!
            # The window is full-screen regardless of what the user has selected.
            # We need to set a clip and a transform to imitate original behaviour
            # Used in wlroots & gamescope
            (floating_x, floating_y, floating_width,
             floating_height) = self.get_floating_coords()
            if self.floating:
                context.new_path()
                context.translate(floating_x, floating_y)
                context.rectangle(0, 0, floating_width, floating_height)
                context.clip()

        current_y = height
        if self.align_vert == 0:
            current_y = 0
        if self.align_vert == 1:  # Center. Oh god why
            current_y = (height/2.0) - (self.calc_all_height() / 2.0)
        if self.testing:
            the_list = self.test_content
        else:
            the_list = self.content
        if self.reverse_order:
            the_list = reversed(the_list)
        for line in the_list:
            col = "#fff"
            if 'body' in line and len(line['body']) > 0:
                m_no_body = "<span foreground='%s'>%s</span>\n%s"
                message = m_no_body % (self.sanitize_string(col),
                                       self.sanitize_string(line["title"]),
                                       self.sanitize_string(line['body']))
            else:
                m_with_body = "<span foreground='%s'>%s</span>"
                message = m_with_body % (self.sanitize_string(col),
                                         self.sanitize_string(line["title"]))

            icon = None
            # If we've got an embedded image
            if "icon_surface" in line and line["icon_surface"]:
                icon = line["icon_surface"]
            # If we're given an icon name, it's in the list of icons, and it's not none
            elif line["icon"] and line["icon"] in self.image_list and self.image_list[line["icon"]]:
                icon = self.image_list[line["icon"]]

            current_y = self.draw_text(current_y, message, icon)
            if current_y <= 0:
                # We've done enough
                break
        context.restore()
        self.context = None

    def draw_text(self, pos_y, text, icon):
        """Draw a text message, returning the Y position of the next message"""
        icon_width = self.icon_size
        icon_pad = self.icon_pad
        if not self.show_icon:
            icon = None
        if not icon:
            icon_pad = 0
            icon_width = 0

        layout = self.create_pango_layout(text)
        layout.set_auto_dir(True)
        layout.set_markup(text, -1)
        attr = layout.get_attributes()

        (_floating_x, _floating_y, floating_width,
         _floating_height) = self.get_floating_coords()
        width = self.limit_width if floating_width > self.limit_width else floating_width
        layout.set_width((Pango.SCALE * (width -
                         (self.border_radius * 4 + icon_width + icon_pad))))
        layout.set_spacing(Pango.SCALE * 3)
        if self.text_font:
            font = Pango.FontDescription(self.text_font)
            layout.set_font_description(font)
        text_width, text_height = layout.get_pixel_size()
        self.col(self.bg_col)
        top = 0
        if self.align_vert == 2:  # Bottom align
            top = pos_y - (text_height + self.border_radius * 4)
        else:  # Top align
            top = pos_y
        if text_height < icon_width:
            text_height = icon_width
        shape_height = text_height + self.border_radius * 4
        shape_width = text_width + self.border_radius*4 + icon_width + icon_pad

        left = 0
        if self.align_right:
            left = floating_width - shape_width

        self.context.save()
        # Draw Background
        self.context.translate(left, top)
        # self.context.rectangle(self.border_radius, 0,
        #                       shape_width - (self.border_radius*2), shape_height)
        # self.context.fill()
        # self.context.rectangle(0, self.border_radius,
        #                       shape_width, shape_height - (self.border_radius * 2))

        # self.context.arc(0.7, 0.3, 0.035, 1.25 * math.pi, 2.25 * math.pi)
        # self.context.arc(0.3, 0.7, 0.035, .25 * math.pi, 1.25 * math.pi)
        if self.border_radius == 0:

            self.context.move_to(0.0, 0.0)
            self.context.line_to(shape_width, 0.0)
            self.context.line_to(shape_width, shape_height)
            self.context.line_to(0.0, shape_height)
            self.context.close_path()
            self.context.fill()
        else:
            # Edge top
            self.context.move_to(self.border_radius, 0.0)
            self.context.line_to(shape_width - self.border_radius, 0.0)

            # Arc topright
            self.context.arc(shape_width - self.border_radius, self.border_radius,
                             self.border_radius, 1.5 * math.pi, 2 * math.pi)

            # Edge right
            self.context.line_to(
                shape_width, shape_height - self.border_radius)

            # Arc bottomright
            self.context.arc(shape_width - self.border_radius, shape_height - self.border_radius,
                             self.border_radius, 0.0, 0.5 * math.pi)

            # Edge bottom
            self.context.line_to(self.border_radius, shape_height)

            # Arch bottomleft
            self.context.arc(self.border_radius, shape_height -
                             self.border_radius, self.border_radius, 0.5 * math.pi, math.pi)

            # Edge left
            self.context.line_to(0.0, self.border_radius)

            # Arc topleft
            self.context.arc(self.border_radius, self.border_radius,
                             self.border_radius, math.pi, 1.5 * math.pi)

            # End
            self.context.close_path()
            self.context.fill()

        self.context.set_operator(cairo.OPERATOR_OVER)
        # Draw Image
        if icon:
            self.context.save()
            if self.icon_left:
                self.context.translate(
                    self.border_radius*2, self.border_radius*2)
                draw_img_to_rect(icon, self.context, 0, 0,
                                 icon_width, icon_width)
            else:
                self.context.translate(
                    self.border_radius*2 + text_width + icon_pad, self.border_radius*2)
                draw_img_to_rect(icon, self.context, 0, 0,
                                 icon_width, icon_width)
            self.context.restore()

        self.col(self.fg_col)

        if self.icon_left:
            self.context.translate(
                self.border_radius*2 + icon_width + icon_pad, self.border_radius*2)
            PangoCairo.context_set_shape_renderer(
                self.get_pango_context(), self.render_custom, None)

            text = layout.get_text()

            layout.set_attributes(attr)

            PangoCairo.show_layout(self.context, layout)
        else:
            self.context.translate(self.border_radius*2, self.border_radius*2)
            PangoCairo.context_set_shape_renderer(
                self.get_pango_context(), self.render_custom, None)

            text = layout.get_text()

            layout.set_attributes(attr)

            PangoCairo.show_layout(self.context, layout)

        self.context.restore()
        next_y = 0
        if self.align_vert == 2:
            next_y = pos_y - (shape_height + self.padding)
        else:
            next_y = pos_y + shape_height + self.padding
        return next_y

    def render_custom(self, ctx, shape, path, _data):
        """Draw an inline image as a custom emoticon"""
        if shape.data >= len(self.image_list):
            log.warning("%s >= %s", shape.data, len(self.image_list))
            return
        # key is the url to the image
        key = self.image_list[shape.data]
        if key not in self.icons:
            get_surface(self.recv_attach,
                        key,
                        key, None)
            return
        pix = self.icons[key]
        (pos_x, pos_y) = ctx.get_current_point()
        draw_img_to_rect(pix, ctx, pos_x, pos_y - self.text_size, self.text_size,
                         self.text_size, path=path)
        return True

    def sanitize_string(self, string):
        """Sanitize a text message so that it doesn't intefere with Pango's XML format"""
        string = string.replace("&", "&amp;")
        string = string.replace("<", "&lt;")
        string = string .replace(">", "&gt;")
        string = string.replace("'", "&#39;")
        string = string.replace("\"", "&#34;")
        return string

    def set_testing(self, testing):
        """Toggle placeholder images for testing"""
        self.testing = testing
        self.set_needs_redraw()
        self.get_all_images()
