# -*- coding: utf-8 -*-
# Copyright 2005-2009 Joe Wreschnig, Steven Robertson
#           2012,2016 Nick Boultbee
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation

import random

from gi.repository import Gtk, GLib

from quodlibet import app
from quodlibet import config
from quodlibet.plugins.events import EventPlugin
from quodlibet import util
from quodlibet.util import print_d
try:
    from quodlibet.qltk import notif, Icons
except Exception:
    notif = None


class RandomAlbum(EventPlugin):
    PLUGIN_ID = 'Random Album Playback'
    PLUGIN_NAME = _('Random Album Playback')
    PLUGIN_DESC = _("Starts a random album when your playlist reaches its "
                    "end. It requires that your active browser supports "
                    "filtering by album.")
    PLUGIN_ICON = Icons.MEDIA_SKIP_FORWARD

    weights = {}
    use_weights = False
    # Not a dict because we want to impose a particular order
    # Third item is to specify a non-default aggregation function
    keys = [
                ("rating", _("Rated higher"), None),
                ("playcount", _("Played more often"), 'avg'),
                ("skipcount", _("Skipped more often"), 'avg'),
                ("lastplayed", _("Played more recently"), None),
                ("laststarted", _("Started more recently"), None),
                ("added", _("Added more recently"), None),
                ("length", _("Longer albums"), None),
            ]

    def __init__(self):
        for (key, text, func) in self.keys:
            val = config.getfloat("plugins", "randomalbum_%s" % key, 0.0)
            self.weights[key] = val

        use = config.getint("plugins", "randomalbum_use_weights", 0)
        self.use_weights = use
        delay = config.getint("plugins", "randomalbum_delay", 0)
        self.delay = delay

    def PluginPreferences(self, song):
        def changed_cb(hscale, key):
            val = hscale.get_value()
            self.weights[key] = val
            config.set("plugins", "randomalbum_%s" % key, val)

        def delay_changed_cb(spin):
            self.delay = int(spin.get_value())
            config.set("plugins", "randomalbum_delay", str(self.delay))

        def toggled_cb(check, widgets):
            self.use_weights = check.get_active()
            for w in widgets:
                w.set_sensitive(self.use_weights)
            config.set("plugins", "randomalbum_use_weights",
                    str(int(self.use_weights)))

        vbox = Gtk.VBox(spacing=12)
        table = Gtk.Table(n_rows=len(self.keys) + 1, n_columns=3)
        table.set_border_width(3)

        hbox = Gtk.HBox(spacing=6)
        spin = Gtk.SpinButton(
            adjustment=Gtk.Adjustment.new(self.delay, 0, 3600, 1, 10, 0))
        spin.connect("value-changed", delay_changed_cb)
        hbox.pack_start(spin, False, True, 0)
        lbl = Gtk.Label(label=_("seconds before starting next album"))
        hbox.pack_start(lbl, False, True, 0)
        vbox.pack_start(hbox, True, True, 0)

        frame = Gtk.Frame(label=_("Weights"))

        check = Gtk.CheckButton(label=_("Play some albums more than others"))
        vbox.pack_start(check, False, True, 0)
        # Toggle both frame and contained table; frame doesn't always work?
        check.connect("toggled", toggled_cb, [frame, table])
        check.set_active(self.use_weights)
        toggled_cb(check, [frame, table])

        frame.add(table)
        vbox.pack_start(frame, True, True, 0)

        # Less label
        less_lbl = Gtk.Label()
        arr = Gtk.Arrow(arrow_type=Gtk.ArrowType.LEFT,
                        shadow_type=Gtk.ShadowType.OUT)
        less_lbl.set_markup("<i>%s</i>" % util.escape(_("avoid")))
        less_lbl.set_alignment(0, 0)
        hb = Gtk.HBox(spacing=0)
        hb.pack_start(arr, False, True, 0)
        hb.pack_start(less_lbl, True, True, 0)
        table.attach(hb, 1, 2, 0, 1, xpadding=3,
                     xoptions=Gtk.AttachOptions.FILL)
        # More label
        more_lbl = Gtk.Label()
        arr = Gtk.Arrow(arrow_type=Gtk.ArrowType.RIGHT,
                        shadow_type=Gtk.ShadowType.OUT)
        more_lbl.set_markup("<i>%s</i>" % util.escape(_("prefer")))
        more_lbl.set_alignment(1, 0)
        hb = Gtk.HBox(spacing=0)
        hb.pack_end(arr, False, True, 0)
        hb.pack_end(more_lbl, True, True, 0)
        table.attach(hb, 2, 3, 0, 1, xpadding=3,
                     xoptions=Gtk.AttachOptions.FILL)

        for (idx, (key, text, func)) in enumerate(self.keys):
            lbl = Gtk.Label(label=text)
            lbl.set_alignment(0, 0)
            table.attach(lbl, 0, 1, idx + 1, idx + 2,
                         xoptions=Gtk.AttachOptions.FILL,
                         xpadding=3, ypadding=3)
            adj = Gtk.Adjustment(lower=-1.0, upper=1.0, step_increment=0.1)
            hscale = Gtk.HScale(adjustment=adj)
            hscale.set_value(self.weights[key])
            hscale.set_draw_value(False)
            hscale.set_show_fill_level(False)
            hscale.connect("value-changed", changed_cb, key)
            lbl.set_mnemonic_widget(hscale)
            table.attach(hscale, 1, 3, idx + 1, idx + 2,
                         xpadding=3, ypadding=3)

        return vbox

    def _score(self, albums):
        """Score each album. Returns a list of (score, name) tuples."""

        # Score the album based on its weighted rank ordering for each key
        # Rank ordering is more resistant to clustering than weighting
        # based on normalized means, and also normalizes the scale of each
        # weight slider in the prefs pane.
        ranked = {}
        for (tag, text, func) in self.keys:
            tag_key = ("~#%s:%s" % (tag, func) if func
                       else "~#%s" % tag)
            ranked[tag] = sorted(albums,
                                 key=lambda al: al.get(tag_key))

        scores = {}
        for album in albums:
            scores[album] = 0
            for (tag, text, func) in self.keys:
                rank = ranked[tag].index(album)
                scores[album] += rank * self.weights[tag]

        return [(score, name) for name, score in scores.items()]

    def plugin_on_song_started(self, song):
        if (song is None and config.get("memory", "order") != "onesong" and
            not app.player.paused):
            browser = app.window.browser

            if not browser.can_filter('album'):
                return

            albumlib = app.library.albums
            albumlib.load()

            if browser.can_filter_albums():
                keys = browser.list_albums()
                values = [albumlib[k] for k in keys]
            else:
                keys = set(browser.list("album"))
                values = [a for a in albumlib if a("album") in keys]

            if self.use_weights:
                # Select 3% of albums, or at least 3 albums
                nr_albums = int(min(len(values), max(0.03 * len(values), 3)))
                chosen_albums = random.sample(values, nr_albums)
                album_scores = sorted(self._score(chosen_albums))
                for score, album in album_scores:
                    print_d("%0.2f scored by %s" % (score, album("album")))
                album = max(album_scores)[1]
            else:
                album = random.choice(values)

            if album is not None:
                self.schedule_change(album)

    def schedule_change(self, album):
        if self.delay:
            srcid = GLib.timeout_add(1000 * self.delay,
                                     self.change_album, album)
            if notif is None:
                return
            task = notif.Task(_("Random Album"),
                              _("Waiting to start %s") %
                                    util.bold(util.escape(album("album"))),
                              stop=lambda: GLib.source_remove(srcid))

            def countdown():
                for i in range(10 * self.delay):
                    task.update(i / (10. * self.delay))
                    yield True
                task.finish()
                yield False
            GLib.timeout_add(100, countdown().next)
        else:
            self.change_album(album)

    def change_album(self, album):
        browser = app.window.browser
        if not browser.can_filter('album'):
            return

        if browser.can_filter_albums():
            browser.filter_albums([album.key])
        else:
            browser.filter('album', [album("album")])
        GLib.idle_add(self.unpause)

    def unpause(self):
        # Wait for the next GTK loop to make sure everything's tidied up
        # after the song ended. Also, if this is program startup and the
        # previous current song wasn't found, we'll get this condition
        # as well, so just leave the player paused if that's the case.
        try:
            app.player.next()
        except AttributeError:
            app.player.paused = True
