import logging
import sys
import random
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
import mc.gui.rest_action_list_wt
import mc.model
import mc.mc_global
import mc.gui.breathing_history_wt
import mc.gui.breathing_settings_wt
import mc.gui.timing_settings_wt
import mc.gui.breathing_phrase_list_wt
import mc.gui.general_settings_wt
import mc.gui.rest_settings_wt
import mc.gui.settings_page_wt
import mc.gui.rest_dlg
import mc.gui.breathing_dlg
import mc.gui.breathing_notification
import mc.gui.rest_notification
import mc.gui.rest_dlg
import mc.gui.intro_dlg
import mc.gui.rest_prepare
import mc.gui.suspend_time_dlg
import mc.gui.feedback_dlg
import mc.gui.sysinfo_dlg
try:
    # noinspection PyUnresolvedReferences
    from PyQt5 import QtMultimedia
except ImportError:
    logging.debug("ImportError for QtMultimedia - maybe because there's no sound card available")
    # -If the system does not have a sound card (as for example Travis CI)
    # -An alternative to this approach is to use this: http://doc.qt.io/qt-5/qaudiodeviceinfo.html#availableDevices


class MainWin(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.sys_tray = SystemTray()
        self.rest_reminder_dialog = None
        self.rest_widget = None
        self.tray_icon = None
        self.rest_reminder_qtimer = None
        self.breathing_qtimer = None
        self.suspend_qtimer = None
        self.rest_prepare_dialog = None
        self.intro_dlg = None
        self.breathing_notification = None
        self.breathing_dialog = None
        self._suspend_time_dlg = None
        self.sound_effect = None
        self.settings_page_wt = mc.gui.settings_page_wt.SettingsPageWt()
        try:
            self.sound_effect = QtMultimedia.QSoundEffect(self)
            # -PLEASE NOTE: A parent has to be given here, otherwise we will not hear anything
        except NameError:
            logging.debug(
                "NameError - Cannot play audio since QtMultimedia has not been imported"
            )

        # self.active_breathing_phrase_qgb = QtWidgets.QGroupBox("Active Breathing Phrase")

        self._setup_initialize()

        self.setCentralWidget(self.settings_page_wt)

        self._connect_slots_to_signals()

        self.setup_systray()

        # Setup of Menu
        self.menu_bar = self.menuBar()
        self.update_menu()

        # Setup of Timers
        self.on_breathing_settings_changed()
        self.update_rest_timer()

        # Startup actions
        if not mc.mc_global.db_file_exists_at_application_startup_bl and not mc.mc_global.testing_bool:
            self.show_intro_dialog()
        self.open_breathing_prepare()

        self.show()
        self.minimize_to_tray()

        settings = mc.model.SettingsM.get()
        if settings.nr_times_started_since_last_feedback_notif != mc.mc_global.FEEDBACK_DIALOG_NOT_SHOWN_AT_STARTUP:
            if (settings.nr_times_started_since_last_feedback_notif
            >= mc.mc_global.NR_OF_TIMES_UNTIL_FEEDBACK_SHOWN_INT - 1):
                self.show_feedback_dialog()
                settings.nr_times_started_since_last_feedback_notif = 0
            else:
                settings.nr_times_started_since_last_feedback_notif += 1
        else:
            pass

    def _setup_initialize(self):
        self.setGeometry(100, 64, 900, 670)
        self.setWindowIcon(QtGui.QIcon(mc.mc_global.get_app_icon_path("icon.png")))
        self._setup_set_window_title()
        self.setStyleSheet(
            "selection-background-color:" + mc.mc_global.MC_LIGHT_GREEN_COLOR_STR + ";"
            "selection-color:#000000;"
        )

    def _connect_slots_to_signals(self):
        self.settings_page_wt.breathing_settings_wt.phrases_qlw.selection_changed_signal.connect(
            self.on_breathing_list_row_changed
        )
        self.settings_page_wt.breathing_settings_wt.phrases_qlw.phrase_changed_signal.connect(
            self.on_breathing_phrase_changed
        )
        self.settings_page_wt.rest_settings_wt.phrases_qlw.update_signal.connect(
            self.on_rest_action_list_updated
        )
        self.settings_page_wt.rest_settings_wt.phrases_qlw.selection_changed_signal.connect(
            self.on_rest_action_list_row_changed
        )
        self.settings_page_wt.breathing_settings_wt.updated_signal.connect(self.on_breathing_settings_changed)
        self.settings_page_wt.timing_settings_wt.breathing_settings_updated_from_settings_signal.connect(
            self.on_breathing_settings_changed
        )
        self.settings_page_wt.timing_settings_wt.rest_settings_updated_from_settings_signal.connect(
            self.update_rest_timer
        )
        self.settings_page_wt.timing_settings_wt.rest_reset_button_clicked_signal.connect(self.update_rest_timer)
        self.settings_page_wt.timing_settings_wt.rest_slider_value_changed_signal.connect(
            self.on_rest_slider_value_changed
        )

    # def _setup_configure_active_breathing_phrase(self, panel_vbox_l4):
    #     self.title_text_qll = QtWidgets.QLabel(self.tr("title"))
    #     self.title_text_qll.setWordWrap(True)
    #     self.in_text_qll = QtWidgets.QLabel(self.tr("in"))
    #     self.in_text_qll.setWordWrap(True)
    #     self.out_text_qll = QtWidgets.QLabel(self.tr("out"))
    #     self.out_text_qll.setWordWrap(True)
    #     active_breathing_phrase_vbox_l5 = QtWidgets.QVBoxLayout()
    #     active_breathing_phrase_vbox_l5.addWidget(self.title_text_qll)
    #     active_breathing_phrase_vbox_l5.addWidget(self.in_text_qll)
    #     active_breathing_phrase_vbox_l5.addWidget(self.out_text_qll)
    #     self.active_breathing_phrase_qgb.setLayout(active_breathing_phrase_vbox_l5)
    #     panel_vbox_l4.addWidget(self.active_breathing_phrase_qgb)

    def _setup_set_window_title(self):
        if mc.mc_global.testing_bool:
            data_storage_str = "{Testing - data stored in memory}"
        else:
            data_storage_str = "{Live - data stored on hard drive}"
        window_title_str = (
            mc.mc_global.APPLICATION_TITLE_STR
            + " [" + mc.mc_global.APPLICATION_VERSION_STR + "] "
            + data_storage_str
        )
        self.setWindowTitle(window_title_str)

    # noinspection PyAttributeOutsideInit
    def setup_systray(self):
        """
        System tray
        Please note: We cannot move the update code into another function, even in
        this file (very strange). If we do, we won't see the texts, only the separators,
        don't know why, potential bug.
        """
        self.tray_icon = QtWidgets.QSystemTrayIcon(
            QtGui.QIcon(self.get_app_systray_icon_path()),
            self
        )
        # self.tray_icon.activated.connect(self.on_systray_activated)
        self.tray_icon.show()

        systray_available_str = "No"
        if self.tray_icon.isSystemTrayAvailable():
            systray_available_str = "Yes"
        mc.mc_global.sys_info_telist.append(("System tray available", systray_available_str))
        notifications_supported_str = "No"
        if self.tray_icon.supportsMessages():
            notifications_supported_str = "Yes"
        mc.mc_global.sys_info_telist.append(("System tray notifications supported", notifications_supported_str))
        sys_info = QtCore.QSysInfo()
        mc.mc_global.sys_info_telist.append(("buildCpuArchitecture", sys_info.buildCpuArchitecture()))
        mc.mc_global.sys_info_telist.append(("currentCpuArchitecture", sys_info.currentCpuArchitecture()))
        mc.mc_global.sys_info_telist.append(("kernel type", sys_info.kernelType()))
        mc.mc_global.sys_info_telist.append(("kernel version", sys_info.kernelVersion()))
        mc.mc_global.sys_info_telist.append(("product name and version", sys_info.prettyProductName()))
        logging.info("##### System Information #####")
        for (descr_str, value) in mc.mc_global.sys_info_telist:
            logging.info(descr_str + ": " + str(value))
        logging.info("#####")

        settings = mc.model.SettingsM.get()

        self.tray_menu = QtWidgets.QMenu(self)

        self.sys_tray.rest_enabled_qaction = QtWidgets.QAction(self.tr("Enable Rest Reminder"))
        self.tray_menu.addAction(self.sys_tray.rest_enabled_qaction)
        self.sys_tray.rest_enabled_qaction.setCheckable(True)
        self.sys_tray.rest_enabled_qaction.toggled.connect(
            self.settings_page_wt.rest_settings_wt.on_switch_toggled
        )
        self.sys_tray.rest_enabled_qaction.setChecked(settings.rest_reminder_active)
        self.sys_tray.rest_progress_qaction = QtWidgets.QAction("")
        self.tray_menu.addAction(self.sys_tray.rest_progress_qaction)
        self.sys_tray.rest_progress_qaction.setDisabled(True)
        self.sys_tray.update_rest_progress_bar(0, 1)
        self.tray_rest_reset_qaction = QtWidgets.QAction(self.tr("Reset Rest Timer (Skip Break)"))
        self.tray_menu.addAction(self.tray_rest_reset_qaction)
        self.tray_rest_reset_qaction.triggered.connect(self.on_rest_skip)
        self.tray_rest_now_qaction = QtWidgets.QAction(self.tr("Take a Break Now"))
        self.tray_menu.addAction(self.tray_rest_now_qaction)
        self.tray_rest_now_qaction.triggered.connect(self.on_rest_rest)

        self.tray_menu.addSeparator()

        self.sys_tray.breathing_enabled_qaction = QtWidgets.QAction(self.tr("Enable Breathing Reminder"))
        self.tray_menu.addAction(self.sys_tray.breathing_enabled_qaction)
        self.sys_tray.breathing_enabled_qaction.setCheckable(True)
        self.sys_tray.breathing_enabled_qaction.setChecked(settings.breathing_reminder_active_bool)
        self.sys_tray.breathing_enabled_qaction.toggled.connect(
            self.settings_page_wt.breathing_settings_wt.on_switch_toggled
        )
        self.tray_open_breathing_dialog_qaction = QtWidgets.QAction(self.tr("Open Breathing Dialog"))
        self.tray_menu.addAction(self.tray_open_breathing_dialog_qaction)
        self.tray_open_breathing_dialog_qaction.triggered.connect(self.open_breathing_dialog)

        self.tray_menu.addSeparator()

        self.tray_suspend_action = QtWidgets.QAction(self.tr("Suspend Application"))
        self.tray_menu.addAction(self.tray_suspend_action)
        self.tray_suspend_action.triggered.connect(self.on_suspend_application_clicked)
        self.tray_restore_action = QtWidgets.QAction(self.tr("Open Settings"))
        self.tray_menu.addAction(self.tray_restore_action)
        self.tray_restore_action.triggered.connect(self.restore_window)

        self.tray_quit_action = QtWidgets.QAction(self.tr("Quit"))
        self.tray_menu.addAction(self.tray_quit_action)
        self.tray_quit_action.triggered.connect(self.exit_application)

        self.tray_icon.setContextMenu(self.tray_menu)

    def on_rest_action_list_updated(self):
        self.update_gui(mc.mc_global.EventSource.rest_action_changed)

    def on_systray_activated(self, i_reason):
        # LXDE:
        # XFCE:
        # MacOS:
        logging.debug("===== on_systray_activated entered =====")
        logging.debug("i_reason = " + str(i_reason))
        logging.debug("mouseButtons() = " + str(QtWidgets.QApplication.mouseButtons()))
        self.tray_icon.activated.emit(i_reason)
        """
        if i_reason == QtWidgets.QSystemTrayIcon.Trigger:
            self.restore_window()
        else:
            self.tray_icon.activated.emit(i_reason)
        """
        logging.debug("===== on_systray_activated exited =====")

    def on_breathing_list_row_changed(self, i_details_enabled: bool):
        self.update_breathing_timer()
        self.settings_page_wt.breathing_settings_wt.setEnabled(i_details_enabled)
        self.sys_tray.breathing_enabled_qaction.setEnabled(i_details_enabled)

        self.update_gui(mc.mc_global.EventSource.breathing_list_selection_changed)

    def on_rest_action_list_row_changed(self):
        self.update_gui(mc.mc_global.EventSource.rest_list_selection_changed)

    def on_breathing_phrase_changed(self, i_details_enabled):
        self.update_breathing_timer()
        self.settings_page_wt.breathing_settings_wt.setEnabled(i_details_enabled)
        self.sys_tray.breathing_enabled_qaction.setEnabled(i_details_enabled)

        self.update_gui(mc.mc_global.EventSource.breathing_list_phrase_updated)

    def stop_suspend_timer(self):
        if self.suspend_qtimer is not None and self.suspend_qtimer.isActive():
            self.suspend_qtimer.stop()

    def start_suspend_timer(self, i_minutes: int):
        if i_minutes == 0:
            logging.debug("Resuming application (after suspending)")
            self.stop_suspend_timer()
        logging.debug("Suspending the application for " + str(i_minutes) + " minutes")

        self.stop_rest_timer()
        self.stop_breathing_timer()

        self.suspend_qtimer = QtCore.QTimer(self)  # -please remember to send "self" to the timer
        self.suspend_qtimer.setSingleShot(True)  # <-------
        self.suspend_qtimer.timeout.connect(self.suspend_timer_timeout)
        self.suspend_qtimer.start(i_minutes * 60 * 1000)
        self.update_gui()

    def suspend_timer_timeout(self):
        self.stop_suspend_timer()  # -making sure that this is stopped, just in case

        self.start_rest_timer()
        self.start_breathing_timer()
        self.update_gui()

    def update_rest_timer(self, origin=None):
        if origin:
            logging.debug("Rest timer updated from " + origin)
        settings = mc.model.SettingsM.get()
        if settings.rest_reminder_active:
            self.start_rest_timer()
        else:
            self.stop_rest_timer()
        self.update_tooltip()
        if origin == 'intro':
            self.update_gui(mc.mc_global.EventSource.rest_settings_changed_from_intro)
        else:
            self.update_gui(mc.mc_global.EventSource.rest_settings_changed_from_settings)

    def on_rest_slider_value_changed(self):
        self.update_tooltip()
        self.update_gui(mc.mc_global.EventSource.rest_slider_value_changed)

    def stop_rest_timer(self):
        if self.rest_reminder_qtimer is not None and self.rest_reminder_qtimer.isActive():
            self.rest_reminder_qtimer.stop()
        self.settings_page_wt.timing_settings_wt.update_gui()  # -so that the progressbar is updated

    def start_rest_timer(self):
        mc.mc_global.rest_reminder_minutes_passed_int = 0
        self.stop_rest_timer()
        self.rest_reminder_qtimer = QtCore.QTimer(self)
        self.rest_reminder_qtimer.timeout.connect(self.rest_timer_timeout)
        self.rest_reminder_qtimer.start(60 * 1000)  # -one minute

    def rest_timer_timeout(self):
        if mc.mc_global.rest_window_shown_bool:
            return
        mc.mc_global.rest_reminder_minutes_passed_int += 1
        if (mc.mc_global.rest_reminder_minutes_passed_int
        == mc.model.SettingsM.get().rest_reminder_interval_int - 1):
            # self.tray_icon.showMessage("Mindfulness at the Computer", "One minute left until the next rest")
            self.show_rest_prepare()
        if (mc.mc_global.rest_reminder_minutes_passed_int
        == mc.model.SettingsM.get().rest_reminder_interval_int):
            self.start_rest_reminder()
        self.settings_page_wt.timing_settings_wt.rest_reminder_qsr.setValue(
            mc.mc_global.rest_reminder_minutes_passed_int
        )

    def on_rest_widget_closed(self, i_open_breathing_dialog: bool):
        mc.mc_global.rest_reminder_minutes_passed_int = 0

        if i_open_breathing_dialog:
            self.open_breathing_dialog()
        self.update_rest_timer()
        self.update_breathing_timer()
        self.update_gui()

    def restore_window(self):
        self.raise_()
        self.showNormal()
        # another alternative (from an SO answer): self.setWindowState(QtCore.Qt.WindowActive)

    def show_rest_prepare(self):
        self.rest_prepare_dialog = mc.gui.rest_prepare.RestPrepareDlg()

    def start_rest_reminder(self):
        notification_type_int = mc.model.SettingsM.get().rest_reminder_notification_type_int

        if (notification_type_int == mc.mc_global.NotificationType.Both.value
        or notification_type_int == mc.mc_global.NotificationType.Visual.value):
            self.show_rest_reminder()

        if (notification_type_int == mc.mc_global.NotificationType.Both.value
        or notification_type_int == mc.mc_global.NotificationType.Audio.value):
            settings = mc.model.SettingsM.get()
            audio_path_str = settings.rest_reminder_audio_filename_str
            volume_int = settings.rest_reminder_volume_int
            self._play_audio(audio_path_str, volume_int)

    def show_rest_reminder(self):
        self.rest_reminder_dialog = mc.gui.rest_notification.RestReminderDlg()
        self.rest_reminder_dialog.rest_signal.connect(self.on_rest_rest)
        self.rest_reminder_dialog.skip_signal.connect(self.on_rest_skip)
        self.rest_reminder_dialog.wait_signal.connect(self.on_rest_wait)
        self.update_gui(mc.mc_global.EventSource.rest_opened)

    def on_rest_wait(self):
        mc.mc_global.rest_reminder_minutes_passed_int -= 2
        self.update_gui()
        self.update_tooltip()

    def on_rest_rest(self):
        self.rest_widget = mc.gui.rest_dlg.RestDlg()
        self.rest_widget.close_signal.connect(self.on_rest_widget_closed)

    def on_rest_skip(self):
        mc.mc_global.rest_reminder_minutes_passed_int = 0
        self.update_gui()
        self.update_tooltip()

    def on_breathing_settings_changed(self, origin=None):
        self.update_breathing_timer()

        if origin == 'intro':
            self.update_gui(mc.mc_global.EventSource.breathing_settings_changed_from_intro)
        else:
            self.update_gui(mc.mc_global.EventSource.breathing_settings_changed_from_settings)

    def update_breathing_timer(self):
        settings = mc.model.SettingsM.get()
        if settings.breathing_reminder_active_bool:
            self.start_breathing_timer()
        else:
            self.stop_breathing_timer()

    def stop_breathing_timer(self):
        if self.breathing_qtimer is not None and self.breathing_qtimer.isActive():
            self.breathing_qtimer.stop()

    def start_breathing_timer(self):
        self.stop_breathing_timer()
        settings = mc.model.SettingsM.get()
        self.breathing_qtimer = QtCore.QTimer(self)  # -please remember to send "self" to the timer
        self.breathing_qtimer.timeout.connect(self.breathing_timer_timeout)
        # -show_breathing_notification
        self.breathing_qtimer.start(settings.breathing_reminder_interval_int * 60 * 1000)

    def update_menu(self):
        self.menu_bar.clear()

        file_menu = self.menu_bar.addMenu(self.tr("&File"))
        export_action = QtWidgets.QAction(self.tr("Export data"), self)
        file_menu.addAction(export_action)
        export_action.triggered.connect(self.export_data_to_csv)
        minimize_to_tray_action = QtWidgets.QAction(self.tr("Minimize to tray"), self)
        file_menu.addAction(minimize_to_tray_action)
        minimize_to_tray_action.triggered.connect(self.minimize_to_tray)
        """
        choose_file_directory_action = QtWidgets.QAction(self.tr("Choose file directory"), self)
        file_menu.addAction(choose_file_directory_action)
        choose_file_directory_action.triggered.connect(pass)
        """
        quit_action = QtWidgets.QAction(self.tr("Quit"), self)
        file_menu.addAction(quit_action)
        quit_action.triggered.connect(self.exit_application)

        """
        preferences_menu = self.menu_bar.addMenu("&Preferences")
        """

        options_menu = self.menu_bar.addMenu("&Options")
        suspend_application_action = QtWidgets.QAction("Suspend application", self)
        options_menu.addAction(suspend_application_action)
        suspend_application_action.triggered.connect(self.on_suspend_application_clicked)

        debug_menu = self.menu_bar.addMenu("&Debug")
        update_gui_action = QtWidgets.QAction("Update GUI", self)
        debug_menu.addAction(update_gui_action)
        update_gui_action.triggered.connect(self.update_gui)
        breathing_full_screen_action = QtWidgets.QAction(self.tr("Full screen"), self)
        debug_menu.addAction(breathing_full_screen_action)
        breathing_full_screen_action.triggered.connect(self.showFullScreen)
        show_rest_reminder_action = QtWidgets.QAction(self.tr("Show rest reminder"), self)
        debug_menu.addAction(show_rest_reminder_action)
        show_rest_reminder_action.triggered.connect(self.start_rest_reminder)
        show_rest_prepare_action = QtWidgets.QAction("Show rest prepare", self)
        debug_menu.addAction(show_rest_prepare_action)
        show_rest_prepare_action.triggered.connect(self.show_rest_prepare)
        show_breathing_notification_action = QtWidgets.QAction("Show breathing notification", self)
        debug_menu.addAction(show_breathing_notification_action)
        show_breathing_notification_action.triggered.connect(self.breathing_timer_timeout)

        help_menu = self.menu_bar.addMenu(self.tr("&Help"))
        show_intro_dialog_action = QtWidgets.QAction("Show intro wizard", self)
        help_menu.addAction(show_intro_dialog_action)
        show_intro_dialog_action.triggered.connect(self.show_intro_dialog)
        about_action = QtWidgets.QAction(self.tr("About"), self)
        help_menu.addAction(about_action)
        about_action.triggered.connect(self.show_about_box)
        online_help_action = QtWidgets.QAction("Online help", self)
        help_menu.addAction(online_help_action)
        online_help_action.triggered.connect(self.show_online_help)
        feedback_action = QtWidgets.QAction("Give feedback", self)
        help_menu.addAction(feedback_action)
        feedback_action.triggered.connect(self.show_feedback_dialog)
        sysinfo_action = QtWidgets.QAction(self.tr("System Information"), self)
        help_menu.addAction(sysinfo_action)
        sysinfo_action.triggered.connect(self.show_sysinfo_box)

    def show_feedback_dialog(self):
        feedback_dlg = mc.gui.feedback_dlg.FeedbackDialog()
        feedback_dlg.exec_()

    def export_data_to_csv(self):
        file_path_str = mc.model.export_all()
        # noinspection PyCallByClass
        QtWidgets.QMessageBox.information(
            self,
            "Data exported",
            "Data exported to file " + '<a href="' + 'file:///' + file_path_str + '">' + file_path_str + '</a>'
        )

    def on_suspend_application_clicked(self):
        self._suspend_time_dlg = mc.gui.suspend_time_dlg.SuspendTimeDialog()
        self._suspend_time_dlg.finished.connect(self.on_suspend_time_dlg_finished)
        self._suspend_time_dlg.show()

    def on_suspend_time_dlg_finished(self, i_result: int):
        if i_result == QtWidgets.QDialog.Accepted:
            self.start_suspend_timer(self._suspend_time_dlg.suspend_time_qsr.value())
        else:
            pass

    def show_intro_dialog(self):
        self.intro_dlg = mc.gui.intro_dlg.IntroDlg()
        self.intro_dlg.initial_setup.breathing_settings_updated_from_intro_signal.connect(
            self.on_breathing_settings_changed
        )
        self.intro_dlg.initial_setup.rest_settings_updated_from_intro_signal.connect(
            self.update_rest_timer
        )
        self.intro_dlg.close_signal.connect(self.on_intro_dialog_closed)
        self.intro_dlg.exec()
        self.intro_dlg.initial_setup.update_gui()
        self.update_gui()

    def on_intro_dialog_closed(self, i_open_breathing_dialog: bool):
        if i_open_breathing_dialog:
            self.open_breathing_dialog()

    # noinspection PyAttributeOutsideInit
    def breathing_timer_timeout(self):
        if not self.breathing_reminder_active():
            return
        if mc.mc_global.rest_window_shown_bool:
            return

        mc.mc_global.breathing_notification_counter_int += 1
        if (mc.mc_global.breathing_notification_counter_int
        > mc.model.SettingsM.get().breathing_reminder_nr_before_dialog_int):
            mc.mc_global.breathing_notification_counter_int = 0
            self.open_breathing_prepare()
        else:
            self.commence_breathing_notification()

    def commence_breathing_notification(self):
        # Skipping the breathing notification if the breathing dialog is shown
        if self.breathing_dialog and self.breathing_dialog.isVisible():
            logging.debug("self.breathing_dialog.isVisible = " + str(self.breathing_dialog.isVisible()))
            return

        notification_type_int = mc.model.SettingsM.get().breathing_reminder_notification_type_int

        if (notification_type_int == mc.mc_global.NotificationType.Both.value
            or notification_type_int == mc.mc_global.NotificationType.Visual.value):
            self.breathing_notification = mc.gui.breathing_notification.BreathingNotification()
            self.breathing_notification.breathe_signal.connect(self.on_breathing_notification_breathe_clicked)
            self.breathing_notification.show()

        if (notification_type_int == mc.mc_global.NotificationType.Both.value
            or notification_type_int == mc.mc_global.NotificationType.Audio.value):
            settings = mc.model.SettingsM.get()
            audio_path_str = settings.breathing_reminder_audio_filename_str
            volume_int = settings.breathing_reminder_audio_volume_int
            self._play_audio(audio_path_str, volume_int)

    def open_breathing_prepare(self):
        # Skipping the breathing notification if the breathing dialog is shown
        if self.breathing_dialog and self.breathing_dialog.isVisible():
            return

        notification_type_int = mc.model.SettingsM.get().breathing_reminder_notification_type_int

        if (notification_type_int == mc.mc_global.NotificationType.Both.value
        or notification_type_int == mc.mc_global.NotificationType.Visual.value):
            self.breathing_notification = mc.gui.breathing_notification.BreathingNotification(i_preparatory=True)
            self.breathing_notification.breathe_signal.connect(self.on_breathing_notification_breathe_clicked)
            self.breathing_notification.show()

        if (notification_type_int == mc.mc_global.NotificationType.Both.value
        or notification_type_int == mc.mc_global.NotificationType.Audio.value):
            settings = mc.model.SettingsM.get()
            audio_path_str = settings.prep_reminder_audio_filename
            volume_int = settings.prep_reminder_audio_volume
            self._play_audio(audio_path_str, volume_int)

        """
        self.breathing_prepare = mc.gui.breathing_prepare.BreathingPrepareDlg()
        self.breathing_prepare.closed_signal.connect(self.open_breathing_dialog)
        """

    def open_breathing_dialog(self, i_mute_override: bool=False):

        if mc.model.SettingsM.get().breathing_dialog_phrase_selection == mc.mc_global.PhraseSelection.random:
            phrases_list = mc.model.PhrasesM.get_all()
            randomly_selected_phrase = random.choice(phrases_list)
            mc.mc_global.active_phrase_id_it = randomly_selected_phrase.id
        else:
            pass

        self.breathing_dialog = mc.gui.breathing_dlg.BreathingDlg()
        self.breathing_dialog.close_signal.connect(self.on_breathing_dialog_closed)
        self.breathing_dialog.phrase_changed_from_breathing_dialog_signal.connect(
            self.on_breathing_dialog_phrase_changed
        )
        self.breathing_dialog.show()

    def _play_audio(self, i_audio_filename: str, i_volume: int) -> None:
        if self.sound_effect is None:
            return
        audio_path_str = mc.mc_global.get_user_audio_path(i_audio_filename)
        # noinspection PyCallByClass
        audio_source_qurl = QtCore.QUrl.fromLocalFile(audio_path_str)
        self.sound_effect.setSource(audio_source_qurl)
        self.sound_effect.setVolume(float(i_volume / 100))
        self.sound_effect.play()

    def on_breathing_dialog_closed(self, i_ib_list, i_ob_list):
        # self.breathing_history_wt.add_from_dialog(i_ib_list, i_ob_list)
        self.settings_page_wt.breathing_history_wt.breathing_history_wt.add_from_dialog(i_ib_list, i_ob_list)
        self.update_breathing_timer()

    def on_breathing_dialog_phrase_changed(self):
        self.update_gui()

    def on_breathing_notification_breathe_clicked(self):
        self.open_breathing_dialog(i_mute_override=True)

    def debug_clear_breathing_phrase_selection(self):
        self.br_phrase_list_wt.list_widget.clearSelection()

    def show_online_help(self):
        url_str = "https://mindfulness-at-the-computer.github.io/user_guide"
        # noinspection PyCallByClass
        QtGui.QDesktopServices.openUrl(QtCore.QUrl(url_str))
        # Python: webbrowser.get(url_str) --- doesn't work

    def show_sysinfo_box(self):
        self._sysinfo_dlg = mc.gui.sysinfo_dlg.SysinfoDialog()
        self._sysinfo_dlg.show()

        """
        info_str = '\n'.join([
            descr_str + ": " + str(value) for (descr_str, value) in mc.mc_global.sys_info_telist
        ])
        # noinspection PyCallByClass
        QtWidgets.QMessageBox.about(
            self,
            "System Information",
            info_str
        )
        """

    def show_about_box(self):
        # noinspection PyCallByClass
        QtWidgets.QMessageBox.about(
            self,
            "About Mindfulness at the Computer",
            (
                '<html>'
                'Originally created by Tord Dellsén'
                '<a href="https://sunyatazero.github.io/"> GitHub website</a><br>'
                '<a href="https://github.com/SunyataZero/mindfulness-at-the-computer/graphs/contributors">'
                '<a href="https://mindfulness-at-the-computer.github.io/"> Github website</a><br>'
                '<a href="https://github.com/mindfulness-at-the-computer/mindfulness-at-the-computer/graphs/contributors">'
                'All contributors</a><br>'
                'Photography for application icon by Torgny Dellsén '
                '<a href="http://torgnydellsen.zenfolio.com">torgnydellsen.zenfolio.com</a><br>'
                'Other icons from Open Iconic - useiconic.com - MIT license<br>'
                'Other images (for the rest actions) have been released into the public domain (CC0)<br>'
                'Audio files have been released into the public domain (CC0)<br>'
                'Software License: GPLv3 (license text available in the install directory)'
                '</html>'
            )
        )

    # overridden
    # noinspection PyPep8Naming
    def closeEvent(self, i_QCloseEvent):
        i_QCloseEvent.ignore()
        self.minimize_to_tray()

    def minimize_to_tray(self):
        self.showMinimized()
        self.hide()

    def exit_application(self):
        sys.exit()

    def update_gui(self, i_event_source=mc.mc_global.EventSource.undefined):
        if mc.mc_global.active_phrase_id_it == mc.mc_global.NO_PHRASE_SELECTED_INT:
            pass
        else:
            # breathing_phrase = mc.model.PhrasesM.get(mc.mc_global.active_phrase_id_it)
            # self.title_text_qll.setText(breathing_phrase.title)
            # self.in_text_qll.setText(breathing_phrase.ib)
            # self.out_text_qll.setText(breathing_phrase.ob)

            if i_event_source != mc.mc_global.EventSource.rest_slider_value_changed:
                self.settings_page_wt.rest_settings_wt.update_gui()
                self.settings_page_wt.timing_settings_wt.update_gui()
            self.settings_page_wt.breathing_settings_wt.update_gui()

            if (i_event_source != mc.mc_global.EventSource.breathing_list_selection_changed
                and i_event_source != mc.mc_global.EventSource.rest_list_selection_changed):
                self.settings_page_wt.breathing_settings_wt.phrases_qlw.update_gui()
                self.settings_page_wt.rest_settings_wt.phrases_qlw.update_gui()

            self.update_systray()

        # Update the timers pages in intro or settings
        if (i_event_source == mc.mc_global.EventSource.breathing_settings_changed_from_intro or
                i_event_source == mc.mc_global.EventSource.rest_settings_changed_from_intro):
            logging.debug("Updating settings page because settings have changed from intro")
            self.settings_page_wt.timing_settings_wt.update_gui()

        if (self.intro_dlg and
                (i_event_source == mc.mc_global.EventSource.breathing_settings_changed_from_settings or
                 i_event_source == mc.mc_global.EventSource.rest_settings_changed_from_settings)):
            logging.debug("Updating intro page because settings have changed from settings")
            self.intro_dlg.initial_setup.update_gui()

    def get_app_systray_icon_path(self) -> str:
        # TODO: Update the three references to this function
        icon_file_name_str = "icon.png"
        settings = mc.model.SettingsM.get()
        if self.breathing_reminder_active() and settings.rest_reminder_active:
            icon_file_name_str = "icon-br.png"
        elif self.breathing_reminder_active():
            icon_file_name_str = "icon-b.png"
        elif settings.rest_reminder_active:
            icon_file_name_str = "icon-r.png"

        if self.suspend_qtimer is not None and self.suspend_qtimer.isActive():
            icon_file_name_str = "icon-suspend.png"

        ret_icon_path_str = mc.mc_global.get_app_icon_path(icon_file_name_str)
        return ret_icon_path_str

    def breathing_reminder_active(self) -> bool:
        settings = mc.model.SettingsM.get()
        breathing_reminder_active_bl = (
            (mc.mc_global.active_phrase_id_it != mc.mc_global.NO_PHRASE_SELECTED_INT)
            and
            settings.breathing_reminder_active_bool
        )
        return breathing_reminder_active_bl

    def update_systray(self):
        if self.tray_icon is None:
            return
        settings = mc.model.SettingsM.get()

        # Icon
        self.tray_icon.setIcon(QtGui.QIcon(self.get_app_systray_icon_path()))
        # self.tray_icon.show()

        # Menu
        self.sys_tray.update_breathing_checked(settings.breathing_reminder_active_bool)
        self.sys_tray.update_rest_checked(settings.rest_reminder_active)
        self.sys_tray.update_rest_progress_bar(
            mc.mc_global.rest_reminder_minutes_passed_int,
            mc.model.SettingsM.get().rest_reminder_interval_int
        )
    
    def update_tooltip(self):
        #Adds tooltip on the systray icon showing the amount of time left until the next break
        settings = mc.model.SettingsM.get()
        if settings.rest_reminder_active:
            self.tray_icon.setToolTip(
                self.tr(
                    str(
                        mc.model.SettingsM.get().rest_reminder_interval_int
                        - mc.mc_global.rest_reminder_minutes_passed_int
                    )
                    + " minute/s left until next rest"
                )
            )
        else:
            self.tray_icon.setToolTip(self.tr("Rest breaks disabled"))


class SystemTray:
    def __init__(self):
        self.rest_progress_qaction = None
        self.rest_enabled_qaction = None
        self.breathing_enabled_qaction = None

        self.phrase_qaction_list = []

    def update_rest_progress_bar(self, time_passed_int, interval_minutes_int):
        if self.rest_progress_qaction is None:
            return
        time_passed_str = ""
        parts_of_ten_int = (10 * time_passed_int) // interval_minutes_int
        for i in range(0, 9):
            if i < parts_of_ten_int:
                time_passed_str += "◾"
            else:
                time_passed_str += "◽"
        self.rest_progress_qaction.setText(time_passed_str)

    def update_rest_checked(self, i_active: bool):
        if self.rest_enabled_qaction is not None:
            self.rest_enabled_qaction.setChecked(i_active)

    def update_breathing_checked(self, i_checked: bool):
        if self.breathing_enabled_qaction is not None:
            self.breathing_enabled_qaction.setChecked(i_checked)

    def update_breathing_enabled(self, i_enabled: bool):
        if self.breathing_enabled_qaction is not None:
            self.breathing_enabled_qaction.setEnabled(i_enabled)