#!/usr/bin/env python3
import os
import yaml
import difflib
import random
from time import time, ctime, sleep, mktime, localtime
from sys import argv, stderr
#from requests.utils import requote_uri
from latest_user_agents import get_random_user_agent
import json
from mpd import MPDClient
import pycurl
import magic
import unicodedata
import discogs_client


from io import BytesIO
try:
    import pylast
    lastfm = True
except ImportError:
    lastfm = False

if __name__ == '__main__':
################################################################################
# use your config file to change these
    config_dir = '~/.config/mpd_what'
# if coverart_dir, mpd_host, and mpd_port are set in the config, the config
# values supercede the following:
    coverart_dir = '~/Pictures/coverart'                                                    #
    lastplayed = '{}/{}'.format(config_dir, 'last')
    playlist = '{}/{}'.format(config_dir, 'playlist.txt')
    errlog = '{}/{}'.format(config_dir, 'log')
    mpd_host="localhost"
    mpd_port="6600"
    discogs_token = ""
    lastfm_user = ""
    lastfm_pass = ""
    lastfm_api_key = ""
    lastfm_api_secret = ""
    showinfo=True
    dl=False
    scrobble=False
################################################################################
# This part is necessary for python to understand what '~' means.
    config_dir = os.path.expanduser(config_dir)
    coverart_dir = os.path.expanduser(coverart_dir)
    lastplayed = os.path.expanduser(lastplayed)
    playlist = os.path.expanduser(playlist)
    errlog = os.path.expanduser(errlog)
################################################################################
    config = '{}/{}'.format(config_dir, 'config.yml')
    if os.path.exists(config):
        with open(config, 'r') as ymlfile:
            try:
                cfg = yaml.load(ymlfile, Loader=yaml.BaseLoader)
                mpd_host = cfg['mpd_host']
                mpd_port = cfg['mpd_port']
                coverart_dir = cfg['coverart_dir']
                discogs_token = cfg['discogs_token']
                lastfm_user = cfg['lastfm_user']
                lastfm_pass = cfg['lastfm_pass']
                lastfm_api_key = cfg['lastfm_api_key']
                lastfm_api_secret = cfg['lastfm_api_secret']
                librefm_user = cfg['librefm_user']
                librefm_pass = cfg['librefm_pass']
            except KeyError:
                pass
            except (yaml.YAMLError, yaml.scanner.ScannerError) as e:
                print("Error parsing your config file. Maybe try rm -r ~/.config/mpd_what and start over.", file=stderr)
                with open (errlog, 'a') as logfile:
                    epoch = time()
                    when = ctime(epoch)
                    logfile.write(str(when) + ': ' + 'bad YAML in your config: ' + str(e) + '\n')
    else:
        os.makedirs(config_dir, exist_ok=True)
        config_dict={'coverart_dir':coverart_dir, 'mpd_host':mpd_host,
                'mpd_port':mpd_port, 'discogs_token':'please_sign_up_at_discogs_and_generate_a_token'}
        with open(config, 'w+') as config_file:
            config_file.write(yaml.dump(config_dict, default_flow_style=False))

    global useragent
    useragent = get_random_user_agent()
    global coverart
    
    def help_msg(me):
        print("usage:\n" +
              "\n{} -sc  or --scrobble to try to scrobble music to last.fm or libre.fm".format(me) +
              "\n{} -g  or --get to download album art".format(me) +
              "\n{} -q  or --quiet to not print info".format(me) +
              "\narguments can be composed, eg:" +
              "\n\n{} -sc -g -q".format(me))

    def init():
        if os.path.exists(coverart_dir):
            pass
        else:
            try:
            #python3 niceness: with exist_ok, python will go ahead an make directories
            #recursively, without complaining that any of the dirs don't exist.
                os.makedirs(coverart_dir, exist_ok=True)
            except OSError as e:
                epoch = time()
                when = ctime(epoch)
                with open (errlog, 'a') as logfile:
                    print("Error writing to your coverart dir. Ensure you have permissions to write there.", file=stderr)
                    logfile.write(str(when) + ': ' + ' in init() ' + str(e) + '\n')
                #print("There was a problem creating coverart_dir. Make sure you have the correct permissions, etc.")
        #fi 

        mpc = MPDClient()
        mpc.connect(mpd_host, mpd_port)
        state = mpc.status()['state']

        if state == "stop":
            exit(0)
        #fi

        what = mpc.currentsong()
        mpc.close()
        mpc.disconnect()
        return(what)

    def remove_accents(input_str):
        nfkd_form = unicodedata.normalize('NFKD', input_str)
        only_ascii = nfkd_form.encode('ASCII', 'ignore')
        return only_ascii

    def prepare_query(stuff):
        q = [''.join(i) for i in stuff]
        s = ''
        for i in q:
            s = s + i
        query = s.replace(' ',
                        '+').replace('/',
                        '+').replace('&',
                        '').replace('++',
                        '+').replace('++',
                        '+')
        query.encode('utf-8')
        query = remove_accents(query)
        query = query.decode()
        return(query)
    
    def get_url(url,datatype):
        #if url == 'musicbrainz':
        #    data = coverart
        #else:
        #life, uh, finds a way
        buffer = BytesIO()
        c = pycurl.Curl()
        c.setopt(c.FOLLOWLOCATION, 1)
        c.setopt(c.USERAGENT, useragent)
        c.setopt(c.URL, url)
        c.setopt(c.WRITEDATA, buffer)
        try:
            c.perform()
            c.close()
        except Exception as e:
            epoch = time()
            when = ctime(epoch)
            with open (errlog, 'a') as logfile:
                logfile.write(str(when) + ': ' + ' in get_url() ' + str(e) + '\n')
            #print("error in geturl: " + e)
            return False
        data = buffer.getvalue()
        buffer.close()
        if datatype != '':
            data = data.decode(datatype, errors="ignore")
        #fi
        return(data)
    
    def is_image(f):
        ret = 'image' in magic.from_file(f, mime=True)
        return ret
    
    def get_coverart(f, url):
        try:
            if coverart_dir in f:
                fname = f
            else:
                fname = '{}/{}'.format(coverart_dir,f)
            disp = '{}/{}'.format(coverart_dir,'cover.jpg')
            if os.path.isfile(fname) and is_image(fname):
                if os.path.isfile(disp) and os.readlink(disp) == fname:
                    return True
                else:
                    if os.path.isfile(disp):
                        os.remove(disp)
                    os.symlink(fname, disp)
                    return True
                #fi
            else:
                #somehow this coverart file isn't an image.
                if os.path.isfile(fname):
                    os.remove(fname)
                if url != '':
                    try:
                        data = get_url(url, '')
                        coverart = open(fname, 'wb')
                        coverart.write(data)
                        coverart.close()
                        if is_image(fname):
                            if os.path.isfile(disp):
                                os.remove(disp)
                            #fi
                            os.symlink(fname, disp)
                            return True
                        else:
                            return False
                    except Exception as e:
                        epoch = time()
                        when = ctime(epoch)
                        with open (errlog, 'a') as logfile:
                            logfile.write(str(when) + ': ' + ' in get_coverart() ' + str(e) + '\n')
                        #print("error getting file " + str(e))
                        return False
            #fi
        except OSError as e:
            epoch = time()
            when = ctime(epoch)
            with open (errlog, 'a') as logfile:
                logfile.write(str(when) + ': ' + ' in get_coverart() ' + str(e) + '\n')
            #print("error in get_coverart: " + str(e))
            return False
    
    def get_station_info(station,dl):
        artist = ""
        album = ""
        song = ""
        coverart = ""
        try:
            station = station.lower()
            if station == 'source':
                #some internet radio stations have the weirdest
                #habits. I'm looking at you, xray.
                mpd_info = init()
                station = mpd_info['file']
            if 'kexp' in station:
                url = "https://legacy-api.kexp.org/play/?format=json&limit=1&ordering=-airdate"
                onair = get_url(url, "utf-8")
                if str(json.loads(onair)["results"][0]["playtype"]["name"]) == '"Air break"':
                    artist = "KEXP"
                    song = "Air Break"
                else:
                    artist = str(json.loads(onair)["results"][0]["artist"]["name"])
                    album = str(json.loads(onair)["results"][0]["release"]["name"])
                    song = str(json.loads(onair)["results"][0]["track"]["name"])
                #fi
            elif 'kusf' in station:
                url = "http://www.kusf.org/api/broadcasting"
                onair = get_url(url, "utf-8")
                artist = str(json.loads(onair)["Track"]["artist"]).replace('"', '')
                album = str(json.loads(onair)["Track"]["album"]).replace('"', '')
                song = str(json.loads(onair)["Track"]["title"]).replace('"', '')
                if album == " ":
                    album = song
                    song = ""
                #fi
            elif 'kcrw' in station:
                if 'kcrw live' in station:
                    channel = "Simulcast"
                elif 'e24' in station:
                    channel = "Music"
                #fi
                url = 'http://tracklist-api.kcrw.com/{}/all/1?page_size=1&callback=$.KCRW.refresh_tracklist'.format(channel)
                onair = get_url(url, "utf-8")
                #the kcrw "json" is not valid json until
                #the first and last line are removed :-/
                onair = '\n'.join(onair.splitlines()[1:-1])
                artist = str(json.loads(onair)["artist"])
                album = str(json.loads(onair)["album"])
                song = str(json.loads(onair)["title"])
                if dl:
                    coverart_url = str(json.loads(onair)["albumImageLarge"])
                    fname = '+'.join((artist,album)).replace(' ', '+')
                    get_coverart(fname, coverart_url)
                #fi
            elif 'mountain chill' in station:
                url='https://api.live365.com/v1/station/b58063'
                onair = get_url(url, "utf-8")
                artist = str(json.loads(onair)["current-track"]["artist"])
                song = str(json.loads(onair)["current-track"]["title"].replace("';StreamURL='",''))
            elif 'wfmu' in station:
                url='https://wfmu.org/wp-content/themes/wfmu-theme/library/php/includes/liveNow.php'
                onair = get_url(url, "utf-8")
                artist = str(json.loads(onair)["artist"]).replace('"', '')
                song = str(json.loads(onair)["song"]).replace('"', '')
                album = str(json.loads(onair)["album"]).replace('"', '')
                if dl:
                    coverart_url = str(json.loads(onair)["image"]["url"])
                    fname = '+'.join((artist,album)).replace(' ', '+')
                    get_coverart(fname, coverart_url)
            elif 'somafm' in station:
                if 'sonic universe' in station:
                    url="https://somafm.com/songs/sonicuniverse.json"
                elif 'suburbs of goa' in station:
                    url="https://somafm.com/songs/suburbsofgoa.json"
                elif 'digitalis' in station:
                    url="https://somafm.com/songs/digitalis.json"
                elif 'live' in station:
                    url="https://somafm.com/songs/live.json"
                elif 'underground eighties' in station:
                    url="https://somafm.com/songs/u80s.json"
                elif 'thistle' in station:
                    url="https://somafm.com/songs/thistle.json"
                elif 'fluid' in station:
                    url="https://somafm.com/songs/fluid.json"
                elif 'poptron' in station:
                    url="https://somafm.com/songs/poptron.json"
                elif 'bagel radio' in station:
                    url="https://somafm.com/songs/bagel.json"
                elif 'beat blender' in station:
                    url="https://somafm.com/songs/beatblender.json"
                elif 'groove salad' in station:
                    url="https://somafm.com/songs/groovesalad.json"
                elif 'indie pop rocks' in station:
                    url="https://somafm.com/songs/indiepoprocks.json"
                elif 'lush' in station:
                    url="https://somafm.com/songs/lush.json"
                elif 'boot liquor' in station:
                    url="https://somafm.com/songs/bootliquor.json"
                elif 'folk forward' in station:
                    url="https://somafm.com/songs/folkfwd.json"
                elif 'ill street' in station:
                    url="https://somafm.com/songs/illstreet.json"
                elif 'seven inch soul' in station:
                    url="https://somafm.com/songs/7soul.json"
                elif 'left coast 70s' in station:
                    url="https://somafm.com/songs/seventies.json"
                #fi
                onair = get_url(url, "utf-8")
                artist = str(json.loads(onair)['songs'][0]['artist']).replace('"', '')
                album = str(json.loads(onair)['songs'][0]['album']).replace('"', '')
                song = str(json.loads(onair)['songs'][0]['title']).replace('"', '')
            elif 'wwoz' in station:
                epoch=mktime(localtime())
                url="https://www.wwoz.org/api/schedule/episode/one/at/{}".format(epoch)
                onair = get_url(url, "utf-8")
                artist = "WWOZ"
                album = json.loads(onair)["showhost"]["name"]
                #if str(album) == 'None':
                #    album=''
                song = str(json.loads(onair)["title"])
            elif 'nts live' in station:
                url = "https://www.nts.live/api/v2/live"
                onair = get_url(url, "utf-8")
                artist = str(json.loads(onair)["results"][0]["now"]["broadcast_title"]).replace('"', '')
                album = "NTS"
                if dl:
                    coverart_url = str(json.loads(onair)["results"][0]["now"]["embeds"]["details"]["media"]["background_medium"]).replace('"', '')
                    fname = '+'.join((artist,album)).replace(' ', '+')
                    get_coverart(fname, coverart_url)
                #fi
            elif 'kutx' in station:
                url = "https://api.composer.nprstations.org/v1/widget/50ef24ebe1c8a1369593d032/now?format=json"
                onair = get_url(url, "utf-8")
                artist = str(json.loads(onair)["onNow"]["song"]["artistName"]).replace('"', '')
                album = str(json.loads(onair)["onNow"]["song"]["collectionName"]).replace('"', '')
                song = str(json.loads(onair)["onNow"]["song"]["trackName"]).replace('"', '')
                if artist == album == song == null:
                    album = str(json.loads(onair)["onNow"]["program"]["name"]).replace('"', '')
                if dl:
                    coverart_url = json.loads(onair)["onNow"]["song"]["artworkUrl100"].replace('"', '').replace('100x100', '300x300')
                    fname = '+'.join((artist,album)).replace(' ', '+')
                    get_coverart(fname, coverart_url)
                #fi
            elif 'xray' in station:
                url="https://xray.fm/api/tracks/current"
                onair = get_url(url, "utf-8")
                artist = str(json.loads(onair)["artist"])
                album = str(json.loads(onair)["album"])
                song = str(json.loads(onair)["title"])
                #if dl:
                #    coverart_url = json.loads(onair)["now"]["Image"]["url_md"].replace('"', '')
                #    fname = '+'.join((artist,album)).replace(' ', '+')
                #    get_coverart(fname, coverart_url)
                #fi
            else:
                #most internet radios show the artist and album as
                #artist - album in mpd, so this is a "best guess" for any
                #station I haven't gotten the stream for.
                mpdinfo = init()
                if 'title' in mpdinfo:
                    info = mpdinfo['title'].split(' - ')
                    if len(info) == 1:
                        (artist)=info[0]
                    elif len(info) == 2:
                        (artist,song) = info
                    elif len(info) == 3:
                        (artist,album,song) = info
            #fi
        except KeyError:
            pass
        return([artist, album, song])
    
    def get_coverart_info(data): 
        nm = prepare_query(data)
        fname = '{}/{}'.format(coverart_dir,nm)
        fname = fname + '.jpg'
        if os.path.isfile(fname):
            #get_coverart links the file if it's already been dled.
            get_coverart(fname, '')
        else:
            try:
                dc = discogs_client.Client('mpd_what/2.0', user_token=discogs_token)
                if len(data) <= 3:
                    results = dc.search(data[1], artist=data[0], type='release')
                    url = results.page(1)[0].images[0]['uri']
                    #with open (errlog, 'a') as logfile:
                    #    logfile.write(str(url) + ': ' + ' in get_coverart_info() ' + str(e) + '\n')
                elif len(data) == 1:
                    results = dc.search(data[0], type='release')
                    url = results.page(1)[0].images[0]['uri']
                    #with open (errlog, 'a') as logfile:
                    #    logfile.write(str(url) + ': ' + ' in get_coverart_info() ' + str(e) + '\n')
                print(fname)
                get_coverart(fname, url)
            except:
                try:
                    get_coverart("generic_lp_icon.jpg", "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/LP_Vinyl_Symbol_Icon.png/240px-LP_Vinyl_Symbol_Icon.png")
                    exit()
                    #print('prepare query failed')
                except OSError as e:
                    epoch = time()
                    when = ctime(epoch)
                    with open (errlog, 'a') as logfile:
                        logfile.write(str(when) + ': ' + ' in get_coverart_info() ' + str(e) + '\n')
                    #print("OS error in get_coverart_info: " + e)
                    exit()
    
    def get_mpd_info():
        global showinfo
        global dl
        global scrobble
        global lastfm
        mpdinfo = init()
    
        #I prefer albumartist
        if 'albumartist' in mpdinfo:
            artist = mpdinfo['albumartist']
        elif 'artist' in mpdinfo:
            artist = mpdinfo['artist']
        elif 'name' in mpdinfo:
            station = mpdinfo['name']
            whatsplaying = get_station_info(station,dl)
            artist = whatsplaying[0]
        elif 'file' in mpdinfo:
            whatsplaying = mpdinfo['file'].split('/')
            artist = whatsplaying[0]
            album = whatsplaying[1]
            song = whatsplaying[2]
        #fi
        #The , '' means, if album isn't there, use null instead of erroring out.
        if 'whatsplaying' in vars():
            pass
        else:
            album = mpdinfo.get('album', '')
            song = mpdinfo.get('title', '')
            whatsplaying = [artist, album, song]

        #why is this needed? because whatsplaying, which should be
        #a list of strings, might contain an empty string as one of its
        #entries, and that messes up the logic.
        if isinstance(whatsplaying, list):
            whatsplaying = [s for s in whatsplaying if s != '']

        infostr = '\n'.join(whatsplaying).strip()

        last = ""

        if os.path.exists(lastplayed):
            with open(lastplayed, 'r') as lastfile:
                last = lastfile.readlines()
                last = ''.join(last).strip()

        if (infostr != last):
            if dl:
                get_coverart_info(whatsplaying)
            if (len(whatsplaying) >= 2):
                if (len(whatsplaying) == 3):
                    artist = whatsplaying[0]
                    album = whatsplaying[1]
                    title = whatsplaying[2]
                    #print(whatsplaying)
                elif (len(whatsplaying) == 2):
                    artist = whatsplaying[0]
                    album = ""
                    title = whatsplaying[1]

                if not ("http" in artist):
                    with open(lastplayed, 'w+') as lastfile:
                        lastfile.write(infostr)

                    oneline_infostr = infostr.replace('\n', ';')

                    with open(playlist, 'a') as p:
                        epoch = time()
                        today_date = ctime(epoch)
                        p.write(today_date + ': ' + oneline_infostr + '\n')

                    if ((lastfm_user, lastfm_pass, lastfm_api_key, lastfm_api_secret) != ("", "", "", "")) and scrobble and lastfm:

                        epoch = time()
                        when = ctime(epoch)
                        try:
                            lastfm = pylast.LastFMNetwork(api_key=lastfm_api_key,
                                                          api_secret=lastfm_api_secret,
                                                          username=lastfm_user,
                                                          password_hash=pylast.md5(lastfm_pass))
                            librefm = pylast.LibreFMNetwork(api_key=lastfm_api_key,
                                                          api_secret=lastfm_api_secret,
                                                          username=librefm_user,
                                                          password_hash=pylast.md5(librefm_pass))
                            if album == "":
                                lastfm.scrobble(artist=artist, title=title, timestamp=epoch) 
                                librefm.scrobble(artist=artist, title=title, timestamp=epoch) 
                            else:
                                lastfm.scrobble(artist=artist, album=album, title=title, timestamp=epoch) 
                                librefm.scrobble(artist=artist, album=album, title=title, timestamp=epoch) 

                        except Exception as e:
                            with open (errlog, 'a') as logfile:
                                logfile.write(str(when) + ': ' + str(e) + '\n')
                        

        if showinfo: 
            print(infostr)
        #fi

    def verify_args(a, gs):
        a = [s.lower() for s in a]
        #now we see if the user entered invalid args
        if len(a) > 1:
            for arg in a[1:]:
                if arg in gs:
                    pass
                else:
                    help_msg(a[0])
                    exit(0)
    
    def parse_args(a):
        valid_singles = ['-g', '--get',  '-sc', '--scrobble', '-h', '--help', '-q', '--quiet']
        verify_args(a, valid_singles)
        global dl, scrobble, showinfo
        if '-h' in a[1:] or '--help' in a[1:]:
            help_msg(a[0])
            exit(0)
        if '-g' in a[1:] or '--get' in a[1:] :
            dl=True
        if '-q' in a[1:] or '--quiet' in a[1:]:
            showinfo=False
        if '-sc' in a[1:] or '--scrobble' in a[1:]:
            scrobble=True
    
parse_args(argv)
get_mpd_info()
