
////////// Custom knerd - jplayer

// Begin global vars

var $jplayer_playlist  = null; // will get set to the container that holds the songs of the active playlist
var kpl                = null; // knerd playlist state manager
var kbtns              = null; // knerd playlist button manager
var plManager          = null;

// End global vars



/**
 * BEGIN: encapsulate vars and utilities for managing the playlist state
 */
function kNerdPlaylistState() {

	// track "Songs I've listened to"
	this.songs_listened_to = ($("#k_recent_song_list").size() == 0)? [] : $("#k_recent_song_list").val().split(",");

	this._nowPlayingTitle     = "None"; // for updating the 'Current Playlist' bar
	this.list_type            = "";     // Valid values: "playlist", "album" or "song"
	this.currentListID        = -1;     // keep track of what playlist is playing. (-1 for none)
} // end prototype

	/**
	 * "private": Set the "Current Playlist" title.
	 */
	kNerdPlaylistState.prototype._setPlaylistTitle = function(newTitle)
	{
		this._nowPlayingTitle = newTitle;
		$("#jplayer_knerd_pl_credit_line .current-playlist-title").text(newTitle);
	};

	/**
	 * public: Set the new playlist "state" (sets title and some internal tracking vars)
	 *
	 * @param title Will be displayed in the "Current Playlist" line
	 * @param list_type one of "playlist" "album" or "song"
	 * @param list_id the correspdonding id for the playlist, album or song
	 */
	kNerdPlaylistState.prototype.setCurrentPlaylist = function(title, list_type, listID)
	{
		this._setPlaylistTitle(title);
		this.list_type     = list_type;
		this.currentListID = listID;
	};

	/**
	 * public: Any time a song is played, add it to the "Songs I've listned to" magic playlist
	 */
	kNerdPlaylistState.prototype.trackSong = function($liSong)
	{
		var index   = $liSong.data("index");
		var song_id = $.jp_playlist[index].song_id;

		// watch out for dupes
		var len = this.songs_listened_to.length;
		if (len == 0 || this.songs_listened_to[len-1] != song_id) {
			// track song only if it wasn't the very last song added
			// safe to track this song..
			this.songs_listened_to.push( song_id );
		}

		// Update song count in the playlist manager view for 'songs i've listened to' playlist.
		$("#knerd-playlist-template .num-songs").text( kpl.songs_listened_to.length );
	};

/**
 * END: kNerdPlaylistState encapsulation
 */


/**
 * BEGIN: playlist button management encapsulation
 */
function kPlaylistButtons() { this.doSetup(); } // Constructor

	kPlaylistButtons.prototype.doSetup = function() {
		kPlaylistButtons.disableAll();
	};

	/**
	 * disables all the buttons..
	 */
	kPlaylistButtons.disableAll = function() {
		// disable all three buttons to start..
		$("#jplayer_knerd_pl_buttons button").button("disable"); // (disables 'add', 'update', and 'clear')
	};

	kPlaylistButtons.clickAddToPlaylist = function()
	{
		// after 'add to playlist' button is clicked, determine what songs were selected
		var selected_song_ids = ActivePlaylist.getSelectedSongIDs();

		if (selected_song_ids.length > 0) // if at least one song is selected, open the "add to playlist" dialog
		{
			// Prepare an informative message on the dialog we're opening..
			$("#jp_add_to_playlist_dlg #add-to-pl-info").text("+"+selected_song_ids.length+" tracks selected"); // E.g. +5 songs selected

			// make sure button is enabled:
			$("#jp_add_to_playlist_dlg button").button("enable");

			$("#jp_add_to_playlist_dlg").slideDown("normal", function() {
				var $myPlaylists = $("#jp_playlist_manager .pl-group-playlists").children();
				var $targetDiv   = $("#jp_add_to_playlist_dlg .existing-playlist-list");
				$targetDiv.empty();
				$.each($myPlaylists, function(i, el)
				{
					$("<div class='existing-playlist'>" + $(el).find(".playlist-title").text() + "</div>")
						.data("playlist_id", $(el).data("playlist_id"))
						.appendTo($targetDiv)
						.click(function() {

							// UPDATE EXISTING PLAYLIST (Append Songs)
							var $targetPlaylist  = $(this);
							kAjax('post', "/scripts/jplayer/playlist-update.php", {
									playlist_id: $targetPlaylist.data("playlist_id"),
									song_list: selected_song_ids.join(","),
									append: true
								},
								function() {  // callback func on successful update
									$targetPlaylist.highlightFade({}, function() {
										$("#jp_add_to_playlist_dlg .close-btn").click();
									});
								}
							); // end kAjax call
						});
				}); // end $.each
			}); // end slideDown callback..
		} else {
			jAlert("You must first select at least one song");
		}
	}; // end 'add to playlist' button click handler


	/**
	 * clicked "Update Playlist" button
	 */
	kPlaylistButtons.clickUpdatePlaylist = function() {

		$("#btn-update-playlist").button("disable");

		var songIDs = ActivePlaylist.getSongIDs();
		var giveOptionToCreate = false;

		// if playing a playlist that the user owns, UPDATE
		if (ActivePlaylist.isActivePlaylistMine()) {
			if (songIDs.length > 0) {
				// save new song list to existing playlist..
				kAjax('post', "/scripts/jplayer/playlist-update.php", {
						playlist_id: kpl.currentListID,
						song_list: songIDs.join(","),
						append: false
					},
					function() {  // callback func on successful update
						$("#jplayer_knerd_pl_buttons").highlightFade();
					}
				); // end kAjax call
			}
			else {
				jAlert("Can't save empty playlist. Try deleting it instead.");
			}
		}
		else {
			// User tried to "update" something other than one of their own playlists. Give option
			// to create a new playlist as an alternative.
			var defaultText = "Enter title for new Playlist";
			jPrompt(
				"Can't update this song list because it's not one of your playlists. " +
				"But you can create a NEW playlist with the current song list if you'd like:",
				defaultText,
				"Create new playlist?",
				function(newPlaylistTitle) {
					// If user provided a title, create a new playlist!
					if (newPlaylistTitle && $.trim(newPlaylistTitle) && newPlaylistTitle != defaultText) {
						kAjax('post', "/scripts/jplayer/playlist-create-new.php", {
							playlist_title: newPlaylistTitle,
							song_list: songIDs.join(",")
						},
						function(json) { // New playlist has been created

							// update tracking info so that it reflects the NEW playlist rather than
							// whatever they were listening to before.
							kpl.setCurrentPlaylist(newPlaylistTitle, "playlist", json.new_playlist_id);

							// update playlist list
							dlgPlaylistManager.refreshLists();

							// a little eye candy:
							$("#jplayer_knerd_pl_credit_line").highlightFade();
						}
					); // end kAjax (create new playlist)
					}
				}
			);
		}

	}; // end clickUpdatePlaylist

	/**
	 * clicked "Clear Playlist" button
	 */
	kPlaylistButtons.clickClearPlaylist = function() {
		ActivePlaylist.clearSongs();
	};

/**
 * END: playlist button management
 */


/**
 * BEGIN: "Add to playlist" dialog encapsulation
 */
function dlgAddToPlaylist() { this.doSetup(); }
	dlgAddToPlaylist.prototype.doSetup = function() { };


	dlgAddToPlaylist.closeDialog = function() {
		$("#jp_add_to_playlist_dlg").slideUp();
	};

	/**
	 * user enters title for new playlist and clicks "Go" button
	 */
	dlgAddToPlaylist.createNewPlaylist = function() {
		var $button = $(this);
		$button.button("disable");

		var selected_song_ids = ActivePlaylist.getSelectedSongIDs();
		if (selected_song_ids.length > 0)
		{
			var $inputNewTitle = $("#jp_add_to_playlist_dlg #add-to-pl-inputdiv input:text");
			var newPlaylistTitle = $inputNewTitle.val();
			if (newPlaylistTitle != "") {

				// Ajax call for CREATE NEW PLAYLIST
				kAjax('post', "/scripts/jplayer/playlist-create-new.php", {
						playlist_title: newPlaylistTitle,
						song_list: selected_song_ids.join(",")
					},
					function(json) { // New playlist has been created, if we got here.

						$("#jp_add_to_playlist_dlg .close-btn").click(); // close the 'add to playlist' dialog

						$inputNewTitle.val("new playlist title"); // reset the message in the 'create new playlist' input field..

						// update playlist list
						dlgPlaylistManager.refreshLists();

						$button.button("enable"); // re-enable button
					}
				); // end kAjax (create new playlist)

			} else {
				jAlert("New playlist title can't be blank");
			}
		} else {
			jAlert("Oops, please select a song first. Can't create an empty playlist");
		}
	};



/**
 * END: "Add to playlist" dialog encapsulation
 */


/**
 * BEGIN: playlist manager dialog encapsulation
 */
function dlgPlaylistManager() { }
	/**
	 * user clicked the 'play' icon for one of their
	 * @return
	 */
	dlgPlaylistManager.prototype.clickPlayPlaylist = function() {
		// Load playlist in player
		var $playlist     = $(this).closest(".playlist");
		var playlistID    = $playlist.data("playlist_id");
		var playlistTitle = $playlist.find(".playlist-title").text();

		if (playlistID && playlistID > 0) {
			ActivePlaylist._loadPlayerWith( 'playlist', playlistID, playlistTitle );
		}
		else if ($playlist.is("#knerd-playlist-template")) {  // clicked play: "Songs I've listend to"
			ActivePlaylist._loadPlayerWith("mult_song", kpl.songs_listened_to.join(","), "Songs I've Listened to");
		}
	};

	/**
	 * Playlist Editor: Share playlist
	 */
	dlgPlaylistManager.prototype.shareMyPlaylist = function () {
		var $playlist = $(this).closest(".playlist");
		var playlistID = $playlist.data("playlist_id");

		if (playlistID != "" && playlistID > 0) {
			$.get("/scripts/jplayer/playlist-share.php", {
					playlist_id: playlistID,
					is_currently_shared: $playlist.data("is_shared")
				},
				function(data) {
					try {
						var json = $.parseJSON(data);
						if (json.success) {
							// successfully updated playlist
							$playlist.highlightFade();
							$playlist.data("is_shared", json.new_shared_value);

							// update the "share" data
							dlgPlaylistManager._setSharedIconsForPlaylist($playlist, json.new_shared_value);
						}
					} catch (err) { debugLog(err); }
				},
				'html'
			);
		}
		else {
			debugLog("Invalid playlist for toggling shared");
		}
	};

	/**
	 * private static helper to hide/show the appropriate
	 * icons for this playlist, given it's shared status
	 *
	 * @param $playlist a jquery div representing the playlist
	 * @param is_shared 0 or 1 indicating whether the playlist is currently shared
	 * @return void
	 */
	dlgPlaylistManager._setSharedIconsForPlaylist = function($playlist, is_shared) {
		if (is_shared == 1) {
			$playlist.find(".pl-is-unlocked").show();
			$playlist.find(".pl-is-locked").hide();
		} else {
			$playlist.find(".pl-is-unlocked").hide();
			$playlist.find(".pl-is-locked").show();
		}
	};

	/**
	 * Rename playlist. Make ajax call and then refresh playlist dropdown
	 */
	dlgPlaylistManager.prototype.renamePlaylist = function() {
		var $playlist    = $(this).closest(".playlist");
		var playlistID   = $playlist.data("playlist_id");
		var currentTitle = $playlist.find(".playlist-title").text();

		if ($playlist.parent().hasClass("pl-group-playlists") > 0 && playlistID != "" && playlistID > 0) {
			jPrompt("Enter a title for the playlist", currentTitle, 'Rename Playlist', function(newTitle) {
				if (newTitle != null && newTitle != currentTitle) {
					$.post("/scripts/jplayer/playlist-rename.php", {
							playlist_id: playlistID,
							new_title: newTitle
						},
						function(data) {
							try {
								var json = $.parseJSON(data);
								if (json.success) {
									// update playlist list
									dlgPlaylistManager.refreshLists(function() {
										dlgPlaylistManager.highlightPlaylist(playlistID); // highlight the updated playlist after reload
									});

									// if this playlist is the current playlist, update "Current Playlist" title as well
									if (kpl.currentListID == playlistID && kpl.list_type == "playlist") {
										// update title but keep type and id the same...
										kpl.setCurrentPlaylist(newTitle, kpl.list_type, kpl.currentListID);
									}
								} else {
									jAlert( (json.message? json.message: "Oops! Failed to rename playlist") );
								}
							} catch (err) { debugLog(err); }
						},
						'html'
					);
				}
			});// end jPrompt
		} else {
			jAlert("Can't rename that playlist");
		}
	}; // end renamePlaylist

	/**
	 * Clone Bookmark/Favorite Playlist:
	 */
	dlgPlaylistManager.prototype.copyBookmarkToMyPlaylists = function ()
	{
		var $playlist     = $(this).closest(".playlist");
		var playlistID    = $playlist.data("playlist_id");
		var playlistTitle = $playlist.find(".playlist-title").text();

		if ($playlist.length > 0 && playlistID != "" && playlistID > 0) {
			jPrompt("Enter a new title for the playlist", playlistTitle, "Copy Bookmark to My Playlists",
				function(newTitle) {
					if (newTitle == "") {
						jAlert("Title can't be blank");
					}
					else if (newTitle != null) {
						$.post("/scripts/jplayer/save-as-my-playlist.php", {
								playlist_id: playlistID,
								new_title:   newTitle
							},
							function(data) {
								try {
									var json = $.parseJSON(data);
									if (json.success) {
										// update playlist list and highlight new entry for a moment
										dlgPlaylistManager.refreshLists(function() {
											if (json.new_playlist_id > 0) {
												dlgPlaylistManager.highlightPlaylist(json.new_playlist_id);
											}
										});
									} else {
										jAlert( (json.message? json.message: "Oops! Failed to copy playlist") );
									}
								} catch (err) {
									debugLog(err);
								}
							},
							'html'
						); // $.post
					}
				}
			); // end jPrompt
		} else {
			jAlert("Can't copy that playlist");
		}
	}; // end copyBookmarkToMyPlaylists


	dlgPlaylistManager.prototype.deleteWrapper = function () { // Delete a playlist or playlist bookmark

	};

	/**
	 * Playlist manager: delete playlist
	 *
	 * @param clickedObj the original DOM element that was clicked
	 */
	dlgPlaylistManager.prototype.deletePlaylist = function() {

		// save "this" so we can reference it inside of kConfirmDelete.delete_func
		var clickedObj = this;

		$(clickedObj).kConfirmDelete({
			delete_func: function() {
				var $playlist = $(clickedObj).closest(".playlist");
				var playlistID = $playlist.data("playlist_id");

				var pl_type = null;
				var ajax_script_url = "";
				var isFavorite = false;

				if ($playlist.length > 0) {
					if ($playlist.parent().hasClass("pl-group-playlists")) {
						// mine
						pl_type = "playlists";
						ajax_script_url = "/scripts/jplayer/delete-playlist.php";
					} else if ($playlist.parent().hasClass("pl-group-playlist_favorites")) {
						// favorite/bookmark
						pl_type = "playlist_favorites";
						ajax_script_url = "/scripts/jplayer/delete-playlist-favorite.php";
						isFavorite = true;
					}
				}

				if (playlistID != "" && playlistID > 0 && ajax_script_url != "")
				{
					$.get(ajax_script_url, {
							playlist_id: playlistID
						},
						function(data) {
							try {
								var json = $.parseJSON(data);
								if (json.success)
								{
									// successfully deleted playlist, remove from list
									$playlist.remove();

									if (isFavorite) {
										// Also! If we're currently in profile-view:
										//    update the "Add to favorites" button to reflect that this favorite
										//    has been removed. (See getProfile.php)
										var $profile_playlist_item = $("#profile-playlist-"+playlistID);
										$profile_playlist_item.parent()
											.find("button.add-pl-to-favorites")
											.find("span")
											.text("Add to Favorites +");
									}
								}
							} catch (err) { debugLog(err); }
						},
						'html'
					); // $.get
				}
				else {
					if (pl_type == null) {
						jAlert("Oops! Could not determine playlist type");
					} else {
						jAlert("Please select a playlist first");
					}
				}
			}
		});
	}; // end deletePlaylist

	/**
	 * refresh the playlist list
	 *
	 * (formerly standalone fn: jpUpdatePlaylistMgrList)
	 *
	 * @param success_callback_fn optional callback function to be executed on success
	 */
	dlgPlaylistManager.refreshLists = function(success_callback_fn) {
		var $my_pl_group   = $(".pl-group-playlists");
		var $fave_pl_group = $(".pl-group-playlist_favorites");

		$.get("/scripts/jplayer/sl-get-playlists.php", {}, function(data) {
			try {
				// Process JSON and update playlist list
				var json = $.parseJSON(data);
				if (json.success) {
					$my_pl_group.empty();
					$fave_pl_group.empty();
					var $newPlaylist = null;
					$.each(json.playlists, function (i, pl) { // add to "My Playlists" optgroup
						dlgPlaylistManager._add_playlist_line(pl, $my_pl_group, false);
					});
					$.each(json.playlist_favorites, function (i, pl) { // add to "Favorites" optgroup
						dlgPlaylistManager._add_playlist_line(pl, $fave_pl_group, true);
					});

					if (success_callback_fn) { success_callback_fn(); } // execute optional callback function
				}
			} catch (err) { debugLog(err); }
		},'html');
	}; // end dlgPlaylistManager.refreshLists

	/**
	 * private static helper function to create a playlist line and add to whichever
	 * group is appropriate. Used by dlgPlaylistManager.refreshLists()
	 *
	 * The process is mostly the same but the bookmarks require slightly
	 * different settings (which icons are shown, etc).
	 *
	 * @param pl data map
	 * @param $appendTo one of "My playlists" or "my bookmarks" groups
	 * @param isFavorite boolean indicating whether it's a bookmark or not
	 */
	dlgPlaylistManager._add_playlist_line = function(pl, $appendTo, isFavorite)
	{
		var $tpl = $("#knerd-playlist-template");
		var $newPlaylist = $tpl.clone();
		var playlistData = {playlist_id: pl.id};

		if (isFavorite) { // FOR "MY BOOKMARKS"
			$newPlaylist.find(".btn-pl-share-toggle").remove();      // bookmarks can't be shared, so remove those icons.
			$newPlaylist.find(".btn-copy-to-my-playlists").show();   // they can be copied to "my playlists", so show that icon
		}
		else {            // FOR "MY PLAYLISTS"
			playlistData.is_shared = pl.is_shared;                   // This property not set in 'bookmarked' PL's.
			dlgPlaylistManager._setSharedIconsForPlaylist($newPlaylist, pl.is_shared);  // Sets visibility on padlock icons appropriately.
		}

		$newPlaylist.find(".btn-pl-delete").show(); // Show 'delete' icon.

		$newPlaylist                                // Associate data and set title.
			.data(playlistData)
			.attr("id", "playlist-mgr-playlist-"+pl.id)
			.find(".playlist-title").text(pl.playlist_title);

		$newPlaylist                                // Add to list and make visible.
			.appendTo($appendTo)
			.show();

		return $newPlaylist;
	};

	/**
	 * call $.highlightFade() on a specific playlist (given its playlist_id)
	 * @param playlistID
	 */
	dlgPlaylistManager.highlightPlaylist = function(playlistID) {
		(dlgPlaylistManager.getPlaylistById(playlistID)).highlightFade();
	};

	/**
	 * locate playlist in the playlist manager via jquery selector and return
	 * @return jQuery
	 */
	dlgPlaylistManager.getPlaylistById = function(playlistID) {
		return $("#playlist-mgr-playlist-"+playlistID);
	};

/**
 * END: "playlist manager" dialog encapsulation
 */



/**
 * BEGIN: active playlist pane (literally, the list of songs playing) encapsulation
 */
function ActivePlaylist() { this.doSetup(); }

	ActivePlaylist.prototype.doSetup = function() { };

	/**
	 * Add single track to playlist and begin playing
	 * @param songID
	 */
	ActivePlaylist.playSong = function(songID) {
		ActivePlaylist._loadPlayerWith('song', songID);
	};

	/**
	 * Add entire album to playlist and play first track
	 * @param albumID
	 */
	ActivePlaylist.playAlbum = function(albumID) {
		ActivePlaylist._loadPlayerWith('album', albumID);
	};

	/**
	 * Add entire songlist (playlist) to the jplayer playlist, and play first track
	 * @param playlistID
	 */
	ActivePlaylist.playPlaylist = function(playlistID) {
		ActivePlaylist._loadPlayerWith('playlist', playlistID);
	};

	/**
	 * generic function to load up player with either an album or playlist
	 * depending on what is passed in
	 * @param type E.g. "album" or "playlist"
	 * @param id the numeric complement of id_label
	 * @param displayPlaylistTitle this will be used for display in the "Current Playlist:" bar
	 * @param autoPlay optional boolean, defaults to true. If false, will send songs to playlist but not "play"
	 */
	ActivePlaylist._loadPlayerWith = function(type, id, displayPlaylistTitle, autoPlay) {
		//var start_of_new_pl = $.jp_playlist.length + 1;

		ActivePlaylist.clearSongs("Loading..."); // clear out old songs, and set "loading" title

		// make sure song list is visible ... it's possible the user was in the playlist manager view
		// or in the 'add to playlist' view when they clicked a listen link elsewhere on the page
		if ($("#jp_add_to_playlist_dlg").is(":visible")) {
			// "Add to playlist" dialog was open:
			$("#jp_add_to_playlist_dlg .close-btn").click(); // close 'Add to playlist' dialog
			$("#btn-add-to-playlist").button("disable");     // and disable 'add to playlist' button
		}
		else if ($("#jp_playlist_manager").is(":visible")) {
			// "playlist manager" dialog was open
			// trigger a click on the 'credit' line to transition back to playlist view
			$("#jplayer_knerd_pl_credit_line").click();
		}

		// If a new title was provided, save it for later
		// will display after songs loaded. Otherwise, needTitle==true
		// will tell the json block to get the title from ajax.
		var needTitle = false;
		var setTitleTo = "Failed to load";
		if (typeof(displayPlaylistTitle) == "undefined" || displayPlaylistTitle == null || displayPlaylistTitle == "") {
			needTitle = true;
		}
		else {
			setTitleTo = displayPlaylistTitle;
		}

		// autoPlay is optional, and defaults to true
		if (autoPlay === undefined) {
			autoPlay = true;
		}

		// Get songs via ajax
		$.get("/scripts/sl-get-songs-info.php", {
				type: type,
				id: id
			},
			function(data) {
				try {
					var json = $.parseJSON(data);
					if (json.success)
					{
						// add each song to the playlist
						$.each(json.songs, function(k,song) {
							ActivePlaylist.sendSongToPlayer(song, json.album_credits);
						});

						$.jp_displayPlayList();

						if (needTitle) { setTitleTo = json.opt_title; }

						// play first item in [new] playlist
						//var $playMe = $("#jplayer_playlist ul li:nth-child("+start_of_new_pl+")");
						var $playMe = $("#jplayer_playlist .ul .li:first");
						if ($playMe.size() > 0) {
							//$.jp_playListChange ( $playMe );
							$.playListInit(autoPlay);
						}
						else {
							jAlert("No songs found");
						}
					}
					else {
						jAlert("No songs found");
					}
				} catch (err) {
					// error parsing json
					jAlert("Oops! An error occured and we were unable to load this playlist");
					debugLog(err);
				}

				kpl.setCurrentPlaylist( setTitleTo, type, id); // Finally, set title (happens even if failed to load..)
			},
			'html'
		);
	}; // end _loadPlayerWith

	/**
	 * Formats HTML for song and pushes onto jplayer's playlist array (but doesn't add to the DOM)
	 * @param jSong JSON object from sl-get-songs-info.php. Must have at least the following fields:
	 *              song_title, album_title, album_copyright, band_name
	 * @return void
	 */
	ActivePlaylist.sendSongToPlayer = function(jSong, album_credits) {
		var songHTML =
			"<span class='song-title'>  " +      jSong.song_title      + "</span> | " +
			//"<span class='album-title'> " +      jSong.album_title     + "</span>" +
			//"<span class='album-copyright'> (" + jSong.album_copyright + ") </span> - " +
			"<span class='band-name'> " +        jSong.band_name       + "</span>";

		// July 20th, 2010 - Rob requested that track number be removed
//		// if song title doesn't start with a track number, prepend it
//		if ( !jSong.song_title.match(/^[0-9]+.*/) ) {
//			var trackNum = jSong.song_track_number;
//			if (trackNum == null) {
//				trackNum = "";
//			}
//			else if (trackNum.length < 2) {
//				trackNum = "0" + trackNum;
//			}
//			songHTML = "<span class='track-number'>" + trackNum + " </span> " + songHTML;
//		}

		// Build song and album credits. Each song in the playlist
		// will have these credit HTML blocks associated with them
		// so that we can easily update the credits when playlist
		// changes.
		var songCreditsHTML  = "";
		var albumCreditsHTML = "";
		$.each(jSong.song_credits, function(i, song_role) { // SONG credits
			songCreditsHTML += "<div>" + song_role.person_name + " - " + song_role.role_name + "</div>\n";
		});
		$.each(album_credits, function(i, album_role) {     // ALBUM credits
			albumCreditsHTML += "<div>" + album_role.person_name + " - " + album_role.role_name + "</div>\n";
		});
		// End: credits

		var song_info = {
			// name and mp3 are all jplayer really needs
			name: songHTML,
			mp3: "/scripts/tokens/gettoken.php?song_id="+jSong.id,

			// the rest are for knerd magic
			song_id:            jSong.id,
			last_played_at:     null,
			song_title:         jSong.song_title,
			band_name:          jSong.band_name,
			song_credits_html:  songCreditsHTML,
			album_credits_html: albumCreditsHTML,
			album_title:        jSong.album_title,
			album_copyright:    jSong.album_copyright
		};

		$.jp_playlist.push(song_info);
	}; // end ActivePlaylist.sendSongToPlayer


	/**
	 * Empties $jplayer_playlist (active playlist pane) and sets the "Current Playlist:" title
	 * @param displayPlaylistTitle new title for display (optional. Default is "None")
	 * @return void
	 */
	ActivePlaylist.clearSongs = function(displayPlaylistTitle) {
		$.jp_playlist = []; // discard old playlist content by setting to empty array
		$.jp_displayPlayList(); // displays the new empty array

		var clearedTitle = "None";
		if (typeof(displayPlaylistTitle) != "undefined" && displayPlaylistTitle) {
			// if title was provided, use it
			clearedTitle = displayPlaylistTitle;
		}

		kpl.setCurrentPlaylist( clearedTitle, null, -1 ); // clear playlist tracking info
		kPlaylistButtons.disableAll(); // re-disable all buttons again..
	};

	/**
	 * wrapper for getSongIDs that restricts the results to songs that are selected
	 *
	 * @return Array of song ids
	 */
	ActivePlaylist.getSelectedSongIDs = function() {
		return ActivePlaylist.getSongIDs(".ui-selected");
	};

	/**
	 * Utility function to loop through jplayer_playlist and grab the song_ids.
	 * if the optional filter parameter is provided, will only return song ids
	 * for songs that match the filter.
	 *
	 * @param useFilter an optional jquery selector for filtering the results. E.g. ".ui-selected"
	 * @return Array of song ids
	 */
	ActivePlaylist.getSongIDs = function(useFilter) {
		var $selectedSongs = $("#jplayer_playlist .ul .li");
		if (useFilter) {
			$selectedSongs = $selectedSongs.filter(useFilter);
		}
		var selected_song_ids = [];
		$.each($selectedSongs, function(i,el) {
			selected_song_ids.push(
				$.jp_playlist[$(el).data("index")].song_id
			);
		});
		return selected_song_ids;
	};


	/**
	 * user wants to remove song from Playlist
	 * delegate 'confirmation' process to kConfirmDelete
	 */
	ActivePlaylist.removeSong = function() {
		$liSong = $(this).parent();
		$(this).kConfirmDelete({
			delete_func: function() {
				$liSong.qtip("destroy").fadeOut("fast", function() { $(this).remove(); } );

				// playlist has changed, enable the 'update playlist' button
				$("#btn-update-playlist").button("enable");
			}
		});
	};

	/**
	 * click  'play' icon next to a specific track already in the playlist.
	 *
	 * $(this).parent() is the "song" in the DOM, so pass that to $.jp_playListChange
	 */
	ActivePlaylist.switchToSong = function() {
		$.jp_playListChange( $(this).parent() );
	};


	/**
	 * Utility fn to check if the current active playlist is listed under "My Playlists", if so return true.
	 * Otherwise if we're playing an album, song, or someone else's playlist, return false.
	 *
	 * @return boolean true if the user "owns" the active playlist. False otherwise.
	 */
	ActivePlaylist.isActivePlaylistMine = function() {
		if (kpl.list_type == "playlist") {
			var $currPlaylist = dlgPlaylistManager.getPlaylistById(kpl.currentListID);
			if ($currPlaylist.closest(".pl-group").hasClass("pl-group-playlists")) {
				return true;
			}
		}
		return false;
	};
/**
 * END: active playlist pane (literally, the list of songs playing) encapsulation
 */









///////// below is an adapted version of the JS provided by jplayer

jQuery(document).ready(function($) {

	$jplayer_playlist = $("#jplayer_playlist .ul");
	kpl               = new kNerdPlaylistState();
	kbtns             = new kPlaylistButtons();
	plManager         = new dlgPlaylistManager();

	// keep track of whether we've marked the current song as played or not
	var kMarkedAlready        = false;
	// temp var to track currently playing song (in onProgressChange we need max performance because it's called every 100ms)
	var kSong                 = null;
	var kExpireTime           = 300*1000; // number of milliseconds between song plays before we consider an ajax call
	var kPlayPercentThreshold = 7.0;


	var playItem = 0;

	// initialize empty play list
	$.jp_playlist = [];

	// Local copy of jQuery selectors, for performance.
	var jpPlayTime  = $("#jplayer_play_time");
	var jpTotalTime = $("#jplayer_total_time");


	$("#jquery_jplayer").jPlayer({
		ready: function() {
			$.jp_displayPlayList();
			$.playListInit(false); // Parameter is a boolean for autoplay.
		},
		oggSupport: false,
		nativeSupport: false,
		swfPath: "/assets/jplayer/",
		volume: 30
	})
	.jPlayer("onProgressChange", function(loadPercent, playedPercentRelative, playedPercentAbsolute, playedTime, totalTime) {
		jpPlayTime.text($.jPlayer.convertTime(playedTime));
		jpTotalTime.text($.jPlayer.convertTime(totalTime));

		// begin knerd playcount check
		if (!kMarkedAlready && playedPercentAbsolute > kPlayPercentThreshold)
		{
			kMarkedAlready = true; // so that we don't get into this block again
			/**
			 * determine the song that's currently playing, and if it's not already
			 * been marked played, fire off an ajax event to mark it as played
			 */
			kSong = $.jp_playlist[playItem];
			if (	kSong.last_played_at == null ||
					((new Date().getTime() - kSong.last_played_at ) > kExpireTime)
				)
			{
				// Song had either not been marked yet, or had been marked but not within the defined
				// threshold. (PHP Side checks threshold too, but we check here to save an ajax call when possible)
				$.get("/scripts/add-song-play.php", {
					song_id:  kSong.song_id
				});
			}
			kSong.last_played_at = new Date().getTime();
		}// end knerd playcount check
	})
	.jPlayer("onSoundComplete", function() {
		playListNext();
	});

	$("#jplayer_previous").click( function() {
		playListPrev();
		return false;
	});

	$("#jplayer_next").click( function() {
		playListNext();
		return false;
	});

	/**
	 * Formats and adds songs to the DOM. Also associates data and sets up bindings for each song
	 * entry.
	 */
	$.jp_displayPlayList = function() {
		$jplayer_playlist.empty(); // clear out old items (barwin)

		// Enable 'clear playlist' button if there's at least one song in the new list
		if ($.jp_playlist.length > 0) { $("#btn-clear-playlist").button("enable" ); }
		else                          { $("#btn-clear-playlist").button("disable"); }

		// Disable the 'Update Playlist' button.  It will be enabled if any changes are made
		// (I.e. if tracks are re-ordered or removed)
		$("#btn-update-playlist").button("disable");


		// Loop thru array of playlist song objects
		for (i=0; i < $.jp_playlist.length; i++)
		{
			// Construct formatted song <div> and append it to $jplayer_playlist
			$newSong = $("<div id='jplayer_playlist_item_"+i+"' class='li'>" + // added class="li" because it used to be <li>
					"<span class='ui-icon ui-icon-play btn-play-song' title='play song'></span>" + // "play"
					"<div class='song-info'>" +
						"<span class='track-song-band'>" + $.jp_playlist[i].name + "</span>" +
						"<span class='credits-scroll'>" +
							$.jp_playlist[i].song_credits_html + $.jp_playlist[i].album_credits_html +
						"</span>" +
					"</div>" +
					"<span class='ui-icon pl-drag-arrow drag-handle' title='drag to reorder songs'></span>" + // the 'handle' for re-ordering
					"<span class='ui-icon pl-icon-circle-minus btn-remove-song' title='remove song fro playlist'></span>" + // the 'delete' button
					"<div style='clear:both;'></div>"+
					"</div>")
			.data( "index", i ) // Save song's index into the $.jp_playlist array so we can reference it's properties later
			.appendTo( $jplayer_playlist )
			.qtip({
				// show tooltip when song is moused over. Get content dynamically from qtip-song-rollover.php
				content: {
					url: "/scripts/jplayer/qtip-song-rollover.php",
					data: {
						song_id:         $.jp_playlist[i].song_id,
						song_title:      $.jp_playlist[i].song_title,
						band_name:       $.jp_playlist[i].band_name,
						album_credits:   $.jp_playlist[i].album_credits_html,
						song_credits:    $.jp_playlist[i].song_credits_html,
						album_title:     $.jp_playlist[i].album_title,
						album_copyright: $.jp_playlist[i].album_copyright
					},
					method: 'get'
				},
				show: { when: 'mouseover', delay: 500 },
				hide: { when: 'mouseout', fixed: true },
				position: {
					corner: { target: 'leftMiddle', tooltip: 'rightMiddle' }
				},
				style: {
					border: { width: 3, radius: 10, color: '#E17009'},
					width: 250
				}
			});

//			$newSong.find(".credits-scroll").serialScroll({
//				items: 'div',
//				duration: 2000,
//				force: true,
//				axis: 'y',
//				easing: 'linear',
//				lazy: true,// NOTE: when true, you can add/remove/reorder items and the changes are taken into account.
//				interval:1, // yeah! I now added auto-scrolling (ba: was 1 initially, changed to 0)
//				step:1, // scroll this many items each time
//
//				cycle: true,
//				event: 'click'
//			});

		}
	};

	$.playListInit = function(autoplay) {
		var $firstItem = $jplayer_playlist.find("div:first");
		if(autoplay) {
			$.jp_playListChange( $firstItem );
		} else {
			playListConfig( $firstItem );
		}
	};

	// Sets up the current track
	function playListConfig($liSong) {
		// give active css class to new track ($liSong) after removing it from prior active track.
		$jplayer_playlist.find(".jplayer_playlist_current").removeClass("jplayer_playlist_current");
		$liSong.addClass("jplayer_playlist_current");

		if ( $liSong.length > 0) {
			playItem = $liSong.data("index");
			$("#jquery_jplayer").jPlayer("setFile", $.jp_playlist[playItem].mp3, $.jp_playlist[playItem].ogg);

			kMarkedAlready = false; // reset play flag (knerd)
		}
	}

	$.jp_playListChange = function ( $liSong ) {
		playListConfig($liSong);
		$("#jquery_jplayer").jPlayer("play"); // TODO: re-enable
		kpl.trackSong( $liSong ); // adds to the Songs I've Played tracker list..
	};

	function playListNext() {
		var $next = $jplayer_playlist.find("div.jplayer_playlist_current").next();
		if ($next.length == 0) {
			// last song ...
			$("#jquery_jplayer").jPlayer("stop");
		}
		else {
			$.jp_playListChange($next);
		}
	}

	function playListPrev() {
		var $prev = $("#jplayer_playlist .ul .li.jplayer_playlist_current").prev();
		if ($prev.length == 0) {
			// can't go any further back - at first song.
			$("#jquery_jplayer").jPlayer("stop");
		}
		else {
			$.jp_playListChange($prev);
		}
	}


	/////// BEGIN: click event bindings for buttons and icons across the different playlist dialogs

	//////////// Bindings for playlist buttons Add, Update, Clear ///////
	$("#btn-update-playlist").click( kPlaylistButtons.clickUpdatePlaylist );
	$("#btn-clear-playlist") .click( kPlaylistButtons.clickClearPlaylist );
	$("#btn-add-to-playlist").click( kPlaylistButtons.clickAddToPlaylist );

	///////////// Bindings for "playlist manager" dialog (visible when user clicks the 'current playlist' bar ///////////
	$("#jp_playlist_manager .playlist .btn-pl-play")             .live("click", plManager.clickPlayPlaylist ); // switch player to new playlist
	$("#jp_playlist_manager .playlist .btn-pl-share-toggle")     .live("click", plManager.shareMyPlaylist   ); // will toggle sharedness
	$("#jp_playlist_manager .pl-group-playlists .playlist .playlist-title").live("click", plManager.renamePlaylist    ); // bring up dialog to rename //
	$("#jp_playlist_manager .playlist .btn-copy-to-my-playlists").live("click", plManager.copyBookmarkToMyPlaylists );
	$("#jp_playlist_manager .playlist .btn-pl-delete")           .live("click", plManager.deletePlaylist    );

	/////////////////// Bindings for the active/main playlist ////////////////
	$(".btn-remove-song").live("click", ActivePlaylist.removeSong );   // Remove a song from active playlist
	$(".btn-play-song")  .live("click", ActivePlaylist.switchToSong ); // "play" icon for individual songs

	//////////// Bindings for 'Add to playlist' dialog /////////////
	$("#jp_add_to_playlist_dlg .close-btn")          .click( dlgAddToPlaylist.closeDialog       ); // (go back to active playlist)
	$("#jp_add_to_playlist_dlg #btn-go-new-playlist").click( dlgAddToPlaylist.createNewPlaylist ); // clicked "Go" button in "add to playlist"

	////////////// END click bindings


	// playlist song list needs to be 'sortable' (allows elements to be dragged and re-ordered)
	// and selectable (for adding multiple songs to a given playlist)
	$("#jplayer_playlist .ul")
	.sortable({
		handle: ".drag-handle", // CSS class of the element user can "drag" song by to re-order
		stop: function() {      // Called after songs are re-ordered
			$("#btn-update-playlist").button("enable"); // enable "Update Playlist" button
		}
	})
	.selectable({
		// Events originating in an element with one of the styles
		// listed below in the 'cancel' property will be ignored by 'selectable'
		// (Allows you to click icons without 'selectable' interfering!)
		cancel: ".drag-handle,.ui-icon,.kconfirm-message",

		// Enable "Add to playlist" button if ANY songs are selected.
		// Otherwise disable the button.
		stop: function(event,ui) {
			if ($("#jp_active_playlist > .ui-selected").size() > 0) {
				$("#btn-add-to-playlist").button("enable");
			} else {
				$("#btn-add-to-playlist").button("disable");
			}
		}
	}); // end .selectable()


	// knerd "accordion" functionality for playlist
	$("#jplayer_knerd_pl_credit_line").click(function() {
		$(".jp_accordion_part").slideToggle();
		$(this).find(".triangle").toggle();
	});

	// do an initial "update" of the playlist list, just to get it populated initially
	dlgPlaylistManager.refreshLists();

}); // end ready()


/**
 * Custom jquery function that turns any element into an in-place delete/confirmation
 * widget.  The first click triggers an inplace confirmation message, and the second
 * click triggers the intended delete.  The ultimate delete action is defined
 * by the delete_func function reference (can be an anonymous function or a reference
 * to a function already defined)
 *
 * Note: meant to be run on a SINGLE element!
 *
 * @param settings override default settings if you want
 * @param delete_func the callback function to be called when user confirms in the positive
 * @return this
 */
function kConfirmDelete(settings) {
	//
	// BEGIN: Config
	//
	var config = { // Set up the default settings (user can override)
		confmsg: "Click to Confirm",
		classesToRemove: [],
		delete_func: function() {
			// This function should pretty much always be overridden.. still
			// here's a reasonable default: fade out and remove the parent element from the DOM
			debugLog("kConfirmDelete:: this method should be overridden!");
			//debugLog(config.target_delete_obj);
			config.target_delete_obj.fadeOut("slow", function() { $(this).remove(); } );
		},
		target_delete_obj: $(this).parent(), // the element to remove from the DOM
		confirm_container: $(this).parent() // element in which to append the kconfirm-message message (must be positioned)

	};
	if (typeof(settings) != "undefined" && settings) { $.extend(config, settings); }

	if (config.classesToRemove == "" || config.classesToRemove == null) { config.classesToRemove = []; }

	//
	// END: config
	//


	// Note: wrapping in $.each() to comply with jQuery standards, but results
	// would be unpredictable if kConfirmDelete were applied to more than one element
	// at a time anyway.
	this.each(function()
	{
		$icon = $(this);

		// "Empty" the icon's contents, removing any prior messages
		$icon.empty();

		// Remove icon class(es) so we can show text rather than icon image
		var removedClasses = []; // keep track of which classes we removed (if any), so we can add them back later
		for (var c=0; c < config.classesToRemove.length; c++) {
			var cssClass = config.classesToRemove[c];
			if ($icon.hasClass( cssClass )) {
				$icon.removeClass  ( cssClass );
				removedClasses.push( cssClass );
			}
		}

		// Build and show confirmation with one-time bindings for the final click OR a 'mouseout' indicating an 'abort'
		// of the delete process.
		$("<div class='kconfirm-message'>"+config.confmsg+"</div>")
			.one("click", { icon: $icon }, config.delete_func) // bind 'delete_func' to the 2nd (confirming) click.
			.one("mouseout", function() { // revert, user left 'delete' area, so abort the delete process.
				for (var c=0; c < removedClasses.length; c++) {
					$icon.addClass( removedClasses[c] ); // add back the css classes we removed, if any..
				}
				$(this).remove(); // remove confirmation message (reverting to icon image only)
			})
			.appendTo( config.confirm_container ); // now we're adding to the icon's parent by default..
	});
	return this;
} // end kConfirmDelete custom jquery fn
$.fn.kConfirmDelete = kConfirmDelete;