# This file is part of beets.
# Copyright 2016, Philippe Mongeau.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

"""Get a random song or album from the library.
"""

import random
from operator import attrgetter
from itertools import groupby


def _length(obj, album):
    """Get the duration of an item or album.
    """
    if album:
        return sum(i.length for i in obj.items())
    else:
        return obj.length


def _equal_chance_permutation(objs, field='albumartist', random_gen=None):
    """Generate (lazily) a permutation of the objects where every group
    with equal values for `field` have an equal chance of appearing in
    any given position.
    """
    rand = random_gen or random

    # Group the objects by artist so we can sample from them.
    key = attrgetter(field)
    objs.sort(key=key)
    objs_by_artists = {}
    for artist, v in groupby(objs, key):
        objs_by_artists[artist] = list(v)

    # While we still have artists with music to choose from, pick one
    # randomly and pick a track from that artist.
    while objs_by_artists:
        # Choose an artist and an object for that artist, removing
        # this choice from the pool.
        artist = rand.choice(list(objs_by_artists.keys()))
        objs_from_artist = objs_by_artists[artist]
        i = rand.randint(0, len(objs_from_artist) - 1)
        yield objs_from_artist.pop(i)

        # Remove the artist if we've used up all of its objects.
        if not objs_from_artist:
            del objs_by_artists[artist]


def _take(iter, num):
    """Return a list containing the first `num` values in `iter` (or
    fewer, if the iterable ends early).
    """
    out = []
    for val in iter:
        out.append(val)
        num -= 1
        if num <= 0:
            break
    return out


def _take_time(iter, secs, album):
    """Return a list containing the first values in `iter`, which should
    be Item or Album objects, that add up to the given amount of time in
    seconds.
    """
    out = []
    total_time = 0.0
    for obj in iter:
        length = _length(obj, album)
        if total_time + length <= secs:
            out.append(obj)
            total_time += length
    return out


def random_objs(objs, album, number=1, time=None, equal_chance=False,
                random_gen=None):
    """Get a random subset of the provided `objs`.

    If `number` is provided, produce that many matches. Otherwise, if
    `time` is provided, instead select a list whose total time is close
    to that number of minutes. If `equal_chance` is true, give each
    artist an equal chance of being included so that artists with more
    songs are not represented disproportionately.
    """
    rand = random_gen or random

    # Permute the objects either in a straightforward way or an
    # artist-balanced way.
    if equal_chance:
        perm = _equal_chance_permutation(objs)
    else:
        perm = objs
        rand.shuffle(perm)  # N.B. This shuffles the original list.

    # Select objects by time our count.
    if time:
        return _take_time(perm, time * 60, album)
    else:
        return _take(perm, number)
