(function (window, angular, undefined) {
  'use strict';

  angular
    .module('spotify', [])
    .provider('Spotify', function () {

      // Module global settings.
      var settings = {};
      settings.clientId = null;
      settings.redirectUri = null;
      settings.scope = null;
      settings.authToken = null;

      this.setClientId = function (clientId) {
        settings.clientId = clientId;
        return settings.clientId;
      };

      this.getClientId = function () {
        return settings.clientId;
      };

      this.setAuthToken = function (authToken) {
        settings.authToken = authToken;
        return settings.authToken;
      };

      this.setRedirectUri = function (redirectUri) {
        settings.redirectUri = redirectUri;
        return settings.redirectUri;
      };

      this.getRedirectUri = function () {
        return settings.redirectUri;
      };

      this.setScope = function (scope) {
        settings.scope = scope;
        return settings.scope;
      };

      var utils = {};
      utils.toQueryString = function (obj) {
        var parts = [];
        angular.forEach(obj, function (value, key) {
          this.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
        }, parts);
        return parts.join('&');
      };

      /**
       * API Base URL
       */
      settings.apiBase = 'https://api.spotify.com/v1';

      this.$get = ['$q', '$http', '$window', function ($q, $http, $window) {

        function NgSpotify () {
          this.clientId = settings.clientId;
          this.redirectUri = settings.redirectUri;
          this.apiBase = settings.apiBase;
          this.scope = settings.scope;
          this.authToken = settings.authToken;
          this.toQueryString = utils.toQueryString;
        }

        function openDialog (uri, name, options, cb) {
          var win = window.open(uri, name, options);
          var interval = window.setInterval(function () {
            try {
              if (!win || win.closed) {
                window.clearInterval(interval);
                cb(win);
              }
            } catch (e) {}
          }, 1000);
          return win;
        }

        NgSpotify.prototype = {
          api: function (endpoint, method, params, data, headers) {
            var deferred = $q.defer();

            $http({
              url: this.apiBase + endpoint,
              method: method ? method : 'GET',
              params: params,
              data: data,
              headers: headers,
              withCredentials: false
            })
            .then(function (data) {
              deferred.resolve(data);
            })
            .catch(function (data) {
              deferred.reject(data);
            });
            return deferred.promise;
          },

          _auth: function (isJson) {
            var auth = {
              'Authorization': 'Bearer ' + this.authToken
            };
            if (isJson) {
              auth['Content-Type'] = 'application/json';
            }
            return auth;
          },

          /**
            ====================== Albums =====================
           */

          /**
           * Gets an album
           * Pass in album id or spotify uri
           */
          getAlbum: function (album) {
            album = album.indexOf('spotify:') === -1 ? album : album.split(':')[2];

            return this.api('/albums/' + album, 'GET', null, null, this._auth());
          },

          /**
           * Gets an album
           * Pass in comma separated string or array of album ids
           */
          getAlbums: function (albums) {
            albums = angular.isString(albums) ? albums.split(',') : albums;
            angular.forEach(albums, function (value, index) {
              albums[index] = value.indexOf('spotify:') > -1 ? value.split(':')[2] : value;
            });
            return this.api('/albums', 'GET', {
              ids: albums ? albums.toString() : ''
            }, null, this._auth());
          },

          /**
           * Get Album Tracks
           * Pass in album id or spotify uri
           */
          getAlbumTracks: function (album, options) {
            album = album.indexOf('spotify:') === -1 ? album : album.split(':')[2];

            return this.api('/albums/' + album + '/tracks', 'GET', options, null, this._auth());
          },


          /**
            ====================== Artists =====================
           */

          /**
           * Get an Artist
           */
          getArtist: function (artist) {
            artist = artist.indexOf('spotify:') === -1 ? artist : artist.split(':')[2];

            return this.api('/artists/' + artist, 'GET', null, null, this._auth());
          },

          /**
           * Get multiple artists
           */
          getArtists: function (artists) {
            artists = angular.isString(artists) ? artists.split(',') : artists;
            angular.forEach(artists, function (value, index) {
              artists[index] = value.indexOf('spotify:') > -1 ? value.split(':')[2] : value;
            });
            return this.api('/artists/', 'GET', {
              ids: artists ? artists.toString() : ''
            }, null, this._auth());
          },

          //Artist Albums
          getArtistAlbums: function (artist, options) {
            artist = artist.indexOf('spotify:') === -1 ? artist : artist.split(':')[2];

            return this.api('/artists/' + artist + '/albums', 'GET', options, null, this._auth());
          },

          /**
           * Get Artist Top Tracks
           * The country: an ISO 3166-1 alpha-2 country code.
           */
          getArtistTopTracks: function (artist, country) {
            artist = artist.indexOf('spotify:') === -1 ? artist : artist.split(':')[2];

            return this.api('/artists/' + artist + '/top-tracks', 'GET', {
              country: country
            }, null, this._auth());
          },

          getRelatedArtists: function (artist) {
            artist = artist.indexOf('spotify:') === -1 ? artist : artist.split(':')[2];

            return this.api('/artists/' + artist + '/related-artists', 'GET', null, null, this._auth());
          },


          /**
            ====================== Browse =====================
           */
          getFeaturedPlaylists: function (options) {
            return this.api('/browse/featured-playlists', 'GET', options, null, this._auth());
          },

          getNewReleases: function (options) {
            return this.api('/browse/new-releases', 'GET', options, null, this._auth());
          },

          getCategories: function (options) {
            return this.api('/browse/categories', 'GET', options, null, this._auth());
          },

          getCategory: function (category_id, options) {
            return this.api('/browse/categories/' + category_id, 'GET', options, null, this._auth());
          },

          getCategoryPlaylists: function (category_id, options) {
            return this.api('/browse/categories/' + category_id + '/playlists', 'GET', options, null, this._auth());
          },

          getRecommendations: function (options) {
            return this.api('/recommendations', 'GET', options, null, this._auth());
          },

          getAvailableGenreSeeds: function () {
            return this.api('/recommendations/available-genre-seeds', 'GET', null, null, this._auth());
          },


          /**
            ====================== Following =====================
           */
          following: function (type, options) {
            options = options || {};
            options.type = type;
            return this.api('/me/following', 'GET', options, null, this._auth());
          },

          follow: function (type, ids) {
            return this.api('/me/following', 'PUT', { type: type, ids: ids }, null, this._auth());
          },

          unfollow: function (type, ids) {
            return this.api('/me/following', 'DELETE', { type: type, ids: ids }, null, this._auth());
          },

          userFollowingContains: function (type, ids) {
            return this.api('/me/following/contains', 'GET', { type: type, ids: ids }, null, this._auth());
          },

          followPlaylist: function (userId, playlistId, isPublic) {
            return this.api('/users/' + userId + '/playlists/' + playlistId + '/followers', 'PUT', null, {
              public: isPublic || null
            }, this._auth(true));
          },

          unfollowPlaylist: function (userId, playlistId) {
            return this.api('/users/' + userId + '/playlists/' + playlistId + '/followers', 'DELETE', null, null, this._auth());
          },

          playlistFollowingContains: function(userId, playlistId, ids) {
            return this.api('/users/' + userId + '/playlists/' + playlistId + '/followers/contains', 'GET', {
              ids: ids.toString()
            }, null, this._auth());
          },


          /**
            ====================== Library =====================
           */
          getSavedUserTracks: function (options) {
            return this.api('/me/tracks', 'GET', options, null, this._auth());
          },

          userTracksContains: function (tracks) {
            tracks = angular.isString(tracks) ? tracks.split(',') : tracks;
            angular.forEach(tracks, function (value, index) {
              tracks[index] = value.indexOf('spotify:') > -1 ? value.split(':')[2] : value;
            });
            return this.api('/me/tracks/contains', 'GET', {
              ids: tracks.toString()
            }, null, this._auth());
          },

          saveUserTracks: function (tracks) {
            tracks = angular.isString(tracks) ? tracks.split(',') : tracks;
            angular.forEach(tracks, function (value, index) {
              tracks[index] = value.indexOf('spotify:') > -1 ? value.split(':')[2] : value;
            });
            return this.api('/me/tracks', 'PUT', {
              ids: tracks.toString()
            }, null, this._auth());
          },

          removeUserTracks: function (tracks) {
            tracks = angular.isString(tracks) ? tracks.split(',') : tracks;
            angular.forEach(tracks, function (value, index) {
              tracks[index] = value.indexOf('spotify:') > -1 ? value.split(':')[2] : value;
            });
            return this.api('/me/tracks', 'DELETE', {
              ids: tracks.toString()
            }, null, this._auth(true));
          },

          saveUserAlbums: function (albums) {
            albums = angular.isString(albums) ? albums.split(',') : albums;
            angular.forEach(albums, function (value, index) {
              albums[index] = value.indexOf('spotify:') > -1 ? value.split(':')[2] : value;
            });
            return this.api('/me/albums', 'PUT', {
              ids: albums.toString()
            }, null, this._auth());
          },

          getSavedUserAlbums: function (options) {
            return this.api('/me/albums', 'GET', options, null, this._auth());
          },

          removeUserAlbums: function (albums) {
            albums = angular.isString(albums) ? albums.split(',') : albums;
            angular.forEach(albums, function (value, index) {
              albums[index] = value.indexOf('spotify:') > -1 ? value.split(':')[2] : value;
            });
            return this.api('/me/albums', 'DELETE', {
              ids: albums.toString()
            }, null, this._auth(true));
          },

          userAlbumsContains: function (albums) {
            albums = angular.isString(albums) ? albums.split(',') : albums;
            angular.forEach(albums, function (value, index) {
              albums[index] = value.indexOf('spotify:') > -1 ? value.split(':')[2] : value;
            });
            return this.api('/me/albums/contains', 'GET', {
              ids: albums.toString()
            }, null, this._auth());
          },


          /**
            ====================== Personalization =====================
           */
           getUserTopArtists: function (options) {
             options = options || {};
             return this.api('/me/top/artists', 'GET', options, null, this._auth());
           },

           getUserTopTracks: function (options) {
             options = options || {};
             return this.api('/me/top/tracks', 'GET', options, null, this._auth());
           },


          /**
            ====================== Playlists =====================
           */
          getUserPlaylists: function (userId, options) {
            return this.api('/users/' + userId + '/playlists', 'GET', options, null, this._auth());
          },

          getPlaylist: function (userId, playlistId, options) {
            return this.api('/users/' + userId + '/playlists/' + playlistId, 'GET', options, null, this._auth());
          },

          getPlaylistTracks: function (userId, playlistId, options) {
            return this.api('/users/' + userId + '/playlists/' + playlistId + '/tracks', 'GET', options, null, this._auth());
          },

          createPlaylist: function (userId, options) {
            return this.api('/users/' + userId + '/playlists', 'POST', null, options, this._auth(true));
          },

          addPlaylistTracks: function (userId, playlistId, tracks, options) {
            tracks = angular.isArray(tracks) ? tracks : tracks.split(',');
            angular.forEach(tracks, function (value, index) {
              tracks[index] = value.indexOf('spotify:') === -1 ? 'spotify:track:' + value : value;
            });
            return this.api('/users/' + userId + '/playlists/' + playlistId + '/tracks', 'POST', {
              uris: tracks.toString(),
              position: options ? options.position : null
            }, null, this._auth(true));
          },

          removePlaylistTracks: function (userId, playlistId, tracks) {
            tracks = angular.isArray(tracks) ? tracks : tracks.split(',');
            var track;
            angular.forEach(tracks, function (value, index) {
              track = tracks[index];
              tracks[index] = {
                uri: track.indexOf('spotify:') === -1 ? 'spotify:track:' + track : track
              };
            });
            return this.api('/users/' + userId + '/playlists/' + playlistId + '/tracks', 'DELETE', null, {
              tracks: tracks
            }, this._auth(true));
          },

          reorderPlaylistTracks: function (userId, playlistId, options) {
            return this.api('/users/' + userId + '/playlists/' + playlistId + '/tracks', 'PUT', null, options, this._auth(true));
          },

          replacePlaylistTracks: function (userId, playlistId, tracks) {
            tracks = angular.isArray(tracks) ? tracks : tracks.split(',');
            var track;
            angular.forEach(tracks, function (value, index) {
              track = tracks[index];
              tracks[index] = track.indexOf('spotify:') === -1 ? 'spotify:track:' + track : track;
            });
            return this.api('/users/' + userId + '/playlists/' + playlistId + '/tracks', 'PUT', {
              uris: tracks.toString()
            }, null, this._auth(true));
          },

          updatePlaylistDetails: function (userId, playlistId, options) {
            return this.api('/users/' + userId + '/playlists/' + playlistId, 'PUT', null, options, this._auth(true));
          },

          /**
            ====================== Profiles =====================
           */

          getUser: function (userId) {
            return this.api('/users/' + userId, 'GET', null, null, this._auth());
          },

          getCurrentUser: function () {
            return this.api('/me', 'GET', null, null, this._auth());
          },



          /**
           * Search Spotify
           * q = search query
           * type = artist, album or track
           */
          search: function (q, type, options) {
            options = options || {};
            options.q = q;
            options.type = type;

            return this.api('/search', 'GET', options, null, this._auth());
          },


          /**
            ====================== Tracks =====================
           */
          getTrack: function (track) {
            track = track.indexOf('spotify:') === -1 ? track : track.split(':')[2];

            return this.api('/tracks/' + track, 'GET', null, null, this._auth());
          },

          getTracks: function (tracks) {
            tracks = angular.isString(tracks) ? tracks.split(',') : tracks;
            angular.forEach(tracks, function (value, index) {
              tracks[index] = value.indexOf('spotify:') > -1 ? value.split(':')[2] : value;
            });
            return this.api('/tracks/', 'GET', {
              ids: tracks ? tracks.toString() : ''
            }, null, this._auth());
          },

          getTrackAudioFeatures: function (track) {
            track = track.indexOf('spotify:') === -1 ? track : track.split(':')[2];
            return this.api('/audio-features/' + track, 'GET', null, null, this._auth());
          },

          getTracksAudioFeatures: function (tracks) {
            tracks = angular.isString(tracks) ? tracks.split(',') : tracks;
            angular.forEach(tracks, function (value, index) {
              tracks[index] = value.indexOf('spotify:') > -1 ? value.split(':')[2] : value;
            });
            return this.api('/audio-features/', 'GET', {
              ids: tracks ? tracks.toString() : ''
            }, null, this._auth());
          },


          /**
            ====================== Login =====================
           */
          setAuthToken: function (authToken) {
            this.authToken = authToken;
            return this.authToken;
          },

          login: function () {
            var deferred = $q.defer();
            var that = this;

            var w = 400,
                h = 500,
                left = (screen.width / 2) - (w / 2),
                top = (screen.height / 2) - (h / 2);

            var params = {
              client_id: this.clientId,
              redirect_uri: this.redirectUri,
              scope: this.scope || '',
              response_type: 'token'
            };

            var authCompleted = false;
            var authWindow = openDialog(
              'https://accounts.spotify.com/authorize?' + this.toQueryString(params),
              'Spotify',
              'menubar=no,location=no,resizable=yes,scrollbars=yes,status=no,width=' + w + ',height=' + h + ',top=' + top + ',left=' + left,
              function () {
                if (!authCompleted) {
                  deferred.reject();
                }
              }
            );

            function storageChanged (e) {
              if (e.key === 'spotify-token') {
                if (authWindow) { authWindow.close(); }
                authCompleted = true;

                that.setAuthToken(e.newValue);
                $window.removeEventListener('storage', storageChanged, false);

                deferred.resolve(e.newValue);
              }
            }

            $window.addEventListener('storage', storageChanged, false);

            return deferred.promise;
          }
        };

        return new NgSpotify();
      }];

    });

}(window, angular));
