import time
from threading import Lock, Thread
from typing import List

from PyQt5.QtCore import QThread, pyqtSignal, QCoreApplication, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu

from bauh import __app_name__
from bauh.api.abstract.controller import SoftwareManager
from bauh.api.abstract.model import PackageUpdate
from bauh.view.util import util, resource
from bauh.view.qt.about import AboutDialog
from bauh.view.qt.window import ManageWindow


class UpdateCheck(QThread):

    signal = pyqtSignal(list)

    def __init__(self, manager: SoftwareManager, check_interval: int, parent=None):
        super(UpdateCheck, self).__init__(parent)
        self.check_interval = check_interval
        self.manager = manager

    def run(self):

        while True:
            updates = self.manager.list_updates()
            self.signal.emit(updates)
            time.sleep(self.check_interval)


class TrayIcon(QSystemTrayIcon):

    def __init__(self, i18n: dict, manager: SoftwareManager, manage_window: ManageWindow, check_interval: int = 60, update_notification: bool = True):
        super(TrayIcon, self).__init__()
        self.i18n = i18n
        self.manager = manager

        self.icon_default = QIcon(resource.get_path('img/logo.png'))
        self.icon_update = QIcon(resource.get_path('img/logo_update.png'))
        self.setIcon(self.icon_default)

        self.menu = QMenu()

        self.action_manage = self.menu.addAction(self.i18n['tray.action.manage'])
        self.action_manage.triggered.connect(self.show_manage_window)

        self.action_about = self.menu.addAction(self.i18n['tray.action.about'])
        self.action_about.triggered.connect(self.show_about)

        self.action_exit = self.menu.addAction(self.i18n['tray.action.exit'])
        self.action_exit.triggered.connect(lambda: QCoreApplication.exit())

        self.setContextMenu(self.menu)

        self.manage_window = None
        self.dialog_about = None
        self.check_thread = UpdateCheck(check_interval=check_interval, manager=self.manager)
        self.check_thread.signal.connect(self.notify_updates)
        self.check_thread.start()

        self.last_updates = set()
        self.update_notification = update_notification
        self.lock_notify = Lock()

        self.activated.connect(self.handle_click)
        self.set_default_tooltip()

        self.manage_window = manage_window

    def set_default_tooltip(self):
        self.setToolTip('{} ({})'.format(self.i18n['manage_window.title'], __app_name__).lower())

    def handle_click(self, reason):
        if reason == self.Trigger:
            self.show_manage_window()

    def verify_updates(self, notify_user: bool = True):
        Thread(target=self._verify_updates, args=(notify_user,)).start()

    def _verify_updates(self, notify_user: bool):
        self.notify_updates(self.manager.list_updates(), notify_user=notify_user)

    def notify_updates(self, updates: List[PackageUpdate], notify_user: bool = True):

        self.lock_notify.acquire()

        try:
            if len(updates) > 0:
                update_keys = {'{}:{}:{}'.format(up.type, up.id, up.version) for up in updates}

                new_icon = self.icon_update

                if update_keys.difference(self.last_updates):
                    self.last_updates = update_keys
                    n_updates = len(updates)
                    msg = self.i18n['notification.update{}'.format('' if n_updates == 1 else 's')].format(n_updates)
                    self.setToolTip(msg)

                    if self.update_notification and notify_user:
                        util.notify_user(msg=msg)

            else:
                self.last_updates.clear()
                new_icon = self.icon_default
                self.set_default_tooltip()

            if self.icon().cacheKey() != new_icon.cacheKey():  # changes the icon if needed
                self.setIcon(new_icon)

        finally:
            self.lock_notify.release()

    def show_manage_window(self):
        if self.manage_window.isMinimized():
            self.manage_window.setWindowState(Qt.WindowNoState)
        elif not self.manage_window.isVisible():
            self.manage_window.refresh_apps()
            self.manage_window.show()

    def show_about(self):

        if self.dialog_about is None:
            self.dialog_about = AboutDialog(self.i18n)

        if self.dialog_about.isHidden():
            self.dialog_about.show()
