from bard.musicdatabase import MusicDatabase, DatabaseEnum, table
from collections import namedtuple
from sqlalchemy import text, insert, select, and_, desc
from bard.config import config
from bard.utils import alignColumns
import os.path

MBDataTuple = namedtuple('MBDataTuple',
                         ['artistids', 'albumartistids', 'workids',
                          'releasegroupid', 'releaseid', 'releasetrackid',
                          'recordingid', 'confirmed'])

FullSongsWebQuery = namedtuple('FullSongsWebQuery',
                               ['columns', 'tables', 'where', 'order_by',
                                'values', 'limit', 'offset'])
FullSongsWebQuery.__new__.__defaults__ = ('', '', '', '', {}, None, None)

MediumFormatEnum = DatabaseEnum('medium_format', schema='musicbrainz')
ReleaseStatusEnum = DatabaseEnum('release_status', schema='musicbrainz')
LanguageEnum = DatabaseEnum('language', schema='musicbrainz')
ReleaseGroupTypeEnum = DatabaseEnum('release_group_type', schema='musicbrainz')

# https://picard.musicbrainz.org/docs/mappings/
convert_tag = {
    'releasestatus':
        ['TXXX:MusicBrainz Album Status',
         'RELEASESTATUS',
         'MUSICBRAINZ_ALBUMSTATUS',
         '----:com.apple.iTunes:MusicBrainz Album Status',
         'MusicBrainz/Album Status'],
    'releasecountry':
        ['TXXX:MusicBrainz Album Release Country',
         'RELEASECOUNTRY',
         '----:com.apple.iTunes:MusicBrainz Album Release Country',
         'MusicBrainz/Album Release Country'],
    'releasetype':
        ['TXXX:MusicBrainz Album Type',
         'RELEASETYPE',
         'musicbrainz_albumtype',
         '----:com.apple.iTunes:MusicBrainz Album Type',
         'MusicBrainz/Album Type'],
    'musicbrainz_artistid':
        ['TXXX:MusicBrainz Artist Id',
         'musicbrainz_artistid',
         '----:com.apple.iTunes:MusicBrainz Artist Id',
         'MusicBrainz/Artist Id'],
    'musicbrainz_albumartistid':
        ['TXXX:MusicBrainz Album Artist Id',
         'musicbrainz_albumartistid',
         '----:com.apple.iTunes:MusicBrainz Album Artist Id',
         'MusicBrainz/Album Artist Id'],
    'musicbrainz_releasegroupid':
        ['TXXX:MusicBrainz Release Group Id',
         'musicbrainz_releasegroupid',
         '----:com.apple.iTunes:MusicBrainz Release Group Id',
         'MusicBrainz/Release Group Id'],
    'musicbrainz_releasetrackid':
        ['TXXX:MusicBrainz Release Track Id',
         'musicbrainz_releasetrackid',
         '----:com.apple.iTunes:MusicBrainz Release Track Id',
         'MusicBrainz/Release Track Id'],
    'musicbrainz_releaseid':
        ['TXXX:MusicBrainz Album Id',
         'musicbrainz_albumid',
         '----:com.apple.iTunes:MusicBrainz Album Id',
         'MusicBrainz/Album Id'],
    'musicbrainz_recordingid':
        ['UFID:http://musicbrainz.org',
         'TXXX:musicbrainz_trackid',
         'musicbrainz_trackid',
         '----:com.apple.iTunes:MusicBrainz Track Id',
         'MusicBrainz/Track Id'],
    'musicbrainz_workid':
        ['TXXX:MusicBrainz Work Id',
         'TXXX:musicbrainz_workid',
         'musicbrainz_workid',
         '----:com.apple.iTunes:MusicBrainz Work Id',
         'MusicBrainz/Work Id']
}


def lowercase_dict_keys(dictionary):
    return {x.lower(): y for x, y in dictionary.items()}


def decode_bytes(func):
    def wrapper(*args, **kwargs):
        r = func(*args, **kwargs)

        if isinstance(r, bytes):
            return r.decode('utf-8')

        if isinstance(r, list):
            return [x.decode('utf-8')
                    if isinstance(x, bytes) else x for x in r]

        return r
    return wrapper


@decode_bytes
def getList(tags, metadata_tags):
    for tag_name in metadata_tags:
        try:
            value = tags[tag_name]
        except KeyError:
            continue
        return value
    # No perfect match. Let's try ignoring case
    lc_tags = lowercase_dict_keys(tags)
    for tag_name in metadata_tags:
        try:
            value = lc_tags[tag_name.lower()]
        except KeyError:
            continue
        return value

    return None


def getValue(tags, metadata_tags):
    value = getList(tags, metadata_tags)
    if not value:
        return None
    if len(value) > 1:
        raise ValueError
    return value[0]


def getSongMusicBrainzIDs(songid, tags=None):
    if not tags:
        tags = MusicDatabase.getSongTags(songid)

    artistids = getList(tags, convert_tag['musicbrainz_artistid'])
    albumartistids = getList(tags, convert_tag['musicbrainz_albumartistid'])
    workids = getList(tags, convert_tag['musicbrainz_workid'])

    releasegroupid = getValue(tags, convert_tag['musicbrainz_releasegroupid'])
    releaseid = getValue(tags, convert_tag['musicbrainz_releaseid'])
    releasetrackid = getValue(tags, convert_tag['musicbrainz_releasetrackid'])
    recordingid = getValue(tags, convert_tag['musicbrainz_recordingid'])

    try:
        confirmed = (tags.get('musicbrainzverified', [None])[0] == '1' or
                     tags.get('uselabel', [None])[0] == '2' or
                     tags.get('usebarcode', [None])[0] == '1')
    except KeyError:
        confirmed = False

    r = MBDataTuple(artistids, albumartistids, workids, releasegroupid,
                    releaseid, releasetrackid, recordingid, confirmed)
    return r


class MusicBrainzDatabase:
    @staticmethod
    def songTags(songIDs=None):
        c = MusicDatabase.getCursor()
        if songIDs:
            sql = text('SELECT song_id, name, value FROM tags '
                       'WHERE song_id IN :id_list '
                       'ORDER BY song_id, pos')
            result = c.execute(sql, {'id_list': tuple(songIDs)})
        else:
            sql = text('SELECT song_id, name, value FROM tags '
                       'ORDER BY song_id, pos')
            result = c.execute(sql)
        tags = {}
        row = result.fetchone()
        current_song_id = None
        while row:
            if row.song_id != current_song_id:
                if current_song_id:
                    yield current_song_id, tags
                    tags = {}
                current_song_id = row.song_id
            if row.name not in tags:
                tags[row.name] = [row.value]
            else:
                tags[row.name] += [row.value]

            row = result.fetchone()
        if current_song_id:
            yield current_song_id, tags

    @staticmethod
    def insertMBArtistIDs(song_id, artistIDs):
        if not artistIDs:
            return
        songs_mb_artistids = table('songs_mb_artistids')

        s = select([songs_mb_artistids.c.artistid]) \
            .where(songs_mb_artistids.c.song_id == song_id)

        result = MusicDatabase.execute(s).fetchall()

        if set(artistIDs) == set(x['artistid'] for x in result):
            return

        d = songs_mb_artistids.delete() \
                              .where(songs_mb_artistids.c.song_id == song_id)
        MusicDatabase.execute(d)
        for artistID in artistIDs:
            i = insert(songs_mb_artistids).values(song_id=song_id,
                                                  artistid=artistID)
            MusicDatabase.execute(i)

    @staticmethod
    def insertMBAlbumArtistIDs(song_id, albumArtistIDs):
        if not albumArtistIDs:
            return
        songs_mb_albumartistids = table('songs_mb_albumartistids')

        s = select([songs_mb_albumartistids.c.albumartistid]) \
            .where(songs_mb_albumartistids.c.song_id == song_id)

        result = MusicDatabase.execute(s).fetchall()

        if set(albumArtistIDs) == set(x['albumartistid'] for x in result):
            return

        d = songs_mb_albumartistids.delete() \
            .where(songs_mb_albumartistids.c.song_id == song_id)
        MusicDatabase.execute(d)
        for artistID in albumArtistIDs:
            i = insert(songs_mb_albumartistids).values(song_id=song_id,
                                                       albumartistid=artistID)
            MusicDatabase.execute(i)

    @staticmethod
    def insertMBWorkIDs(song_id, workIDs):
        if not workIDs:
            return
        songs_mb_workids = table('songs_mb_workids')

        s = select([songs_mb_workids.c.workid]) \
            .where(songs_mb_workids.c.song_id == song_id)

        result = MusicDatabase.execute(s).fetchall()

        if set(workIDs) == set(x['workid'] for x in result):
            return

        d = songs_mb_workids.delete() \
                            .where(songs_mb_workids.c.song_id == song_id)
        MusicDatabase.execute(d)
        for workID in workIDs:
            i = insert(songs_mb_workids).values(song_id=song_id,
                                                workid=workID)
            MusicDatabase.execute(i)

    @staticmethod
    def insertMusicBrainzTags(song_id, mbIDs):
        MusicBrainzDatabase.insertMBArtistIDs(song_id, mbIDs.artistids)
        MusicBrainzDatabase.insertMBAlbumArtistIDs(song_id,
                                                   mbIDs.albumartistids)
        MusicBrainzDatabase.insertMBWorkIDs(song_id, mbIDs.workids)
        songs_mb = table('songs_mb')
        mbTagRecord = songs_mb.select(songs_mb.c.song_id == song_id)
        mbTagRecord = MusicDatabase.execute(mbTagRecord).fetchone()
        if mbTagRecord:
            if (mbTagRecord['releasegroupid'] != mbIDs.releasegroupid or
                mbTagRecord['releaseid'] != mbIDs.releaseid or
                mbTagRecord['releasetrackid'] != mbIDs.releasetrackid or
                mbTagRecord['recordingid'] != mbIDs.recordingid or
                    mbTagRecord['confirmed'] != mbIDs.confirmed):
                print(f'update mb data for {song_id}')
                u = songs_mb.update() \
                            .where(songs_mb.c.song_id == song_id) \
                            .values(song_id=song_id,
                                    releasegroupid=mbIDs.releasegroupid,
                                    releaseid=mbIDs.releaseid,
                                    releasetrackid=mbIDs.releasetrackid,
                                    recordingid=mbIDs.recordingid,
                                    confirmed=mbIDs.confirmed)
                MusicDatabase.execute(u)
        else:
            print(f'insert mb data for {song_id}')
            i = songs_mb.insert().values(song_id=song_id,
                                         releasegroupid=mbIDs.releasegroupid,
                                         releaseid=mbIDs.releaseid,
                                         releasetrackid=mbIDs.releasetrackid,
                                         recordingid=mbIDs.recordingid,
                                         confirmed=mbIDs.confirmed)
            MusicDatabase.execute(i)

    @staticmethod
    def songsWithoutMBData():
        c = MusicDatabase.getCursor()
        sql = text('SELECT id FROM songs '
                   'WHERE NOT EXISTS (SELECT song_id '
                   '                    FROM songs_mb '
                   '                   WHERE song_id = id) '
                   'ORDER BY id')
        result = c.execute(sql)
        return [x[0] for x in result.fetchall()]

    @staticmethod
    def updateMusicBrainzIDs(songIDs=None):
        if not songIDs:
            return
        for song_id, tags in MusicBrainzDatabase.songTags(songIDs):
            mbIDs = getSongMusicBrainzIDs(song_id, tags)
            if any(mbIDs):
                MusicBrainzDatabase.insertMusicBrainzTags(song_id, mbIDs)
                MusicDatabase.commit()

    @staticmethod
    def checkMusicBrainzTags():
        c = MusicDatabase.getCursor()
        sql = text('SELECT id, path FROM songs '
                   'WHERE root = :root '
                   '  AND NOT EXISTS (SELECT song_id '
                   '                   FROM songs_mb '
                   '                  WHERE recordingid is not NULL '
                   '                    AND song_id = id)'
                   '     ORDER BY id')
        table = []
        for root in config['musicbrainzTaggedMusicPaths']:
            result = c.execute(sql, {'root': root})
            table.extend((str(song_id), path)
                         for song_id, path in result.fetchall())
        if table:
            table.insert(0, ('SONGID', 'PATH'))
            aligned = alignColumns(table, (False, True))
            print('Songs which should have musicbrainz tags but don\'t:')
            for line in aligned:
                print(line)
        return bool(table)

    @staticmethod
    def checkAlbumsWithDifferentReleases():
        c = MusicDatabase.getCursor()
        sql = text('SELECT album_id, path, '
                   '       COUNT(DISTINCT musicbrainz.release.id) '
                   '  FROM songs_mb, albums, album_songs, musicbrainz.release '
                   ' WHERE albums.id = album_songs.album_id '
                   '   AND releaseid = mbid '
                   '   AND songs_mb.song_id = album_songs.song_id '
                   ' GROUP BY album_songs.album_id, albums.path '
                   ' HAVING COUNT(DISTINCT musicbrainz.release.id) > 1')

        result = c.execute(sql)
        table = [(str(album_id), path, str(count))
                 for album_id, path, count in result.fetchall()]
        if table:
            table.insert(0, ('ALBUMID', 'PATH', 'NUMBER OF RELEASES'))
            aligned = alignColumns(table, (False, True, False))
            print('Albums that contain songs from different releases:')
            for line in aligned:
                print(line)
        return bool(table)

    @staticmethod
    def checkAlbumsWithDifferentFormats():
        c = MusicDatabase.getCursor()
        sql = text('select id, path, format '
                   '  from albums, album_properties '
                   ' where id in (select album_id '
                   '                from (select  album_id, count(*) '
                   '                        from album_properties '
                   '                    group by album_id '
                   '                      having count(*)>1) '
                   '                  as foo) '
                   '   and id = album_id')

        result = c.execute(sql)
        table = [(str(album_id), path, audioFormat)
                 for album_id, path, audioFormat in result.fetchall()]
        if table:
            table.insert(0, ('ALBUMID', 'PATH', 'FORMAT'))
            aligned = alignColumns(table, (False, True, True))
            print('Albums that contain songs with different formats:')
            for line in aligned:
                print(line)
        return bool(table)

    @staticmethod
    def get_all_artists():
        """Return all artists (used by the mb importer)."""
        songs_mb_artistids = table('songs_mb_artistids')

        s = select([songs_mb_artistids.c.artistid]).distinct()

        result_artists = MusicDatabase.execute(s).fetchall()
        print(len(result_artists))
        s1 = set(x['artistid'] for x in result_artists)

        songs_mb_albumartistids = table('songs_mb_albumartistids')

        s = select([songs_mb_albumartistids.c.albumartistid]).distinct()

        result_albumartists = MusicDatabase.execute(s).fetchall()
        print(len(result_albumartists))
        r = s1.union(x['albumartistid'] for x in result_albumartists)
        print('artists', len(r))
        return r

    @staticmethod
    def get_all_elements_from_songs_mb(column=None):
        if not column:
            return []

        songs_mb = table('songs_mb')

        s = select([getattr(songs_mb.c, column)]).distinct()

        result = MusicDatabase.execute(s).fetchall()
        r = set(x[column] for x in result)
        r.difference_update({None})

        return r

    @staticmethod
    def get_all_recordings():
        return MusicBrainzDatabase.get_all_elements_from_songs_mb(
            'recordingid')

    @staticmethod
    def get_all_releasegroups():
        return MusicBrainzDatabase.get_all_elements_from_songs_mb(
            'releasegroupid')

    @staticmethod
    def get_all_releases():
        return MusicBrainzDatabase.get_all_elements_from_songs_mb(
            'releaseid')

    @staticmethod
    def get_all_tracks():
        return MusicBrainzDatabase.get_all_elements_from_songs_mb(
            'releasetrackid')

    @staticmethod
    def get_all_works():
        songs_mb_workids = table('songs_mb_workids')

        s = select([songs_mb_workids.c.workid]).distinct()

        result = MusicDatabase.execute(s).fetchall()
        print('works', len(result))
        r = set(x['workid'] for x in result)
        return r

    @staticmethod
    def get_range_artists(offset=0, page_size=500, metadata=False):
        artist = table('musicbrainz.artist')
        artists_mb = table('artists_mb')
        s = (select([artist.c.id, artist.c.mbid, artist.c.name,
                    artist.c.artist_type, artist.c.area_id, artist.c.gender,
                    artist.c.disambiguation,
                    artists_mb.c.locale_name, artists_mb.c.locale_sort_name,
                    artists_mb.c.image_path])
             .where(artist.c.id == artists_mb.c.id)
             .order_by(artists_mb.c.locale_name)
             .limit(page_size)
             .offset(offset))
        return MusicDatabase.execute(s).fetchall()

    @staticmethod
    def is_better_alias(a, b):
        """Return True if a is a better alias than b."""
        if a and not b:
            return True
        if a['locale'] == 'es':
            if b['locale'] != 'es':
                return True

            if a['primary_for_locale']:
                return True

        if a['locale'] == 'en':
            if b['locale'] not in ('es', 'en'):
                return True

            if b['locale'] == 'en' and a['primary_for_locale']:
                return True

        return False

    @staticmethod
    def cacheMusicBrainzDB():
        artist = table('musicbrainz.artist')
        aa = table('musicbrainz.artist_alias')
        s = select([artist.c.id, artist.c.mbid, artist.c.name,
                    artist.c.sort_name, artist.c.artist_type, artist.c.area_id,
                    artist.c.gender, artist.c.disambiguation])
        locales = ['es', 'en']
        c = MusicDatabase.getCursor()
        for a in MusicDatabase.execute(s).fetchall():
            s2 = (select([aa.c.name, aa.c.sort_name, aa.c.locale,
                         aa.c.artist_alias_type, aa.c.primary_for_locale])
                  .where(and_(aa.c.artist_id == a['id'],
                              aa.c.locale.in_(locales))))
            current = {}
            for x in MusicDatabase.execute(s2).fetchall():
                if MusicBrainzDatabase.is_better_alias(x, current):
                    current = {'locale_name': x['name'],
                               'locale_sort_name': x['sort_name'],
                               'locale': x['locale'],
                               'artist_alias_type': x['artist_alias_type']}
            if not current:
                current = {'locale_name': a['name'],
                           'locale_sort_name': a['sort_name'],
                           'locale': None,
                           'artist_alias_type': None}

            current['id'] = a['id']
            MusicDatabase.insert_or_update('artists_mb', current, connection=c)
        MusicDatabase.commit()

    @staticmethod
    def get_letter_offset_for_artist(letter):
        if letter == '0':
            return 0
        c = MusicDatabase.getCursor()
        sql = ('select min(subq.offset) from ('
               '       select row_number() over(order by locale_name)'
               '                           as offset,'
               '              locale_name'
               '         from artists_mb) as subq'
               '  where subq.locale_name ilike :search')
        result = c.execute(sql, {'search': letter + '%'})
        return result.fetchone()[0] - 1

    @staticmethod
    def get_artist_image_path(artistID):
        c = MusicDatabase.getCursor()
        sql = 'select image_path from artists_mb where id = :artistid'
        result = c.execute(sql, {'artistid': artistID})
        return result.fetchone()[0]

    @staticmethod
    def get_artist_info(artistID):
        artist = table('musicbrainz.artist')
        artists_mb = table('artists_mb')
        s = (select([artist.c.id, artist.c.mbid, artist.c.name,
                    artist.c.artist_type, artist.c.area_id, artist.c.gender,
                    artist.c.disambiguation,
                    artists_mb.c.locale_name, artists_mb.c.locale_sort_name,
                    artists_mb.c.image_path])
             .where(and_(artist.c.id == artists_mb.c.id,
                         artist.c.id == artistID)))
        return MusicDatabase.execute(s).fetchone()

    @staticmethod
    def get_artists_info(artistIDs):
        artist = table('musicbrainz.artist')
        artists_mb = table('artists_mb')
        s = (select([artist.c.id, artist.c.mbid, artist.c.name,
                    artist.c.artist_type, artist.c.area_id, artist.c.gender,
                    artist.c.disambiguation,
                    artists_mb.c.locale_name, artists_mb.c.locale_sort_name,
                    artists_mb.c.image_path])
             .where(and_(artist.c.id == artists_mb.c.id,
                         artist.c.id.in_(artistIDs))))
        return MusicDatabase.execute(s).fetchall()

    @staticmethod
    def get_artist_aliases(artistID, locales=None, only_primary=False):
        alias = table('musicbrainz.artist_alias')
        s = (select([alias.c.name, alias.c.sort_name, alias.c.locale,
                     alias.c.artist_alias_type, alias.c.primary_for_locale])
             .where(alias.c.artist_id == artistID)
             .order_by(alias.c.locale)
             .order_by(desc(alias.c.primary_for_locale)))
        # query is ordered by locale and inside each locale, the primary
        # is returned first
        if locales:
            s = s.where(alias.c.locale.in_(locales))
        if only_primary:
            s = s.where(alias.c.primary_for_locale.is_(True))
        return MusicDatabase.execute(s).fetchall()

    @staticmethod
    def get_artist_release_groups(artistID):
        c = MusicDatabase.getCursor()
        sql = ('select rg.id, mbid, rg.name, disambiguation, '
               'rgt.name as release_group_type, rg.artist_credit_id, '
               'ac.name as artist_credit_name'
               ' from musicbrainz.release_group as rg, '
               '      musicbrainz.artist_credit as ac, '
               '      musicbrainz.artist_credit_name as acn, '
               '      musicbrainz.enum_release_group_type_values as rgt '
               'where rg.artist_credit_id = ac.id '
               '  and rg.artist_credit_id = acn.artist_credit_id '
               '  and rg.release_group_type = rgt.id_value '
               '  and acn.artist_id = :artistID')
        result = c.execute(sql, {'artistID': artistID})
        return result.fetchall()

    @staticmethod
    def get_release_group_directories(rgMBID):
        c = MusicDatabase.getCursor()
        sql = ('select path '
               '  from songs '
               ' where id in (select song_id '
               '               from songs_mb '
               '              where releasegroupid=:rgMBID)')
        result = c.execute(sql, {'rgMBID': rgMBID})
        return set(os.path.dirname(path) for (path,) in result.fetchall())

    @staticmethod
    def get_link_type_id(name, entity_type0, entity_type1):
        if not getattr(MusicBrainzDatabase, 'link_types', None):
            c = MusicDatabase.getCursor()
            sql = ('select id, name, entity_type0, entity_type1 '
                   'from musicbrainz.link_type')
            r = c.execute(sql)
            MusicBrainzDatabase.link_types = \
                {(lt_name, lt_entity_t0, lt_entity_t1): lt_id
                 for lt_id, lt_name, lt_entity_t0, lt_entity_t1
                 in r.fetchall()}

        return MusicBrainzDatabase.link_types[(name,
                                               entity_type0, entity_type1)]

    @staticmethod
    def get_links(entity_type0, entity_type1, lt_id, entity_positions, entity):
        c = MusicDatabase.getCursor()
        if 0 in entity_positions:
            if 1 in entity_positions:
                clause = '(l.entity0=:entity or l.entity1=:entity)'
            else:
                clause = 'l_entity0=:entity'
        else:
            clause = 'l_entity1=:entity'

        rel_table = f'l_{entity_type0}_{entity_type1}'

        sql = ('select link.id link_id, entity0, entity1, link_order, '
               '        entity0_credit, entity1_credit, '
               '        begin_date_year, begin_date_month, begin_date_day,'
               '        end_date_year, end_date_month, end_date_day'
               f'   from musicbrainz.{rel_table} l, '
               '        musicbrainz.link link '
               '  where link.id = l.link_id '
               '    and link.link_type_id=:lt_id '
               f'    and {clause}')
        r = c.execute(sql, {'lt_id': lt_id, 'entity': entity})
        return r.fetchall()

    @staticmethod
    def get_related_entities(relation, entity, entity_type):
        lt_id = MusicBrainzDatabase.get_link_type_id(*relation)
        entity_positions = [i for i in (0, 1)
                            if relation[i + 1] == entity_type]

        r = MusicBrainzDatabase.get_links(relation[1], relation[2], lt_id,
                                          entity_positions, entity)
        return r

    @staticmethod
    def get_link_attributes(link_id):
        c = MusicDatabase.getCursor()
        sql = ('select id, name '
               '   from musicbrainz.link_attribute la, '
               '        musicbrainz.link_attribute_type lat '
               '  where la.link_id = :link_id '
               '    and la.link_attribute_type_id = lat.id ')
        r = c.execute(sql, {'link_id': link_id})
        return r.fetchall()

    @staticmethod
    def get_artist_members_of_band_relations(artistID):
        relation = ('member of band', 'artist', 'artist')
        r = MusicBrainzDatabase.get_related_entities(relation, artistID,
                                                     'artist')
        result1 = []
        result2 = []
        ids = []
        for x in r:
            if x['entity0'] != artistID:
                ids.append(x['entity0'])
            if x['entity1'] != artistID:
                ids.append(x['entity1'])

        artists = {x['id']: dict(x)
                   for x in MusicBrainzDatabase.get_artists_info(ids)}
        print(artists)

        for x in r:
            begin_date = (x['begin_date_year'],
                          x['begin_date_month'],
                          x['begin_date_day'])
            end_date = (x['end_date_year'],
                        x['end_date_month'],
                        x['end_date_day'])
            attrs = MusicBrainzDatabase.get_link_attributes(x['link_id'])
            if x['entity0'] != artistID:
                result1.append((artists[x['entity0']], begin_date, end_date,
                                [x['name'] for x in attrs] if attrs else None))
                print('###', x)
            if x['entity1'] != artistID:
                result2.append((artists[x['entity1']], begin_date, end_date,
                                [x['name'] for x in attrs] if attrs else None))
                print('   ', x)

        print('->', result1)
        print('<-', result2)
        return (result1, result2)

    @staticmethod
    def get_release_group_info(rgID):
        rg = table('musicbrainz.release_group')
        ac = table('musicbrainz.artist_credit')
        s = (select([rg.c.id, rg.c.mbid, rg.c.name,
                    rg.c.disambiguation,
                    rg.c.release_group_type, rg.c.artist_credit_id,
                    ac.c.name.label('artist_name')])
             .where(and_(rg.c.artist_credit_id == ac.c.id,
                         rg.c.id == rgID)))
        return MusicDatabase.execute(s).fetchone()

    @staticmethod
    def get_release_group_secondary_types(rgID):
        c = MusicDatabase.getCursor()
        sql = ('select name '
               '  from musicbrainz.release_group_secondary_type_join, '
               '       musicbrainz.enum_release_group_secondary_type_values '
               ' where release_group_id = :rgID '
               '   and secondary_type = id_value')
        r = c.execute(text(sql), {'rgID': rgID})
        return [x[0] for x in r.fetchall()]

    @staticmethod
    def get_release_group_releases(rgID):
        c = MusicDatabase.getCursor()
        sql = text('select album_id, r.id, mbid, r.name, disambiguation, '
                   '       release_status, language, barcode, '
                   '       artist_credit_id, ac.name, '
                   '       r.release_group_id '
                   '  from musicbrainz.release r, '
                   '       musicbrainz.artist_credit ac, '
                   '       album_release ar'
                   ' where ar.release_id = r.id '
                   '   and r.artist_credit_id = ac.id '
                   '   and r.release_group_id = :rgID ')
        r = c.execute(sql, {'rgID': rgID})
        return r.fetchall()

    @staticmethod
    def get_release_group_album_count(rgID):
        c = MusicDatabase.getCursor()
        sql = text('select count(*) '
                   '  from album_release ar, musicbrainz.release r '
                   ' where ar.release_id = r.id '
                   '   and r.release_group_id = :rgID')
        r = c.execute(sql, {'rgID': rgID}).fetchone()
        if not r or not r[0]:
            return 0
        return r[0]

    @staticmethod
    def get_release_group_albums(rgID):
        c = MusicDatabase.getCursor()
        sql = text('select ar.album_id'
                   '  from album_release ar, musicbrainz.release r '
                   ' where ar.release_id = r.id '
                   '   and r.release_group_id = :rgID')
        r = c.execute(sql, {'rgID': rgID}).fetchall()
        return [x[0] for x in r]

    @staticmethod
    def get_release_mediums(releaseID):
        m = table('musicbrainz.medium')
        emfv = table('musicbrainz.enum_medium_format_values')
        s = (select([m.c.id, m.c.release_id, m.c.position,
                     emfv.c.name.label('format_name'),
                     m.c.name])
             .where(and_(m.c.format == emfv.c.id_value,
                         m.c.release_id == releaseID))
             .order_by(m.c.position))
        return MusicDatabase.execute(s).fetchall()

    @staticmethod
    def mediumlist_to_string(mediumlist):
        r = []
        format_name = None
        num = 0
        for medium in mediumlist:
            if medium['format_name'] != format_name:
                if num != 0:
                    r.append(f'{num}x{format_name}')
                num = 0
                format_name = medium['format_name']
            num += 1

        if num != 0:
            r.append(f'{num}x{format_name}')

        return '+'.join(r)

    @staticmethod
    def get_release_directories(releaseMBID):
        c = MusicDatabase.getCursor()
        sql = ('select path '
               '  from songs '
               ' where id in (select song_id '
               '               from songs_mb '
               '              where releaseid=:releaseMBID)')
        result = c.execute(text(sql), {'releaseMBID': releaseMBID})
        return set(os.path.dirname(path) for (path,) in result.fetchall())

    @staticmethod
    def get_release_label(releaseID):
        rl = table('musicbrainz.release_label')
        label = table('musicbrainz.label')
        s = (select([label.c.name.label('label_name'), rl.c.catalog_number])
             .where(and_(rl.c.label_id == label.c.id,
                         rl.c.release_id == releaseID)))
        return MusicDatabase.execute(s).fetchall()

    @staticmethod
    def getAlbumDisambiguation(release):
        c = MusicDatabase.getCursor()
        sql = text('select name, value '
                   '  from tags, album_songs '
                   ' where tags.song_id = album_songs.song_id '
                   '   and album_songs.album_id = :albumID '
                   '   and name IN (\'comment\',\'usereleasecomment\','
                   '                \'uselabel\') '
                   ' group by name, value')
        r = c.execute(sql, {'albumID': release['album_id']})
        album = {x['name']: x['value'] for x in r.fetchall()}
        print(release, album)
        try:
            usereleasecomment = int(album['usereleasecomment'])
        except KeyError:
            usereleasecomment = 1

        try:
            uselabel = int(album['uselabel'])
        except KeyError:
            uselabel = 0

        result = []
        if usereleasecomment == 2:
            result.append(album['comment'])
        elif usereleasecomment == 1:
            if release['disambiguation']:
                result.append(release['disambiguation'])
        elif usereleasecomment == 3:
            rg = MusicBrainzDatabase.get_release_group_info(
                release['release_group_id'])
            if rg['disambiguation']:
                result.append(rg['disambiguation'])

        if uselabel > 0:
            release_label = \
                MusicBrainzDatabase.get_release_label(release['id'])[0]
            if uselabel == 1:
                result.append(release_label['label_name'])
            else:
                print(release_label)
                print(release_label['label_name'])
                print(release_label['catalog_number'])
                result.append(release_label['label_name'] + ':' +
                              release_label['catalog_number'])

        return ','.join(result)

    @staticmethod
    def get_album_info(albumID):
        c = MusicDatabase.getCursor()
        sql = text('select album_id, r.id release_id, mbid release_mbid, '
                   '       r.name, disambiguation, '
                   '       release_status, language, barcode, '
                   '       artist_credit_id, ac.name artist_credit_name, '
                   '       r.release_group_id '
                   '  from musicbrainz.release r, '
                   '       musicbrainz.artist_credit ac, '
                   '       album_release ar'
                   ' where ar.release_id = r.id '
                   '   and r.artist_credit_id = ac.id '
                   '   and ar.album_id = :albumID ')
        result = c.execute(sql, {'albumID': albumID})
        return result.fetchone()

    @staticmethod
    def get_release_events(releaseID):
        c = MusicDatabase.getCursor()
        sql = text('select a.name country, date_year, date_month, date_day '
                   '  from musicbrainz.release_country rc, '
                   '       musicbrainz.area a '
                   ' where rc.release_id = :releaseID '
                   '   and rc.country_id = a.id')
        result = c.execute(sql, {'releaseID': releaseID})
        r = result.fetchall()
        sql = text('select NULL country, date_year, date_month, date_day '
                   '  from musicbrainz.release_unknown_country rc '
                   ' where rc.release_id = :releaseID')
        result = c.execute(sql, {'releaseID': releaseID})
        r.extend(result.fetchall())
        return r

    @staticmethod
    def get_album_tracks(albumID):
        c = MusicDatabase.getCursor()
        sql = ('select ar.album_id, '
               '       m.position as medium_number,'
               '       m.format as medium_format_id,'
               # '       emfv.name as medium_format, '
               '       m.name as medium_name, '
               '       t.position as track_position, t.mbid as track_mbid, '
               '       t.recording_id, t.number_text , t.name, '
               '       ac.name as artist_name, t.artist_credit_id, '
               '       t.is_data_track, t.length/1000 as duration'
               '  from album_release ar, musicbrainz.medium m, '
               # '       musicbrainz.enum_medium_format_values emfv, '
               '       musicbrainz.track t, musicbrainz.artist_credit ac '
               ' where ar.release_id = m.release_id '
               '   and m.id = t.medium_id '
               # '   and m.format = emfv.id_value '
               '   and ar.album_id = :albumID '
               '   and t.artist_credit_id = ac.id '
               ' order by m.position, t.position')
        result = c.execute(text(sql), {'albumID': albumID})
        return result.fetchall()

    @staticmethod
    def get_album_songs(albumID):
        c = MusicDatabase.getCursor()
        sql = ('select songs_mb.song_id, '
               '       songs_mb.releasetrackid '
               '  from album_songs, songs_mb '
               ' where album_songs.song_id = songs_mb.song_id '
               '   and album_songs.album_id = :albumID ')
        result = c.execute(text(sql), {'albumID': albumID})
        return result.fetchall()

    @staticmethod
    def get_songs_information_for_webui(*, songIDs=None,
                                        query=FullSongsWebQuery()):
        if songIDs:
            query = FullSongsWebQuery(where=['als.song_id in :songIDs'],
                                      order_by=['als.song_id'],
                                      values={'songIDs': tuple(songIDs)})

        c = MusicDatabase.getCursor()
        cq = (',' + ','.join(query.columns)) if query.columns else ''
        ct = (',' + ','.join(query.tables)) if query.tables else ''
        cw = (' AND ' + ' AND '.join(query.where)) if query.where else ''
        co = ('ORDER BY ' + ','.join(query.order_by)) if query.order_by else ''
        climit = f'LIMIT {query.limit}' if query.limit else ''
        coffset = f'OFFSET {query.offset}' if query.offset else ''
        sql = ('select als.album_id, als.song_id, '
               '       m.position as medium_number, '
               '       m.format as medium_format_id, '
               # '       emfv.name as medium_format, '
               '       r.name as release_name, '
               '       m.name as medium_name, '
               '       t.position as track_position, t.mbid as track_mbid, '
               '       t.recording_id, t.number_text , t.name, '
               '       ac.name as artist_name, t.artist_credit_id, '
               '       t.is_data_track, p.duration, p.format, p.bitrate, '
               f'       p.bits_per_sample, p.sample_rate, p.channels {cq}'
               '  from album_songs als, album_release ar, '
               '       musicbrainz.medium m, '
               '       musicbrainz.release r, '
               # '       musicbrainz.enum_medium_format_values emfv, '
               '       musicbrainz.track t, '
               '       musicbrainz.artist_credit ac, '
               f'       songs_mb smb, properties p {ct}'
               ' where ar.release_id = m.release_id '
               '   and ar.release_id = r.id'
               '   and m.id = t.medium_id '
               # '   and m.format = emfv.id_value '
               '   and ar.album_id = als.album_id '
               '   and t.artist_credit_id = ac.id '
               '   and smb.song_id = als.song_id '
               '   and p.song_id = als.song_id '
               '   and smb.releasetrackid = t.mbid '
               f'  {cw} {co} {climit} {coffset}')
        result = c.execute(text(sql), query.values)
        return result.fetchall()

    @staticmethod
    def get_playlist_songs_information_for_webui(playlistID):
        query = (FullSongsWebQuery(
                 columns=['pl.pos as position'],
                 tables=['playlist_songs pl'],
                 where=['pl.playlist_id = :playlistID',
                        'als.song_id = pl.song_id'],
                 order_by=['pl.pos'],
                 values={'playlistID': playlistID}))
        return MusicBrainzDatabase.get_songs_information_for_webui(query=query)

    @staticmethod
    def get_album_songs_information_for_webui(albumID):
        query = (FullSongsWebQuery(
                 columns=[],
                 tables=[],
                 where=['ar.album_id = :albumID'],
                 order_by=['m.position', 't.position'],
                 values={'albumID': albumID}))
        return MusicBrainzDatabase.get_songs_information_for_webui(query=query)

    @staticmethod
    def get_artist_credit_info(artistCreditID):
        c = MusicDatabase.getCursor()
        sql = ('select artist_id, position, name, join_phrase'
               '  from musicbrainz.artist_credit_name '
               ' where artist_credit_id = :artistCreditID '
               '  order by position')
        result = c.execute(text(sql), {'artistCreditID': artistCreditID})
        return result.fetchall()

    @staticmethod
    def search_songs_for_webui(query, offset=None, page_size=200):
        query = (FullSongsWebQuery(
                 tables=['songs s'],
                 where=['s.path ilike :query',
                        'als.song_id = s.id'],
                 order_by=['als.album_id', 'm.position', 't.position'],
                 values={'query': '%' + query + '%'},
                 limit=page_size,
                 offset=offset,
                 ))
        return MusicBrainzDatabase.get_songs_information_for_webui(query=query)

    @staticmethod
    def get_release_group_date(releaseGroupID):
        c = MusicDatabase.getCursor()
        sql = ('select min(date_year)'
               '  from musicbrainz.release_country rc,'
               '       musicbrainz.release r'
               ' WHERE r.id = rc.release_id '
               '   AND r.release_group_id = :releaseGroupID ')
        r = c.execute(text(sql), {'releaseGroupID': releaseGroupID}).fetchone()
        if r and r[0]:
            return r[0]

        sql = ('select min(s.date)'
               '  from songs s, album_songs aso, album_release ar, '
               '       musicbrainz.release r'
               ' WHERE r.release_group_id = :releaseGroupID '
               '   AND ar.release_id = r.id '
               '   AND aso.album_id = ar.album_id '
               '   AND s.id = aso.song_id ')
        r = c.execute(text(sql), {'releaseGroupID': releaseGroupID}).fetchone()
        if not r or not r[0]:
            return None
        return r[0]

    @staticmethod
    def get_release_group_ratings(release_group_id, user_id):
        album_ids = MusicBrainzDatabase.get_release_group_albums(
            release_group_id)
        ratings = MusicDatabase.get_albums_ratings(album_ids, user_id)
        if any(x[1] == 'user' for x in ratings.values()):
            return (max(x[0] for x in ratings.values() if x[1] == 'user'),
                    'user')
        kind = 'avg' if any(x[1] == 'avg' for x in ratings.values()) else None
        return (sum(x[0] for x in ratings.values()) / len(ratings), kind)