import os
import sys
import mutagen
import io
import random
import socket
import re
import json
from functools import partial

from PIL import Image as PImage
import kivy
from kivy.app import App
from kivy.logger import Logger
from kivy.uix.screenmanager import Screen
from kivy.graphics import Rectangle, Color
from kivy.core.image import Image as CoreImage
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.uix.popup import Popup
from kivy.properties import ObjectProperty
from kivy.core.window import Window
from kivy.factory import Factory
from kivy.metrics import sp
from twisted.internet.defer import Deferred, DeferredList

from kmpc.extra import KmpcHelpers
from kmpc.widgets import (CoverButton, clearimage, ratingstars,
                          normalfont, fontawesomefont, backdrop)

# make sure we are on updated version of kivy
kivy.require('1.10.0')

Helpers = KmpcHelpers()

class NowPlayingScreen(Screen):
    """The Now Playing screen."""

    def __init__(self, *args, **kwargs):
        super(NowPlayingScreen, self).__init__(*args, **kwargs)
        self.accessoryPopup = Factory.AccessoryPopup()
        self.settingsPopup = Factory.SettingsPopup()
        # create the once-per-second update of the track slider
        self.track_slider_task = Clock.schedule_interval(
                self.update_track_slider, 1)
        self.track_slider_task.cancel()

    def enter_screen(self, *args):
        ss = self.ids.ss
        ss.ids.nowplayingbutton.state = 'down'
        ss.ids.nowplayingbutton.disabled = True
        app = App.get_running_app().root
        if app:
            (app.mpdconnection.protocol.status().
                addCallback(self.update_mpd_status).
                addErrback(app.mpdconnection.handle_mpd_error))

    def leave_screen(self, *args):
        # pause the track slider task
        self.track_slider_task.cancel()

    def settings_popup(self):
        self.settingsPopup.open()
        app = App.get_running_app().root
        # get the host's IP address and display it
        iptext = "IP Address: " + format(self.get_ip())
        self.settingsPopup.ids.ip_label.text = iptext
        (app.mpdconnection.protocol.status().
            addCallback(partial(self.update_mixers, self.settingsPopup)).
            addErrback(app.mpdconnection.handle_mpd_error))
        (app.mpdconnection.protocol.replay_gain_status().
            addCallback(partial(self.update_replaygain, self.settingsPopup)).
            addErrback(app.mpdconnection.handle_mpd_error))

    def update_replaygain(self, p, result):
        rg = str(result)
        p.ids['rg_'+rg].state = 'down'

    def update_mixers(self, p, result):
        # set up the crossfade slider
        if 'xfade' in result:
            v = int(result['xfade'])
        else:
            v = 0
        p.ids.crossfade_slider.value = v
        # set up the mixrampdb slider
        if 'mixrampdb' in result:
            v = round(float(result['mixrampdb']), 6)
        else:
            v = 0.0
        p.ids.mixrampdb_slider.value = float(str(v)[1:])
        # set up the mixrampdelay slider
        if 'mixrampdelay' in result:
            v = round(float(result['mixrampdelay']), 6)
        else:
            v = 0.0
        p.ids.mixrampdelay_slider.value = v

    def get_ip(self):
        """Method that tries to get the local IP address, and returns localhost
        if there isn't one."""
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        try:
            # doesn't even have to be reachable
            s.connect(('10.255.255.255', 1))
            IP = s.getsockname()[0]
        except Exception:
            IP = '127.0.0.1'
        finally:
            s.close()
        return IP

    def handle_mpd_message(self, result):
        """Callback for mpd 'kmpc' messages."""
        # result is an iterator although it only generally has one member
        for m in result:
            message = m['message']
            Logger.info("Application: MPD Message: "+message)
            if message == 'AccessoryOff':
                # generated by mausberry circuit, accessory power is off
                self.accessoryPopup.open()
            elif message == 'AccessoryOn':
                # generated by mausberry circuit, accessory power is on
                self.accessoryPopup.dismiss()

    def current_track_slider_release(self):
        """Callback when track slider is released."""
        # get the value of the track slider
        curpos = int(self.ids.current_track_slider.value)
        Logger.debug("Application: touch up on track slider at "+str(curpos))
        app = App.get_running_app().root
        # seek to that position in the track
        app.mpdconnection.protocol.seekcur(
                str(curpos)).addErrback(app.mpdconnection.handle_mpd_error)

    def current_track_slider_down(self):
        """Callback when track slider is pressed."""
        Logger.debug("Application: touch down on track slider")
        # cancel the once-per-second update while finger is down so it doesn't
        # move out from under it
        self.track_slider_task.cancel()

    def update_track_slider(self, dt):
        """Increment the slider by 1 second every second."""
        Logger.debug("Application: update_track_slider")
        curpos = int(self.ids.current_track_slider.value) + 1
        self.ids.current_track_slider.value = curpos

    def stop_zero_stuff(self):
        """Clear the screen and variables if playback is fully stopped."""
        app = App.get_running_app().root
        self.ids.current_track_slider.value = 0
        self.ids.current_track_slider.max = 0
        self.ids.current_queue_track_number_label.text = ''
        self.ids.next_song_artist_label.text = ''
        app.currfile = None
        app.currsong = None
        app.nextsong = None
        self.ids.song_star_button.text = ''
        self.ids.song_star_button.disabled = True
        self.ids.album_cover_layout.clear_widgets()
        self.ids.trackinfo.clear_widgets()
        lbl = Factory.BaseLabel(text="Playback Stopped")
        self.ids.trackinfo.add_widget(lbl)
        self.ids.player.canvas.before.clear()
        self.ids.releasetypelabel.text = ''
        self.ids.yearlabel.text = ''
        self.ids.remasterlabel.text = ''

    def update_mpd_status(self, result):
        """Callback when mpd status changes."""
        Logger.debug('NowPlaying: update_mpd_status()')
        # probably state is the only one necessary, but hey
        app = App.get_running_app().root
        app.mpd_status['state'] = result['state']
        app.mpd_status['repeat'] = result['repeat']
        app.mpd_status['single'] = result['single']
        app.mpd_status['random'] = result['random']
        app.mpd_status['consume'] = result['consume']
        if app.mpd_status['state'] == 'stop':
            # if stopped, there are no current or next songs
            app.currsong = None
            app.nextsong = None
            Logger.debug("update_mpd_status: Nothing playing")
        else:
            # save current song, this is a 0-based index into the queue
            app.currsong = result['song']
            # ask mpd for current song data
            (app.mpdconnection.protocol.currentsong().
                addCallback(partial(self.update_mpd_currentsong, False)).
                addErrback(app.mpdconnection.handle_mpd_error))
            # save next song, this is a 0-based index into the queue
            if 'nextsong' in result:
                app.nextsong = result['nextsong']
                # ask mpd for next song data
                (app.mpdconnection.protocol.playlistinfo(app.nextsong).
                    addCallback(self.update_mpd_nextsong).
                    addErrback(app.mpdconnection.handle_mpd_error))
            else:
                app.nextsong = None
        stflags = ['normal', 'down']
        # check various flag states
        self.ids.repeat_button.state = stflags[
                int(app.mpd_status['repeat'])]
        self.ids.single_button.state = stflags[
                int(app.mpd_status['single'])]
        self.ids.random_button.state = stflags[
                int(app.mpd_status['random'])]
        self.ids.consume_button.state = stflags[
                int(app.mpd_status['consume'])]
        if (app.mpd_status['state'] == 'pause' or
                app.mpd_status['state'] == 'stop'):
            # play/pause button should be a play button that is unpressed
            self.ids.play_button.state = 'normal'
            self.ids.play_button.text = u"\uf04b"
            self.track_slider_task.cancel()
        else:
            # play/pause button should be a pause button that is pressed
            self.ids.play_button.state = 'down'
            self.ids.play_button.text = u"\uf04c"
        if app.mpd_status['state'] == 'stop':
            # zero everything out if we are stopped
            self.stop_zero_stuff()
        else:
            # mpd returns {elapsed seconds}:{total seconds}, the following
            # splits each to minute:second
            c, t = result['time'].split(":")
            # set the max slider value to the total seconds
            self.ids.current_track_slider.max = int(t)
            # set the current slider value to the current seconds
            app.mpd_status['curpos'] = int(c)
            self.ids.current_track_slider.value = int(c)
            if app.mpd_status['state'] != 'pause':
                self.track_slider_task()
            # throws an exception if i don't do this
            a = int(result['song'])+1
            b = int(result['playlistlength'])
            self.ids.current_queue_track_number_label.text = \
                "%d of %d" % (a, b)
        # update the queue tab with status results so that current track
        # will be highlighted
        app.queue.update_mpd_status(result)

    def change_artist_image(self, img, al_path, instance):
        """Called when you click on an artist logo, changes it to another at
        random."""
        Logger.debug("change_artist_image: (current path is "+img.source+")")
        img_path = img.source
        if len(os.listdir(al_path)) > 1:
            while img_path == img.source:
                img_path = os.path.join(
                        al_path,
                        random.choice(os.listdir(al_path)))
            if os.path.isfile(img_path):
                img.source = img_path
            Logger.debug("change_artist_image: (new path is "+img.source+")")
        else:
            Logger.debug("change_artist_image: no other choices for logo")

    def get_cache(self, ctype, uuid):
        """Returns data object for an artist, recording, or release from
        cache."""
        # pull the mb-cache folder from ini file
        cachepath = App.get_running_app().root.config.get('paths', 'cachepath')
        mbpath = os.path.join(cachepath, ctype, Helpers.upath(uuid),
                uuid+'.json')
        Logger.debug("Application: reading cache data from "+mbpath)
        data = {}
        try:
            with open(mbpath) as json_file:
                data = json.load(json_file)
        except:
            Logger.debug("Application: cache file could not be read")
        return data

    def get_artist_image(self, artist_id):
        img = None
        cachepath = App.get_running_app().root.config.get('paths', 'cachepath')
        try:
            # look in the 'logo' subfolder of the artistid path
            #al_path = os.path.join(fa_path, mb_aid, 'logo')
            al_path = os.path.join(cachepath, 'artist', Helpers.upath(artist_id), 'logo')
            # pick one at random
            img_path = os.path.join(
                    al_path,
                    random.choice(os.listdir(al_path)))
            if os.path.isfile(img_path):
                # create an image button out of the logo so you can
                # press it
                img = Factory.ImageButton(
                        source=os.path.join(al_path, img_path),
                        allow_stretch=True,
                        color=(1, 1, 1, 0.65))
                img.bind(on_press=partial(self.change_artist_image,
                                          img, al_path))
        except OSError:
            Logger.debug(
                    "update_mpd_currentsong: No logos for artist "
                    + artist_id)
        except Exception as e:
            Logger.exception("update_mpd_currentsong: "+format(e))
        return img

    def get_cover_from_file(self, p, res):
        # try to pull the cover art from the file
        img = None
        if os.path.isfile(p):
            Logger.debug('get_cover_from_file: found good file at path ' + p)
            # load up the file to read the tags
            f = mutagen.File(p)
            cimg = None
            data = None
            # try to get mp3 cover, if this throws an exception it's
            # not an mp3 or it doesn't have a cover
            try:
                pframes = f.tags.getall("APIC")
                # id3v2 can store any number of image frames, we just
                # want the first one
                for frame in pframes:
                    ext = 'img'
                    # figure out the file type
                    if (frame.mime.endswith('jpeg') or
                            frame.mime.endswith('jpg')):
                        ext = 'jpg'
                    elif frame.mime.endswith('png'):
                        ext = 'png'
                    elif frame.mime.endswith('bmp'):
                        ext = 'bmp'
                    elif frame.mime.endswith('gif'):
                        ext = 'gif'
                    # pull the raw image data into a variable
                    data = io.BytesIO(bytearray(frame.data))
                    break
            except AttributeError:
                pass
            # try to get mp4 cover
            if 'covr' in f.keys():
                # figure out the file type
                if f['covr'][0].imageformat == \
                        mutagen.mp4.MP4Cover.FORMAT_PNG:
                    ext = 'png'
                else:
                    ext = 'jpg'
                # pull the raw image data into a variable
                data = io.BytesIO(bytearray(f['covr'][0]))
            if data:
                # if we got image data, load it as a kivy.core.image
                # filter through PIL first to resize it if it is too
                # large for a texture
                pimg = PImage.open(data)
                (w, h) = pimg.size
                (mw, mh) = Window.size
                if w > mw or h > mh:
                    Logger.debug('get_cover_from_file: '
                            + 'resizing cover image to maximum of '
                            + format(mh)+'x'
                            + format(mh))
                    pimg.thumbnail((mh, mh))
                    data2 = io.BytesIO()
                    pimg.convert('RGB').save(data2, 'PNG')
                    data2.seek(0)
                    cimg = CoreImage(data2, ext='png')
                else:
                    Logger.debug(
                            'get_cover_from_file: '
                            + 'pulling cover directly from tag')
                    data.seek(0)
                    cimg = CoreImage(data, ext=ext)
            if cimg:
                img = CoverButton(
                        img=cimg,
                        layout=self.ids.album_cover_layout,
                        halign='center')
            else:
                img = CoverButton(
                        img=CoreImage(clearimage),
                        layout=self.ids.album_cover_layout,
                        halign='center')
        else:
            # this should _probably_ never happen
            Logger.debug('NowPlaying: no file found at path '+p)
        return img

    def format_track_title(self, title):
        # check to see if song title has any data deliminated by ()
        # or []
        stitle = re.split('[\(\[\]\)]', title)
        # if the first item is empty, the title starts with () or
        # []
        if not stitle[0]:
            stitle.pop(0)
            stitle[0] = '('+stitle[0]+')'
        ftitle = filter(None, stitle)
        Logger.debug('TITLE: '+format(stitle))
        if len(ftitle) > 1:
            title_widget = BoxLayout(orientation='vertical',
                             padding_y='2sp')
            # split the title up and put the parentheses in smaller
            # text below
            title_widget.add_widget(Factory.InfoLargeLabel(text=stitle[0]))
            l2 = ""
            for i, v in enumerate(stitle):
                if i > 0 and v.strip():
                    if l2:
                        l2 += ' '
                    l2 += '('+v.strip()+')'
            title_widget.add_widget(Factory.InfoLargeLabel(text=l2,
                    _scale=0.5))
        else:
            title_widget = Factory.InfoLargeLabel(text=title)
        return title_widget

    def format_album_title(self, album):
        # check to see if album title is a split (has a ' / ' in
        # the middle)
        talbum = album.split(' / ')
        Logger.debug('ALBUM1: '+format(talbum))
        album_widget = BoxLayout(orientation='horizontal', padding_x='2sp')
        if len(talbum) > 1:
            Logger.debug('ALBUM1: album is a split')
            split = True
        else:
            split = False
        for j, a in enumerate(talbum):
            if j > 0:
                album_widget.add_widget(Factory.InfoLargeLabel(
                        font_size='50sp',
                        text=u'\u2571',
                        size_hint_x=None,
                        font_name=normalfont))
            # check to see if album title has any data deliminated
            # by () or []
            salbum = re.split('[\(\[\]\)]', a)
            Logger.debug('ALBUM2: '+format(salbum))
            if len(salbum) > 1:
                bl2 = BoxLayout(
                        orientation='vertical',
                        padding_y='2sp')
                # split the album up and put the parentheses in
                # smaller text below
                if split:
                    bl2.add_widget(Factory.InfoLargeLabel(text=salbum[0],
                            _scale=0.66))
                else:
                    bl2.add_widget(Factory.InfoLargeLabel(text=salbum[0]))
                l2 = ""
                for i, v in enumerate(salbum):
                    if i > 0 and v.strip():
                        if l2:
                            l2 += ' '
                        l2 += '('+v.strip()+')'
                bl2.add_widget(Factory.InfoLargeLabel(text=l2, _scale=0.5))
                album_widget.add_widget(bl2)
            else:
                if split:
                    album_widget.add_widget(Factory.InfoLargeLabel(text=a,
                            _scale=0.66))
                else:
                    album_widget.add_widget(Factory.InfoLargeLabel(text=a))
        return album_widget

    def create_artist_widget(self, recording_data, mpd_data):
        artist_ids = []
        if recording_data:
            Logger.debug("create_artist_widget: parsing cache data")
            # create an artist widget
            artist_bl = BoxLayout(size_hint=(1, 1),
                orientation='horizontal', spacing=0)
            # loop through artist credits
            f_flag = False
            for row in recording_data['recording']['artist-credit']:
                if isinstance(row, dict) and 'artist' in row:
                    # found artist block
                    artist_id = row['artist']['id']
                    artist_ids.append(artist_id)
                    artist_name = row['artist']['name']
                    artist_image = self.get_artist_image(artist_id)
                    if f_flag:
                        nbl = BoxLayout(orientation='vertical')
                        ftext = Factory.InfoSmallLabel(text="featuring")
                        nbl.add_widget(ftext)
                    else:
                        nbl = artist_bl
                    if artist_image:
                        nbl.add_widget(artist_image)
                    else:
                        atext = Factory.InfoLargeLabel(text=artist_name)
                        nbl.add_widget(atext)
                    if f_flag:
                        artist_bl.add_widget(nbl)
                        f_flag = False
                elif 'feat' in row.lower():
                    # found featuring string, set the flag
                    f_flag = True
                elif isinstance(row, basestring):
                    # found join characters
                    jtext = Factory.InfoSmallLabel(text=row, size_hint_x=None)
                    artist_bl.add_widget(jtext)
            return (artist_ids, artist_bl)
        else:
            Logger.debug("create_artist_widget: parsing tags")
            t = mpd_data['artist']
            artist_text = Factory.InfoLargeLabel(text=t)
            if 'musicbrainz_artistid' in mpd_data:
                artist_ids = mpd_data['musicbrainz_artistid'].split('/')
            return (artist_ids, artist_text)

    def create_track_widget(self, recording_data, mpd_data):
        if recording_data:
            t = recording_data['recording']['title']
        else:
            t = mpd_data['title']
        if App.get_running_app().root.config.getboolean('system',
                'advancedtitles'):
            title_text = self.format_track_title(t)
        else:
            title_text = Factory.InfoLargeLabel(text=t)
        return title_text

    def create_album_widget(self, release_data, mpd_data):
        if release_data:
            t = release_data['release']['title']
        else:
            t = mpd_data['album']
        if App.get_running_app().root.config.getboolean('system',
                'advancedtitles'):
            album_text = self.format_album_title(t)
        else:
            album_text = Factory.InfoLargeLabel(text=t)
        return album_text

    def update_mpd_currentsong(self, force, result):
        """Callback for mpd currentsong data."""
        Logger.debug('NowPlaying: update_mpd_currentsong()')
        app = App.get_running_app().root
        # this is so expensive screen updates only happen if the song has
        # changed since the last time this callback was called
        songchange = False
        # ti is the track info widget
        ti = self.ids.trackinfo
        # if result is undefined, there's not actually a song playing
        if result:
            # if class's current file doesn't match what mpd returns, the song
            # has changed
            if app.currfile != result['file']:
                songchange = True
            # update class's current file
            app.currfile = result['file']
            if songchange or force:
                # clear the track info widget
                ti.clear_widgets()
                # clear the album cover
                self.ids.album_cover_layout.clear_widgets()
                # get the stored star rating
                (app.mpdconnection.protocol.
                    sticker_get('song', app.currfile,
                            'rating').
                    addCallback(self.update_mpd_sticker_rating).
                    addErrback(self.handle_mpd_no_sticker))
                # clear the background of the player canvas so we can show the
                # artist background
                self.ids.player.canvas.before.clear()
                # figure out the full path of the file
                bp = app.config.get('paths', 'musicpath')
                # p is the absolute path
                p = os.path.join(bp, result['file'])
                # pull the mb-cache folder from ini file
                cachepath = app.config.get('paths', 'cachepath')
                # try to get recording data from cache
                if 'musicbrainz_trackid' in result:
                    recording_data = self.get_cache('recording',
                            result['musicbrainz_trackid'])
                # try to get release data from cache
                if 'musicbrainz_albumid' in result:
                    release_data = self.get_cache('release',
                            result['musicbrainz_albumid'])

                # not entirely convinced that the size_hints in the following 
                # widgets actually do any good
                # create an artist title widget and get a list of artist_ids
                (artist_ids, artist_widget) = self.create_artist_widget(
                        recording_data, result)
                artist_widget.size_hint_y = 1
                artist_widget.size_hint_min_y = sp(95)
                ti.add_widget(artist_widget)

                # create a track title widget
                title_text = self.create_track_widget(recording_data, result)
                title_text.size_hint_y = 1
                title_text.size_hint_max_y = sp(95)
                ti.add_widget(title_text)

                # create an album title widget
                album_text = self.create_album_widget(release_data, result)
                album_text.size_hint_y = 1
                album_text.size_hint_max_y = sp(95)
                ti.add_widget(album_text)

                # figure out release dates
                yy = {'year': None, 'oyear': None,
                        'rdate': None, 'ordate': None}
                if release_data:
                    rg = release_data['release']['release-group']
                    if ('date' not in release_data['release'] and
                            'first-release-date' in rg):
                        release_data['release']['date'] = rg['first-release-date']
                    if 'date' in release_data['release']:
                        yy['year'] = release_data['release']['date'][:4]
                        if len(release_data['release']['date']) > 4:
                            yy['rdate'] = release_data['release']['date']
                    if ('first-release-date' in rg
                            and (rg['first-release-date'] !=
                                    release_data['release']['date'])):
                        yy['oyear'] = rg['first-release-date'][:4]
                        if len(rg['first-release-date']) > 4:
                            yy['ordate'] = rg['first-release-date']
                else:
                    if 'date' in result:
                        yy['year'] = result['date'][:4]
                        if len(result['date']) > 4:
                            yy['rdate'] = result['date']
                    if ('originaldate' in result
                            and result['date'] != result['originaldate']):
                        yy['oyear'] = result['originaldate'][:4]
                        if len(result['originaldate']) > 4:
                            yy['ordate'] = result['originaldate']

                # parse the release type
                if release_data:
                    t = release_data['release']['release-group']['type']
                    rl = self.ids.releasetypelabel
                    rl.text = t
                    rl.font_size = '20sp'
                else:
                    self.ids.releasetypelabel.text = ''

                # pick an artistid at random for the background
                if artist_ids:
                    mb_aid = random.choice(artist_ids)
                    try:
                        # look in the 'artistbackground' subfolder of the artistid
                        # path
                        ab_path = os.path.join(cachepath, 'artist',
                                Helpers.upath(mb_aid), 'artistbackground')
                        # pick one at random
                        img_path = random.choice(os.listdir(ab_path))
                        # update the player background with the image
                        with self.ids.player.canvas.before:
                            Color(1, 1, 1)
                            Rectangle(
                                    source=os.path.join(ab_path, img_path),
                                    size=self.ids.player.size,
                                    pos=self.ids.player.pos)
                    except Exception:
                        # if we can't get an artistbackground image, just do
                        # nothing
                        pass

                # try to pull the cover art from the file
                callbacks = []
                d = Deferred()
                callbacks.append(d.addCallbacks(
                        partial(self.get_cover_from_file, p),
                        app.mpdconnection.handle_mpd_error))
                callbacks.append(d.addCallbacks(
                        partial(self.handle_get_cover,
                                release_data, result, yy),
                        app.mpdconnection.handle_mpd_error))
                dlist = DeferredList(callbacks, consumeErrors=False)
                d.callback('BEGIN')

                # format year labels
                if yy['year']:
                    self.ids.yearlabel.text = "[" + yy['year'] + "]"
                else:
                    self.ids.yearlabel.text = ""
                if (app.config.getboolean('system', 'originalyear')
                        and yy['oyear'] and yy['year'] != yy['oyear']):
                    self.ids.yearlabel.text = "["+yy['oyear']+"]"
                    self.ids.remasterlabel.text = "{"+yy['year']+"}"
                else:
                    self.ids.remasterlabel.text = ""

        else:
            # there's not a current song, so zero everything out
            self.stop_zero_stuff()

    def handle_get_cover(self, release_data, mpd_data, yy, img):
        # create cover art popup
        if img:
            app = App.get_running_app().root
            Logger.debug("handle_get_cover: got an image")
            self.ids.album_cover_layout.add_widget(img)
            # popup the cover large if you press it
            if release_data:
                Logger.debug("handle_get_cover: formatting title from cache")
                try:
                    t = release_data['release']['release-group']['type']
                    title = (t + ' "' + release_data['release']['title'] +
                            '" by ')
                    t = release_data['release']['artist-credit-phrase']
                    title += (t + ' released ' +
                            Helpers.country(
                                    release_data['release']['country']))
                    if yy['rdate']:
                        title += ' on ' + yy['rdate']
                    elif yy['year']:
                        title += ' in ' + yy['year']
                    if (app.config.getboolean('system', 'originalyear')
                            and yy['oyear']):
                        if yy['ordate']:
                            title += (' (originally released ' +
                                yy['ordate'] + ')')
                        else:
                            title += (' (originally released ' +
                                yy['oyear'] + ')')
                    if 'disambiguation' in release_data['release']:
                        t = release_data['release']['disambiguation']
                        title += ' [' + t + ']'
                except Exception as e:
                    Logger.error("handle_get_cover: "+format(e))
            else:
                Logger.debug("handle_get_cover: formatting title from tags")
                title = ('"' + mpd_data['album'] + '" by ' +
                        mpd_data['albumartist'] + ' released ')
                if yy['rdate']:
                    title += 'on ' + yy['rdate']
                elif yy['year']:
                    title += 'in ' + yy['year']
                if (app.config.getboolean('system', 'originalyear') and
                        yy['oyear']):
                    if yy['ordate']:
                        title += (' (originally released ' + yy['ordate']
                            + ')')
                    else:
                        title += (' (originally released ' + yy['oyear']
                            + ')')
            img.bind(on_press=partial(self.cover_popup, title))
        return True

    def update_mpd_sticker_rating(self, result):
        """Callback for song that has a rating in mpd."""
        Logger.debug('NowPlaying: update_mpd_sticker_rating')
        self.ids.song_star_button.disabled = False
        self.ids.song_star_button.text = ratingstars[int(result)]

    def handle_mpd_no_sticker(self, result):
        """Callback for song that has no rating in mpd."""
        Logger.debug('NowPlaying: handle_mpd_no_sticker')
        self.ids.song_star_button.disabled = False
        self.ids.song_star_button.text = ratingstars[11]

    def update_mpd_nextsong(self, result):
        """Callback for next song data from mpd."""
        Logger.debug('NowPlaying: update_mpd_nextsong()')
        # result is a list with one member
        for obj in result:
            # set the next song label
            self.ids.next_song_artist_label.text = \
                    'Up Next: '+obj['artist']+' - '+obj['title']

    def prev_pressed(self):
        """Callback for prev button pressed."""
        Logger.debug('Application: prev_pressed()')
        App.get_running_app().root.mpdconnection.protocol.previous()

    def play_pressed(self):
        """Callback for play/pause button pressed."""
        Logger.debug('Application: play_pressed()')
        app = App.get_running_app().root
        if app.mpd_status['state'] == 'play':
            # pause if playing
            app.mpdconnection.protocol.pause()
        else:
            # play if paused or stopped
            app.mpdconnection.protocol.play()

    def next_pressed(self):
        """Callback for next button pressed."""
        Logger.debug('Application: next_pressed()')
        App.get_running_app().root.mpdconnection.protocol.next()

    def repeat_pressed(self):
        """Callback for repeat button pressed."""
        Logger.debug('Application: repeat_pressed()')
        app = App.get_running_app().root
        # toggle on/off
        app.mpdconnection.protocol.repeat(
                str(1-int(app.mpd_status['repeat'])))

    def single_pressed(self):
        """Callback for single button pressed."""
        Logger.debug('Application: single_pressed()')
        app = App.get_running_app().root
        # toggle on/off
        app.mpdconnection.protocol.single(
                str(1-int(app.mpd_status['single'])))

    def random_pressed(self):
        """Callback for random button pressed."""
        Logger.debug('Application: random_pressed()')
        app = App.get_running_app().root
        # toggle on/off
        app.mpdconnection.protocol.random(
                str(1-int(app.mpd_status['random'])))

    def consume_pressed(self):
        """Callback for consume button pressed."""
        Logger.debug('Application: consume_pressed()')
        app = App.get_running_app().root
        # toggle on/off
        app.mpdconnection.protocol.consume(
                str(1-int(app.mpd_status['consume'])))

    def rating_popup(self, instance):
        """Popup for setting song rating."""
        Logger.debug('Application: rating_popup()')
        popup = Factory.RatingPopup(
                rating_set=self.rating_set,
                song=App.get_running_app().root.currfile)
        popup.open()

    def rating_set(self, song, rating, popup):
        """Method that sets a song's rating."""
        Logger.debug('Application: rating_set('+rating+')')
        app = App.get_running_app().root
        popup.dismiss()
        if rating:
            app.mpdconnection.protocol.sticker_set(
                    'song',
                    song,
                    'rating',
                    rating)
            app.mpdconnection.protocol.sendmessage("ratingchange",song)
        else:
            app.mpdconnection.protocol.sticker_delete(
                    'song',
                    song,
                    'rating')

    def cover_popup(self, title, instance):
        """Popup for showing a larger version of the album cover."""
        Logger.debug('Application: cover_popup()')
        popup = Popup(title=title,size_hint=(0.6,1))
        popup.add_widget(Image(texture=instance.img.texture))
        popup.open()

    def change_backlight(self, value):
        """Method that sets the backlight to a certain value."""
        app = App.get_running_app().root
        Logger.info('Application: '
                    + 'change_backlight('+str(value)
                    + ') rpienable = '
                    + app.config.get('system', 'rpienable'))
        # only if the ini file says it's ok
        if app.config.getboolean('system', 'rpienable'):
            import rpi_backlight as bl
            # set the brightness
            bl.set_brightness(int(value), smooth=True, duration=1)

    def settings_update(self):
        app = App.get_running_app().root
        (app.mpdconnection.protocol.currentsong().
            addCallback(partial(self.update_mpd_currentsong, True)).
            addErrback(app.mpdconnection.handle_mpd_error))

