#!/usr/bin/env python3
# coding=utf-8
from os.path import join, expanduser, isfile
from os import makedirs
from itertools import zip_longest
from getpass import getpass
import sys

import urwid
import gmusicapi
import logging
import yaml

WELCOME = '''
   ▄             ▀       ▀                
 ▄▄█▄▄  ▄   ▄  ▄▄▄     ▄▄▄    ▄▄▄   ▄▄▄▄▄ 
   █    █   █    █       █   ▀   █  █ █ █ 
   █    █   █    █       █   ▄▀▀▀█  █ █ █ 
   ▀▄▄  ▀▄▄▀█  ▄▄█▄▄     █   ▀▄▄▀█  █ █ █ 
                         █                
                       ▀▀                 
         - A Google Music Player -        
'''  # noqa

CONFIG_DIR = join(expanduser('~'), '.config', 'tuijam')

RATE_UI = {
    0: '－',
    1: '👎',
    5: '👍',
}


def sec_to_min_sec(sec_tot):
    if sec_tot is None:
        return 0, 0
    else:
        min_ = int(sec_tot // 60)
        sec = int(sec_tot % 60)
        return min_, sec


class MusicObject:

    @staticmethod
    def to_ui(*txts, weights=()):
        first, *rest = [(weight, str(txt)) for weight, txt in zip_longest(weights, txts, fillvalue=1)]
        items = [('weight', first[0], urwid.SelectableIcon(first[1], 0))]
        for weight, line in rest:
            items.append(('weight', weight, urwid.Text(line)))
        line = urwid.Columns(items)
        line = urwid.AttrMap(line, 'search normal', 'search select')
        return line

    @staticmethod
    def header_ui(*txts, weights=()):
        header = urwid.Columns([('weight', weight, urwid.Text(('header', txt)))
                                for weight, txt in zip_longest(weights, txts, fillvalue=1)])
        return urwid.AttrMap(header, 'header_bg')


class Song(MusicObject):
    ui_weights = (1, 1, 1, 0.2, 0.2)

    def __init__(self, title, album, albumId, albumArtRef, artist, artistId, id_, type_, trackType, length, rating):
        self.title = title
        self.album = album
        self.albumId = albumId
        self.albumArtRef = albumArtRef
        self.artist = artist
        self.artistId = artistId
        self.id = id_
        self.type = type_
        self.trackType = trackType
        self.length = length
        self.rating = rating
        self.stream_url = ''

    def __repr__(self):
        return f'<Song title:{self.title}, album:{self.album}, artist:{self.artist}>'

    def __str__(self):
        return f'{self.title} by {self.artist}'

    def fmt_str(self):
        return [('np_song', f'{self.title} '), 'by ', ('np_artist', f'{self.artist}')]

    def ui(self):
        return self.to_ui(self.title, self.album, self.artist,
                          '{:02d}:{:02d}'.format(*self.length), RATE_UI[self.rating],
                          weights=self.ui_weights)

    @classmethod
    def header(cls):
        return MusicObject.header_ui('Title', 'Album', 'Artist', 'Length', 'Rating', weights=cls.ui_weights)

    @staticmethod
    def from_dict(d):
        try:
            title = d['title']
            album = d['album']
            albumId = d['albumId']
            albumArtRef = d['albumArtRef'][0]['url']
            artist = d['artist']
            artistId = d['artistId'][0]
            try:
                id_ = d['id']
                type_ = 'library'
            except KeyError:
                id_ = d['storeId']
                type_ = 'store'
            trackType = d.get('trackType', None)
            length = sec_to_min_sec(int(d['durationMillis']) / 1000)
            # rating scheme
            #  0 - No Rating
            #  1 - Thumbs down
            #  5 - Thumbs up
            rating = int(d.get('rating', 0))
            return Song(title, album, albumId, albumArtRef, artist, artistId, id_, type_, trackType, length, rating)
        except KeyError as e:
            logging.exception(f"Missing Key {e} in dict \n{d}")
            return None


class Album(MusicObject):

    def __init__(self, title, artist, artistId, year, id_):
        self.title = title
        self.artist = artist
        self.artistId = artistId
        self.year = year
        self.id = id_

    def __repr__(self):
        return f'<Album title:{self.title}, artist:{self.artist}, year:{self.year}>'

    def ui(self):
        return self.to_ui(self.title, self.artist, self.year)

    @staticmethod
    def header():
        return MusicObject.header_ui('Album', 'Artist', 'Year')

    @staticmethod
    def from_dict(d):
        try:
            try:
                title = d['name']
            except KeyError:
                title = d['title']
            try:
                artist = d['albumArtist']
                artistId = d['artistId'][0]
            except KeyError:
                artist = d['artist_name']
                artistId = d['artist_metajam_id']
            try:
                year = d['year']
            except KeyError:
                year = ''
            try:
                id_ = d['albumId']
            except KeyError:
                id_ = d['id']['metajamCompactKey']
            return Album(title, artist, artistId, year, id_)
        except KeyError as e:
            logging.exception(f"Missing Key {e} in dict \n{d}")
            return None


class Artist(MusicObject):

    def __init__(self, name, id_):
        self.name = name
        self.id = id_

    def __repr__(self):
        return f'<Artist name:{self.name}>'

    def ui(self):
        return self.to_ui(self.name)

    @staticmethod
    def header():
        return MusicObject.header_ui('Artist')

    @staticmethod
    def from_dict(d):
        try:
            name = d['name']
            id_ = d['artistId']
            return Artist(name, id_)
        except KeyError as e:
            logging.exception(f"Missing Key {e} in dict \n{d}")
            return None


class Situation(MusicObject):
    ui_weights = (0.2, 1)

    def __init__(self, title, description, id_, stations):
        self.title = title
        self.description = description
        self.id = id_
        self.stations = stations

    def __repr__(self):
        return f'<Situation title:{self.title}>'

    def ui(self):
        return self.to_ui(self.title, self.description)

    @staticmethod
    def header():
        return MusicObject.header_ui('Situation', 'Description')

    @staticmethod
    def from_dict(d):
        try:
            title = d['title']
            description = d['description']
            id_ = d['id']
            situations = [d]
            stations = []
            while situations:
                situation = situations.pop()
                if 'situations' in situation:
                    situations.extend(situation['situations'])
                else:
                    stations.extend([RadioStation(station['name'], [], id_=station['seed']['curatedStationId'])
                                     for station in situation['stations']])
            return Situation(title, description, id_, stations)
        except KeyError as e:
            logging.exception(f"Missing Key {e} in dict \n{d}")
            return None


class RadioStation(MusicObject):

    def __init__(self, title, seeds, id_=None):
        self.title = title
        self.seeds = seeds
        self.id = id_

    def __repr__(self):
        return f'<RadioStation title:{self.title}>'

    def ui(self):
        return self.to_ui(self.title)

    def get_station_id(self, api):
        if self.id:
            return api.create_station(self.title, curated_station_id=self.id)
        else:
            seed = self.seeds[0]
            return api.create_station(self.title, artist_id=seed['artistId'])

    @staticmethod
    def header():
        return MusicObject.header_ui('Station Name')

    @staticmethod
    def from_dict(d):
        try:
            title = d['title']
            seeds = d['id']['seeds']
            return RadioStation(title, seeds)
        except KeyError as e:
            logging.exception(f"Missing Key {e} in dict \n{d}")
            return None


class Playlist(MusicObject):
    ui_weights = (.2, 1)

    def __init__(self, name, songs=None, id_=None):
        self.name = name
        self.songs = songs
        self.id = id_

    def __repr__(self):
        return f'<Playlist name:{self.name}>'

    def ui(self):
        return self.to_ui(self.name, str(len(self.songs)), weights=self.ui_weights)

    @classmethod
    def header(cls):
        return MusicObject.header_ui('Playlist Name', '# Songs', weights=cls.ui_weights)

    @staticmethod
    def from_dict(d):
        try:
            name = d['name']
            id_ = d['id']
            songs = [Song.from_dict(song['track']) for song in d['tracks'] if 'track' in song]
            if songs:
                return Playlist(name, songs, id_)
            else:
                return None
        except KeyError as e:
            logging.exception(f"Missing Key {e} in dict \n{d}")
            return None


class SearchInput(urwid.Edit):

    def __init__(self, app):
        self.app = app
        super().__init__('search > ', multiline=False, allow_tab=False)

    def keypress(self, size, key):
        if key == 'enter':
            txt = self.edit_text
            if txt:
                self.set_edit_text('')
                self.app.search(txt)
            else:
                self.app.listen_now()
            return None
        else:
            size = (size[0],)
            return super().keypress(size, key)


class SearchPanel(urwid.ListBox):

    def __init__(self, app):
        self.app = app
        self.walker = urwid.SimpleFocusListWalker([])
        self.history = []
        self.search_results = ([], [], [], [], [], [])
        super().__init__(self.walker)

        self.walker.append(urwid.Text(WELCOME, align='center'))

    def keypress(self, size, key):
        if key == 'q':
            selected = self.selected_search_obj()
            if selected:
                if type(selected) == Song:
                    self.app.queue_panel.add_song_to_queue(selected)
                elif type(selected) == Album:
                    self.app.queue_panel.add_album_to_queue(selected)
                elif type(selected) == RadioStation:
                    for song in self.app.get_radio_songs(selected.get_station_id(self.app.g_api)):
                        self.app.queue_panel.add_song_to_queue(song)
                elif type(selected) == Playlist:
                    self.app.queue_panel.add_songs_to_queue(selected.songs)
        elif key in ('e', 'enter'):
            if self.selected_search_obj() is not None:
                self.app.expand(self.selected_search_obj())
        elif key == 'backspace':
            self.back()
        elif key == 'r':
            if self.selected_search_obj() is not None:
                self.app.create_radio_station(self.selected_search_obj())
        elif key == 'j':
            super().keypress(size, 'down')
        elif key == 'k':
            super().keypress(size, 'up')
        else:
            super().keypress(size, key)

    def back(self):
        if self.history:
            prev_focus, history = self.history.pop()
            self.set_search_results(*history)
            try:
                self.set_focus(prev_focus)
            except IndexError:
                pass

    def update_search_results(self, songs, albums, artists, situations, radio_stations, playlists):
        self.history.append((self.get_focus()[1], self.search_results))
        self.set_search_results(songs, albums, artists, situations, radio_stations, playlists)

    def set_search_results(self, songs, albums, artists, situations, radio_stations, playlists):
        songs = [obj for obj in songs if obj is not None]
        albums = [obj for obj in albums if obj is not None]
        artists = [obj for obj in artists if obj is not None]
        situations = [obj for obj in situations if obj is not None]
        radio_stations = [obj for obj in radio_stations if obj is not None]
        playlists = [obj for obj in playlists if obj is not None]
        self.search_results = (songs, albums, artists, situations, radio_stations, playlists)

        self.walker.clear()

        for group in [artists, albums, songs, situations, radio_stations, playlists]:
            if group:
                self.walker.append(type(group[0]).header())
            for item in group:
                self.walker.append(item.ui())

        if self.walker:
            self.walker.set_focus(1)

    def selected_search_obj(self):
        focus_id = self.walker.get_focus()[1]
        songs, albums, artists, situations, radio_stations, playlists = self.search_results

        try:
            for group in [artists, albums, songs, situations, radio_stations, playlists]:
                if group:
                    focus_id -= 1
                    if focus_id < len(group):
                        return group[focus_id]
                    focus_id -= len(group)
        except (IndexError, TypeError):
            return None


class PlayBar(urwid.ProgressBar):
    vol_inds = [' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']

    def __init__(self, app, *args, **kwargs):
        super(PlayBar, self).__init__(*args, **kwargs)
        self.app = app

    def get_prog_tot(self):
        curr_time_s = self.app.player.time_pos
        rem_time_s = self.app.player.time_remaining
        if curr_time_s is None or rem_time_s is None:
            return 0, 0
        else:
            progress = int(curr_time_s)
            total = int(curr_time_s + rem_time_s)
            return progress, total

    def get_text(self):
        if self.app.current_song is None:
            return 'Idle'
        progress, total = self.get_prog_tot()
        rating = ''
        if self.app.current_song.rating in (1, 5):
            rating = '('+RATE_UI[self.app.current_song.rating]+')'
        return ' {} {} - {} {}[{:02d}:{:02d} / {:02d}:{:02d}] {}'.format(
            '▶' if self.app.play_state == 'play' else '■',
            self.app.current_song.artist,
            self.app.current_song.title,
            rating,
            progress // 60,
            progress % 60,
            total // 60,
            total % 60,
            self.vol_inds[self.app.volume]
        )

    def update(self):
        self._invalidate()

        progress, total = self.get_prog_tot()
        if progress >= 0 and total > 0:
            percent = progress / total * 100
            self.set_completion(percent)
        else:
            self.set_completion(0)


class QueuePanel(urwid.ListBox):

    def __init__(self, app):
        self.app = app
        self.walker = urwid.SimpleFocusListWalker([])
        self.queue = []
        super().__init__(self.walker)

    def add_song_to_queue(self, song):
        if song:
            self.queue.append(song)
            self.walker.append(song.ui())

    def add_songs_to_queue(self, songs):
        for song in songs:
            self.add_song_to_queue(song)

    def add_album_to_queue(self, album):
        album_info = self.app.g_api.get_album_info(album.id)

        for track in album_info['tracks']:
            song = Song.from_dict(track)
            if song:
                self.queue.append(song)
                self.walker.append(song.ui())

    def drop(self, idx):
        if 0 <= idx < len(self.queue):
            self.queue.pop(idx)
            self.walker.pop(idx)

    def clear(self):
        self.queue.clear()
        self.walker.clear()

    def swap(self, idx1, idx2):
        if (0 <= idx1 < len(self.queue)) and (0 <= idx2 < len(self.queue)):
            obj1, obj2 = self.queue[idx1], self.queue[idx2]
            self.queue[idx1], self.queue[idx2] = obj2, obj1

            ui1, ui2 = self.walker[idx1], self.walker[idx2]
            self.walker[idx1], self.walker[idx2] = ui2, ui1

    def shuffle(self):
        from random import shuffle as rshuffle
        rshuffle(self.queue)
        self.walker.clear()
        for s in self.queue:
            self.walker.append(s.ui())

    def play_next(self):
        if self.walker:
            self.walker.pop(0)
            self.app.play(self.queue.pop(0))
        else:
            self.app.stop()

    def selected_queue_obj(self):
        try:
            focus_id = self.walker.get_focus()[1]
            return self.queue[focus_id]
        except (IndexError, TypeError):
            return None

    def keypress(self, size, key):
        focus_id = self.walker.get_focus()[1]
        if focus_id is None:
            return super().keypress(size, key)

        if key == 'u':
            self.swap(focus_id, focus_id-1)
            self.keypress(size, 'up')
        elif key == 'd':
            self.swap(focus_id, focus_id+1)
            self.keypress(size, 'down')
        elif key == 'delete':
            self.drop(focus_id)
        elif key == 'j':
            super().keypress(size, 'down')
        elif key == 'k':
            super().keypress(size, 'up')
        elif key == 'e':
            self.app.expand(self.selected_queue_obj())
        elif key == ' ':
            if self.app.play_state == 'stop':
                self.play_next()
            else:
                self.app.toggle_play()
        else:
            return super().keypress(size, key)


class App(urwid.Pile):

    palette = [
        ('header', 'white,underline', 'black'),
        ('header_bg', 'white', 'black'),
        ('line', 'white', ''),
        ('search normal', 'white', ''),
        ('search select', '', '', '', 'white', '#D32'),

        ('region_bg normal', '', ''),
        ('region_bg select', '', 'black'),

        ('progress', '', '', '', '#FFF', '#F54'),
        ('progress_remaining', '', '', '', '#FFF', '#444'),
    ]

    def __init__(self):

        import mpv
        self.player = mpv.MPV()
        self.player.volume = 100
        self.volume = 8
        self.g_api = None
        self.loop = None
        self.config_pw = None

        @self.player.event_callback('end_file')
        def callback(event):
            if event['event']['reason'] == 0:
                self.queue_panel.play_next()

        self.search_panel = SearchPanel(self)
        search_panel_wrapped = urwid.LineBox(self.search_panel, title='Search Results')
        search_panel_wrapped = urwid.AttrMap(search_panel_wrapped, 'region_bg normal', 'region_bg select')
        self.search_panel_wrapped = search_panel_wrapped

        self.playbar = PlayBar(self, 'progress_remaining', 'progress', current=0, done=100)

        self.queue_panel = QueuePanel(self)
        queue_panel_wrapped = urwid.LineBox(self.queue_panel, title='Queue')
        queue_panel_wrapped = urwid.AttrMap(queue_panel_wrapped, 'region_bg normal', 'region_bg select')
        self.queue_panel_wrapped = queue_panel_wrapped

        self.search_input = urwid.Edit('> ', multiline=False)
        self.search_input = SearchInput(self)

        urwid.Pile.__init__(self, [('weight', 12, search_panel_wrapped),
                                   ('pack', self.playbar),
                                   ('weight', 7, queue_panel_wrapped),
                                   ('pack', self.search_input)
                                   ])
        self.set_focus(self.search_input)

        self.play_state = 'stop'
        self.current_song = None
        self.history = []

    def login(self):
        self.g_api = gmusicapi.Mobileclient(debug_logging=False)

        credentials = self.read_config()
        if credentials is None:
            return False
        else:
            return self.g_api.login(*credentials)

    def read_config(self):
        config_file = join(CONFIG_DIR, 'config.yaml')
        if not isfile(config_file):
            if not self.first_time_setup():
                return None

        email, password, device_id = None, None, None
        with open(config_file) as f:
            config = yaml.load(f.read())
            if config['encrypted']:
                from scrypt import decrypt
                try:
                    config_pw = self.config_pw  # From first_time_setup
                except AttributeError:
                    config_pw = getpass("Enter tuijam config pw: ")
                try:
                    email = decrypt(config['email'], config_pw, maxtime=20)
                    password = decrypt(config['password'], config_pw, maxtime=20)
                    device_id = decrypt(config['device_id'], config_pw, maxtime=20)
                except Exception as e:
                    print(e)
                    print("Could not decrypt config file.")
                    exit(1)
            else:
                email = config['email']
                password = config['password']
                device_id = config['device_id']
            self.mpris_enabled = config.get('mpris_enabled', True)
        return email, password, device_id

    def first_time_setup(self):
        print("Need to perform first time setup")
        while True:
            email = input("Enter gmusic email (empty to quit): ")
            if not email:
                return False
            pw = getpass("Enter gmusic password: ")

            d_id = self.get_device_id(email, pw)

            if d_id is not None:
                break  # Success!
            print(('Login failed, verify your email and password.\n'
                   'Remember, you need an app-password if you have 2FA enabled.'))

        print("Enter a password to encrypt/decrypt the generated config file.")
        print("You will need to enter this each time you start tuijam.")
        print("If you forget this, delete the config file and start tuijam.")
        print("Leave blank if no encryption is desired.")
        self.config_pw = getpass("password: ")

        print()
        self.write_config(email, pw, d_id, self.config_pw)
        return True

    @staticmethod
    def write_config(email, password, device_id, config_pw):
        use_encryption = len(config_pw) > 0
        if use_encryption:
            from scrypt import encrypt
            data = dict(
                encrypted=True,
                email=encrypt(email, config_pw, maxtime=0.5),
                password=encrypt(password, config_pw, maxtime=0.5),
                device_id=encrypt(device_id, config_pw, maxtime=0.5)
            )
        else:
            data = dict(
                encrypted=False,
                email=email,
                password=password,
                device_id=device_id
            )
        data['mpris_enabled'] = True
        config_file = join(CONFIG_DIR, 'config.yaml')
        with open(config_file, "w") as outfile:
            yaml.dump(data, outfile, default_flow_style=False)

    def get_device_id(self, email, password):
        if not self.g_api.login(email, password, self.g_api.FROM_MAC_ADDRESS):
            return None

        ids = [d['id'][2:] if d['id'].startswith('0x') else d['id'].replace(':', '')
               for d in self.g_api.get_registered_devices()]
        self.g_api.logout()
        try:
            return ids[0]
        except IndexError:
            print("No device ids found. This shouldn't happen...")
            return None

    def refresh(self, *args, **kwargs):
        if self.play_state == 'play' and self.player.eof_reached:
            self.queue_panel.play_next()
        self.playbar.update()
        self.loop.draw_screen()
        if self.play_state == 'play':
            self.schedule_refresh()

    def schedule_refresh(self, dt=0.5):
        self.loop.set_alarm_in(dt, self.refresh)

    def play(self, song):
        self.current_song = song
        self.current_song.stream_url = self.g_api.get_stream_url(song.id)
        self.player.play(self.current_song.stream_url)
        self.player.pause = False
        self.play_state = 'play'
        self.playbar.update()
        self.history.append(song)
        self.schedule_refresh()

    def stop(self):
        self.player.pause = True
        self.player.seek(0, reference='absolute')
        self.play_state = 'stop'
        self.playbar.update()

    def seek(self, dt):
        try:
            self.player.seek(dt)
        except SystemError:
            pass

    def toggle_play(self):
        if self.play_state == 'play':
            self.player.pause = True
            self.play_state = 'pause'
            self.playbar.update()
        elif (self.play_state == 'pause' or (self.play_state == 'stop' and self.current_song is not None)):
            self.player.pause = False
            self.play_state = 'play'
            self.playbar.update()
            self.schedule_refresh()
        elif self.play_state == 'stop':
            self.queue_panel.play_next()

    def volume_down(self):
        self.volume = max([0, self.volume-1])
        self.player.volume = int(self.volume * 100 / 8)
        self.playbar.update()

    def volume_up(self):
        self.volume = min([8, self.volume+1])
        self.player.volume = int(self.volume * 100 / 8)
        self.playbar.update()

    def keypress(self, size, key):
        if key == 'tab':
            current_focus = self.focus
            if current_focus == self.search_panel_wrapped:
                self.set_focus(self.queue_panel_wrapped)
            elif current_focus == self.queue_panel_wrapped:
                self.set_focus(self.search_input)
            else:
                self.set_focus(self.search_panel_wrapped)
        elif key == 'shift tab':
            current_focus = self.focus
            if current_focus == self.search_panel_wrapped:
                self.set_focus(self.search_input)
            elif current_focus == self.queue_panel_wrapped:
                self.set_focus(self.search_panel_wrapped)
            else:
                self.set_focus(self.queue_panel_wrapped)
        elif key == 'ctrl p':
            self.toggle_play()
        elif key == 'ctrl q':
            self.stop()
        elif key == 'ctrl n':
            self.queue_panel.play_next()
        elif key == 'ctrl r':
            self.search_panel.update_search_results(self.history, [], [], [], [], [])
        elif key == 'ctrl s':
            self.queue_panel.shuffle()
        elif key == 'ctrl u':
            self.rate_current_song(5)
        elif key == 'ctrl d':
            self.rate_current_song(1)
        elif self.focus != self.search_input:
            if key == '>':
                self.seek(10)
            elif key == '<':
                self.seek(-10)
            elif key in '-_':
                self.volume_down()
            elif key in '+=':
                self.volume_up()
            else:
                return self.focus.keypress(size, key)
        else:
            return self.focus.keypress(size, key)

    def expand(self, obj):
        if obj is None:
            return

        songs, albums, artists, situations, radio_stations, playlists = [[]]*6
        if type(obj) == Song:
            album_info = self.g_api.get_album_info(obj.albumId)

            songs = [Song.from_dict(track) for track in album_info['tracks']]
            albums = [Album.from_dict(album_info)]
            artists = [Artist(obj.artist, obj.artistId)]
        elif type(obj) == Album:
            album_info = self.g_api.get_album_info(obj.id)

            songs = [Song.from_dict(track) for track in album_info['tracks']]
            albums = [obj]
            artists = [Artist(obj.artist, obj.artistId)]
        elif type(obj) == Artist:
            artist_info = self.g_api.get_artist_info(obj.id)

            songs = [Song.from_dict(track) for track in artist_info['topTracks']]
            albums = [Album.from_dict(album) for album in artist_info.get('albums', [])]
            artists = [Artist.from_dict(artist) for artist in artist_info['related_artists']]
            artists.insert(0, obj)
        elif type(obj) == Situation:
            radio_stations = obj.stations
            situations = [obj]
        elif type(obj) == RadioStation:
            station_id = obj.get_station_id(self.g_api)
            songs = self.get_radio_songs(station_id)
            radio_stations = [obj]
        elif type(obj) == Playlist:
            songs = obj.songs
            playlists = [obj]
        self.search_panel.update_search_results(songs, albums, artists, situations, radio_stations, playlists)

    def search(self, query):
        results = self.g_api.search(query)

        songs = [Song.from_dict(hit['track']) for hit in results['song_hits']]
        albums = [Album.from_dict(hit['album']) for hit in results['album_hits']]
        artists = [Artist.from_dict(hit['artist']) for hit in results['artist_hits']]

        self.search_panel.update_search_results(songs, albums, artists, [], [], [])
        self.set_focus(self.search_panel_wrapped)

    def listen_now(self):
        situations = self.g_api.get_listen_now_situations()
        items = self.g_api.get_listen_now_items()
        playlists = self.g_api.get_all_user_playlist_contents()

        situations = [Situation.from_dict(hit) for hit in situations]
        albums = [Album.from_dict(hit['album']) for hit in items if 'album' in hit]
        radio_stations = [RadioStation.from_dict(hit['radio_station']) for hit in items if 'radio_station' in hit]
        playlists = [Playlist.from_dict(playlist) for playlist in playlists]

        self.search_panel.update_search_results([], albums, [], situations, radio_stations, playlists)
        self.set_focus(self.search_panel_wrapped)

    def create_radio_station(self, obj):
        if type(obj) == Song:
            station_id = self.g_api.create_station(obj.title, track_id=obj.id)
        elif type(obj) == Album:
            station_id = self.g_api.create_station(obj.title, album_id=obj.id)
        elif type(obj) == Artist:
            station_id = self.g_api.create_station(obj.name, artist_id=obj.id)
        elif type(obj) == RadioStation:
            station_id = obj.get_station_id(self.g_api)
        else:
            return
        for song in self.get_radio_songs(station_id):
            self.queue_panel.add_song_to_queue(song)

    def get_radio_songs(self, station_id, n=50):
        song_dicts = self.g_api.get_station_tracks(station_id, num_tracks=n)
        return [Song.from_dict(song_dict) for song_dict in song_dicts]

    def rate_current_song(self, rating):
        if self.current_song is None:
            return
        self.current_song.rating = rating
        track = {}
        if self.current_song.type == 'library':
            track['id'] = self.current_song.id
        else:
            track['nid'] = self.current_song.id
            track['trackType'] = self.current_song.trackType
        self.g_api.rate_songs(track, rating)
        self.playbar.update()
        self.loop.draw_screen()

    def cleanup(self, *args, **kwargs):
        self.player.quit()
        del self.player
        self.g_api.logout()
        self.loop.stop()
        sys.exit()

    def setup_mpris(self):
        from pydbus import SessionBus, Variant
        from pydbus.generic import signal

        class MPRIS:
            """
<node>
  <interface name="org.mpris.MediaPlayer2">
    <property name="CanQuit" type="b" access="read" />
    <property name="CanRaise" type="b" access="read" />
    <property name="HasTrackList" type="b" access="read" />
    <property name="Identity" type="s" access="read" />
    <property name="SupportedMimeTypes" type="as" access="read" />
    <property name="SupportedUriSchemes" type="as" access="read" />
    <method name="Quit" />
    <method name="Raise" />
  </interface>
  <interface name="org.mpris.MediaPlayer2.Player">
    <property name="PlaybackStatus" type="s" access="read" />
    <property name="Rate" type="d" access="readwrite" />
    <property name="Metadata" type="a{sv}" access="read" />
    <property name="Volume" type="d" access="readwrite" />
    <property name="Position" type="x" access="read" />
    <property name="MinimumRate" type="d" access="readwrite" />
    <property name="MaximumRate" type="d" access="readwrite" />
    <property name="CanGoNext" type="b" access="read" />

    <property name="CanGoPrevious" type="b" access="read" />
    <property name="CanPlay" type="b" access="read" />
    <property name="CanPause" type="b" access="read" />
    <property name="CanSeek" type="b" access="read" />
    <property name="CanControl" type="b" access="read" />

    <method name="Next" />
    <method name="Previous" />
    <method name="Pause" />
    <method name="PlayPause" />
    <method name="Stop" />
    <method name="Play" />
    <method name="Seek">
      <arg type="x" direction="in" />
    </method>
    <method name="SetPosition">
      <arg type="o" direction="in" />
      <arg type="x" direction="in" />
    </method>
    <method name="OpenUri">
      <arg type="s" direction="in" />
    </method>
  </interface>
</node>
            """
            #  TODO: Decide which properties should get wired up to this signal
            PropertiesChanged = signal()

            def __init__(self, app):
                self.app = app

            @property
            def CanQuit(self):
                return False

            @property
            def CanRaise(self):
                return False

            @property
            def HasTrackList(self):
                return False

            @property
            def Identity(self):
                return "TUIJam"

            @property
            def SupportedMimeTypes(self):
                return []

            @property
            def SupportedUriSchemes(self):
                return []

            def Raise(self):
                pass

            def Quit(self):
                pass

            @property
            def PlaybackStatus(self):
                return {
                    'play': 'Playing',
                    'pause': 'Paused',
                    'stop': 'Stopped'
                }[self.app.play_state]

            @property
            def Rate(self):
                return 1.0

            @Rate.setter
            def Rate(self, rate):
                pass

            @property
            def Metadata(self):
                song = self.app.current_song
                if song is not None:
                    return {
                        'mpris:trackid': Variant('o', '/org/tuijam/'+str(song.id)),
                        'mpris:artUrl': Variant('s', song.albumArtRef),
                        'xesam:title': Variant('s', song.title),
                        'xesam:artist': Variant('as', [song.artist]),
                        'xesam:album': Variant('s', song.album),
                        'xesam:url': Variant('s', song.stream_url)
                    }
                else:
                    return {}

            @property
            def Volume(self):
                return self.app.volume / 8.0

            @Volume.setter
            def Volume(self, volume):
                volume = max(0, min(volume, 1))
                self.app.volume = int(volume*8)
                self.app.player.volume = volume * 100
                self.PropertiesChanged("org.mpris.MediaPlayer2.tuijam", {"Volume": volume}, [])

            @property
            def Position(self):
                try:
                    return int(1000000 * self.app.player.time_pos)
                except TypeError:
                    return 0

            @property
            def MinimumRate(self):
                return 1.0

            @property
            def MaximumRate(self):
                return 1.0

            @property
            def CanGoNext(self):
                return len(self.app.queue_panel.queue) > 0

            @property
            def CanGoPrevious(self):
                return False

            @property
            def CanPlay(self):
                return (len(self.app.queue_panel.queue) > 0 or self.app.current_song is not None)

            @property
            def CanPause(self):
                return self.app.current_song is not None

            @property
            def CanSeek(self):
                return self.app.current_song is not None

            @property
            def CanControl(self):
                return True

            def Next(self):
                self.app.queue_panel.play_next()

            def Previous(self):
                pass

            def Pause(self):
                if self.app.play_state == 'play':
                    self.app.toggle_play()

            def PlayPause(self):
                self.app.toggle_play()

            def Stop(self):
                self.app.stop()

            def Play(self, song_id):
                self.app.toggle_play()

            def Seek(self, offset):
                pass

            def SetPosition(self, track_id, position):
                pass

            def OpenUri(self, uri):
                pass

        self.mpris = MPRIS(self)
        bus = SessionBus()
        bus = bus.publish('org.mpris.MediaPlayer2.tuijam',
                          ('/org/mpris/MediaPlayer2', self.mpris))


def main(debug=False):
    print("starting up.")
    makedirs(CONFIG_DIR, exist_ok=True)
    log_file = join(CONFIG_DIR, 'log.txt')
    logging.basicConfig(filename=log_file, filemode='w', level=logging.WARNING)
    app = App()
    print("logging in.")
    if not app.login():
        return

    if app.mpris_enabled:
        print("Enabling external control.")
        app.setup_mpris()

    if not debug:

        import signal
        signal.signal(signal.SIGINT, app.cleanup)

        loop = urwid.MainLoop(app, palette=app.palette,
                              event_loop=urwid.GLibEventLoop())
        app.loop = loop
        loop.screen.set_terminal_properties(256)
        try:
            loop.run()
        except Exception as e:
            logging.exception(e)
            print("Something bad happened! :( see log file ($HOME/.config/tuijam/log.txt) for more information.")
            app.cleanup()
    return app


if __name__ == '__main__':
    debug = sys.argv[-1] == 'debug'
    app = main(debug=debug)
