/***
*	Playlist 0.0.1
*
*	A public class representing a music program playlist in a cononical JSON Object format
* 	NOTE:  Probably should be an implementation of an abstract interface, but this is
*	currently the only file format specified for the playlist. 
*
*	(c) 2006 Tachometry Corporation.  
*	contact: support@tachometry.com
*
*	Licensed under the Apache License (v. 2.0)
*	http://www.apache.org/licenses/LICENSE-2.0
*/                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
/**
*	Constructor
*/
function Playlist( pl )
{
	this.init( pl );
}

/**
*	Initializor
*/ 
Playlist.prototype.init = function( pl ) 
{    
	this.ENTRIES_KEY = "entries";
	this.START_KEY = "start";
	this.ARTIST_KEY = "artist";
	this.TRACK_KEY = "track";
	this.ALBUM_KEY = "album";
	this.LISTEN_KEY = "listen";
	this.BUY_KEY = "buy";
	this.PREVIOUS_ENTRY_CSS_CLASS = "entry"; // css class definition 
	this.CURRENT_ENTRY_CSS_CLASS = "current"; // css class definition 
	this.NEXT_ENTRY_CSS_CLASS = "next"; // css class definition 
	this.list = pl; //|| new Object(); // JSON playlist object
	this.current_entry_index = 0;
	this.current_entry = new Object;  // a cached enry object
}

Playlist.prototype.getEntriesKey = function ( )
{
	return this.ENTRIES_KEY;
};
	
Playlist.prototype.setEntriesKey = function ( key )
{
	this.ENTRIES_KEY = key;
};

Playlist.prototype.getStartKey = function ( )
{
	return this.START_KEY;
};
	
Playlist.prototype.setStartKey = function ( key )
{
	this.START_KEY = key;
};

Playlist.prototype.getArtistKey = function ( )
{
	return this.ARTIST_KEY;
};
	
Playlist.prototype.setArtistKey = function ( key )
{
	this.ARTIST_KEY = key;
};

Playlist.prototype.getTrackKey = function ( )
{
	return this.TRACK_KEY;
};
	
Playlist.prototype.setTrackKey = function ( key )
{
	this.TRACK_KEY = key;
};

Playlist.prototype.getAlbumKey = function ( )
{
	return this.ALBUM_KEY;
};
	
Playlist.prototype.setAlbumKey = function ( key )
{
	this.ALBUM_KEY = key;
};

Playlist.prototype.getListenKey = function ( )
{
	return this.LISTEN_KEY;
};
	
Playlist.prototype.setListenKey = function ( key )
{
	this.LISTEN_KEY = key;
};

Playlist.prototype.getBuyKey = function ( )
{
	return this.BUY_KEY;
};
	
Playlist.prototype.setBuyKey = function ( key )
{
	this.BUY_KEY = key;
};

Playlist.prototype.getNextCssKey = function ( )
{
	return this.NEXT_ENTRY_CSS_CLASS;
};
	
Playlist.prototype.setNextCssKey = function ( key )
{
	this.NEXT_ENTRY_CSS_CLASS = key;
};

Playlist.prototype.getPreviousCssKey = function ( )
{
	return this.PREVIOUS_ENTRY_CSS_CLASS;
};
	
Playlist.prototype.setPreviousCssKey = function ( key )
{
	this.PREVIOUS_ENTRY_CSS_CLASS = key;
};

Playlist.prototype.getCurrentCssKey = function ( )
{
	return this.CURRENT_ENTRY_CSS_CLASS;
};
	
Playlist.prototype.setCurrentCssKey = function ( key )
{
	this.CURRENT_ENTRY_CSS_CLASS = key;
};

Playlist.prototype.getList = function ( )
{
	return this.list;
};
	
Playlist.prototype.setList = function ( json_playlist_object )
{
	// TODO:  Identify and implement error handling 
	this.list = json_playlist_object;
};

Playlist.prototype.getCurrentEntryIndex = function ( )
{
	return this.current_entry_index;
};

Playlist.prototype.setCurrentEntryIndex = function ( entry_index )
{
	if ( this.isInRange( entry_index ) ) {
		this.current_entry_index = entry_index;
	}
};


/* Is the given entry_index in range of the available playlist? */
Playlist.prototype.isInRange = function( index )
{
	if (index < 0 || index > this.getNumEntries() - 1 ) {
		return false;
	} else {
		return true;
	}
}

/* Returns an entry object. If no entry_index is given, will return the current entry */
Playlist.prototype.getEntryByIndex = function ( index )
{
	var index = index || this.getCurrentEntryIndex();
	var target_entry = eval('this.getList().'+ this.toSafe( this.getEntriesKey() ) +'['+ parseInt( index ) +']');
	
	// Make sure that it is a valid entry
	if (this.isValidEntry( target_entry ) ) {
		return this.current_entry;
	} else {
		throw new Error("There was an error retrieving the entry element for index "+ index);
	}
};

/* 	Gets the current entry object.  Convenience method for current entry.  
*	All other entries can be retrieved by using getEntryByIndex( index )
*/
Playlist.prototype.getCurrentEntry = function ( entry_index )
{
	return this.getEntryByIndex( this.getCurrentEntryIndex() );
};
	
/* Sets the current_entry object by given entry index */
Playlist.prototype.setCurrentEntry = function ( entry_index )
{
	this.current_entry = eval('this.getList().'+ this.toSafe( this.getEntriesKey() ) +'['+ parseInt( entry_index ) +']');
};

Playlist.prototype.getNumEntries = function ()
{
	var count = 0;
	var list = this.getList();
	var entries = list[ this.getEntriesKey() ];
	if (entries) {
		for (var i in entries ) {
			var start = entries[i][ this.getStartKey() ];
			if ( start )  {
				count++;
			}
		}
	}
	return count;
};

/* Returns the start value for the given element index.  defaults to the current element index */
Playlist.prototype.getStart = function( index )
{
	var index = isNaN(index) ? this.getCurrentEntryIndex() : index; 
	// The index should never be larger than the number of entries.
	if ( index < this.getNumEntries() ) {
		return eval('this.getList().'+ this.toSafe( this.getEntriesKey() ) +'['+ parseInt( index ) +'].'+ this.toSafe( this.getStartKey() ) );
	}	else {
		throw new Error("The given index, "+ index +" is out of range.");
	}
};

/* Returns the start value for the given element index in milliseconds */
Playlist.prototype.getStartInMillis = function( index )
{
	var index = index;
	return this.getMillisFromHMS( this.getStart( index ) );
};

Playlist.prototype.getArtist = function( index )
{
	index = index || this.getCurrentEntryIndex();
	return eval('this.getList().'+ this.toSafe( this.getEntriesKey() ) +'['+ parseInt( index ) +'].'+ this.toSafe( this.getArtistKey() ) );
};

Playlist.prototype.getTrack = function( index )
{
	index = index || this.getCurrentEntryIndex();
	return eval('this.getList().'+ this.toSafe( this.getEntriesKey() ) +'['+ parseInt( index ) +'].'+ this.toSafe( this.getTrackKey() ) );
};

Playlist.prototype.getAlbum = function( index )
{
	index = index || this.getCurrentEntryIndex();
	return eval('this.getList().'+ this.toSafe( this.getEntriesKey() ) +'['+ parseInt( index ) +'].'+ this.toSafe( this.getAlbumKey() ) );
};

Playlist.prototype.getListen = function( index )
{
	index = index || this.getCurrentEntryIndex();
	return eval('this.getList().'+ this.toSafe( this.getEntriesKey() ) +'['+ parseInt( index ) +'].'+ this.toSafe( this.getListenKey() ) );
};

Playlist.prototype.getBuy = function( index )
{
	index = index || this.getCurrentEntryIndex();
	return eval('this.getList().'+ this.toSafe( this.getEntriesKey() ) +'['+ parseInt( index ) +'].'+ this.toSafe( this.getBuyKey() ) );
};



Playlist.prototype.getMillisFromHMS = function( hms_string )
{
	if (!hms_string) {
		hms_string = this.getStart();
	}
	var time_array = hms_string.split(":");
	var secs = 0;
	var mins = 0;
	var hrs = 0;
	var millis = 0;
	switch(	time_array.length )
	{
		case 3 :
			hrs = parseInt(time_array[time_array.length - 3]);
		case 2 :
			mins = parseInt(time_array[time_array.length - 2]);
		case 1 :
			secs = parseInt(time_array[time_array.length - 1]);
			break;
		default :
			throw new Error("Could not parse H:M:S value from " + hms_string );
	}
	millis = parseInt(( (hrs*60*60) + (mins*60) + secs )*1000); 
	if(millis%1 == 0) { // this is an integer
		return  millis;
	} else {
		throw new Error("Could not return a millisecond value for " + millis);
	}
};


/* Returns true is all of the required keys are defined */
Playlist.prototype.isValidEntry = function( entry )
{	
	if ( (!typeof(entry) == "object") || entry.length == 0 ) {
		return false;
	}
	var required = this.getRequiredEntryKeys();
	for (key in entry) {
		if (!required.inArray(key)) {
			return false;
		}
	}
	return true;
};	

/* Returns an array of required entry keys */
Playlist.prototype.getRequiredEntryKeys = function()
{
	return Array( this.getStartKey(), this.getArtistKey(), this.getTrackKey(), this.getAlbumKey(), this.getListenKey(), this.getBuyKey() );
};

/* 
* Builds the HTML representation of the playlist. 
*/
Playlist.prototype.getHTML = function( callback )
{
	var html = "";
	var nowplayingindex = this.getCurrentEntryIndex();
	for(index=0;index<this.getNumEntries();index++)	{	// Loop through entries...
		html += "<div " + (index==nowplayingindex ? "id='current'" : "") + "' class='show'>";
		html += this.getEntryHTML( ""+ index, this.getPreviousCssKey(), callback );
		html += "</div>";
	}
	
/*	
	var index = parseInt(this.getCurrentEntryIndex()); // current entry
	// Next Entry
	if ( this.isInRange( index + 1 ) ) {
		html += "<div id='entry_"+ this.getNextCssKey() +"' class='show'><span class='label'>Up Next:</span>";
		html += this.getEntryHTML( index + 1, this.getNextCssKey(), callback );
		html += "</div>";
	}
	// Current Entry
	html += "<div id='entry_"+ this.getCurrentCssKey() +"' class='show'><span class='label'>Now Playing:</span>";
	html += this.getEntryHTML( index, this.getCurrentCssKey(), callback );
	html += "</div>";
	// Previous Entries
	if ( this.isInRange( index - 1 ) ) {
		html += "<div id='entry_"+ this.getPreviousCssKey() +"' class='show'><span class='label'>Recently Played:</span>";
		for ( i= (index - 1); i >= 0; i-- ) 
		{
			html += this.getEntryHTML( ""+ i, this.getPreviousCssKey(), callback );
		}
		html += "</div>";
	}
*/
	return html;
}

Playlist.prototype.getEntryHTML = function( index, css_class, callback )
{
	var html = "";
/*	if(index == 0)	// If it's the first entry, add a divider line...
	{
		html += "<div id='show'><div>Possible show - playlist divider row.  Needs styling</div></div>";
	}
*/
		html += "<div id='"+ this.getEntriesKey() + index +"' class='"+ css_class +"'>";
//		html +="<div class='time_info'><span class='time'>" + this.getStart( index ) + "</span></div>";
		html += "<div id='"+ this.getTrackKey() + index + "' class='song_info'><span class='song'>";
		if ( callback != "") {
			html += '<a href="#" onclick="'+ callback +'(\''+ index +'\');">'; 
		}
		html += this.getTrack( index );
		if ( callback != "") {
			html += "</a>";
		}
		html += "</span> <span class='time'>" + this.getStart( index ) + "</span></div>";
		html += "<div id='"+ this.getAlbumKey() + index +"' class='album_info'><span class='album'>"
		if(this.getBuy(index)!="")	{
			html += "<a href='" + this.getBuy(index) + "' target='_blank'>" + this.getAlbum( index ) + "</a>";
		}	else	{
		html += this.getAlbum( index );
		}
			
		html += "</span></div>";

		html += "<div id='"+ this.getArtistKey() + index +"' class='artist_info'><span class='artist'>"+ this.getArtist( index ) +"</span></div>"; 
/*		if ( this.getListen( index ) ) {
			html += "<div id='"+ this.getListenKey() + index +"' class='"+ this.getListenKey() +"'>";
			html += "<a href='"+ this.getListen( index ) +"'>Listen</a></div>";	
		}
		if ( this.getBuy( index ) ) {
			html += "<div id='"+ this.getBuyKey() + index +"' class='"+ this.getBuyKey() +"'>";
			html += "<a href='"+ this.getBuy( index ) +"'>Buy</a></div>";	
		}
*/
		html += "</div>";

		return html;
}

/*
Playlist.prototype.getHTML = function( callback )
{
	var html = "";
	var index = parseInt(this.getCurrentEntryIndex()); // current entry
	// Next Entry
	if ( this.isInRange( index + 1 ) ) {
		html += "<div id='entry_"+ this.getNextCssKey() +"'><span class='label'>Up Next:</span>";
		html += this.getEntryHTML( index + 1, this.getNextCssKey(), callback );
		html += "</div>";
	}
	// Current Entry
	html += "<div id='entry_"+ this.getCurrentCssKey() +"'><span class='label'>Now Playing:</span>";
	html += this.getEntryHTML( index, this.getCurrentCssKey(), callback );
	html += "</div>";
	// Previous Entries
	if ( this.isInRange( index - 1 ) ) {
		html += "<div id='entry_"+ this.getPreviousCssKey() +"'><span class='label'>Recently Played:</span>";
		for ( i= (index - 1); i >= 0; i-- ) 
		{
			html += this.getEntryHTML( ""+ i, this.getPreviousCssKey(), callback );
		}
		html += "</div>";
	}
	return html;
}


Playlist.prototype.getEntryHTML = function( index, css_class, callback )
{
	var html = "<div id='"+ this.getEntriesKey() + index +"' class='"+ css_class +"'>";
		html += "<div id='"+ this.getTrackKey() + index +"' class='"+ this.getTrackKey() +"'>";
		if ( callback != "") {
			html += '<a href="#" onclick="'+ callback +'(\''+ index +'\');">'; 
		}
		html += this.getTrack( index );
		if ( callback != "") {
			html += "</a>";
		}
		html += "</div>";
		html += "<div id='"+ this.getArtistKey() + index +"' class='"+ this.getArtistKey() +"'>"+ this.getArtist( index ) +"</div>"; 
		html += "<div id='"+ this.getAlbumKey() + index +"' class='"+ this.getAlbumKey() +"'>"+ this.getAlbum( index ) +"</div>";
		if ( this.getListen( index ) ) {
			html += "<div id='"+ this.getListenKey() + index +"' class='"+ this.getListenKey() +"'>";
			html += "<a href='"+ this.getListen( index ) +"'>Listen</a></div>";	
		}
		if ( this.getBuy( index ) ) {
			html += "<div id='"+ this.getBuyKey() + index +"' class='"+ this.getBuyKey() +"'>";
			html += "<a href='"+ this.getBuy( index ) +"'>Buy</a></div>";	
		}
		html += "</div>";
		return html;
}
*/
Playlist.prototype.render = function( elementId, callback )
{
	if (elementId) {
        document.getElementById( elementId ).innerHTML = this.getHTML( callback );
    }
    else {
        document.write(this.getHTML( callback ));
    }
};

Playlist.prototype.getElement = function( elementId )
{
	if (document.all) {
		return document.all[ elementId ];
	} else {
		return document.getElementById( elementId );
	}
};



Playlist.prototype.toSafe = function (value)
// return equivilent to htmlspecialchars and urlencode in PHP
{
	value = escape(value);
	value = (value.replace(/</g, "&#60;")).replace(/>/g, "&#62;");
	return value;
};

Playlist.prototype.fromSafe = function (value)
// Equivilent to reversing htmlspecialchars and urlencode in PHP
{
	value = (value.replace(/&#60;/g, "<")).replace(/&#62;/g, ">");
	value = unescape(value);
	return value; // return a string
};


/*
*	Javascript Core Extensions
*/
Array.prototype.inArray = function (value)
// Returns true if the passed value is found in the
// array.  Returns false if it is not.
{
    var i;
    for (i=0; i < this.length; i++) {
        // Matches identical (===), not just similar (==).
        if (this[i] === value) {
            return true;
        }
    }
    return false;
};


