if(typeof Schematic == 'undefined') {
    if(this.debugEnabled()) {
        console.error('Schematic.js must be included in this page');
    }
}

/**
* VideoManager singleton handles getting and setting videos
* on an HTML page. Embedding videos can happen any point before 
* or after the page loads.
* 
* @author cbennett@schematic.com
* @author mbester@schematic.com
* @version 0.1
*
* @requires Schematic
* @requires Schematic.Event
* @requires Schematic.Dom 
* @requires SWFObject
* @requires FABridge
*/

Schematic.VideoManager = (function() {

	if(location.hash) {
		try {
			var clipId = location.hash.split('/video/')[1].split("/")[0];
			if(clipId) {
				alert('taking you to /videos/video/' + clipId);
				// FIXME: Change this once detail pages exist... 
				//document.location.href = '/videos/video/' + clipId;
			}
		} catch(e) {

		}
	} 	

	var DOMContentLoaded = false;

	/**
	* Array of player instances
	* @private
	*/
	var playerList = [];

	/**
	* Get the array of player instances
	* @private
	* @type Array
	*/
	function getPlayerList() {
		return playerList;
	}

	/**
	* Adds a new player instance to the Player List
	* @private
	* @param {Object} playerInstance
	* @returns 
	* @type String|Object|Array|Boolean|Number
	*/
	function addToPlayerList(playerInstance) {
		getPlayerList().push(playerInstance);
	};

	// Set DOMContentLoaded flag for internal reference
	Schematic.Event.add(document,'DOMContentLoaded', function() { DOMContentLoaded = true; } );

	return {

		/**
		* path and filename of the swf to be embedded
		*/
		appSource: null,

		/**
		* Disable the FAbridge by default.  Will be set to true if FAbridge is available
		*/
		enableFABridge: false,

		/**
		* Checks to see if firebug is on
		* @returns true if firebug is available
		* @type boolean
		*/
		debugEnabled: function () {
			try { 
				if(console.firebug) {
					return true;
				}
			} catch(e) {
				return false;
			}
		},

		/** 
		* Setter for appSource
		* @param {String} src path to application swf
		* @constructor
		*/		
		setAppSource: function(src) {
			if(!src) {
				return;
			}

			this.appSource = src;

			// Debug
			if(this.debugEnabled()) {
				console.info('VideoManager: Application Source SWF Path was set to: ' + src);
			}
		},

		/**
		* Getter for appSource
		* @private
		* @returns appSource
		* @type String
		*/
		getAppSource: function() {
			return this.appSource;
		},

		/**
		* Loops through player list and get's by player ID
		* @private
		* @param {String} id player id
		* @returns playerObject
		* @type Object
		*/
		getPlayerById: function(id) {

			// Find the player in the list and return it
			for(var i=0; i<getPlayerList().length; i++) {
				if(getPlayerList()[i].id === id) {
					return getPlayerList()[i];
				} 
			}

			// Debug
			if(this.debugEnabled()) {
				console.info('VideoManager: Could not find VideoPlayer instance: "' + id + '"');
			}

		},

		/**
		* Gets an array of all the players that have been intantiated
		* @returns the player list
		* @type Array
		*/		
		getPlayers: function() {
			if(getPlayerList().length) {
				return getPlayerList();
			}
		},


		/**
		* Creates a new instance of VideoPlayer and embeds (using SWFObject) to an HTMLElement (targetElement)
		* This method can be called at anytime, anywhere.  It knows whether or not the DOM has loaded.
		* If the DOM has not loaded, it waits for it to load before writing to the page
		*
		* @param {String} id  String ID of the player. Used to reference the player by name, as well reference the FABridge 
		* @param {String} target String ID of the element where the player should be embedded
		* @param {Object} clipList a JSON Object of clips
		* @param {String} page a string that represents the page name for tracking purposes. Defaults to ""
		* @param {Boolean} standalone Denotes whether or not the player is rendered in a standalone popup window. Defaults to false
		* @param {Function} onReady a reference to a callback function you want to load when the video player has initialized.
		* @param {String} configUrl Path to the Player configuration XML file
		* @param {String} expressInstallUrl Path to express install swf. Defaults to "/cmn/swf/expressinstall.swf";
		* @param {Object} additionalEmbedVars A JSON object of any other name/value pairs that you want to pass to the Player when embedding.
		* @returns a reference to the player object  
		* @type Object
		*/
		embedPlayer: function(opts) {

			opts = Schematic.merge({
				page : '',
				standalone : false,
				expressInstallUrl : "http://static.oprah.com/flash/expressinstall/expressinstall.swf",
				additionalEmbedVars : (opts.additionalEmbedVars || {}),
				backgroundColor : (opts.backgroundColor || "#ffffff")
			}, opts);

			if (opts.id && opts.target) {

				// Test for Flex/Ajax Bridge
				try { 
					var hasFABridge = FABridge;
					this.enableFABridge = true;
				} catch(e) {
					this.enableFABridge = false;
					if (this.debugEnabled()) {
						console.warn('VideoManager: Flex/Ajax Bridge was not found. JavaScript API is disabled.');
					}
				}

				// Create new instance of Video Player
				var v = new Schematic.VideoPlayer(opts.id, opts.clipList, opts.page, opts.standalone, opts.onReady, opts.configUrl, opts.expressInstallUrl, opts.backgroundColor, opts.additionalEmbedVars, opts.target, opts.deepLinkParams);
				
				// Add it to the playerlist
				addToPlayerList(v); 

				// Test if Dom has already loaded
				if(DOMContentLoaded) {	
					v.write(opts.target);
					// Debug
					if(Schematic.VideoManager.debugEnabled()) { 
						console.info('VideoManager: "%s" was embedded into %o using this queue: %o', opts.id, opts.target, opts.clipList);
					} 
					// If the DOM is not loaded, queue up the emebed for when it is
				} else {
					Schematic.Event.add(document,'DOMContentLoaded', function() {
						v.write(opts.target);
					});
					// Debug
					if(Schematic.VideoManager.debugEnabled()) { 
						console.info('VideoManager: "%s" was embedded into %o using this queue: %o', opts.id, opts.target, opts.clipList);
					}
				}

				// Return the reference to the player
				return this.getPlayers()[getPlayerList().length-1];

			} else {
				if (this.debugEnabled()) {
					console.warn('VideoManager: Video not embedded.  You need to define at least an id and a target element for the video player');
				}
			}
		}
	};
})();



/**
* Schematic.VideoPlayer is an FLV player built with Flex2/AS3 that provides 
* an API to allow for scripting via JavaScript. The core functionality for 
* the video player is written in JavaScript, allowing for the playlist/queue 
* component to be written as HTML. When the page loads, both an HTML view and 
* a JSON Queue (containing an array of Clips) is written to the page. 
*
* @class
* @author cbennett@schematic.com
* @author mbester@schematic.com
* @requires Schematic.VideoManager
* @constructor
*
* @param {String} id Unique ID for the player
* @param {Object} clipList JSON list of clip objects - could be rendered on the page, or retrieved with XHR
* @param {String} page a string that represents the page name for tracking purposes.
* @param {Boolean} standalone Denotes whether or not the player is rendered in a "stand alone" popup window.
* @param {Function} initCallBack allows you to add a call back function for when this video play is full initialized
* @param {String} configUrl URL to the Video Player configuration XML file.
* @param {String} expressInstallUrl URL to the Express Install SWF file. Defaults to "cmn/swf/expressinstall.swf"
* @param {Object} additionalEmbedVars A JSON object of any other name/value pairs that you want to pass to the Player when embedding.
*/
Schematic.VideoPlayer = function(id, clipList, page, standalone, initCallBack, configUrl, expressInstallUrl, backgroundColor, additionalEmbedVars, target, deepLinkParams) {
	this.id = id;
	if(typeof clipList !== 'undefined') { this.clipList = clipList; }
	if(typeof page !== 'undefined') { this.page = page; }
	if(typeof standalone !== 'undefined') { this.standalone = standalone; }
	if(typeof initCallBack !== 'undefined') { this.initCallBack = initCallBack; }
	if(typeof configUrl !== 'undefined') { this.configUrl = configUrl; }
	if(typeof expressInstallUrl !== 'undefined') { this.expressInstallUrl = expressInstallUrl; }
	if(typeof backgroundColor !== 'undefined') { this.backgroundColor = backgroundColor; }
	if(typeof additionalEmbedVars === 'object') { this.additionalEmbedVars = additionalEmbedVars; }
	if(typeof target !== 'undefined') { this.target = target; }
	if(typeof deepLinkParams !== 'undefined') { this.deepLinkParams = deepLinkParams; }
};

Schematic.VideoPlayer.prototype = {

	/**
	* Shorthand reference to VideoManager
	*/
	manager: Schematic.VideoManager,

	/**
	* Flex/Ajax Bridge reference
	*/	 
	FABridge: null,

	/**
	* Initializes the player after the Flash applition has been loaded.
	* This method is called by the Flex/Ajax bridge once its gone through it's 
	* own init.
	*/
	init: function() {
		// Associate FABridge
		this.FABridge = FABridge[this.id].root();
		
		// Associate target element.
		this.target = document.getElementById(this.target) || null;

		// Fix scope for event listeners
		var _this = this;

		// Instantiate new controls object
		this.controls = new Schematic.VideoPlayer.Controls(this.FABridge, this.id);

		// If we have a playlst, instantiate new playlist object
		this.setPlaylist(this.clipList);  
		
		// Subscribe to Flash events using the FABridge
		this.getFABridge().addEventListener('APPLICATION_READY', function(e) { _this.onComplete(e); });

		// Fire init call back
		/*if(typeof this.initCallBack === "function") {
			//mharrison. commented out will be called onComplete, after flash is ready
			//this.initCallBack();
		}*/ 
	},


	/** 
	* Writes a video player to the page using SWFObject
	* @param {String} targetElement the ID of the element you wish to embed the player to
	*/
	write: function(targetElement) {
		try {
			var so = new SWFObject(this.manager.getAppSource(), this.id, "100%", "100%", "9.0.115", this.backgroundColor);

			if (typeof this.configUrl !== 'undefined') { so.addVariable("configUrl", this.configUrl); }
			if (typeof this.page !== 'undefined') { so.addVariable('page', this.page); }
			if (typeof this.standalone !== 'undefined') { so.addVariable('standalone', this.standalone); }
			if (typeof this.additionalEmbedVars === 'object') {
				for (var key in this.additionalEmbedVars) {
					so.addVariable(key, this.additionalEmbedVars[key]);
				}
			}
			
			// Tell flash the ID of the FABridge
			so.addVariable("bridgeName", this.id); 
			so.addVariable("appId", this.id);	
			so.addParam("allowScriptAccess", 'always');
			so.addParam("allowFullScreen", "true");
			
			// Set up Express Install
			if (typeof this.expressInstallUrl !== 'undefined') { so.useExpressInstall(this.expressInstallUrl); }
			
			// Might think about removing this for performance reasons.
			// And because it blocks fullscreen in Flash.
			so.addParam("wmode", "transparent");
			// Do it!
			var t = so.write(targetElement);

		} catch(e) {
			if(this.manager.debugEnabled()) { 
				console.error('VideoManager: Could not Embed "' +  this.manager.getAppSource() + '" in "' + targetElement + '"! \n '+ e); 
			} 
		}

		try {
			// Add init callback to FABridge
			if(this.manager.enableFABridge) {
				var _this = this;
				FABridge.addInitializationCallback(this.id, function() {
					_this.init();
				});
			}
		} catch(e) {
			// Debug
			if(this.manager.debugEnabled()) { 
				console.error('Could not initialize Flex/Ajax Bridge:' + e.message);
			}	
		}
	},

	/**
	* Loads a clip into the video player. Does not auto play the clip
	* @param {String} url Clip url
	* @param {String} poster Url for a still image to load in the player.
	*/	
	loadClip: function(url, poster) {
		var vp = this.getFABridge();//.getVideoPlayer();
		if (typeof vp !== 'undefined') {
			vp.setSource(url);
			vp.setPoster(poster);
		}
	},

	/**
	* Unloads the current clip from the video player.
	* @returns The state the clip was in before unloading.
	* @type String
	*/
	unloadClip: function() {
		
		var s, vp = this.getFABridge(), //.getVideoPlayer(),
			pl = this.getPlaylist();
			
		if (typeof vp !== 'undefined') {
			
			s = this.getState();
			if (s === 'paused' || s === 'stopped') {
				this.controls.play();
			} 
			
			s = this.getState();
			if (s === 'playing' || s === 'buffering' || s === 'seeking') {
				vp.getFlvPlayback().getNetStream().close();  //will need to look into this one.
				//vp.getFlvPlayback().getVideoPlayer(vp.getActivePlayerIndex()).getNetStream().close();
			}
		}
	},

	/**
	* Returns the FABridge reference, if available
	* @type Object
	*/
	getFABridge: function() {
		try {
			return this.FABridge;
		} catch(e) {
			//
			if(this.manager.debugEnabled()) { 
				console.error('FABridge not available.' + e);
			}
		}
	},

	/**
	* Gets the playlist associated with this video player
	* @returns the playlist object
	* @type Object
	*/	
	getPlaylist: function() {
		return this.playlist;
	},
	
	/**
	* Instanciates a new playlist object in the video player and associates it with the video player.  Used internally.
	* @param {Object} playlist - a JSON formatted playlist.
	*/
	setPlaylist: function(playlist) {		
		if(typeof playlist === "object"){
			this.playlist = new Schematic.VideoPlayer.Playlist(this, playlist);
		}
	},
		
	/**
	* Loads a new playlist object into the video player, overriding whatever playlist is currently there.
	* @param {Object} playlist - a JSON formatted playlist.
	*/
	loadPlaylist: function(playlist) {
		
		if (typeof playlist !== 'object') {
			return;
		}
		
		if (typeof this.getPlaylist() !== "undefined") {		
			//this.unloadClip();			
			this.playlist = null;
		}
		
		this.setPlaylist(playlist);
		
	},

	/**
	* Gets the current video player state
	* @returns "buffering", "connectionError", "disconnected", "loading", "paused", "playing", "rewinding", "seeking", or "stopped"
	* @type String
	*/
	getState: function() {
		return this.getFABridge().getState() || null;
		//return this.getFABridge().getVideoPlayer().getState() || null;
	},
	
	/**
	* Sets the playhead at the time in seconds 
	* @param {Integer} time The time you want to set the playhead to, in seconds.
	* @returns the playhead time
	* @type String
	*/
	setPlayheadTime: function(time) {
		this.getFABridge().setPlayheadTime(time);
		//this.getFABridge().getVideoPlayer().setPlayheadTime(time);
	},		

	/**
	* Gets the elapsed time in seconds  
	* @returns the playhead time
	* @type String
	*/
	getPlayheadTime: function() {
		return (parseInt((this.getFABridge().getPlayheadTime() * 100), 10) / 100) || null;
		//return (parseInt((this.getFABridge().getVideoPlayer().getPlayheadTime() * 100), 10) / 100) || null;
	},
	
	/**
	* Adds an event to the embedded video player 
	* @param {String} e The event you want to hook onto.  Possible values are "autoRewound", "close", "complete", "fastForward", "fullScreen", "metadataRecieved", "playheadUpdate", "progress", "ready", "rewind", "scrubFinish", "scrubStart", "seeked", "soundUpdate"
	* @param {Function} func The callback function you want to hook onto an event.
	*/
	addEventListener: function(e, func) {
		this.getFABridge().addEventListener(e, func);
		//this.getFABridge().getVideoPlayer().addEventListener(e, func);
	},
	
	/**
	* Removes an event from the embedded video player 
	* @param {String} e The event you want to hook onto.  Possible values are "autoRewound", "close", "complete", "fastForward", "fullScreen", "metadataRecieved", "playheadUpdate", "progress", "ready", "rewind", "scrubFinish", "scrubStart", "seeked", "soundUpdate"
	* @param {Function} func The callback function you want to remove from an event.
	*/
	removeEventListener: function(e, func) {
		this.getFABridge().removeEventListener(e, func);
	},

	/**
	* Handles the oncomplete event fired from the video player swf. 
	* onComplete fires when the video has full completed
	* @param {Object} event Event Object
	*/
	onComplete: function(event) {
		
		//this.getFABridge().addEventListener('SEND_TO_FRIEND', function(e) { document.sendaFriend(); });
		
		if(this.manager.debugEnabled()) { 
			console.info('Player has fired onComplete');
		}
		// Check for new item in playlist
		var playlist = this.getPlaylist();
		//if(playlist && playlist.autoPlay) {
		if(playlist) {
			//this.playlist.loadNextClip();
			this.playlist.playAll();
		}
		
		// Fire init call back. Flash Object is initialized now
		if(typeof this.initCallBack === "function") {	
			this.initCallBack();
		} 
		
	}
};



/**
* Video Player Controls. 
* Wraps the fabridge controls into our API
* @extends Schematic.VideoPlayer
*/
Schematic.VideoPlayer.Controls = function(FABridge, id) {
	this.FABridge = FABridge;
	this.id = id;
};

Schematic.extend(Schematic.VideoPlayer.Controls, Schematic.VideoPlayer, {

	/**
	* Stops the current video
	*/
	stop:function() {
		this.getFABridge().stop();
	}, 

	/**
	* Plays the loaded video
	*/
	play: function() {
		this.getFABridge().play();
	}, 

	/**
	* Pauses the loaded video
	*/
	pause: function() {
		this.getFABridge().pause();
	},
	
	/**
	* Gets the current Volume setting
	*/
	getVolume: function() {
		return this.getFABridge().getVolume();
	},
	
	/**
	* Sets the current Volume. Level limiting built in to prevent clipping. 
	* @param {Integer} level The volume level you want to set. Should be in a range from 0 - 1.
	*/
	setVolume: function(level) {
		if (typeof level !== 'number') {
			return;
		}
		level = Math.max(0, Math.min(1, level));	// Limits Volume from 0 to 1
		this.getFABridge().getVideoPlayer().setVolume(level);
	},
	
	/**
	* Adds an event to the video player controls
	* @param {String} e The event you want to hook onto.  Possible values are "controlClick" and "sendClick"
	* @param {Function} func The callback function you want to hook onto an event.
	*/
	addEventListener: function(e, func) {
		//this.getFABridge().getVideoControls().addEventListener(e, func);
		this.getFABridge().addEventListener(e, func);
	},
	
	/**
	* Removes an event from the video player controls
	* @param {String} e The event you want to hook onto.  Possible values are "controlClick" and "sendClick"
	* @param {Function} func The callback function you want to remove from an event.
	*/
	removeEventListener: function(e, func) {
		//this.getFABridge().getVideoControls().removeEventListener(e, func);
		this.getFABridge().removeEventListener(e, func);
	}
	
});





/**
* The Playlist class offers the ability to  add and remove clips 
* from a playlist, as well navigate through them with an HTML interface.
* @class
* @extends Schematic.VideoPlayer
* @constructor
*/
Schematic.VideoPlayer.Playlist = function(player, clipList) {
	this.player = player;
	this.FABridge = player.getFABridge();
	this.id = player.id;
	this.clipList = clipList;

	/**
	* Autoplay flag. Defaults to false
	*/
	this.autoPlay = clipList.autoPlay || false;

	/**
	* currentIndex refers the index in the clip list of 
	* the clip thats currently playing or loaded
	*/
	this.currentIndex;

	/**
	* An array of clip objects
	*/
	this.queue = [];

	/**
	* An array of HTMLElements 
	*/
	this.clipNodes = null;

	/**
	* HTMLElement of the playlist. Should be formatted as "{videoPlayerID}_Playlist"
	*/
	this.playlistNodeRef = null;

	/**
	* HTMLElement of the "PlayAll" button. Should be formatted as "{videoPlayerID}_PlayAll"
	*/
	this.playAllNodeRef = null;

	/**
	* HTMLElement of the meta data view. Should be
	*/
	this.metaDataViewNodeRef = null;

	try {


		/**
		* Look for the HTML playlist node.  If it exists, attach all the necessary events to it.
		* Playlists are determined by looking for *_Playlist, where "*" is the ID of the video
		* player instance. 
		*/
		this.playlistNodeRef = document.getElementById(this.id+'_Playlist') || null;
		this.clipNodes = (this.playlistNodeRef) ? this.playlistNodeRef.getElementsByTagName('a') : null;

		if(this.playlistNodeRef !== null && this.clipNodes !== null) {
			// Set up event delegation on playlist UL
			var _this = this;

			Schematic.Event.add(this.playlistNodeRef,'click',function(e) {
				_this.onPlaylistClick(e);
			});
		}

	} catch(e) {
		if(this.manager.debugEnabled()) { 
			console.info('No HTML playlist node was found.  Will continue assuming there is no HTML playlist. See error:' + e.message);
		}
	} 

	try {

		/** 
		* Look for the play all button.  TODO:  Add the ability to have more than one
		* Play All button. The id of the play all button must be *_PlayAll where "*"
		* is the ID of the video player instance. 
		*/
		this.playAllNodeRef = document.getElementById(this.id + '_PlayAll') || null;

		if(this.playAllNodeRef !== null) {
			Schematic.Event.add(this.playAllNodeRef,'click',function(e) {
				_this.playAll();
				Schematic.Event.preventDefault(e);
			});
		}
	} catch(e) {
		if(this.manager.debugEnabled()) { 
			console.info('No "Play All" button was found.  Will continue assuming there is no button. See error:' + e.message);
		}
	}  

	try {

		/** 
		* Look for the metadata view.  The metadata view can be any div that contains
		* other elements for displaying metadata about the current clip. The id attribute 
		* must be named *_MetaDataView where "*" is the ID video player instance
		*/
		this.metaDataViewNodeRef = document.getElementById(this.id+'_MetaDataView') || null;
	} catch(e) {
		if(this.manager.debugEnabled()) { 
			console.info('No metadata view was found.  Will continue assuming there is no metadata view. See error:' + e.message);
		}
	}


	// Add clips to playlist
	if(typeof clipList === "object") {
		for(var i=0; i<clipList.clips.length; i++) {
			this.addClip(clipList.clips[i], true);
		}
	}
	
	// If the player has deep link params
	if (this.player.deepLinkParams && this.player.deepLinkParams.clipId) {
		var clip = this.ById(this.player.deepLinkParams.clipId);
		if (clip) {
			this.defaultIndex = clip.index;
			this.defaultPlayTime = this.player.deepLinkParams.playTime;
		}
	}
	
	// Play all if autoplay is set
	if(this.autoPlay) {
		
		//this.playAll();
	}
};


Schematic.extend(Schematic.VideoPlayer.Playlist, Schematic.VideoPlayer, {


	/**
	* Shorthand reference to VideoManager
	*/
	manager: Schematic.VideoManager,

	/**
	* Gets the array of clip objects in the form of a queue
	* @type Array
	*/
	getQueue: function() {
		return this.queue;
	},

	/**
	* Gets the next clip in the queue assuming it's not the end
	* of the array, otherwise returns null.
	* @returns an instance of VideoClip or null
	* @type Object
	*/
	getNextClip: function() {
		return (this.currentIndex < this.queue.length) ? this.queue[this.currentIndex + 1] : null;
	},

	/**
	* Gets the previous clip in the queue assuming it's not the 
	* beginning of the array, otherwise returns null.
	* @returns instance of VideoClip or null
	* @type Object
	*/
	getPreviousClip: function() {
		return (this.currentIndex >= 0) ? this.queue[this.currentIndex - 1] : null;
	},
	
	loadNextClip: function() {
		
		var n = this.getNextClip();
		if(n) {
			this.play(n);
		} else {
			if(this.manager.debugEnabled()) { 
				console.info('You are at the end of the playlist - no next clip found.');
			}
		}
	},
	
	loadPreviousClip: function() {
		
		var p = this.getPreviousClip();
		if(p) {
			this.play(p);
		} else {			
			if(this.manager.debugEnabled()) { 
				console.info('You are at the beginning of the playlist - no previous clip found.');
			}
		}
	},

	/**
	* Gets a clip by its unique ID.  
	* @param {String} clipId 
	* @returns an instance of VideoClip
	* @type Object
	*/

	ById: function(clipId) {
		var q = this.getQueue();
		for(var i=0; i<q.length; i++) {
			if(clipId == q[i].id) {
				return q[i];
			}
		}
	},

	/**
	* Gets a DOM node by passing in a clip id. It loops through all 
	* the clip DOM nodes and then extracts the video ID from the href and returns 
	* it when it finds a match. 
	* @param {Object} clip Instance of VideoClip
	* @returns HTMLElement associated with the VideoClip
	* @type Object
	*/
	getNodeRefByClip: function(clip) {
		for(var i=0; i<this.clipNodes.length; i++) {
			var nodeClipId = this.extractClipIdFromHref(this.clipNodes[i].href);
			if(clip.id == nodeClipId) {
				return this.clipNodes[i];
			}
		}
	}, 

	/**
	* Gets a video clip by passing in the associated clip reference
	* in the DOM. Extracts the clipID from the href of the node, and then 
	* loops through all of the VideoClips in the queue and returns when it finds a match.  
	* @param {Object} node HTMLElement of the node associated with the clip we're trying to find
	* @returns an instance of VideoClip
	* @type Object
	*/
	getClipByNodeRef: function(node) {
		var queue = this.getQueue();
		var nodeClipId = this.extractClipIdFromHref(node.href);
		for(var i=0; i< queue.length; i++) {
			if(nodeClipId == queue[i].id) {
				return queue[i];
			}
		}
	},

	/**
	* Returns the VideoClip that is currently loaded
	* @returns an instance of VideoClip
	* @type Object
	*/
	getCurrentClip: function() {
		return this.getQueue()[this.currentIndex];
	},
	
	/**
	* Returns whether or not the current clip in the playlist queue is the last clip
	* @returns a conditional result
	* @type Boolean
	*/
	isLastClip: function() {
		return ((this.getQueue().length - 1) === this.currentIndex);
	},

	/**
	* Getter for the metadata view HTMLelement
	* @returns HTMLElement
	* @type Object
	*/	
	getMetaDataViewNodeRef: function() {
		return this.metaDataViewNodeRef;
	},

	/**
	* Plays the clip passed in, selects the current clip in the playlist, and refreshes metadata
	* @param {Object} clip Instance of VideoClip
	*/	
	play: function(clip, playTime) {
		
		try {
			
			var fab, vp;
			
			if(typeof clip === "object") {
				
				this.currentIndex = clip.index;
				
				fab = this.getFABridge();
				if (typeof fab !== 'undefined') {
					if(this.manager.debugEnabled()) {
						console.log("Setting new clip at index "+this.currentIndex);
					}
					fab.setClip(this.clipList.clips[this.currentIndex], playTime);
				}
				
				if (this.player.lastClip && this.player.lastClip.getSource() === clip.getSource()  && clip.getSource().length>0) {
					// Rewind the current clip and start it again.  No need to reload.
					if(this.manager.debugEnabled()) {
						console.log("Got the same clip - no need to reload it.  Rewinding and starting again.");
					}
					this.player.setPlayheadTime(0);
					if (this.autoPlay) {
						this.player.controls.play();
					}
				} else {
					// Load the clip for playback
					//this.loadClip(clip.getSource(), clip.getPoster());
					this.player.lastClip = clip;
				}

				// Set the HTML node as selected
				if (this.clipNodes !== null) {
					this.setSelectedNode(this.getNodeRefByClip(clip));
				}

				// Refresh the meta data to show the new clip
				this.refreshMetaDataView();

			}
		} catch(e) {
			if(this.manager.debugEnabled()) { 
				console.error('Could not play this clip: %o.  Error:' + e.message, clip);
			}
		}
	},

	/**
	* Puts the video player into "PlayAll" mode. Resets the current index and then
	* plays the entire playlist continually without stopping by setting 
	* the autoplay flag to true. 
	*/
	playAll: function() {
		
		this.currentIndex = this.defaultIndex || 0;
		this.autoPlay = true;
		this.play(this.getCurrentClip(), this.defaultPlayTime);
		if (this.clipNodes !== null) {
			this.setSelectedNode(this.getNodeRefByClip(this.getCurrentClip()));
		}
	},

	/**
	* Adds a VideoClip instance to our video queue
	* @param {Object} clip Instance of VideoClip
	*/
	addClip: function(clip, fromPlaylistClass) {
		if (typeof clip !== 'object') {
			return;
		}
		
		// Are we calling this function when we're invoking a new Playlist class?
		fromPlaylistClass = fromPlaylistClass || false;
		
		try {
			
			// Pass the clip to the SWF
			if (!fromPlaylistClass) {
				this.getFABridge().getClipList().addClip(clip);
			}
			
			// Add to JS playlist
			var vc = (!clip.setProperty) ? new Schematic.VideoClip(clip) : clip;		
			this.queue.push(vc);
			var i = this.queue.length - 1;
			var c = this.queue[i];
			c.setProperty('index', i);
			
			if(this.manager.debugEnabled()) { 
				console.info('Added clip %o to playlist.', clip);
			}
		} catch(e) {
			if(this.manager.debugEnabled()) { 
				console.error('Clip wasn\'t added. Error: ' + e.message);
			}
		}
	},

	/**
	* Handle for events on the playlist element. If the element
	* has the clip class, then we can extract the clip id from 
	* the node and play the clip.  
	* @private
	* @param {Event} e Event Object
	*/
	onPlaylistClick: function(e) {
		var target = Schematic.Event.target(e);

		// If the targe was an image, lets go up a level to find the clip.
		if(target.nodeName == 'IMG') {
			target = target.parentNode;
		}
		if(Schematic.Dom.hasClass(target,'clip')) {

			this.play(this.getClipByNodeRef(target));
			Schematic.Event.preventDefault(e);
			Schematic.Event.stopPropagation(e);  
		}
	},

	/**
	* Sets the node to selected by adding "selected" class
	* @param {Object} selectedNode HTMLelement that you wish to set as "selected"
	*/
	setSelectedNode: function(selectedNode) {
		for(var i=0; i<this.clipNodes.length; i++) {
			Schematic.Dom.removeClass(this.clipNodes[i].parentNode,'selected');
		}
		Schematic.Dom.addClass(selectedNode.parentNode,'selected');
	},

	/**
	* Refreshes the meta data view if it's available. Meta data is refreshed
	* by retrieving the data from the clip object and setting the data in the related HTML field.
	* @private
	*/
	refreshMetaDataView: function() {
		try {
			var prop;
			
			var data = {
				list : {
					obj : this.clipList,
					prefix : "playlist"
				},       	
				clip : {
					obj : this.getCurrentClip(), 
					prefix : "clip"
				},	
				prev : {
					obj : this.getPreviousClip(),	
					prefix : "clip_prev"
				},
				next : {
					obj : this.getNextClip(),
					prefix : "clip_next"
				}				
			};

			if(typeof data.list.obj === 'object' && data.clip.obj) { 
				for (entry in data) {					
					for (prop in data[entry].obj) {
						if (typeof data[entry].obj[prop] !== 'function') {
							this.setMetaDataViewField(prop, data[entry].obj[prop], data[entry].prefix);
						}
					}
					if (typeof data[entry].obj === 'undefined') { // handle prev and next outer boundries
						for (prop in data.clip.obj) {
							if (typeof data.clip.obj[prop] !== 'function') {
								this.setMetaDataViewField(prop, null, data[entry].prefix);
							}
						}
					}
				}
			}
			
		} catch (e) {
			if(this.manager.debugEnabled()) { 
				console.error('Could not refresh the meta data. Error:' + e.message);
			}
		}		
	}, 


	/**
	* Sets a specific field in the meta data view by getting it by it's classname
	* and then creating text nodes and appending them to the parent.  If the value
	* is an array, it creates multiple nodes as well as an anchor node around it. It assumes
	* that all arrays will be linked to search pages.
	* @private
	* @param {String} field fieldname you wish to update
	* @param {String} value Value to apply to the field name
	* @param {Object} basenode Pass the root node to speed up getByClass queries.
	* @returns Describe what it returns
	* @type String|Object|Array|Boolean|Number 
	*/

	setMetaDataViewField: function(field, value, prefix) {
		if(!prefix) { return; }
		try {
			var targ = document.getElementById(prefix + "_" + field);
			if (!targ)	{ return; }
			
			Schematic.Dom.removeChildNodes(targ);
			
			if (!value) { return; }
			
			if (value.constructor === Array) {
				for(var j = 0; j < value.length; j++) {
					if (typeof value[j] === 'string' || typeof value[j] === 'number') {
						targ.appendChild(document.createTextNode(value[j]));
						if (j !== value.length - 1) {
							targ.appendChild(document.createTextNode(", "));
						}
					}
				}
			} else {
				targ.appendChild(document.createTextNode(value));
			}
		} catch(e) {
			if(this.manager.debugEnabled()) { 
				console.error('Could not refresh the meta data. Error:' + e.message);
			}
		}
	}	

});



/**
* Creates a VideoClip instance from a JSON clip object. 
* Serves no specific purpose other than to formalize the creation
* of VideoClips and allow for modification of VideoClips should
* it be necessary
* @author cbennett
* @constructor
* @classs
*/
Schematic.VideoClip = function(clip) {
	if(typeof clip != 'object') {
		// TODO Debug
		return;
	} else {
		for(var data in clip) {
			if(clip.hasOwnProperty(data)) {
				this.setProperty(data, clip[data]);
			}
		}
	}
};

Schematic.VideoClip.prototype = {
	/**
	* Set a new property for this clip
	*/
	setProperty: function(name,val) {
		this[name] = val;
	},

	/**
	* Simple getters.
	*/
	getSource: function() {
		return this.contentPath;
	},

	getPoster: function() {
		return this.posterFramePath || null;
	},	

	getControlsType: function() {
		return this.controlsType || null;
	},

	getTags: function() {
		return this.tags || null;
	},

	getTitle: function() {
		return this.title || null;
	},

	getDescription: function() {
		return this.description || null;
	},

	getDownloadUrl: function() {
		return this.downloadUrl || null;
	},

	isDownloadable: function() {
		return (this.getDownloadUrl() !== "") ? true : false;
	},
	
	/**
	* Generic Getter
	*/
	getProperty: function(name) {
		return this[name] || null;
	}
};