# Copyright 3D Control Systems, Inc. All Rights Reserved 2017-2019.

# Built in San Francisco.

# This software is distributed under a commercial license for personal,
# educational, corporate or any other use.
# The software as a whole or any parts of it is prohibited for distribution or
# use without obtaining a license from 3D Control Systems, Inc.

# All software licenses are subject to the 3DPrinterOS terms of use
# (available at https://www.3dprinteros.com/terms-and-conditions/),
# and privacy policy (available at https://www.3dprinteros.com/privacy-policy/)

import os
import sys
import time

# fix of broken paths for windows
if not sys.path:
    sys.path = []
path = os.getcwd()
if path not in sys.path:
    sys.path.insert(0, path)

import pystray
import tray_common

import cv2
import struct

# Little endian int encoding (for bmp/icon writing)
w1 = lambda x: struct.pack('<B', x)
w2 = lambda x: struct.pack('<H', x)
w4 = lambda x: struct.pack('<I', x)

# quack like a PIL.Image without depending on PIL
class _Img(object):
    def __init__(self, path):
        self._arr = cv2.imread(path, cv2.IMREAD_UNCHANGED)
        if self._arr.shape[2] != 4:
            raise ValueError('must be PNG with alpha channel')

    @property
    def img(self):
        return self._arr[:, :, :3]

    def save(self, fd, format):
        if format == 'ICO':
            self._save_ico(fd)
        elif format in ('PNG', 'JPG'):
            _, arr = cv2.imencode('.%s' % format.lower(), self._arr)
            arr.tofile(fd)
        else:
            raise NotImplementedError

    @staticmethod
    def _to_bmp(im, file_header=False):
        _h, width = im.shape[:2]
        if _h != width:
            raise ValueError('must be square')
        height = reported_height = width
        if not file_header:
            reported_height *= 2  # This is soo weird, but it needs to be so

        # Flip vertically
        im = cv2.flip(im, 0)

        # DIB header
        bb = b''
        bb += w4(40)  # header size
        bb += w4(width)
        bb += w4(reported_height)
        bb += w2(1)  # 1 color plane
        bb += w2(32)
        bb += w4(0)  # no compression
        bb += w4(len(im))
        bb += w4(2835) + w4(2835)  # 2835 pixels/meter, ~ 72 dpi
        bb += w4(0)  # number of colors in palette
        bb += w4(0)  # number of important colors (0->all)

        # File header (not when bm is in-memory)
        header = b''
        if file_header:
            header += b'BM'
            header += w4(14 + 40 + len(im))  # file size
            header += b'\x00\x00\x00\x00'
            header += w4(14 + 40)  # pixel data offset

        # Add pixels
        # No padding, because we assume power of 2 image sizes
        return header + bb + im.tobytes()

    def _save_ico(self, fd):

        sizes = [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)]

        bb = b''
        imdatas = []

        # Header
        bb += w2(0)
        bb += w2(1)  # 1:ICO, 2:CUR
        bb += w2(len(sizes))

        # Put offset right after the last directory entry
        offset = len(bb) + 16 * len(sizes)

        # Directory (header for each image)
        for sw, sh in sizes:
            # icons are square
            size = sw

            im = cv2.resize(self._arr, (sw, sh))
            if size > 256:
                continue
            elif size >= 64:
                _, arr = cv2.imencode('.png', self._arr)
                imdata = arr.tobytes()
            else:
                imdata = self._to_bmp(im)

            imdatas.append(imdata)
            # Prepare dimensions
            w = h = 0 if size == 256 else size
            # Write directory entry
            bb += w1(w)
            bb += w1(h)
            bb += w1(0)  # number of colors in palette, assume no palette (0)
            bb += w1(0)  # reserved (must be 0)
            bb += w2(0)  # color planes
            bb += w2(32)  # bits per pixel
            bb += w4(len(imdata))  # size of image data
            bb += w4(offset)
            # Set offset pointer
            offset += len(imdata)

        fd.write(b''.join([bb] + imdatas))


class PyStrayTray:

    def __init__(self):
        self.icon = None
        self.status_checker = tray_common.StatusChecker(self.show_balloon)
        if not pystray.Icon.HAS_NOTIFICATION:
            self.status_checker.toggle_notifications(False)
        self.icon = pystray.Icon('3DPrinterOS', icon=self.load_icon(), menu=pystray.Menu(*self.menu_items()))
        self.icon.run()

    def show_balloon(self, title, body):
        if self.icon and self.icon.HAS_NOTIFICATION:
            self.icon.notify(body, title)

    def are_notifications_enabled(self, *args, **kwargs):
        return self.status_checker.notifications

    def load_icon(self):
        return _Img(tray_common.ICON_PATH_PNG)

    def on_toggle_notifications(self, _, item):
        # print('Item pre checked:', item.checked)
        # print('Item pre enabled:', item.enabled)
        # print('Notifications pre:', self.are_notifications_enabled())
        self.status_checker.toggle_notifications(not item.checked)
        # print('Item post checked:', item.checked)
        # print('Item post enabled:', item.enabled)
        # print('Notifications post:', self.are_notifications_enabled())
        #self.icon.items = self.menu_items()
        self.icon.update_menu()

    def on_enable_notifications(self, _, __):
        self.status_checker.toggle_notifications(True)

    def on_disable_notifications(self, _, __):
        self.status_checker.toggle_notifications(False)

    def on_quit(self):
        self.icon.stop()
        self.status_checker.stop()
        time.sleep(self.status_checker.LOOP_SLEEP_TIME / self.status_checker.LOOP_SLEEP_TIME + 0.01)
        tray_common.stop_app()
        sys.exit()

    def on_ui(self):
        tray_common.open_browser_tab()

    # def menu_items(self):
    #     menu = [pystray.MenuItem('Open UI', self.on_ui, default=True)]
    #     print("!!!HAS_NOTIFIS:", pystray.Icon.HAS_NOTIFICATION)
    #     if pystray.Icon.HAS_NOTIFICATION:
    #         if self.are_notifications_enabled():
    #             item = pystray.MenuItem('Enable notifications', action=self.on_enable_notifications)
    #         else:
    #             item = pystray.MenuItem('Disable notifications', action=self.on_disable_notifications)
    #         menu.append(item)
    #     menu.append(pystray.MenuItem('Quit', self.on_quit))
    #     return menu

    def menu_items(self):
        return (pystray.MenuItem('Open UI', self.on_ui, default=True),
                pystray.MenuItem(
                    'Toggle notifications',
                    action=self.on_toggle_notifications,
                    checked=self.are_notifications_enabled,
                    visible=pystray.Icon.HAS_NOTIFICATION),
                pystray.MenuItem('Quit', self.on_quit))

    # def menu_items(self):
    #     return (pystray.MenuItem('Open UI', self.on_ui, default=True),
    #             pystray.MenuItem(
    #                 'Enable notifications',
    #                 action=self.on_enable_notifications,
    #                 visible=pystray.Icon.HAS_NOTIFICATION),
    #             pystray.MenuItem(
    #                 'Disable notifications',
    #                 action=self.on_disable_notifications,
    #                 visible=pystray.Icon.HAS_NOTIFICATION),
    #             pystray.MenuItem('Quit', self.on_quit))


if __name__ == '__main__':
    icon = PyStrayTray()
