from __future__ import unicode_literals

import logging
import re

from mopidy import backend, models
from mopidy.models import SearchResult

from mopidy_beets.browsers.albums import (
    AlbumsByArtistBrowser, AlbumsByGenreBrowser, AlbumsByYearBrowser)
from mopidy_beets.translator import assemble_uri, parse_uri


logger = logging.getLogger(__name__)

# match dates of the following format:
#   YYYY, YYYY-MM, YYYY-MM-DD, YYYY/MM, YYYY/MM/DD
DATE_REGEX = re.compile(
    r'^(?P<year>\d{4})(?:[-/](?P<month>\d{1,2})(?:[-/](?P<day>\d{1,2}))?)?$')


class BeetsLibraryProvider(backend.LibraryProvider):

    root_directory = models.Ref.directory(uri='beets:library',
                                          name='Beets library')
    root_categorie_list = [
        ('albums-by-artist', 'Albums by Artist', AlbumsByArtistBrowser),
        ('albums-by-genre', 'Albums by Genre', AlbumsByGenreBrowser),
        ('albums-by-year', 'Albums by Year', AlbumsByYearBrowser),
    ]

    def __init__(self, *args, **kwargs):
        super(BeetsLibraryProvider, self).__init__(*args, **kwargs)
        self.remote = self.backend.beets_api
        self.category_browsers = []
        for key, label, browser_class in self.root_categorie_list:
            ref = models.Ref.directory(name=label, uri=assemble_uri(
                self.root_directory.uri, key))
            browser = browser_class(ref, self.remote)
            self.category_browsers.append(browser)

    def browse(self, uri):
        logger.debug('Browsing Beets at: %s', uri)
        path, item_id = parse_uri(uri, uri_prefix=self.root_directory.uri)
        if path is None:
            logger.error('Beets - failed to parse uri: %s', uri)
            return []
        elif uri == self.root_directory.uri:
            # top level - show the categories
            refs = [browser.ref for browser in self.category_browsers]
            refs.sort(key=lambda item: item.name)
            return refs
        elif path == 'album':
            # show an album
            try:
                album_id = int(item_id)
            except ValueError:
                logger.error('Beets - invalid album ID in URI: %s', uri)
                return []
            tracks = self.remote.get_tracks_by([('album_id', album_id)], True,
                                               ['track+'])
            return [models.Ref.track(uri=track.uri, name=track.name)
                    for track in tracks]
        else:
            # show a generic category directory
            for browser in self.category_browsers:
                if path == parse_uri(browser.ref.uri,
                                     uri_prefix=self.root_directory.uri)[0]:
                    if item_id is None:
                        return browser.get_toplevel()
                    else:
                        return browser.get_directory(item_id)
            else:
                logger.error('Invalid browse URI: %s / %s', uri, path)
                return []

    def search(self, query=None, uris=None, exact=False):
        # TODO: restrict the result to 'uris'
        logger.debug('Beets Query (exact=%s) within "%s": %s',
                     exact, uris, query)
        if not self.remote.has_connection:
            return SearchResult(uri='beets:search-disconnected', tracks=[])

        self._validate_query(query)
        search_list = []
        for (field, values) in query.items():
            for val in values:
                # missing / unsupported fields: uri, performer
                if field == 'any':
                    search_list.append(val)
                elif field == 'album':
                    search_list.append(('album', val))
                elif field == 'artist':
                    search_list.append(('artist', val))
                elif field == 'albumartist':
                    search_list.append(('albumartist', val))
                elif field == 'track_name':
                    search_list.append(('title', val))
                elif field == 'track_no':
                    search_list.append(('track', val))
                elif field == 'composer':
                    search_list.append(('composer', val))
                elif field == 'genre':
                    search_list.append(('genre', val))
                elif field == 'comment':
                    search_list.append(('comments', val))
                elif field == 'date':
                    # supported date formats: YYYY, YYYY-MM, YYYY-MM-DD
                    # Days and months may consist of one or two digits.
                    # A slash (instead of a dash) is acceptable as a separator.
                    match = DATE_REGEX.search(val)
                    if match:
                        # remove None values
                        for key, value in match.groupdict().items():
                            if value:
                                search_list.append((key, int(value)))
                    else:
                        logger.info(
                            'Beets search: ignoring unknown date format (%s). '
                            'It should be "YYYY", "YYYY-MM" or "YYYY-MM-DD".',
                            val)
                else:
                    logger.info('Beets: ignoring unknown query key: %s', field)
                    break
        logger.debug('Search query: %s', search_list)
        tracks = self.remote.get_tracks_by(search_list, exact, [])
        uri = '-'.join([item if isinstance(item, str) else '='.join(item)
                        for item in search_list])
        return SearchResult(uri='beets:search-' + uri, tracks=tracks)

    def lookup(self, uri=None, uris=None):
        logger.debug('Beets lookup: %s', uri or uris)
        if uri:
            # the older method (mopidy < 1.0): return a list of tracks
            # handle one or more tracks given with multiple semicolons
            logger.debug('Beets lookup: %s', uri)
            path, item_id = parse_uri(uri, uri_prefix=self.root_directory.uri)
            if path == 'track':
                tracks = [self.remote.get_track(item_id)]
            elif path == 'album':
                tracks = self.remote.get_tracks_by([('album_id', item_id)],
                                                   True, ('disc+', 'track+'))
            elif path == 'artist':
                artist_tracks = self.remote.get_tracks_by(
                    [('artist', item_id)], True, [])
                composer_tracks = self.remote.get_tracks_by(
                    [('composer', item_id)], True, [])
                # Append composer tracks to the artist tracks (unique items).
                tracks = list(set(artist_tracks + composer_tracks))
                tracks.sort(key=lambda t: (t.date, t.disc_no, t.track_no))
            else:
                logger.info('Unknown Beets lookup URI: %s', uri)
                tracks = []
            # remove occourences of None
            return [track for track in tracks if track]
        else:
            # the newer method (mopidy>=1.0): return a dict of uris and tracks
            return {uri: self.lookup(uri=uri) for uri in uris}

    def get_distinct(self, field, query=None):
        logger.debug('Beets distinct query: %s (uri=%s)', field, query)
        if not self.remote.has_connection:
            return []
        else:
            return self.remote.get_sorted_unique_track_attributes(field)

    def _validate_query(self, query):
        for values in query.values():
            if not values:
                raise LookupError('Missing query')
            for value in values:
                if not value:
                    raise LookupError('Missing query')
