import spotipy.util as util
import pandas as pd
import spotipy
from datetime import datetime

class SpotifyUtil:
    '''
    Utility class for accessing Spotify API
    '''
    query_dict = {
        'current_user_recently_played': 'parse_songplays',
        'current_user_top_artists': 'parse_top_artists',
        'current_user_top_tracks': 'parse_top_tracks'
    }

    def __init__(self, username, client_id, client_secret, redirect_uri):
        self.username = username
        self.client_id = client_id
        self.client_secret = client_secret
        self.redirect_uri = redirect_uri

    def get_spotify_data(self, scope, query, limit=50):
        '''
        Retrieves data from Spotify
        '''
        token = self.get_token(scope=scope)
        spotify = self.get_connection(token=token)  
        json = getattr(spotify, query)(limit=limit)
        self.df = getattr(self, self.query_dict[query])(data=json, spotify=spotify)
        return self.df

    def get_token(self, scope):
        '''
        Obtains the token for user authorization
        '''
        token = util.prompt_for_user_token(username=self.username,
                                           scope=scope,
                                           client_id=self.client_id,
                                           client_secret=self.client_secret,
                                           redirect_uri=self.redirect_uri)
        return token
    
    def get_connection(self, token):
        '''
        Sets up the Spotify Connection
        '''
        spotify = spotipy.Spotify(auth=token)
        return spotify
    
    def parse_json(self, data, columns, *args, **kwargs):
        '''
        Parses response data in JSON format
        '''
        if not (kwargs.get('result_key')==None):
            data = data[kwargs['result_key']]
        df = pd.json_normalize(data).reset_index()
        df['index'] = df['index'] + 1
        df = df[columns.keys()].rename(columns=columns)
        return df
    
    def parse_primary_other(self, parse_list=[]):
        '''
        Parses primary and other values for lists
        '''
        parse_list = parse_list.copy()
        try:
            primary = parse_list.pop(0)
        except IndexError:
            primary = None

        others = ", ".join(parse_list)
        return primary, others

    def parse_songplays(self, data, spotify, columns=None):
        '''
        Parses songplays data of user
        '''
        if columns is None:
            columns = {
                'index': 'songplays_id',
                'track.id': 'track_id',
                'track.name': 'track_name', 
                'track.artists': 'artists', 
                'track.duration_ms' : 'track_duration', 
                'track.explicit': 'track_is_explicit', 
                'track.popularity': 'track_popularity',
                'played_at': 'track_played_at',
                'track.album.id': 'album_id', 
                'track.album.name': 'album_name', 
                'track.album.release_date': 'album_release_year', 
                'track.album.type': 'album_type'
            }
        songplays = self.parse_json(data=data, columns=columns, result_key='items')

        # Parse artists
        def parse_artist(artists):
            # parse primary and other artists
            artist_name, artist_name_others = self.parse_primary_other([artist['name'] for artist in artists])
            artist_id, artist_id_others = self.parse_primary_other([artist['id'] for artist in artists])
            return artist_name, artist_name_others, artist_id, artist_id_others

        (songplays['artist_name'], songplays['artist_name_others'],
         songplays['artist_id'], songplays['artist_id_others']) = zip(*songplays['artists'].apply(parse_artist))

        # Get release year
        def parse_year(album_release_year):
            try:
                year = datetime.strptime(album_release_year, '%Y-%m-%d').year
            except (ValueError, NameError):
                year = datetime.strptime(album_release_year, '%Y').year
            return year
        
        songplays['album_release_year'] = songplays['album_release_year'].apply(lambda x: parse_year(x))
        
        # Convert timestamp
        try:
            songplays['track_played_at'] = songplays['track_played_at'] \
                                            .apply(lambda x: pd.Timestamp(x).strftime('%Y-%m-%d %H:%M:%S'))
        except KeyError:
            pass

        #Convert track duration
        songplays['track_duration'] = songplays['track_duration'].apply(lambda x: x/60000)
        
        # Get features
        def get_features(key, method, df, columns, result_key=None):
            features = getattr(spotify, method)(df[key].values.tolist())
            features_df = self.parse_json(data=features, columns=columns, result_key=result_key)
            features_df.drop_duplicates(subset=key, inplace=True)
            df = df.merge(features_df, how='left', on=key)
            return df

        # Get track features
        track_features_columns = {
            'id': 'track_id',
            'danceability': 'track_danceability', 
            'energy': 'track_energy', 
            'key': 'track_key', 
            'loudness': 'track_loudness', 
            'mode': 'track_mode', 
            'speechiness': 'track_speechiness', 
            'acousticness': 'track_acousticness', 
            'instrumentalness': 'track_instrumentalness', 
            'liveness': 'track_liveness', 
            'valence': 'track_valence'
        }
        songplays = get_features(key='track_id', 
                                method='audio_features', 
                                df=songplays, 
                                columns=track_features_columns)
        
        # Get artist features
        artist_features_columns = {
            'id': 'artist_id',
            'genres': 'artist_genres',
            'popularity': 'artist_popularity',
            'followers.total': 'artist_followers'
        }
        songplays = get_features(key='artist_id', 
                                method='artists', 
                                df=songplays, 
                                columns=artist_features_columns,
                                result_key='artists')
        # Parse genres
        (songplays['artist_genre'], 
         songplays['artist_genre_others']) = zip(*songplays['artist_genres'].apply(self.parse_primary_other))

        songplays.drop(columns=['artist_genres', 'artists'], axis=1, inplace=True)
        return songplays
    
    def parse_top_artists(self, data, spotify):
        '''
        Parses top artists of user
        '''
        columns = {
            'index': 'artist_rank',
            'id': 'artist_id',
            'name': 'artist_name',
            'genres': 'artist_genres',
            'popularity': 'artist_popularity',
            'followers.total': 'artist_followers'
        }
        top_artists = self.parse_json(data=data, columns=columns, result_key='items')
        
        # Parse genres
        (top_artists['artist_genre'], 
         top_artists['artist_genre_others']) = zip(*top_artists['artist_genres'].apply(self.parse_primary_other))

        top_artists.drop(columns=['artist_genres'], axis=1, inplace=True)
        return top_artists

    def parse_top_tracks(self, data, spotify):
        '''
        Parses top tracks of user
        '''
        columns = {
            'index': 'track_rank',
            'id': 'track_id',
            'name': 'track_name', 
            'artists': 'artists', 
            'duration_ms' : 'track_duration', 
            'explicit': 'track_is_explicit', 
            'popularity': 'track_popularity',
            'album.id': 'album_id', 
            'album.name': 'album_name', 
            'album.release_date': 'album_release_year', 
            'album.type': 'album_type'
        }
        top_tracks = self.parse_songplays(data=data, spotify=spotify, columns=columns)
        return top_tracks
import spotipy.util as util
import pandas as pd
import spotipy
from datetime import datetime

class SpotifyUtil:
    '''
    Utility class for accessing Spotify API
    '''
    query_dict = {
        'current_user_recently_played': 'parse_songplays',
        'current_user_top_artists': 'parse_top_artists',
        'current_user_top_tracks': 'parse_top_tracks'
    }

    def __init__(self, username, client_id, client_secret, redirect_uri):
        self.username = username
        self.client_id = client_id
        self.client_secret = client_secret
        self.redirect_uri = redirect_uri

    def get_spotify_data(self, scope, query, limit=50):
        '''
        Retrieves data from Spotify
        '''
        token = self.get_token(scope=scope)
        spotify = self.get_connection(token=token)  
        json = getattr(spotify, query)(limit=limit)
        self.df = getattr(self, self.query_dict[query])(data=json, spotify=spotify)
        return self.df

    def get_token(self, scope):
        '''
        Obtains the token for user authorization
        '''
        token = util.prompt_for_user_token(username=self.username,
                                           scope=scope,
                                           client_id=self.client_id,
                                           client_secret=self.client_secret,
                                           redirect_uri=self.redirect_uri)
        return token
    
    def get_connection(self, token):
        '''
        Sets up the Spotify Connection
        '''
        spotify = spotipy.Spotify(auth=token)
        return spotify
    
    def parse_json(self, data, columns, *args, **kwargs):
        '''
        Parses response data in JSON format
        '''
        if not (kwargs.get('result_key')==None):
            data = data[kwargs['result_key']]
        df = pd.json_normalize(data).reset_index()
        df['index'] = df['index'] + 1
        df = df[columns.keys()].rename(columns=columns)
        return df
    
    def parse_primary_other(self, parse_list=[]):
        '''
        Parses primary and other values for lists
        '''
        parse_list = parse_list.copy()
        try:
            primary = parse_list.pop(0)
        except IndexError:
            primary = None

        others = ", ".join(parse_list)
        return primary, others

    def parse_songplays(self, data, spotify, columns=None):
        '''
        Parses songplays data of user
        '''
        if columns is None:
            columns = {
                'index': 'songplays_id',
                'track.id': 'track_id',
                'track.name': 'track_name', 
                'track.artists': 'artists', 
                'track.duration_ms' : 'track_duration', 
                'track.explicit': 'track_is_explicit', 
                'track.popularity': 'track_popularity',
                'played_at': 'track_played_at',
                'track.album.id': 'album_id', 
                'track.album.name': 'album_name', 
                'track.album.release_date': 'album_release_year', 
                'track.album.type': 'album_type'
            }
        songplays = self.parse_json(data=data, columns=columns, result_key='items')

        # Parse artists
        def parse_artist(artists):
            # parse primary and other artists
            artist_name, artist_name_others = self.parse_primary_other([artist['name'] for artist in artists])
            artist_id, artist_id_others = self.parse_primary_other([artist['id'] for artist in artists])
            return artist_name, artist_name_others, artist_id, artist_id_others

        (songplays['artist_name'], songplays['artist_name_others'],
         songplays['artist_id'], songplays['artist_id_others']) = zip(*songplays['artists'].apply(parse_artist))

        # Get release year
        def parse_year(album_release_year):
            try:
                year = datetime.strptime(album_release_year, '%Y-%m-%d').year
            except (ValueError, NameError):
                year = datetime.strptime(album_release_year, '%Y').year
            return year
        
        songplays['album_release_year'] = songplays['album_release_year'].apply(lambda x: parse_year(x))
        
        # Convert timestamp
        try:
            songplays['track_played_at'] = songplays['track_played_at'] \
                                            .apply(lambda x: pd.Timestamp(x).strftime('%Y-%m-%d %H:%M:%S'))
        except KeyError:
            pass

        #Convert track duration
        songplays['track_duration'] = songplays['track_duration'].apply(lambda x: x/60000)
        
        # Get features
        def get_features(key, method, df, columns, result_key=None):
            features = getattr(spotify, method)(df[key].values.tolist())
            features_df = self.parse_json(data=features, columns=columns, result_key=result_key)
            features_df.drop_duplicates(subset=key, inplace=True)
            df = df.merge(features_df, how='left', on=key)
            return df

        # Get track features
        track_features_columns = {
            'id': 'track_id',
            'danceability': 'track_danceability', 
            'energy': 'track_energy', 
            'key': 'track_key', 
            'loudness': 'track_loudness', 
            'mode': 'track_mode', 
            'speechiness': 'track_speechiness', 
            'acousticness': 'track_acousticness', 
            'instrumentalness': 'track_instrumentalness', 
            'liveness': 'track_liveness', 
            'valence': 'track_valence'
        }
        songplays = get_features(key='track_id', 
                                method='audio_features', 
                                df=songplays, 
                                columns=track_features_columns)
        
        # Get artist features
        artist_features_columns = {
            'id': 'artist_id',
            'genres': 'artist_genres',
            'popularity': 'artist_popularity',
            'followers.total': 'artist_followers'
        }
        songplays = get_features(key='artist_id', 
                                method='artists', 
                                df=songplays, 
                                columns=artist_features_columns,
                                result_key='artists')
        # Parse genres
        (songplays['artist_genre'], 
         songplays['artist_genre_others']) = zip(*songplays['artist_genres'].apply(self.parse_primary_other))

        songplays.drop(columns=['artist_genres', 'artists'], axis=1, inplace=True)
        return songplays
    
    def parse_top_artists(self, data, spotify):
        '''
        Parses top artists of user
        '''
        columns = {
            'index': 'artist_rank',
            'id': 'artist_id',
            'name': 'artist_name',
            'genres': 'artist_genres',
            'popularity': 'artist_popularity',
            'followers.total': 'artist_followers'
        }
        top_artists = self.parse_json(data=data, columns=columns, result_key='items')
        
        # Parse genres
        (top_artists['artist_genre'], 
         top_artists['artist_genre_others']) = zip(*top_artists['artist_genres'].apply(self.parse_primary_other))

        top_artists.drop(columns=['artist_genres'], axis=1, inplace=True)
        return top_artists

    def parse_top_tracks(self, data, spotify):
        '''
        Parses top tracks of user
        '''
        columns = {
            'index': 'track_rank',
            'id': 'track_id',
            'name': 'track_name', 
            'artists': 'artists', 
            'duration_ms' : 'track_duration', 
            'explicit': 'track_is_explicit', 
            'popularity': 'track_popularity',
            'album.id': 'album_id', 
            'album.name': 'album_name', 
            'album.release_date': 'album_release_year', 
            'album.type': 'album_type'
        }
        top_tracks = self.parse_songplays(data=data, spotify=spotify, columns=columns)
        return top_tracks
Utility class for accessing Spotify API Retrieves data from Spotify Obtains the token for user authorization Sets up the Spotify Connection Parses response data in JSON format Parses primary and other values for lists Parses songplays data of user # Parse artists # parse primary and other artists # Get release year # Convert timestamp #Convert track duration # Get features # Get track features # Get artist features # Parse genres Parses top artists of user # Parse genres Parses top tracks of user