# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2018 The gPodder Team
#
# gPodder is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# gPodder is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#


#
#  libplayers.py -- get list of potential playback apps
#  thomas perl <thp@perli.net>   20060329
#
#

import glob
import logging
import os
import os.path
import re
import threading
from configparser import RawConfigParser

from gi.repository import GdkPixbuf, GObject, Gtk

import gpodder

_ = gpodder.gettext

logger = logging.getLogger(__name__)

# where are the .desktop files located?
userappsdirs = [os.path.expanduser(p) for p in (
    '/usr/share/applications/',
    '/usr/local/share/applications/',
    '~/.local/share/applications',
    '/var/lib/flatpak/exports/share/applications/',
    '~/.local/share/flatpak/exports/share/applications/',
)]

# the name of the section in the .desktop files
sect = 'Desktop Entry'


class PlayerListModel(Gtk.ListStore):
    C_ICON, C_NAME, C_COMMAND, C_CUSTOM = list(range(4))

    def __init__(self):
        Gtk.ListStore.__init__(self, GdkPixbuf.Pixbuf, str, str, bool)

    def insert_app(self, pixbuf, name, command):
        self.append((pixbuf, name, command, False))

    def get_command(self, index):
        return self[index][self.C_COMMAND]

    def get_index(self, value):
        for index, row in enumerate(self):
            if value == row[self.C_COMMAND]:
                return index

        last_row = self[-1]
        name = _('Command: %s') % value
        if last_row[self.C_CUSTOM]:
            last_row[self.C_COMMAND] = value
            last_row[self.C_NAME] = name
        else:
            self.append((None, name, value, True))

        return len(self) - 1

    @classmethod
    def is_separator(cls, model, iterator):
        return model.get_value(iterator, cls.C_COMMAND) == ''


class UserApplication(object):
    def __init__(self, name, cmd, mime, icon):
        self.name = name
        self.cmd = cmd
        self.icon = icon
        self.mime = mime

    def get_icon(self):
        if self.icon is not None:
            # Load it from an absolute filename
            if os.path.exists(self.icon):
                try:
                    return GdkPixbuf.Pixbuf.new_from_file_at_size(self.icon, 24, 24)
                except GObject.GError:
                    pass

            # Load it from the current icon theme
            (icon_name, extension) = os.path.splitext(os.path.basename(self.icon))
            theme = Gtk.IconTheme()
            if theme.has_icon(icon_name):
                return theme.load_icon(icon_name, 24, Gtk.IconLookupFlags.FORCE_SIZE)

    def is_mime(self, mimetype):
        return self.mime.find(mimetype + '/') != -1


WIN32_APP_REG_KEYS = [
    ('Winamp', ('audio',), r'HKEY_CLASSES_ROOT\Winamp.File\shell\Play\command'),
    ('foobar2000', ('audio',), r'HKEY_CLASSES_ROOT\Applications\foobar2000.exe\shell\open\command'),
    ('Windows Media Player 11', ('audio', 'video'), r'HKEY_CLASSES_ROOT\WMP11.AssocFile.MP3\shell\open\command'),
    ('QuickTime Player', ('audio', 'video'), r'HKEY_CLASSES_ROOT\QuickTime.mp3\shell\open\command'),
    ('VLC', ('audio', 'video'), r'HKEY_CLASSES_ROOT\VLC.mp3\shell\open\command'),
    ('PotPlayer', ('audio', 'video'), r'HKEY_CLASSES_ROOT\potrun\shell\open\command'),
]


def win32_read_registry_key(path):
    import winreg

    rootmap = {
        'HKEY_CLASSES_ROOT': winreg.HKEY_CLASSES_ROOT,
    }

    parts = path.split('\\')
    root = parts.pop(0)
    key = winreg.OpenKey(rootmap[root], parts.pop(0))

    while parts:
        key = winreg.OpenKey(key, parts.pop(0))

    value, type_ = winreg.QueryValueEx(key, '')
    if type_ == winreg.REG_EXPAND_SZ:
        cmdline = re.sub(r'%([^%]+)%', lambda m: os.environ[m.group(1)], value)
    elif type_ == winreg.REG_SZ:
        cmdline = value
    else:
        raise ValueError('Not a string: ' + path)

    return cmdline.replace('%1', '%f').replace('%L', '%f')


class UserAppsReader(object):
    # XDG categories, plus some others found in random .desktop files
    # https://standards.freedesktop.org/menu-spec/latest/apa.html
    PLAYER_CATEGORIES = ('Audio', 'Video', 'AudioVideo', 'Player')

    def __init__(self, mimetypes):
        self.apps = []
        self.mimetypes = mimetypes
        self.__has_read = False
        self.__finished = threading.Event()
        self.__has_sep = False
        self.apps.append(UserApplication(
            _('Default application'), 'default',
            ';'.join((mime + '/*' for mime in self.mimetypes)),
            'document-open'))

    def add_separator(self):
        self.apps.append(UserApplication(
            '', '',
            ';'.join((mime + '/*' for mime in self.mimetypes)), ''))
        self.__has_sep = True

    def read(self):
        if self.__has_read:
            return

        self.__has_read = True
        if gpodder.ui.win32:
            for caption, types, hkey in WIN32_APP_REG_KEYS:
                try:
                    cmdline = win32_read_registry_key(hkey)
                    self.apps.append(UserApplication(
                        caption, cmdline,
                        ';'.join(typ + '/*' for typ in types), None))
                except Exception as e:
                    logger.warning('Parse HKEY error: %s (%s)', hkey, e)

        for appdir in userappsdirs:
            if os.path.exists(appdir):
                for file in glob.glob(os.path.join(appdir, '*.desktop')):
                    self.parse_and_append(file)
        self.__finished.set()

    def parse_and_append(self, filename):
        try:
            parser = RawConfigParser()
            parser.read([filename])
            if not parser.has_section(sect):
                return

            app_categories = parser.get(sect, 'Categories')
            if not app_categories:
                return

            if not any(category in self.PLAYER_CATEGORIES
                       for category in app_categories.split(';')):
                return

            # Find out if we need it by comparing mime types
            app_mime = parser.get(sect, 'MimeType')
            for needed_type in self.mimetypes:
                if app_mime.find(needed_type + '/') != -1:
                    app_name = parser.get(sect, 'Name')
                    app_cmd = parser.get(sect, 'Exec')
                    app_icon = parser.get(sect, 'Icon')
                    if not self.__has_sep:
                        self.add_separator()
                    self.apps.append(UserApplication(app_name, app_cmd, app_mime, app_icon))
                    return
        except:
            return

    def get_model(self, mimetype):
        self.__finished.wait()

        model = PlayerListModel()
        for app in self.apps:
            if app.is_mime(mimetype):
                model.insert_app(app.get_icon(), app.name, app.cmd)
        return model
