#!/usr/bin/python3
import gettext
import locale
import os
import shutil
import sys
import time
import traceback
import warnings
import subprocess
from functools import partial
from pathlib import Path

# Force X11 on a Wayland session
if "WAYLAND_DISPLAY" in os.environ:
    os.environ["WAYLAND_DISPLAY"] = ""

# Suppress GTK deprecation warnings
warnings.filterwarnings("ignore")

import gi

gi.require_version("Gtk", "3.0")
gi.require_version("XApp", "1.0")
from gi.repository import Gtk, Gdk, Gio, XApp, GdkPixbuf, GLib, Pango

import mpv
import requests
import setproctitle
from unidecode import unidecode

from common import Manager, Provider, Channel, MOVIES_GROUP, PROVIDERS_PATH, SERIES_GROUP, TV_GROUP,\
    async_function, idle_function


setproctitle.setproctitle("hypnotix")

# i18n
APP = "hypnotix"
LOCALE_DIR = "/usr/share/locale"
locale.bindtextdomain(APP, LOCALE_DIR)
gettext.bindtextdomain(APP, LOCALE_DIR)
gettext.textdomain(APP)
_ = gettext.gettext


PROVIDER_OBJ, PROVIDER_NAME = range(2)
PROVIDER_TYPE_ID, PROVIDER_TYPE_NAME = range(2)

GROUP_OBJ, GROUP_NAME = range(2)
CHANNEL_OBJ, CHANNEL_NAME, CHANNEL_LOGO = range(3)

COL_PROVIDER_NAME, COL_PROVIDER = range(2)

PROVIDER_TYPE_URL = "url"
PROVIDER_TYPE_LOCAL = "local"
PROVIDER_TYPE_XTREAM = "xtream"

UPDATE_BR_INTERVAL = 5

AUDIO_SAMPLE_FORMATS = {
    "u16": "unsigned 16 bits",
    "s16": "signed 16 bits",
    "s16p": "signed 16 bits, planar",
    "flt": "float",
    "float": "float",
    "fltp": "float, planar",
    "floatp": "float, planar",
    "dbl": "double",
    "dblp": "double, planar",
}

COUNTRY_CODES = {}
with open("/usr/share/hypnotix/countries.list") as f:
    for line in f:
        line = line.strip()
        code, name = line.split(":")
        COUNTRY_CODES[name] = code

class ChannelWidget(Gtk.ListBoxRow):
    """ A custom widget for displaying and holding channel data. """

    def __init__(self, channel, logo, **kwargs):
        super().__init__(**kwargs)
        self._channel = channel
        self.set_tooltip_text(channel.name)

        frame = Gtk.Frame()
        label = Gtk.Label(channel.name)
        label.set_max_width_chars(30)
        label.set_ellipsize(Pango.EllipsizeMode.END)
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, border_width=6)
        box.pack_start(logo, False, False, 0)
        box.pack_start(label, False, False, 0)
        box.set_spacing(6)
        frame.add(box)
        self.add(frame)

    @property
    def channel(self):
        return self._channel

class MyApplication(Gtk.Application):
    # Main initialization routine
    def __init__(self, application_id, flags):
        Gtk.Application.__init__(self, application_id=application_id, flags=flags)
        self.connect("activate", self.activate)

    def activate(self, application):
        windows = self.get_windows()
        if len(windows) > 0:
            window = windows[0]
            window.present()
            window.show()
        else:
            window = MainWindow(self)
            self.add_window(window.window)
            window.window.show()


class MainWindow:
    def __init__(self, application):

        self.application = application
        self.settings = Gio.Settings(schema_id="org.x.hypnotix")
        self.icon_theme = Gtk.IconTheme.get_default()
        self.manager = Manager(self.settings)
        self.providers = []
        self.favorite_data = []
        self.active_provider = None
        self.active_group = None
        self.active_serie = None
        self.marked_provider = None
        self.content_type = TV_GROUP  # content being browsed
        self.back_page = None  # page to go back to if the back button is pressed
        self.active_channel = None
        self.inhibit_id = 0
        self.fullscreen = False
        self.latest_search_bar_text = None
        self.visible_search_results = 0
        self.mpv = None
        self.page_is_loading = False # used to ignore signals while we set widget states

        self.video_properties = {}
        self.audio_properties = {}
        self.volume = 100

        # Used for redownloading timer
        self.reload_timeout_sec = 60 * 5
        self._timerid = -1
        gladefile = "/usr/share/hypnotix/hypnotix.ui"
        self.builder = Gtk.Builder()
        self.builder.set_translation_domain(APP)
        self.builder.add_from_file(gladefile)
        self.window = self.builder.get_object("main_window")
        self.window.set_title(_("Hypnotix"))
        self.window.set_icon_name("hypnotix")

        # The window used to display stream information
        self.info_window = self.builder.get_object("stream_info_window")

        provider = Gtk.CssProvider()
        provider.load_from_path("/usr/share/hypnotix/hypnotix.css")
        screen = Gdk.Display.get_default_screen(Gdk.Display.get_default())
        # I was unable to found instrospected version of this
        Gtk.StyleContext.add_provider_for_screen(
            screen, provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

        # Prefs variables
        self.selected_pref_provider = None
        self.edit_mode = False

        # Create variables to quickly access dynamic widgets
        widget_names = [
            "headerbar",
            "status_label",
            "status_bar",
            "sidebar",
            "go_back_button",
            "search_button",
            "search_bar",
            "channels_box",
            "current_provider_label",
            "mpv_drawing_area",
            "stack",
            "channel_stack",
            "fullscreen_button",
            "mpv_top_box",
            "mpv_bottom_box",
            "label_channel_name",
            "label_channel_url",
            "provider_ok_button",
            "provider_cancel_button",
            "name_entry",
            "path_label",
            "path_entry",
            "browse_button",
            "url_label",
            "url_entry",
            "username_label",
            "username_entry",
            "password_label",
            "password_entry",
            "epg_label",
            "epg_entry",
            "tv_logo",
            "movies_logo",
            "series_logo",
            "tv_button",
            "movies_button",
            "series_button",
            "providers_button",
            "preferences_button",
            "favorites_button",
            "tv_label",
            "movies_label",
            "series_label",
            "categories_flowbox",
            "channels_listbox",
            "vod_flowbox",
            "episodes_box",
            "stop_button",
            "pause_button",
            "show_button",
            "playback_label",
            "playback_bar",
            "providers_flowbox",
            "new_provider_button",
            "reset_providers_button",
            "delete_no_button",
            "delete_yes_button",
            "reset_no_button",
            "reset_yes_button",
            "useragent_entry",
            "referer_entry",
            "mpv_entry",
            "mpv_link",
            "ytdlp_local_switch",
            "ytdlp_system_version_label",
            "ytdlp_local_version_label",
            "ytdlp_update_button",
            "mpv_stack",
            "spinner",
            "info_window_close_button",
            "video_properties_box",
            "video_properties_label",
            "colour_properties_box",
            "colour_properties_label",
            "audio_properties_box",
            "audio_properties_label",
            "layout_properties_box",
            "layout_properties_label",
            "favorite_button",
            "favorite_button_image",
            "new_channel_button",
            "new_name_entry",
            "new_url_entry",
            "new_logo_entry",
            "new_ok_button",
            "new_cancel_button"
        ]

        for name in widget_names:
            widget = self.builder.get_object(name)
            if widget is None:
                print("Could not find widget %s!" % name)
                sys.exit(1)
            else:
                setattr(self, name, widget)

        # Widget signals
        self.window.connect("key-press-event", self.on_key_press_event)
        self.mpv_drawing_area.connect("realize", self.on_mpv_drawing_area_realize)
        self.mpv_drawing_area.connect("draw", self.on_mpv_drawing_area_draw)
        self.fullscreen_button.connect("clicked", self.on_fullscreen_button_clicked)

        self.info_window.connect("delete-event", self.on_close_info_window)
        self.info_window_close_button.connect("clicked", self.on_close_info_window_button_clicked)

        self.new_ok_button.connect("clicked", self.on_new_ok_button)
        self.new_cancel_button.connect("clicked", self.on_new_cancel_button)

        self.provider_ok_button.connect("clicked", self.on_provider_ok_button)
        self.provider_cancel_button.connect("clicked", self.on_provider_cancel_button)

        self.name_entry.connect("changed", self.toggle_ok_sensitivity)
        self.url_entry.connect("changed", self.toggle_ok_sensitivity)
        self.path_entry.connect("changed", self.toggle_ok_sensitivity)

        self.new_name_entry.connect("changed", self.toggle_new_ok_sensitivity)
        self.new_url_entry.connect("changed", self.toggle_new_ok_sensitivity)
        self.new_logo_entry.connect("changed", self.toggle_new_ok_sensitivity)

        self.tv_button.connect("clicked", self.show_groups, TV_GROUP)
        self.movies_button.connect("clicked", self.show_groups, MOVIES_GROUP)
        self.series_button.connect("clicked", self.show_groups, SERIES_GROUP)
        self.new_channel_button.connect("clicked", self.open_new_channel)
        self.favorites_button.connect("clicked", self.show_favorites)
        self.providers_button.connect("clicked", self.open_providers)
        self.preferences_button.connect("clicked", self.open_preferences)
        self.go_back_button.connect("clicked", self.on_go_back_button)

        self.search_button.connect("toggled", self.on_search_button_toggled)
        self.search_bar.connect("activate", self.on_search_bar)

        self.stop_button.connect("clicked", self.on_stop_button)
        self.pause_button.connect("clicked", self.on_pause_button)
        self.show_button.connect("clicked", self.on_show_button)

        self.new_provider_button.connect("clicked", self.on_new_provider_button)
        self.reset_providers_button.connect("clicked", self.on_reset_providers_button)
        self.delete_no_button.connect("clicked", self.on_delete_no_button)
        self.delete_yes_button.connect("clicked", self.on_delete_yes_button)
        self.reset_no_button.connect("clicked", self.on_reset_no_button)
        self.reset_yes_button.connect("clicked", self.on_reset_yes_button)

        self.browse_button.connect("clicked", self.on_browse_button)

        self.channels_listbox.connect("row-activated", self.on_channel_activated)

        self.favorite_button.connect("toggled", self.on_favorite_button_toggled)

        # Settings widgets
        self.bind_setting_widget("user-agent", self.useragent_entry)
        self.bind_setting_widget("http-referer", self.referer_entry)
        self.bind_setting_widget("mpv-options", self.mpv_entry)

        # ytdlp
        self.ytdlp_local_switch.set_active(self.settings.get_boolean("use-local-ytdlp"))
        self.ytdlp_local_switch.connect("notify::active", self.on_ytdlp_local_switch_activated)
        self.ytdlp_system_version_label.set_text(subprocess.getoutput("/usr/bin/yt-dlp --version"))
        if os.path.exists(os.path.expanduser("~/.cache/hypnotix/yt-dlp/yt-dlp")):
            self.ytdlp_local_version_label.set_text(subprocess.getoutput("~/.cache/hypnotix/yt-dlp/yt-dlp --version"))
        self.ytdlp_update_button.connect("clicked", self.update_ytdlp)

        # Dark mode manager
        # keep a reference to it (otherwise it gets randomly garbage collected)
        try:
            self.dark_mode_manager = XApp.DarkModeManager.new(prefer_dark_mode=True)
        except Exception:
            pass

        # Menubar
        accel_group = Gtk.AccelGroup()
        self.window.add_accel_group(accel_group)
        menu = self.builder.get_object("main_menu")
        item = Gtk.ImageMenuItem()
        item.set_image(Gtk.Image.new_from_icon_name("xsi-keyboard-shortcuts-symbolic", Gtk.IconSize.MENU))
        item.set_label(_("Keyboard Shortcuts"))
        item.connect("activate", self.open_keyboard_shortcuts)
        key, mod = Gtk.accelerator_parse("<Control>K")
        item.add_accelerator("activate", accel_group, key, mod, Gtk.AccelFlags.VISIBLE)
        menu.append(item)
        self.info_menu_item = Gtk.ImageMenuItem()
        self.info_menu_item.set_image(Gtk.Image.new_from_icon_name("xsi-dialog-information-symbolic", Gtk.IconSize.MENU))
        self.info_menu_item.set_label(_("Stream Information"))
        self.info_menu_item.connect("activate", self.open_info)
        key, mod = Gtk.accelerator_parse("F2")
        self.info_menu_item.add_accelerator("activate", accel_group, key, mod, Gtk.AccelFlags.VISIBLE)
        self.info_menu_item.set_sensitive(False)
        menu.append(self.info_menu_item)
        item = Gtk.ImageMenuItem()
        item.set_image(Gtk.Image.new_from_icon_name("xsi-help-about-symbolic", Gtk.IconSize.MENU))
        item.set_label(_("About"))
        item.connect("activate", self.open_about)
        key, mod = Gtk.accelerator_parse("F1")
        item.add_accelerator("activate", accel_group, key, mod, Gtk.AccelFlags.VISIBLE)
        menu.append(item)
        item = Gtk.ImageMenuItem(label=_("Quit"))
        image = Gtk.Image.new_from_icon_name("xsi-exit-symbolic", Gtk.IconSize.MENU)
        item.set_image(image)
        item.connect("activate", self.on_menu_quit)
        key, mod = Gtk.accelerator_parse("<Control>Q")
        item.add_accelerator("activate", accel_group, key, mod, Gtk.AccelFlags.VISIBLE)
        key, mod = Gtk.accelerator_parse("<Control>W")
        item.add_accelerator("activate", accel_group, key, mod, Gtk.AccelFlags.VISIBLE)
        menu.append(item)
        menu.show_all()

        # Type combox box (in preferences)
        model = Gtk.ListStore(str, str)  # PROVIDER_TYPE_ID, PROVIDER_TYPE_NAME
        model.append([PROVIDER_TYPE_URL, _("M3U URL")])
        model.append([PROVIDER_TYPE_LOCAL, _("Local M3U File")])
        model.append([PROVIDER_TYPE_XTREAM, _("Xtream API")])
        self.provider_type_combo = self.builder.get_object("provider_type_combo")
        renderer = Gtk.CellRendererText()
        self.provider_type_combo.pack_start(renderer, True)
        self.provider_type_combo.add_attribute(renderer, "text", PROVIDER_TYPE_NAME)
        self.provider_type_combo.set_model(model)
        self.provider_type_combo.set_active(0)  # Select 1st type
        self.provider_type_combo.connect("changed", self.on_provider_type_combo_changed)

        self.tv_logo.set_from_surface(self.get_surface_for_file("/usr/share/hypnotix/pictures/tv.svg", 258, 258))
        self.movies_logo.set_from_surface(self.get_surface_for_file("/usr/share/hypnotix/pictures/movies.svg", 258, 258))
        self.series_logo.set_from_surface(self.get_surface_for_file("/usr/share/hypnotix/pictures/series.svg", 258, 258))

        self.reload(page="landing_page")

        # Redownload playlists by default
        # This is going to get readjusted
        self._timerid = GLib.timeout_add_seconds(self.reload_timeout_sec, self.force_reload)

        self.window.show()
        self.playback_bar.hide()
        self.search_bar.hide()

        # Historic bitrates of the currently playing media
        self.video_bitrates = []
        self.audio_bitrates = []

    def get_surface_for_file(self, filename, width, height):
        scale = self.window.get_scale_factor()
        if width != -1:
            width = width * scale
        if height != -1:
            height = height * scale

        pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, width, height)
        surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale)
        return surface

    def get_surf_based_image(self, filename, width, height):
        surf = self.get_surface_for_file(filename, width, height)
        return Gtk.Image.new_from_surface(surf)

    def add_flag(self, code, box):
        path = f"/usr/share/circle-flags-svg/{code.lower()}.svg"
        if os.path.exists(path):
            try:
                image = self.get_surf_based_image(path, -1, 32)
                box.pack_start(image, False, False, 0)
            except Exception as e:
                print("Could not load flag", path)
                print(e)
        else:
            print("Couldn't find flag", path)

    def add_badge(self, word, box, added_words):
        if word not in added_words:
            for extension in ["svg", "png"]:
                path = "/usr/share/hypnotix/pictures/badges/%s.%s" % (word, extension)
                if os.path.exists(path):
                    try:
                        image = self.get_surf_based_image(path, -1, 32)
                        box.pack_start(image, False, False, 0)
                        added_words.append(word)
                        break
                    except Exception as e:
                        print("Could not load badge", path)
                        print(e)

    def show_groups(self, widget, content_type):
        self.content_type = content_type
        self.navigate_to("categories_page")
        for child in self.categories_flowbox.get_children():
            self.categories_flowbox.remove(child)
        self.active_group = None
        found_groups = False
        for group in self.active_provider.groups:
            if group.group_type != self.content_type:
                continue
            found_groups = True
            button = Gtk.Button()
            button.connect("clicked", self.on_category_button_clicked, group)
            label = Gtk.Label()
            if self.content_type == TV_GROUP:
                label.set_text("%s (%d)" % (group.name, len(group.channels)))
            elif self.content_type == MOVIES_GROUP:
                label.set_text("%s (%d)" % (self.remove_word("VOD", group.name), len(group.channels)))
            else:
                label.set_text("%s (%d)" % (self.remove_word("SERIES", group.name), len(group.series)))
            box = Gtk.Box()
            name = group.name.lower().replace("(", " ").replace(")", " ")
            added_words = []

            found_flag = False
            for country_name in COUNTRY_CODES.keys():
                if country_name.lower() == group.name.lower():
                    found_flag = True
                    self.add_flag(COUNTRY_CODES[country_name], box)
                    break

            for word in name.split():
                self.add_badge(word, box, added_words)

            box.pack_start(label, False, False, 0)
            box.set_spacing(6)
            button.add(box)
            self.categories_flowbox.add(button)

        self.categories_flowbox.show_all()

        if not found_groups:
            self.on_category_button_clicked(None, None)

    def on_category_button_clicked(self, widget, group):
        self.active_group = group
        if self.content_type == TV_GROUP:
            if group is not None:
                self.show_channels(group.channels)
            else:
                self.show_channels(self.active_provider.channels)
        elif self.content_type == MOVIES_GROUP:
            if group is not None:
                self.show_vod(group.channels)
            else:
                self.show_vod(self.active_provider.movies)
        elif self.content_type == SERIES_GROUP:
            if group is not None:
                self.show_vod(group.series)
            else:
                self.show_vod(self.active_provider.series)

    def show_favorites(self, widget=None):
        self.content_type = TV_GROUP
        channels = []
        for line in self.favorite_data:
            info, url = line.split(":::")
            channel = Channel(None, info)
            channel.url = url
            channels.append(channel)
        self.show_channels(channels, favorites=True)

    def show_channels(self, channels, favorites=False):
        self.navigate_to("channels_page", "", favorites)
        if self.content_type == TV_GROUP:
            self.sidebar.show()
            for child in self.channels_listbox.get_children():
                self.channels_listbox.remove(child)

            logos_to_refresh = []
            for channel in channels:
                image = Gtk.Image().new_from_surface(self.get_channel_surface(channel.logo_path))
                logos_to_refresh.append((channel, image))
                self.channels_listbox.add(ChannelWidget(channel, image))

            self.channels_listbox.show_all()
            self.visible_search_results = len(self.channels_listbox.get_children())
            if len(logos_to_refresh) > 0:
                self.download_channel_logos(logos_to_refresh)
        else:
            self.sidebar.hide()

    def show_vod(self, items):
        logos_to_refresh = []
        self.navigate_to("vod_page")
        for child in self.vod_flowbox.get_children():
            self.vod_flowbox.remove(child)
        for item in items:
            button = Gtk.Button()
            button.set_tooltip_text(item.name)
            if self.content_type == MOVIES_GROUP:
                button.connect("clicked", self.on_vod_movie_button_clicked, item)
            else:
                button.connect("clicked", self.on_vod_series_button_clicked, item)
            label = Gtk.Label()
            label.set_text(item.name)
            label.set_max_width_chars(30)
            label.set_ellipsize(Pango.EllipsizeMode.END)
            box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            image = Gtk.Image().new_from_surface(self.get_channel_surface(item.logo_path))
            logos_to_refresh.append((item, image))
            box.pack_start(image, False, False, 0)
            box.pack_start(label, False, False, 0)
            box.set_spacing(6)
            button.add(box)
            self.vod_flowbox.add(button)
        self.vod_flowbox.show_all()
        if len(logos_to_refresh) > 0:
            self.download_channel_logos(logos_to_refresh)

    def remove_word(self, word, string):
        if " " not in string:
            return string
        words = string.split()
        if word in words:
            words.remove(word)
        return " ".join(words)

    def show_episodes(self, serie):
        logos_to_refresh = []
        self.active_serie = serie
        # If we are using xtream provider
        # Load every Episodes of every Season for this Series
        if self.active_provider.type_id == "xtream":
            self.x.get_series_info_by_id(self.active_serie)

        self.navigate_to("episodes_page")
        for child in self.episodes_box.get_children():
            self.episodes_box.remove(child)
        for season_name in serie.seasons.keys():
            season = serie.seasons[season_name]
            label = Gtk.Label()
            label.set_text(_("Season %s") % season_name)
            label.get_style_context().add_class("season-label")
            flowbox = Gtk.FlowBox()
            self.episodes_box.pack_start(label, False, False, 0)
            self.episodes_box.pack_start(flowbox, False, False, 0)
            for episode_name in season.episodes.keys():
                episode = season.episodes[episode_name]
                button = Gtk.Button()
                button.set_tooltip_text(episode_name)
                button.connect("clicked", self.on_episode_button_clicked, episode)
                label = Gtk.Label()
                label.set_text(_("Episode %s") % episode_name)
                label.set_max_width_chars(30)
                label.set_ellipsize(Pango.EllipsizeMode.END)
                box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
                image = Gtk.Image().new_from_surface(self.get_channel_surface(episode.logo_path))
                logos_to_refresh.append((episode, image))
                box.pack_start(image, False, False, 0)
                box.pack_start(label, False, False, 0)
                box.set_spacing(6)
                button.add(box)
                flowbox.add(button)
        self.episodes_box.show_all()

        if len(logos_to_refresh) > 0:
            self.download_channel_logos(logos_to_refresh)

    def on_vod_movie_button_clicked(self, widget, channel):
        self.active_channel = channel
        self.show_channels(None)
        self.play_async(channel)

    def on_episode_button_clicked(self, widget, channel):
        self.active_channel = channel
        self.show_channels(None)
        self.play_async(channel)

    def on_vod_series_button_clicked(self, widget, serie):
        self.show_episodes(serie)

    def bind_setting_widget(self, key, widget):
        widget.set_text(self.settings.get_string(key))
        widget.connect("changed", self.on_entry_changed, key)

    def on_entry_changed(self, widget, key):
        self.settings.set_string(key, widget.get_text())

    def on_ytdlp_local_switch_activated(self, widget, data=None):
        self.settings.set_boolean("use-local-ytdlp", widget.get_active())
        if widget.get_active():
            self.update_ytdlp()

    def update_ytdlp(self, widget=None):
        path = os.path.expanduser("~/.cache/hypnotix/yt-dlp")
        os.chdir(path)
        if os.path.exists("yt-dlp"):
            subprocess.getoutput("./yt-dlp --update")
        else:
            subprocess.getoutput("wget https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp")
            subprocess.getoutput("chmod a+rx ./yt-dlp")
        self.ytdlp_local_version_label.set_text(subprocess.getoutput("~/.cache/hypnotix/yt-dlp/yt-dlp --version"))

    @async_function
    def download_channel_logos(self, logos_to_refresh):
        headers = {
            "User-Agent": self.settings.get_string("user-agent"),
            "Referer": self.settings.get_string("http-referer"),
        }
        for channel, image in logos_to_refresh:
            if channel.logo_path is None:
                continue
            if os.path.isfile(channel.logo_path):
                continue
            try:
                response = requests.get(channel.logo, headers=headers, timeout=10, stream=True)
                if response.status_code == 200:
                    response.raw.decode_content = True
                    with open(channel.logo_path, "wb") as f:
                        shutil.copyfileobj(response.raw, f)
                        self.refresh_channel_logo(channel, image)
            except Exception as e:
                print(e)

    @idle_function
    def refresh_channel_logo(self, channel, image):
        image.set_from_surface(self.get_channel_surface(channel.logo_path))

    def get_channel_surface(self, path):
        try:
            if self.content_type == TV_GROUP:
                surface = self.get_surface_for_file(path, 64, 32)
            elif self.content_type == MOVIES_GROUP:
                surface = self.get_surface_for_file(path, 200, 200)
            else:
                surface = self.get_surface_for_file(path, 200, 200)
        except Exception:
            surface = self.get_surface_for_file("/usr/share/hypnotix/generic_tv_logo.png", 22, 22)
        return surface

    def on_go_back_button(self, widget=None):
        self.navigate_to(self.back_page)
        if self.active_channel is not None:
            self.playback_bar.show()
        if self.active_group and self.back_page == "categories_page":
            self.init_channels_listbox()

    def on_search_button_toggled(self, widget):
        if self.search_button.get_active():
            self.search_bar.show()
            self.search_bar.grab_focus()
        else:
            self.search_bar.hide()

    def on_search_bar(self, widget):
        self.content_type = TV_GROUP
        search_bar_text = unidecode(self.search_bar.get_text()).lower()
        if search_bar_text != self.latest_search_bar_text:
            self.latest_search_bar_text = search_bar_text
            self.search_bar.set_sensitive(False)
            GLib.timeout_add_seconds(0.1, self.on_search)

    def on_search(self):
        self.visible_search_results = 0
        channels = []
        for channel in self.active_provider.channels:
            if self.latest_search_bar_text in channel.name.lower():
                channels.append(channel)
        self.show_channels(channels)
        if self.visible_search_results == 0:
            self.status(_("No channels found"))
        else:
            self.status(None)
        self.search_bar.set_sensitive(True)
        self.search_bar.grab_focus_without_selecting()
        self.navigate_to("channels_page")

    def init_channels_listbox(self):
        self.latest_search_bar_text = None
        self.active_group = None
        for child in self.channels_listbox.get_children():
            self.channels_listbox.remove(child)
        self.visible_search_results = 0

    @idle_function
    def navigate_to(self, page, name="", favorites=False):
        self.go_back_button.show()
        self.search_button.show()
        self.fullscreen_button.hide()
        self.stack.set_visible_child_name(page)
        provider = self.active_provider
        self.back_page = "landing_page"
        if page == "landing_page":
            self.headerbar.set_title("Hypnotix")
            self.headerbar.set_subtitle(_("Watch TV"))
            if provider is None:
                self.current_provider_label.set_text(_("No provider selected"))
                self.tv_label.set_text(_("TV Channels (%d)") % 0)
                self.movies_label.set_text(_("Movies (%d)") % 0)
                self.series_label.set_text(_("Series (%d)") % 0)
                self.tv_button.set_sensitive(False)
                self.movies_button.set_sensitive(False)
                self.series_button.set_sensitive(False)
            else:
                self.current_provider_label.set_text(provider.name)
                self.tv_label.set_text(_("TV Channels (%d)") % len(provider.channels))
                self.movies_label.set_text(_("Movies (%d)") % len(provider.movies))
                self.series_label.set_text(_("Series (%d)") % len(provider.series))
                self.tv_button.set_sensitive(len(provider.channels) > 0)
                self.movies_button.set_sensitive(len(provider.movies) > 0)
                self.series_button.set_sensitive(len(provider.series) > 0)
            self.go_back_button.hide()
        elif page == "categories_page":
            self.headerbar.set_title(provider.name)
            if self.content_type == TV_GROUP:
                self.headerbar.set_subtitle(_("TV Channels"))
            elif self.content_type == MOVIES_GROUP:
                self.headerbar.set_subtitle(_("Movies"))
            else:
                self.headerbar.set_subtitle(_("Series"))
        elif page == "channels_page":
            self.fullscreen_button.show()
            self.playback_bar.hide()
            if favorites:
                self.headerbar.set_title("Hypnotix")
                self.headerbar.set_subtitle(_("Favorites"))
            else:
                self.headerbar.set_title(provider.name)
                if self.content_type == TV_GROUP:
                    if self.active_group is None:
                        self.headerbar.set_subtitle(_("TV Channels"))
                    else:
                        self.back_page = "categories_page"
                        self.headerbar.set_subtitle(_("TV Channels > %s") % self.active_group.name)
                elif self.content_type == MOVIES_GROUP:
                    self.headerbar.set_subtitle(self.active_channel.name)
                    self.back_page = "vod_page"
                else:
                    self.headerbar.set_subtitle(self.active_channel.name)
                    self.back_page = "episodes_page"
        elif page == "vod_page":
            self.headerbar.set_title(provider.name)
            if self.content_type == MOVIES_GROUP:
                if self.active_group is None:
                    self.headerbar.set_subtitle(_("Movies"))
                else:
                    self.back_page = "categories_page"
                    self.headerbar.set_subtitle(_("Movies > %s") % self.active_group.name)
            else:
                if self.active_group is None:
                    self.headerbar.set_subtitle(_("Series"))
                else:
                    self.back_page = "categories_page"
                    self.headerbar.set_subtitle(_("Series > %s") % self.active_group.name)
        elif page == "episodes_page":
            self.back_page = "vod_page"
            self.headerbar.set_title(provider.name)
            self.headerbar.set_subtitle(self.active_serie.name)
        elif page == "new_channel_page":
            self.headerbar.set_title("Hypnotix")
            self.headerbar.set_subtitle(_("New Channel"))
        elif page == "preferences_page":
            self.headerbar.set_title("Hypnotix")
            self.headerbar.set_subtitle(_("Preferences"))
        elif page == "providers_page":
            self.headerbar.set_title("Hypnotix")
            self.headerbar.set_subtitle(_("Providers"))
        elif page == "add_page":
            self.back_page = "providers_page"
            self.headerbar.set_title("Hypnotix")
            if self.edit_mode:
                self.headerbar.set_subtitle(_("Edit %s") % name)
            else:
                self.headerbar.set_subtitle(_("Add a new provider"))
        elif page == "delete_page":
            self.back_page = "providers_page"
            self.headerbar.set_title("Hypnotix")
            self.headerbar.set_subtitle(_("Delete %s") % name)
        elif page == "reset_page":
            self.back_page = "providers_page"
            self.headerbar.set_title("Hypnotix")
            self.headerbar.set_subtitle(_("Reset providers"))

    def open_keyboard_shortcuts(self, widget):
        gladefile = "/usr/share/hypnotix/shortcuts.ui"
        builder = Gtk.Builder()
        builder.set_translation_domain(APP)
        builder.add_from_file(gladefile)
        window = builder.get_object("shortcuts")
        window.set_title(_("Hypnotix"))
        window.show()

    def on_favorite_button_toggled(self, widget):
        if self.page_is_loading:
            return
        name = self.active_channel.name
        data = f"{self.active_channel.info}:::{self.active_channel.url}"
        if widget.get_active() and data not in self.favorite_data:
            print (f"Adding {name} to favorites")
            self.favorite_data.append(data)
        elif widget.get_active() == False and data in self.favorite_data:
            print (f"Removing {name} from favorites")
            self.favorite_data.remove(data)
        self.favorite_button_image.set_from_icon_name("xsi-starred-symbolic" if widget.get_active() else "non-xsi-starred-symbolic", Gtk.IconSize.BUTTON)
        self.manager.save_favorites(self.favorite_data)

    def on_channel_activated(self, box, widget):
        self.active_channel = widget.channel
        self.play_async(self.active_channel)

    def on_prev_channel(self):
        if self.stack.get_visible_child_name() == "channels_page":
            self.channels_listbox.do_move_cursor(self.channels_listbox, Gtk.MovementStep.DISPLAY_LINES, -1)
            self.channels_listbox.do_activate_cursor_row(self.channels_listbox)

    def on_next_channel(self):
        if self.stack.get_visible_child_name() == "channels_page":
            self.channels_listbox.do_move_cursor(self.channels_listbox, Gtk.MovementStep.DISPLAY_LINES, 1)
            self.channels_listbox.do_activate_cursor_row(self.channels_listbox)

    @async_function
    def play_async(self, channel):
        if self.mpv is not None:
            self.mpv.stop()
            self.mpv.pause = False
        print("CHANNEL: '%s' (%s)" % (channel.name, channel.url))
        if channel is not None and channel.url is not None:
            # os.system("mpv --wid=%s %s &" % (self.wid, channel.url))
            # self.mpv_drawing_area.show()
            self.info_menu_item.set_sensitive(False)
            self.before_play(channel)
            self.reinit_mpv()
            self.mpv.play(channel.url)
            self.mpv.wait_until_playing()
            self.after_play(channel)

    @idle_function
    def before_play(self, channel):
        self.channel_stack.set_visible_child_name("channel_page")
        self.mpv_stack.set_visible_child_name("spinner_page")
        self.video_properties.clear()
        self.video_properties[_("General")] = {}
        self.video_properties[_("Color")] = {}

        self.audio_properties.clear()
        self.audio_properties[_("General")] = {}
        self.audio_properties[_("Layout")] = {}

        self.video_bitrates.clear()
        self.audio_bitrates.clear()
        self.spinner.start()

        self.label_channel_name.set_text(channel.name)
        self.label_channel_url.set_text(channel.url)

        self.page_is_loading = True
        data = f"{channel.info}:::{channel.url}"
        if data in self.favorite_data:
            self.favorite_button.set_active(True)
            self.favorite_button_image.set_from_icon_name("xsi-starred-symbolic", Gtk.IconSize.BUTTON)
            self.favorite_button.set_tooltip_text(_("Remove from favorites"))
        else:
            self.favorite_button.set_active(False)
            self.favorite_button_image.set_from_icon_name("xsi-non-starred-symbolic", Gtk.IconSize.BUTTON)
            self.favorite_button.set_tooltip_text(_("Add to favorites"))
        self.page_is_loading = False

    @idle_function
    def after_play(self, channel):
        self.mpv_stack.set_visible_child_name("player_page")
        self.spinner.stop()
        self.playback_label.set_text(channel.name)
        self.info_menu_item.set_sensitive(True)
        self.monitor_playback()

    def monitor_playback(self):
        try:
            self.mpv.unobserve_property("video-params", self.on_video_params)
            self.mpv.unobserve_property("video-format", self.on_video_format)
            self.mpv.unobserve_property("audio-params", self.on_audio_params)
            self.mpv.unobserve_property("audio-codec", self.on_audio_codec)
            self.mpv.unobserve_property("video-bitrate", self.on_bitrate)
            self.mpv.unobserve_property("audio-bitrate", self.on_bitrate)
            self.mpv.unobserve_property("core-idle", self.on_playback_changed)
        except:
            pass
        self.mpv.observe_property("video-params", self.on_video_params)
        self.mpv.observe_property("video-format", self.on_video_format)
        self.mpv.observe_property("audio-params", self.on_audio_params)
        self.mpv.observe_property("audio-codec", self.on_audio_codec)
        self.mpv.observe_property("video-bitrate", self.on_bitrate)
        self.mpv.observe_property("audio-bitrate", self.on_bitrate)
        self.mpv.observe_property("core-idle", self.on_playback_changed)

    @idle_function
    def on_playback_changed(self, prop, idle):
        if idle:
            if self.inhibit_id != 0:
                self.application.uninhibit(self.inhibit_id)
                self.inhibit_id = 0
        else:
            if self.inhibit_id == 0:
                self.inhibit_id = self.application.inhibit(
                    self.window,
                    Gtk.ApplicationInhibitFlags.IDLE | Gtk.ApplicationInhibitFlags.SUSPEND,
                    "Playing media"
                )

    @idle_function
    def on_bitrate(self, prop, bitrate):
        if not bitrate or prop not in ["video-bitrate", "audio-bitrate"]:
            return

        """
        Only update the bitrates when the info window is open unless
        we don't have any data yet.
        """
        if _("Average Bitrate") in self.video_properties:
            if _("Average Bitrate") in self.audio_properties:
                if not self.info_window.props.visible:
                    return

        rates = {"video": self.video_bitrates, "audio": self.audio_bitrates}
        rate = "video"
        if prop == "audio-bitrate":
            rate = "audio"

        rates[rate].append(int(bitrate) / 1000.0)
        rates[rate] = rates[rate][-30:]
        br = sum(rates[rate]) / float(len(rates[rate]))

        if rate == "video":
            self.video_properties[_("General")][_("Average Bitrate")] = "%.f Kbps" % br
        else:
            self.audio_properties[_("General")][_("Average Bitrate")] = "%.f Kbps" % br

    @idle_function
    def on_video_params(self, property, params):
        if not params or not type(params) == dict:
            return
        if "w" in params and "h" in params:
            self.video_properties[_("General")][_("Dimensions")] = "%sx%s" % (params["w"],params["h"])
        if "aspect" in params:
            aspect = round(float(params["aspect"]), 2)
            self.video_properties[_("General")][_("Aspect")] = "%s" % aspect
        if "pixelformat" in params:
            self.video_properties[_("Color")][_("Pixel Format")] = params["pixelformat"]
        if "gamma" in params:
            self.video_properties[_("Color")][_("Gamma")] = params["gamma"]
        if "average-bpp" in params:
            self.video_properties[_("Color")][_("Bits Per Pixel")] = params["average-bpp"]

    @idle_function
    def on_video_format(self, property, vformat):
        if not vformat:
            return
        self.video_properties[_("General")][_("Codec")] = vformat

    @idle_function
    def on_audio_params(self, property, params):
        if not params or not type(params) == dict:
            return
        if "channels" in params:
            chans = params["channels"]
            if "5.1" in chans or "7.1" in chans:
                chans += " " + _("surround sound")
            self.audio_properties[_("Layout")][_("Channels")] = chans
        if "samplerate" in params:
            sr = float(params["samplerate"]) / 1000.0
            self.audio_properties[_("General")][_("Sample Rate")] = "%.1f KHz" % sr
        if "format" in params:
            fmt = params["format"]
            if fmt in AUDIO_SAMPLE_FORMATS:
                fmt = AUDIO_SAMPLE_FORMATS[fmt]
            self.audio_properties[_("General")][_("Format")] = fmt
        if "channel-count" in params:
            self.audio_properties[_("Layout")][_("Channel Count")] = params["channel-count"]

    @idle_function
    def on_audio_codec(self, property, codec):
        if not codec:
            return
        self.audio_properties[_("General")][_("Codec")] = codec.split()[0]

    def on_stop_button(self, widget):
        self.mpv.stop()
        # self.mpv_drawing_area.hide()
        self.active_channel = None
        self.info_menu_item.set_sensitive(False)
        self.playback_bar.hide()

    def on_pause_button(self, widget):
        self.mpv.pause = not self.mpv.pause

    def on_show_button(self, widget):
        self.navigate_to("channels_page")

    def open_providers(self, widget):
        self.navigate_to("providers_page")

    @idle_function
    def refresh_providers_page(self):
        for child in self.providers_flowbox.get_children():
            self.providers_flowbox.remove(child)
        for provider in self.providers:
            labels_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            image = Gtk.Image()
            image.set_from_icon_name("xsi-tv-symbolic", Gtk.IconSize.BUTTON)
            labels_box.pack_start(image, False, False, 0)
            label = Gtk.Label()
            label.set_markup("<b>%s</b>" % provider.name)
            labels_box.pack_start(label, False, False, 0)
            num = len(provider.channels)
            if num > 0:
                label = Gtk.Label()
                label.set_text(gettext.ngettext("%d TV channel", "%d TV channels", num) % num)
                labels_box.pack_start(label, False, False, 0)
            num = len(provider.movies)
            if num > 0:
                label = Gtk.Label()
                label.set_text(gettext.ngettext("%d movie", "%d movies", num) % num)
                labels_box.pack_start(label, False, False, 0)
            num = len(provider.series)
            if num > 0:
                label = Gtk.Label()
                label.set_text(gettext.ngettext("%d series", "%d series", num) % num)
                labels_box.pack_start(label, False, False, 0)
            button = Gtk.Button()
            button.connect("clicked", self.on_provider_selected, provider)
            label = Gtk.Label()
            if provider == self.active_provider:
                label.set_text("%s %d (active)" % (provider.name, len(provider.channels)))
            else:
                label.set_text(provider.name)
            button.add(labels_box)
            box = Gtk.Box()
            box.pack_start(button, True, True, 0)
            box.set_spacing(6)

            # Edit button
            button = Gtk.Button()
            button.set_relief(Gtk.ReliefStyle.NONE)
            button.connect("clicked", self.on_edit_button_clicked, provider)
            image = Gtk.Image()
            image.set_from_icon_name("xsi-edit-symbolic", Gtk.IconSize.BUTTON)
            button.set_tooltip_text(_("Edit"))
            button.add(image)
            box.pack_start(button, False, False, 0)

            # Clear icon cache button
            button = Gtk.Button()
            button.set_relief(Gtk.ReliefStyle.NONE)
            button.connect("clicked", self.on_clear_icon_cache_button_clicked, provider)
            image = Gtk.Image()
            image.set_from_icon_name("xsi-edit-clear-symbolic", Gtk.IconSize.BUTTON)
            button.set_tooltip_text(_("Clear icon cache"))
            button.add(image)
            box.pack_start(button, False, False, 0)

            # Remove button
            button = Gtk.Button()
            button.set_relief(Gtk.ReliefStyle.NONE)
            button.connect("clicked", self.on_delete_button_clicked, provider)
            image = Gtk.Image()
            image.set_from_icon_name("xsi-edit-delete-symbolic", Gtk.IconSize.BUTTON)
            button.set_tooltip_text(_("Remove"))
            button.add(image)
            box.pack_start(button, False, False, 0)

            self.providers_flowbox.add(box)

        self.providers_flowbox.show_all()

    def on_provider_selected(self, widget, provider):
        self.active_provider = provider
        self.settings.set_string("active-provider", provider.name)
        self.init_channels_listbox()
        self.navigate_to("landing_page")

    def open_new_channel(self, widget):
        self.new_name_entry.grab_focus()
        self.new_name_entry.set_text("")
        self.new_url_entry.set_text("")
        self.new_logo_entry.set_text("")
        self.new_ok_button.set_sensitive(False)
        self.navigate_to("new_channel_page")

    def open_preferences(self, widget):
        self.navigate_to("preferences_page")

    def on_new_provider_button(self, widget):
        self.name_entry.set_text("")
        self.url_entry.set_text("")
        self.set_provider_type(PROVIDER_TYPE_URL)
        model = self.provider_type_combo.get_model()
        iter = model.get_iter_first()
        while iter:
            type_id = model.get_value(iter, PROVIDER_TYPE_ID)
            if type_id == PROVIDER_TYPE_URL:
                self.provider_type_combo.set_active_iter(iter)
                break
            iter = model.iter_next(iter)
        self.navigate_to("add_page")
        self.edit_mode = False
        self.provider_ok_button.set_sensitive(False)
        self.name_entry.grab_focus()

    def on_reset_providers_button(self, widget):
        self.navigate_to("reset_page")

    def on_close_info_window(self, widget, event):
        self.info_window.hide()
        return True

    @async_function
    def on_clear_icon_cache_button_clicked(self, widget, provider: Provider):
        for channel in provider.channels:
            if channel.logo_path:
                Path(channel.logo_path).unlink(missing_ok=True)

    def on_delete_button_clicked(self, widget, provider):
        self.navigate_to("delete_page", provider.name)
        self.marked_provider = provider

    def on_edit_button_clicked(self, widget, provider):
        self.marked_provider = provider
        self.name_entry.set_text(provider.name)
        self.username_entry.set_text(provider.username)
        self.password_entry.set_text(provider.password)
        self.epg_entry.set_text(provider.epg)
        if provider.type_id == PROVIDER_TYPE_LOCAL:
            self.url_entry.set_text("")
            self.path_entry.set_text(provider.url)
        else:
            self.path_entry.set_text("")
            self.url_entry.set_text(provider.url)

        model = self.provider_type_combo.get_model()
        iter = model.get_iter_first()
        while iter:
            type_id = model.get_value(iter, PROVIDER_TYPE_ID)
            if provider.type_id == type_id:
                self.provider_type_combo.set_active_iter(iter)
                break
            iter = model.iter_next(iter)
        self.edit_mode = True
        self.navigate_to("add_page", provider.name)
        self.provider_ok_button.set_sensitive(True)
        self.name_entry.grab_focus()
        self.set_provider_type(provider.type_id)

    def on_delete_no_button(self, widget):
        self.navigate_to("providers_page")

    def on_reset_no_button(self, widget):
        self.navigate_to("providers_page")

    def on_delete_yes_button(self, widget):
        self.providers.remove(self.marked_provider)
        if self.active_provider == self.marked_provider:
            self.active_provider = None
        self.marked_provider = None
        self.save()

    def on_reset_yes_button(self, widget):
        self.settings.reset("providers")
        self.reload(page="providers_page")

    def on_browse_button(self, widget):
        dialog = Gtk.FileChooserDialog(parent=self.window, action=Gtk.FileChooserAction.OPEN)
        dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
        filter_m3u = Gtk.FileFilter()
        filter_m3u.set_name(_("M3U Playlists"))
        filter_m3u.add_pattern("*.m3u*")
        dialog.add_filter(filter_m3u)
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            self.path_entry.set_text(dialog.get_filename())
        dialog.destroy()

    ######################
    #### PREFERENCES #####
    ######################

    def save(self):
        provider_strings = []
        for provider in self.providers:
            provider_strings.append(provider.get_info())
        self.settings.set_strv("providers", provider_strings)
        self.reload(page="providers_page", refresh=True)

    def on_provider_type_combo_changed(self, widget):
        type_id = self.provider_type_combo.get_model()[self.provider_type_combo.get_active()][PROVIDER_TYPE_ID]
        self.set_provider_type(type_id)

    def set_provider_type(self, type_id):
        widgets = [
            self.path_entry,
            self.path_label,
            self.browse_button,
            self.url_entry,
            self.url_label,
            self.username_entry,
            self.username_label,
            self.password_entry,
            self.password_label,
            self.epg_label,
            self.epg_entry,
        ]
        for widget in widgets:
            widget.hide()
        visible_widgets = []
        if type_id == PROVIDER_TYPE_URL:
            visible_widgets.append(self.url_entry)
            visible_widgets.append(self.url_label)
            visible_widgets.append(self.epg_label)
            visible_widgets.append(self.epg_entry)
        elif type_id == PROVIDER_TYPE_LOCAL:
            visible_widgets.append(self.path_entry)
            visible_widgets.append(self.path_label)
            visible_widgets.append(self.browse_button)
        elif type_id == PROVIDER_TYPE_XTREAM:
            visible_widgets.append(self.url_entry)
            visible_widgets.append(self.url_label)
            visible_widgets.append(self.username_entry)
            visible_widgets.append(self.username_label)
            visible_widgets.append(self.password_entry)
            visible_widgets.append(self.password_label)
            visible_widgets.append(self.epg_label)
            visible_widgets.append(self.epg_entry)
        else:
            print("Incorrect provider type: ", type_id)

        for widget in visible_widgets:
            widget.show()

    def on_provider_ok_button(self, widget):
        type_id = self.provider_type_combo.get_model()[self.provider_type_combo.get_active()][PROVIDER_TYPE_ID]
        name = self.name_entry.get_text()
        if self.edit_mode:
            provider = self.marked_provider
            provider.name = name
        else:
            provider = Provider(name=name, provider_info=None)
            self.providers.append(provider)
        provider.type_id = type_id
        provider.url = self.get_url()
        provider.username = self.username_entry.get_text()
        provider.password = self.password_entry.get_text()
        provider.epg = self.epg_entry.get_text()
        self.save()

    def on_new_ok_button(self, widget):
        name = self.new_name_entry.get_text()
        url = self.new_url_entry.get_text()
        logo = self.new_logo_entry.get_text()
        data = f'#EXTINF:-1 tvg-name="{name}" tvg-logo="{logo}" tvg-id="{name}" group-title="",{name}:::{url}'
        if data not in self.favorite_data:
            print (f"Adding {name} to favorites")
            self.favorite_data.append(data)
        self.manager.save_favorites(self.favorite_data)
        self.show_favorites()

    def on_new_cancel_button(self, widget):
        self.navigate_to("landing_page")

    def on_provider_cancel_button(self, widget):
        self.navigate_to("providers_page")

    def toggle_ok_sensitivity(self, widget=None):
        if self.name_entry.get_text() == "":
            self.provider_ok_button.set_sensitive(False)
        elif self.get_url() == "":
            self.provider_ok_button.set_sensitive(False)
        else:
            self.provider_ok_button.set_sensitive(True)

    def toggle_new_ok_sensitivity(self, widget=None):
        self.new_ok_button.set_sensitive(True)
        if self.new_name_entry.get_text() == "":
            self.new_ok_button.set_sensitive(False)
        for widget in (self.new_url_entry, self.new_logo_entry):
            if "://" not in widget.get_text():
                self.new_ok_button.set_sensitive(False)

    def get_url(self):
        type_id = self.provider_type_combo.get_model()[self.provider_type_combo.get_active()][PROVIDER_TYPE_ID]
        if type_id == PROVIDER_TYPE_LOCAL:
            widget = self.path_entry
        else:
            widget = self.url_entry

        url = widget.get_text().strip()
        if url == "":
            return ""
        if "://" not in url:
            if type_id == PROVIDER_TYPE_LOCAL:
                url = "file://%s" % url
            else:
                url = "http://%s" % url
        return url

    ##############################
    def open_info(self, widget):
        """
        Display a dialog containing information about the currently
        playing stream based on properties emitted by MPV during playback
        """
        sections = [
            self.video_properties_box,
            self.colour_properties_box,
            self.audio_properties_box,
            self.layout_properties_box,
        ]

        for section in sections:
            for child in section.get_children():
                section.remove(child)

        props = [
            self.video_properties[_("General")],
            self.video_properties[_("Color")],
            self.audio_properties[_("General")],
            self.audio_properties[_("Layout")],
        ]

        for section, props in zip(sections, props):
            for prop_k, prop_v in props.items():
                box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
                    margin_left=24, margin_right=24, margin_top=6, margin_bottom=6)
                box.set_halign(Gtk.Align.FILL)
                box_inner = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
                    spacing=14 * 12, expand=True)
                k = Gtk.Label(label=prop_k, margin_top=12, margin_bottom=12)
                k.set_halign(Gtk.Align.START)
                v = Gtk.Label(label=prop_v, margin_top=12, margin_bottom=12)

                def update_bitrate(label, properties):
                    """
                    Periodically update a label displaying the average
                    bitrate whilst the info dialog is visible.
                    """
                    if not self.info_window.props.visible:
                        return False
                    if _("Average Bitrate") in properties:
                        label.set_text(properties[_("Average Bitrate")])
                    return True

                if prop_k == _("Average Bitrate") and props == self.video_properties[_("General")]:
                    cb = partial(update_bitrate, v, props)
                    GLib.timeout_add_seconds(UPDATE_BR_INTERVAL, cb)

                elif prop_k == _("Average Bitrate") and props == self.audio_properties[_("General")]:
                    cb = partial(update_bitrate, v, props)
                    GLib.timeout_add_seconds(UPDATE_BR_INTERVAL, cb)

                v.set_halign(Gtk.Align.CENTER)
                box_inner.pack_start(k, True, True, 0)
                box_inner.pack_end(v, False, False, 0)
                box.add(box_inner)
                seperator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
                section.add(seperator)
                section.add(box)
        self.info_window.show_all()

    def open_about(self, widget):
        dlg = Gtk.AboutDialog()
        dlg.set_transient_for(self.window)
        dlg.set_title(_("About"))
        dlg.set_program_name(_("Hypnotix"))
        dlg.set_comments(_("Watch TV"))
        try:
            h = open("/usr/share/common-licenses/GPL", encoding="utf-8")
            s = h.readlines()
            gpl = ""
            for line in s:
                gpl += line
            h.close()
            dlg.set_license(gpl)
        except Exception as e:
            print(e)

        dlg.set_version("5.6")
        dlg.set_icon_name("hypnotix")
        dlg.set_logo_icon_name("hypnotix")
        dlg.set_website("https://www.github.com/linuxmint/hypnotix")

        def close(w, res):
            if res == Gtk.ResponseType.CANCEL or res == Gtk.ResponseType.DELETE_EVENT:
                w.destroy()

        dlg.connect("response", close)
        dlg.show()

    def on_menu_quit(self, widget):
        self.application.quit()

    def on_key_press_event(self, widget, event):
        if isinstance(widget.get_focus(), Gtk.Entry):
            return False
        # Get any active, but not pressed modifiers, like CapsLock and NumLock
        persistant_modifiers = Gtk.accelerator_get_default_mod_mask()

        # Determine the actively pressed modifier
        modifier = event.get_state() & persistant_modifiers
        # Bool of Control or Shift modifier states
        ctrl = modifier == Gdk.ModifierType.CONTROL_MASK
        shift = modifier == Gdk.ModifierType.SHIFT_MASK

        if ctrl and event.keyval == Gdk.KEY_r:
            self.reload(page=None, refresh=True)
        elif ctrl and event.keyval == Gdk.KEY_f:
            if self.search_button.get_active():
                self.search_button.set_active(False)
            else:
                self.search_button.set_active(True)
        elif event.keyval == Gdk.KEY_F11 or \
                (event.keyval == Gdk.KEY_f and not ctrl):
            self.full_screen_mode()
        elif event.keyval == Gdk.KEY_F6:
            self.theather_mode()
        elif event.keyval == Gdk.KEY_F7:
            self.borderless_mode()
        elif event.keyval == Gdk.KEY_Escape:
            self.normal_mode()
        elif event.keyval == Gdk.KEY_BackSpace and not ctrl:
            self.normal_mode()
            self.on_go_back_button()
        elif event.keyval == Gdk.KEY_Up:
            self.on_prev_channel()
            return True
        elif event.keyval == Gdk.KEY_Down:
            self.on_next_channel()
            return True
        elif not event.keyval in [Gdk.KEY_F1, Gdk.KEY_F2]:
            try:
                self.mpv.command("keypress", Gdk.keyval_name(event.keyval))
            except:
                pass
            return True
        # elif event.keyval == Gdk.KEY_Up:
        #     # Up of in the list
        #     pass
        # elif event.keyval == Gdk.KEY_Down:
        #     # Down of in the list
        #     pass
        # elif event.keyval == Gdk.KEY_Escape:
        #    # Go back one level
        #    pass
        # #elif event.keyval == Gdk.KEY_Return:
        #     # Same as click
        # #    pass

    @async_function
    def reload(self, page=None, refresh=False):
        self.favorite_data = self.manager.load_favorites()
        self.status(_("Loading providers..."))
        self.providers = []
        for provider_info in self.settings.get_strv("providers"):
            try:
                provider = Provider(name=None, provider_info=provider_info)

                # Add provider to list. This must be done so that it shows up in the
                # list of providers for editing.
                self.providers.append(provider)

                if provider.type_id != "xtream":
                    # Download M3U
                    if refresh:
                        self.status(_("Downloading playlist..."), provider)
                    else:
                        self.status(_("Getting playlist..."), provider)
                    ret = self.manager.get_playlist(provider, refresh=refresh)
                    if ret:
                        self.status(_("Checking playlist..."), provider)
                        if self.manager.check_playlist(provider):
                            self.status(_("Loading channels..."), provider)
                            self.manager.load_channels(provider)
                            if provider.name == self.settings.get_string("active-provider"):
                                self.active_provider = provider
                            self.status(None)
                            print("%s: %d channels, %d groups, %d series, %d movies" % (provider.name, \
                                len(provider.channels), len(provider.groups), len(provider.series), len(provider.movies)))
                    else:
                        self.status(_("Failed to download playlist from %s") %  provider.name, provider)

                else:
                    # Load xtream class
                    from xtream import XTream

                    # Download via Xtream
                    self.x = XTream(
                        provider.name,
                        provider.username,
                        provider.password,
                        provider.url,
                        hide_adult_content=False,
                        user_agent=self.settings.get_string("user-agent"),
                        cache_path=PROVIDERS_PATH,
                    )
                    if self.x.auth_data != {}:
                        print("XTREAM `{}` Loading Channels".format(provider.name))
                        # Save default cursor
                        current_cursor = self.window.get_window().get_cursor()
                        # Set waiting cursor
                        self.window.get_window().set_cursor(Gdk.Cursor.new_from_name(Gdk.Display.get_default(), "wait"))
                        # Load data
                        self.x.load_iptv()
                        # Restore default cursor
                        self.window.get_window().set_cursor(current_cursor)
                        # Inform Provider of data
                        provider.channels = self.x.channels
                        provider.movies = self.x.movies
                        provider.series = self.x.series
                        provider.groups = self.x.groups

                        # Change redownload timeout
                        self.reload_timeout_sec = 60 * 60 * 2  # 2 hours
                        if self._timerid:
                            GLib.source_remove(self._timerid)
                        self._timerid = GLib.timeout_add_seconds(self.reload_timeout_sec, self.force_reload)

                        # If no errors, approve provider
                        if provider.name == self.settings.get_string("active-provider"):
                            self.active_provider = provider
                        self.status(None)
                    else:
                        print("XTREAM Authentication Failed")

            except Exception as e:
                print(e)
                traceback.print_exc()
                print("Couldn't parse provider info: ", provider_info)

        # If there are more than 1 providers and no Active Provider, set to the first one
        if len(self.providers) > 0 and self.active_provider is None:
            self.active_provider = self.providers[0]

        self.refresh_providers_page()

        if page is not None:
            self.navigate_to(page)
        self.status(None)
        self.latest_search_bar_text = None

    def force_reload(self):
        self.reload(page=None, refresh=True)
        return False

    @idle_function
    def status(self, string, provider=None):
        if string is None:
            self.status_label.set_text("")
            self.status_label.hide()
            return
        self.status_label.show()
        if provider is not None:
            self.status_label.set_text("%s: %s" % (provider.name, string))
            print("%s: %s" % (provider.name, string))
        else:
            self.status_label.set_text(string)
            print(string)

    def on_mpv_drawing_area_realize(self, widget):
        self.reinit_mpv()

    def reinit_mpv(self):
        if self.mpv is not None:
            self.mpv.stop()
        options = {}
        options["vo"] = "x11"
        try:
            mpv_options = self.settings.get_string("mpv-options")
            if ("=") in mpv_options:
                pairs = mpv_options.split()
                for pair in pairs:
                    key, value = pair.split("=", 1)
                    options[key] = value
        except Exception as e:
            print("Could not parse MPV options!")
            print(e)

        options["user_agent"] = self.settings.get_string("user-agent")
        options["referrer"] = self.settings.get_string("http-referer")

        while not self.mpv_drawing_area.get_window() and not Gtk.events_pending():
            time.sleep(0.1)

        osc = True
        if "osc" in options:
            # To prevent 'multiple values for keyword argument'!
            osc = options.pop("osc") != "no"

        if self.mpv is None:
            self.mpv = mpv.MPV(
                **options,
                script_opts="osc-layout=box,osc-seekbarstyle=bar,osc-deadzonesize=0,osc-minmousemove=3",
                input_default_bindings=True,
                input_vo_keyboard=True,
                osc=osc,
                ytdl=True,
                wid=str(self.mpv_drawing_area.get_window().get_xid())
            )

        self.mpv.volume = self.volume
        self.mpv.observe_property("volume", self.on_volume_prop)

    def on_mpv_drawing_area_draw(self, widget, cr):
        cr.set_source_rgb(0.0, 0.0, 0.0)
        cr.paint()

    def normal_mode(self):
        self.window.get_window().set_cursor(None)
        self.window.unfullscreen()
        self.mpv_top_box.show()
        self.mpv_bottom_box.hide()
        if self.content_type == TV_GROUP:
            self.sidebar.show()
        self.headerbar.show()
        self.channels_box.set_border_width(12)
        self.fullscreen = False

    def theather_mode(self):
        if self.stack.get_visible_child_name() == "channels_page":
            if self.sidebar.get_visible():
                self.mpv_top_box.hide()
                self.mpv_bottom_box.hide()
                self.sidebar.hide()
                # self.headerbar.hide()
                self.status_label.hide()
                self.channels_box.set_border_width(0)
            else:
                self.normal_mode()

    def borderless_mode(self):
        if self.stack.get_visible_child_name() == "channels_page":
            if self.headerbar.get_visible():
                self.mpv_top_box.hide()
                self.mpv_bottom_box.hide()
                self.sidebar.hide()
                self.headerbar.hide()
                self.status_label.hide()
                self.channels_box.set_border_width(0)
            else:
                self.normal_mode()

    def full_screen_mode(self):
        if self.stack.get_visible_child_name() == "channels_page":
            self.fullscreen = not self.fullscreen
            if self.fullscreen:
                self.window.get_window().set_cursor(Gdk.Cursor.new_from_name(Gdk.Display.get_default(), "none"))
                # Fullscreen mode
                self.window.fullscreen()
                self.mpv_top_box.hide()
                self.mpv_bottom_box.hide()
                self.sidebar.hide()
                self.headerbar.hide()
                self.status_label.hide()
                self.channels_box.set_border_width(0)
            else:
                self.normal_mode()

    def on_fullscreen_button_clicked(self, widget):
        self.full_screen_mode()

    def on_close_info_window_button_clicked(self, widget):
        self.info_window.hide()

    def on_volume_prop(self, name, value ):
        self.volume = value

if __name__ == "__main__":
    application = MyApplication("org.x.hypnotix", Gio.ApplicationFlags.FLAGS_NONE)
    application.run()
