# This file is part of Xpra.
# Copyright (C) 2010-2019 Antoine Martin <antoine@xpra.org>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

from time import time
from gi.repository import GLib

from xpra.client.client_widget_base import ClientWidgetBase
from xpra.client.window_backing_base import WindowBackingBase
from xpra.os_util import memoryview_to_bytes
from xpra.util import envbool
from xpra.log import Logger

log = Logger("tray")

SAVE = envbool("XPRA_SAVE_SYSTRAY", False)


class ClientTray(ClientWidgetBase):
    """
        This acts like a widget and we use the TrayBacking
        to capture the tray pixels and forward them
        to the real tray widget class.
    """
    DEFAULT_LOCATION = [0, 0]
    DEFAULT_SIZE = [64, 64]
    DEFAULT_GEOMETRY = DEFAULT_LOCATION + DEFAULT_SIZE

    def __init__(self, client, wid, w, h, metadata, tray_widget, mmap_enabled, mmap_area):
        log("ClientTray%s", (client, wid, w, h, tray_widget, mmap_enabled, mmap_area))
        super().__init__(client, 0, wid, True)
        self._metadata = metadata
        self.title = metadata.strget("title", "")
        self.tray_widget = tray_widget
        self._geometry = None
        self._window_alpha = True
        self.group_leader = None

        self.mmap_enabled = mmap_enabled
        self.mmap = mmap_area
        self._backing = None
        self.new_backing(w, h)
        self.idle_add(self.reconfigure)
        #things may have settled by now
        self.timeout_add(1000, self.send_configure)

    def reset_size_constraints(self):
        pass

    def resize(self, nw, nh):
        pass

    def set_alpha(self):
        #nothing to do,
        #trays aren't really windows and transparency is always supported
        pass

    def get_backing_class(self):
        return TrayBacking

    def is_OR(self):
        return True

    def is_tray(self):
        return True

    def get_window(self):
        return None

    def get_geometry(self):
        return self._geometry or ClientTray.DEFAULT_GEOMETRY

    def get_tray_geometry(self):
        tw = self.tray_widget
        if not tw:
            return None
        return tw.get_geometry()

    def get_tray_size(self):
        tw = self.tray_widget
        if not tw:
            return None
        return tw.get_size()


    def freeze(self):
        """
        System trays are small, no point in freezing anything
        """

    def send_configure(self):
        self.reconfigure(True)

    def reconfigure(self, force_send_configure=False):
        geometry = None
        tw = self.tray_widget
        if tw:
            geometry = tw.get_geometry()
        log("%s.reconfigure(%s) geometry=%s", self, force_send_configure, geometry)
        if geometry is None:
            if self._geometry or not tw:
                geometry = self.get_geometry()
            else:
                #make one up as best we can - maybe we have the size at least?
                size = tw.get_size()
                log("%s.reconfigure() guessing location using size=%s", self, size)
                geometry = ClientTray.DEFAULT_LOCATION + list(size or ClientTray.DEFAULT_SIZE)
        x, y, w, h = geometry
        if w<=1 or h<=1:
            w, h = ClientTray.DEFAULT_SIZE
            geometry = x, y, w, h
        if force_send_configure or self._geometry is None or geometry!=self._geometry:
            self._geometry = geometry
            client_properties = {
                "encoding.transparency" : True,
                "encodings.rgb_formats" : ["RGBA", "RGB", "RGBX"],
                }
            if tw:
                orientation = tw.get_orientation()
                if orientation:
                    client_properties["orientation"] = orientation
            #scale to server coordinates
            sx, sy, sw, sh = self._client.crect(x, y, w, h)
            log("%s.reconfigure(%s) sending configure for geometry=%s : %s",
                self, force_send_configure, geometry, (sx, sy, sw, sh, client_properties))
            self._client.send("configure-window", self._id, sx, sy, sw, sh, client_properties)
        if self._size!=(w, h):
            self.new_backing(w, h)

    def move_resize(self, x, y, w, h):
        log("%s.move_resize(%s, %s, %s, %s)", self, x, y, w, h)
        w = max(1, w)
        h = max(1, h)
        self._geometry = x, y, w, h
        self.reconfigure(True)

    def new_backing(self, w, h):
        self._size = w, h
        data = None
        if self._backing:
            data = self._backing.data
        self._backing = TrayBacking(self._id, w, h, self._has_alpha, data)
        if self.mmap_enabled:
            self._backing.enable_mmap(self.mmap)

    def update_metadata(self, metadata):
        log("%s.update_metadata(%s)", self, metadata)

    def update_icon(self, img):
        #this is the window icon... not the tray icon!
        pass


    def draw_region(self, x, y, width, height, coding, img_data, rowstride, packet_sequence, options, callbacks):
        log("%s.draw_region%s", self,
            (x, y, width, height, coding, "%s bytes" % len(img_data), rowstride, packet_sequence, options, callbacks))

        #note: a new backing may be assigned between the time we call draw_region
        # and the time we get the callback (as the draw may use idle_add)
        backing = self._backing
        def after_draw_update_tray(success, message=None):
            log("%s.after_draw_update_tray(%s, %s)", self, success, message)
            if not success:
                log.warn("after_draw_update_tray(%s, %s) options=%s", success, message, options)
                return
            tray_data = backing.data
            log("tray backing=%s, data: %s", backing, tray_data is not None)
            if tray_data is None:
                log.warn("Warning: no pixel data in tray backing for window %i", backing.wid)
                return
            self.idle_add(self.set_tray_icon, tray_data)
            self.idle_add(self.reconfigure)
        callbacks.append(after_draw_update_tray)
        backing.draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks)

    def set_tray_icon(self, tray_data):
        enc, w, h, rowstride, pixels, options = tray_data
        log("%s.set_tray_icon(%s, %s, %s, %s, %s bytes)", self, enc, w, h, rowstride, len(pixels))
        has_alpha = enc=="rgb32"
        tw = self.tray_widget
        if tw:
            #some tray implementations can't deal with memoryviews..
            if isinstance(pixels, (memoryview, bytearray)):
                pixels = memoryview_to_bytes(pixels)
            tw.set_icon_from_data(pixels, has_alpha, w, h, rowstride, options)


    def destroy(self):
        tw = self.tray_widget
        if tw:
            self.tray_widget = None
            tw.cleanup()

    def __repr__(self):
        return "ClientTray(%i:%s)" % (self._id, self.title)


class TrayBacking(WindowBackingBase):
    """
        This backing only stores the rgb pixels so
        we can use them with the real widget.
    """

    #keep it simple: only accept 32-bit RGB(X),
    #all tray implementations support alpha
    RGB_MODES = ("RGBA", "RGBX")
    HAS_ALPHA = True

    def __init__(self, wid, _w, _h, _has_alpha, data=None):
        self.data = data
        super().__init__(wid, True)
        self._backing = object()    #pretend we have a backing structure

    def get_encoding_properties(self):
        #override so we skip all csc caps:
        return {
            "encodings.rgb_formats" : self.RGB_MODES,
            "encoding.transparency" : True,
            }

    def idle_add(self, *args, **kwargs):
        return GLib.idle_add(*args, **kwargs)

    def paint_scroll(self, img_data, options, callbacks):
        raise Exception("scroll should not be used with tray icons")

    def _do_paint_rgb24(self, img_data, x, y, width, height, render_width, render_height, rowstride, options):
        assert width==render_width and height==render_height, "tray rgb must not use scaling"
        self.data = ("rgb24", width, height, rowstride, img_data[:], options)
        if SAVE:
            self.save_tray_png()
        return True

    def _do_paint_rgb32(self, img_data, x, y, width, height, render_width, render_height, rowstride, options):
        assert width==render_width and height==render_height, "tray rgb must not use scaling"
        self.data = ("rgb32", width, height, rowstride, img_data[:], options)
        if SAVE:
            self.save_tray_png()
        return True

    def save_tray_png(self):
        log("save_tray_png()")
        rgb_mode, width, height, _, img_data = self.data[:5]
        mode = "RGB"
        data_mode = "RGB"
        if rgb_mode=="rgb32":
            mode += "A"
            data_mode += "A"
        from PIL import Image
        img = Image.frombytes(mode, (width, height), img_data, "raw", data_mode, width*len(data_mode), 1)
        filename = "./tray-%s-%s.png" % (rgb_mode, time())
        img.save(filename, "PNG")
        log.info("tray %s update saved to %s", rgb_mode, filename)
