function cwpQS(qs) { // optionally pass a querystring to parse
	this.params = {};
	if (!qs){qs = location.search.substring(1, location.search.length);}
	if (qs.length === 0){return;}
	qs = qs.replace(/\+/g, ' ');
	var args = qs.split('&'); // parse out name/value pairs separated via &
	for (var i=0;i<args.length;i++) {
		var pair = args[i].split('=');
		var name = decodeURIComponent(pair[0]);
		var value = (pair.length==2)?decodeURIComponent(pair[1]):name;
		this.params[name] = value;
	}
}
cwpQS.prototype.get=function(key, default_){var value = this.params[key];return (typeof(value)=="string")?value:default_;};
cwpQS.prototype.contains=function(key){var value = this.params[key];return (typeof(value)=="string");};

var cwp = { // cwp object encapsulates all Canwest Player functionality
// Constants
	version: "2.0",
	RELEASE: 1,
	CATEGORY: 2,
	UNRENDERED: 0,
	RENDERED: 1,
	PLAYING: 2,
	PAUSED: 3,
	CLASS_CwpChildrens: "ContentBrowser-LowerContent-children",
	CLASS_SEL: "ContentBrowser-LeftMenu-Selected",
	PLACEHOLDER_ID: 1, // ID used when we create fake categories, won't try to match the categoryIDs of child releases,
	NOSHOW: "NOSHOW",
// Properties
	flashID: "playerwidget",
	manager: null,
	sortTypes:{
		a:"title",
		p:"requestCount",
		d:"airdate"
	},
// Methods
	insertComScoreBeacon: function(manager){
		if ($("#cwpComScore").length===0){
			$(document.body).append("<img id='cwpComScore' height='1' width='1' />"); 
		}

		$("#cwpComScore").attr("src","http://beacon.securestudies.com/scripts/beacon.dll?C1=1&C2=3005660&C3=3005660&C4=" + window.location.hostname.toLowerCase() + window.location.pathname.toLowerCase() + "&C5=" + manager.currentPlay.Show + "&C6=&C7=" + escape(window.location.href) + "&C8=" + escape(document.title) + "&C9=" + escape(document.referrer) + "&rn=" + Math.floor(Math.random()*99999999));
	},

// Helper methods
	symbolsToEntities: function(sText) {
		if(!sText){sText="";}
		var sNewText = "";
		var iLen = sText.length;
		for (var i=0;i<iLen;i++) {
			var iCode = sText.charCodeAt(i);
			sNewText += (iCode > 256? "&#" + iCode + ";": sText.charAt(i));
		}
		return sNewText;
	},

	convertLength: function(data){
		var tl = new Date(data);
		var HH = tl.getHours(); var MM = tl.getMinutes(); var ss = tl.getSeconds();
		var str;

		if (ss<10){str=":0"+ss;}
		else{str=":"+ss;}
		str = MM + str;
		if (HH - 19 > 0){
			if (MM < 10){str="0"+str;}
			str = (HH-19) + ":" + str;
		}
		return str;
	},

	addLeadingZero: function(sText){
		var text = this.symbolsToEntities(sText);
		if (text.length == 1){
			text = "0" + text;
		}
		return text;
	},


	removeSpaces: function(s){return s && s.replace(/[ ]/g,"");},
	cleanStr: function(s){return s && s.toLowerCase().replace(/\s|[.,<>?\/;:"'{\[}\]|\\+=_-`!@#$%\^&*()]*|~*/g,"");},
	cleanAdParams: function(s){return s && s.toLowerCase().replace(/[.,<>?\/;:"'{\[}\]|\\+=_-`!@#$%\^&*()]*|~*/g,"").replace(/[ ]/g,"_");},

	truncate: function(str,truncAt){
		// If it's not a string, return the empty string
		if(typeof(str)!=="string"){return "";}
		var truncatedString;
		if(str.length > truncAt){
			truncatedString = str.slice(0,truncAt);
			truncatedString = str.slice(0,truncatedString.lastIndexOf(" "));
			truncatedString += "&hellip;";
		}else{
			truncatedString = str;
		}
		return truncatedString;
	},

	logError: function(errorMsg){
		alert(errorMsg);
		return false;
	},

	logDebug: function(debugMsg){
		var dbgDiv = $("#debugOutput");
		if (dbgDiv.length>0){
			var d = new Date();
			dbgDiv.prepend("<" + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() + ">: " + debugMsg + "<br/>");
		}
	},

	getFeedContents: function(feedJSON, callback){
		$.ajaxSetup({cache:"true"});
		$.getJSON(feedJSON, function(data){
			var str = "";
			for (var key in data.listInfo){
				if (data.listInfo.hasOwnProperty(key)){
					str += key + ": " + data.listInfo[key] + "|";	
				}
			}
			str += "<br/>";
			$.each(data.items,function(i,dataItem){
				str += i + " - ";
				for (var key in dataItem){
					if (dataItem.hasOwnProperty(key)){
						if (key.indexOf("ustomData") >0){
							str += key + ":: ";
							for (var cdKey in dataItem[key]){
								if (dataItem[key].hasOwnProperty(cdKey)){
									str += dataItem[key][cdKey].title + ": " + dataItem[key][cdKey].value + ", ";
								}
							}
							str += " | ";
						}else{
							str += key + ": " + dataItem[key] + " | ";
						}
					}
				}
				str += "<br/>";
			});
			callback(str);
		});
		$.ajaxSetup({cache:"false"});
	}
};

cwp.initThePlatform = function(params){
	params.swfFolder = params.swfFolder || "http://webdata.globaltv.com/global/canwestPlayer/swf/4.1/";
	params.flashID = params.flashID || cwp.flashID;
	cwp.flashID = params.flashID; // reset the flash ID we'll use in all the canwestPlayer code
	// Initialize the commManager and register the player we are going to create
	if (params.commBeforeThisDiv){
		tpSetCommManagerID("communicationwidget",true,params.swfFolder+"commManager.swf",params.commBeforeThisDiv);
	}else{
		tpSetCommManagerID("communicationwidget",true,params.swfFolder+"commManager.swf");
	}
	tpRegisterID(params.flashID);
};

// Outputs the appropriate flash script on the page
cwp.loadFlashObjects = function(params){
	var key, i;
	if (params.finishPlayerOutput && cwp.flashParams){
		params = cwp.flashParams;
		params.skipCommOutput = true;
		params.skipPlayerOutput = false;
	}else{
		params.akamaiRoot = params.akamaiRoot || "http://a123.g.akamai.net/f/123/41524/60m/webdata.globaltv.com/";
		params.width = params.width || 608;
		params.height = params.height || params.width/16*9+20;
		params.url = params.url || window.location.href.replace(/(&|)(release|category)(ID|id|PID|pid)=[^&]*/,"").replace(/[?]$/,"");
		params.targetContainer = params.targetContainer || "playerFrame";
		params.swfFolder = params.swfFolder || "http://webdata.globaltv.com/global/canwestPlayer/swf/4.1/";
		params.omnitureAccount = params.omnitureAccount || "canwestdev";
		params.gaAccount = params.gaAccount || "UA-840802-12";
		
		params.flashID = params.flashID || cwp.flashID;
		cwp.flashID = params.flashID; // reset the flash ID we'll use in all the canwestPlayer code
		params.version = params.version || "9.0.115.0";
		params.flashBGColor = params.flashBGColor || "#000000";
		params.companionAdInfo = params.companionAdInfo || "|bannerSizes=120x240,468x60,250x250,160x90|bannerRegions=sky,banner,bbox,button";
		params.suppressDimensionVars = (params.suppressDimensionVars===true);
		params.initialTime = params.initialTime || 0;
		params.omniturePlayerName = params.omniturePlayerName || document.title; // use the page title if not told otherwise

		// Add parameters for other things we may want to override
		params.defaultParams = {
			quality:"high",
			scale:"noscale",
			salign:"tl",
			menu:"true",
			bgcolor:"#131313",
			allowFullScreen:"true",
			allowScriptAccess:"always"
		};
		// Add/override any provided parameters
		if (params.params){
			for (key in params.params){
				if (params.params.hasOwnProperty(key)){
					params.defaultParams[key] = params.params[key];
				}
			}
		}
	
		params.defaultVariables = {
			allowFullScreen:"true",
			allowLoad:"true",
			autoPlay:"true",
			showFullTime:"false",
			showTitle:"false",
			useDefaultLoadingIndicator:"false",
			useDefaultBufferingIndicator:"false",
			logLevel:"debug",
			ID:params.flashID,
			width:params.width,
			height:params.height,
			/*
			controlColor:"0xbebebe",
			controlHoverColor:"0xff0000",
			controlSelectedColor:"0xffffff",
			textBackgroundColor:"0x383838",
			textColor:"0xbebebe",
			backgroundColor:"0x131313",
			controlBackgroundColor:"0x9AD3B7",
			controlFrameColor:"0x545759",
			frameColor:"0xFFFFFF",
			pageBackgroundColor:"0x000000",
			playProgressColor:"0x05593d",
			scrubberColor:"0xFFFFFF",
			scrubberFrameColor:"0x00CCFF",
			scrubTrackColor:"0xFFFFFF",
			loadProgressColor:"0xFFFFFF",
			*/
			layoutURL:params.swfFolder + "metaLayout_Showcase.xml",
			skinURL:params.swfFolder + "skinShowcase.swf"
		};
		if (params.targetRelease){
			params.defaultVariables.releaseURL = cwp.manager.constructReleaseURLForTargetRelease(params.targetRelease);
		}
	
		// Add/override any provided variables
		if (params.variables){
			for (key in params.variables){
				if (params.variables.hasOwnProperty(key)){
					params.defaultVariables[key] = params.variables[key].replace("FLASHPATH/", params.swfFolder);
				}
			}
		}
	
		params.defaultPlugins = {
			control:["URL=" + params.swfFolder + "DimControlPlugin.swf|scale=true", "URL=" + params.swfFolder + "BandwidthControlPlugin.swf", "URL=" + params.swfFolder + "ResizeControlPlugIn.swf|scale=true"],
			adcomponent:["URL=" + params.swfFolder + "adInsertionPlugin.swf|priority=1|numPreRolls=0|numMidRolls=2|numPostRolls=0","URL=" + params.swfFolder + "googleInStream.swf|priority=1|host=ad.ca.doubleclick.net|overlaySize=400x50" + params.companionAdInfo],
			tracking:["URL=" + params.swfFolder + "omnitureMedia.swf|account=" + params.omnitureAccount + "|visitorNamespace=" + params.omnitureAccount + "|dc=112|debug=true|frequency=25,50,75|playerName=" + params.omniturePlayerName,
				"URL=" + params.swfFolder + "googleAnalytics.swf|playerName=" + params.omniturePlayerName + "|ID=" + params.gaAccount + "|mode=Bridge|debug=false", "URL=" + params.swfFolder + "comScore.swf|c2=3005660|c4="+params.comscoreSiteId],
			form:["URL=" + params.swfFolder + "EOCCPlugin.swf|countdownTimer=5","URL=" + params.swfFolder + "PlaybackPlugIn.swf|resizeMediaArea=true"]
		};
	
		// Add/override any provided plugins
		for (key in params.plugins){
			if (params.plugins.hasOwnProperty(key)){
				// If it's a string, just add it
				if (typeof(params.plugins[key])=="string"){
					params.defaultPlugins[key] = params.plugins[key].replace("FLASHPATH/", params.swfFolder);
				// otherwise it's an array and we'll need to replace the FLASHPATH in all values
				}else{
					params.defaultPlugins[key] = params.plugins[key];
					for (i=0;i<params.defaultPlugins[key].length;i++){
						params.defaultPlugins[key][i] = params.defaultPlugins[key][i].replace("FLASHPATH/", params.swfFolder);
					}
				}
			}
		}
	}

	if (params.skipPlayerOutput){
		cwp.flashParams = params;
	}
	
	if (!params.skipPlayerOutput){
		// Create the flash object
		var so = new SWFObject(params.swfFolder+"flvPlayer.swf", params.flashID, params.width, params.height, params.version, params.flashBGColor);
		// Add all params
		for (key in params.defaultParams){
			if (params.defaultParams.hasOwnProperty(key)){
				so.addParam(key, params.defaultParams[key]);
			}
		}
		// Add all variables, except for plugins
		for (key in params.defaultVariables){
			if (params.defaultVariables.hasOwnProperty(key)){
				if (!(params.suppressDimensionVars && key=="width" || key=="height")){
					so.addVariable(key, params.defaultVariables[key]);
				}
			}
		}
		// Add all plugins
		var pluginIndex=0;
		for (key in params.defaultPlugins){
			if (params.defaultPlugins.hasOwnProperty(key)){
				// If it's a string, just add it
				if (typeof(params.defaultPlugins[key])=="string"){
					so.addVariable("plugin"+pluginIndex, "type=" + key + "|" + params.defaultPlugins[key]);
					pluginIndex++;
					// otherwise it's an array and we have to replace all of them
				}else{
					for (i=0;i<params.defaultPlugins[key].length;i++){
						so.addVariable("plugin"+pluginIndex, "type=" + key + "|" + params.defaultPlugins[key][i]);
						pluginIndex++;
					}
				}
			}
		}
		
		// Write the flash object to the page
		so.write(params.targetContainer);
		cwp.params = null;
	}
};

function cwpEventProto(obj,name,callback){
	if (this.mode == cwp.UNRENDERED){return;}
	if (typeof callback == "function"){
		obj[name + "Callback"] = callback;
	}else{
		if (obj[name + "Callback"]){
			obj[name + "Callback"](obj); // use local callback if available 
		}else{
			if (obj.type==cwp.CATEGORY) {
				obj.manager.catExists(obj.depth)[name](obj);
			}else if (obj.manager.rel[name]){
				obj.manager.rel[name](obj);
			}
		}
	}
}

function cwpVideoEventProto(obj,name,pdkEvent,callback){
	if (this.mode == cwp.UNRENDERED){return;}
	if (typeof callback == "function"){
		obj[name + "Callback"] = callback;
	}else{
		if (obj[name + "Callback"]){
			obj[name + "Callback"](obj,pdkEvent); // use local callback if available 
		}else{
			if (obj.type==cwp.CATEGORY) {
				cwp.logDebug("Looking for category event (" + obj.ID + ", " + obj.depth + ", '" + name + "').");
				obj.manager.catExists(obj.depth)[name](obj,pdkEvent);
			}else{
				cwp.logDebug("Looking for release event (" + obj.ID + ", '" + name + "').");
				if (obj.manager.rel[name]){
					obj.manager.rel[name](obj,pdkEvent);
				}
			}
		}
	}
}

// Release (video) object
function cwpRelease(manager, data, parentCat, fieldNames, customFieldNames){
	var i,j;
	// Initialization
	this.ID = data.ID;
	this.contentID = data.contentID;
	this.PID = data.PID;
	this.manager = manager;
	this.parentCat = parentCat || null;
	this.depth = (this.parentCat)?this.parentCat.depth+1:0;
	this.URL = data.URL;
	this.length = data.length;
	this.mode = cwp.UNRENDERED;
	this.page = 1;
	this.categoryIDs = data.categoryIDs;
	fieldNames = fieldNames || manager.relFields;
	for (i=0;i<fieldNames.length;i++){
		//if assets contains more than one thumbnail, we can choose the one that size is close to the required size.
		//if assets have only one thumbnail, which will be same as the data["thumbnail"]
		if((fieldNames[i] == "thumbnailURL") && data.assets && (data.assets.length>1)){
			var thumbnailWidth = this.manager.relThumbnailWidth;
			var thumbnailHeight = this.manager.relThumbnailHeight;
			
			this.thumbnailURL = data.assets[0].URL;
			var diff = data.assets[0].height * data.assets[0].width - thumbnailHeight * thumbnailWidth; 
			
			for(j=1; j<data.assets.length; j++){
				var newDiff = data.assets[j].height * data.assets[j].width - thumbnailHeight * thumbnailWidth; 
				if(newDiff < diff) {
					diff = newDiff;
					this.thumbnailURL = data.assets[j].URL;
				}
			}
		}else{
			this[fieldNames[i]] = data[fieldNames[i]];
		}
	}
	customFieldNames = customFieldNames || manager.customRelFields;
	// There will always be custom data because of the ad content
	for (i=0;i<data.contentCustomData.length;i++){
		this[cwp.removeSpaces(data.contentCustomData[i].title)] = data.contentCustomData[i].value;
	}
	if (!this.ClipType){
		this.ClipType = (!this.WebExclusive || this.WebExclusive=="No")?"episode":"other";
	}

	// Modify release URL with advertising information
	// set a cnt keyvalue for each item in the comma separated Subject field
	if (this.Subject){
		var cntVals = this.Subject.split(",");
		for (i=0;i<cntVals.length;i++){
			this.URL += "&k"+(this.manager.extraParamCount+i)+"=cnt&v"+(this.manager.extraParamCount+i)+"="+cwp.cleanAdParams(cntVals[i]);	
		}
	}

	// Calculate the correct zone
	var newZone = "Zone=";
	if (this.Zone){
		newZone += this.Zone;
	}else if (this.Network||this.Show){
		newZone += cwp.cleanStr(this.Network+this.Show);
	}else{
		newZone += this.manager.defaultZone;
	}
	if (/Zone=[^&]*/.test(this.URL)){
		this.URL = this.URL.replace(/Zone=[^&]*/, newZone);
	}else{
		this.URL += "&" + newZone;
	}
	if (/Format=[^&]*/.test(this.URL)){
		this.URL += "&format=SMIL";
	}
	// If the clip has a duration greater than 2min, set clipLength to "long", otherwise it is "show"
	this.clipLength = "short";	
	if (this.length>120000){
		this.clipLength = "long";
	}
	
	// Add some metadata to the release call
	this.URL += "&show=" + cwp.cleanAdParams(this.Show) + "&episode=" + cwp.cleanAdParams(this.Episode) + "&network=" + cwp.cleanAdParams(this.Network) + "&season=" + cwp.cleanAdParams(this.Season) + "&clipLength=" + this.clipLength;
	this.isSelected = false;
	this.airdate = data.airdate;
	this.requestCount = data.requestCount;

	// Methods
	this.click = function(callback){cwpEventProto(this.cwpTarget,"click",callback);};
	this.mouseover = function(callback){
		cwpEventProto(this.cwpTarget,"mouseover",callback);
	};
	this.mouseout = function(callback){
		cwpEventProto(this.cwpTarget,"mouseout",callback);
	};
	this.mouseenter = function(callback){
		cwpEventProto(this.cwpTarget,"mouseenter",callback);
	};
	this.mouseleave = function(callback){
		cwpEventProto(this.cwpTarget,"mouseleave",callback);
	};
	this.select = function(callback){
		cwp.logDebug("Selecting: "  + this.title);
		if (typeof(callback)==="function"){
			cwpEventProto(this,"select",callback);
		}else if (!this.isSelected){
			this.isSelected = true;
			this.manager.setSelected(this,true);
			cwpEventProto(this,"select",callback);
			if (this.element){
				this.element.addClass(cwp.CLASS_SEL);
			}
		}else{
			cwp.logDebug("Cannot double select release: " + this.title);
		}
	};
	this.unselect = function(callback){
		if (typeof(callback)==="function"){
			cwpEventProto(this,"select",callback);
		}else if (this.isSelected){
			this.isSelected = false;
			this.manager.setSelected(this,false);
			cwpEventProto(this,"unselect",callback);
			if (this.element){
				this.element.removeClass(cwp.CLASS_SEL);
			}
		}else{
			cwp.logDebug("Cannot double unselect release: " + this.title);
		}
	};
	this.onVideoStart = function(evt,callback){cwpVideoEventProto(this,"onVideoStart",evt,callback);};
	this.onVideoPause = function(evt,callback){cwpVideoEventProto(this,"onVideoPause",evt,callback);};
	this.onVideoUnpause = function(evt,callback){cwpVideoEventProto(this,"onVideoUnpause",evt,callback);};
	this.onVideoEnd = function(evt,callback){cwpVideoEventProto(this,"onVideoEnd",evt,callback);};
	this.onVideoComplete = function(evt,callback){
		cwpVideoEventProto(this,"onVideoComplete",evt,callback);
	};
}
cwpRelease.prototype.type = cwp.RELEASE;

// Methods
cwpRelease.prototype.setUnrendered = function(){
	this.element = null;
	this.mode = cwp.UNRENDERED;
	this.isSelected = false;
};

cwpRelease.prototype.renderHTML = function(targetContainer,startDepth,endDepth,withReleases){
	if (typeof(this.manager.relRendering)!="function"){
		cwp.logError("Cannot render Release. No rendering method defined.");
	}else{
		this.element = this.manager.relRendering(targetContainer,this);
		this.element[0].cwpTarget = this;
		this.element.click(this.click);
		this.element.mouseover(this.mouseover);
		this.element.mouseout(this.mouseout);
		this.element.mouseenter(this.mouseenter);
		this.element.mouseleave(this.mouseleave);
		this.mode = cwp.RENDERED;
	}
};

cwpRelease.prototype.playVideo = function(){
	location.href = this.URL;
};

cwpRelease.prototype.pauseVideo = function(doPause){
	tpController.pause(doPause);
};

cwpRelease.prototype.playNextVideo = function(){
	if (!this.manager.relPlaylist){
		// If we don't have a parent there is no next video
		if (this.parentCat){
			// If the current video is the last in the list, we can't play next
			for (var i=0;i<this.parentCat.children.length-1;i++){
				if (this.parentCat.children[i]==this){
					this.parentCat.children[i+1].playVideo();
					break;
				}
			}
		}
	}
};

cwpRelease.prototype.playPrevVideo = function(){
	if (!this.manager.relPlaylist){
		// If we don't have a parent there is no next video
		if (this.parentCat){
			// If the current video is the first in the list, we couldn't play next
			for (var i=1;i<this.parentCat.children.length;i++){
				if (this.parentCat.children[i]==this){
					this.parentCat.children[i-1].playVideo();
					this.parentCat.children[i-1].select();
					break;
				}
			}
		}
	}
};

cwpRelease.prototype.getRelease = function(key,value){
	return (this[key]==value)?this:null;
};

cwpRelease.prototype.selectWithParents = function(toDepth){
	if (this.depth>=toDepth){
		if (this.parentCat){
			this.parentCat.selectWithParents(toDepth);
		}
		this.select();
	}
};

cwpRelease.prototype.getProperty = function(propertyName){
	return this[propertyName];
};

function cwpCategory(manager, data, parentCat, fieldNames, customFieldNames){
	var i;
	// Initialization
	this.ID = data.ID;
	this.manager = manager;
	this.parentCat = parentCat || null;
	this.children = [];
	this.fullTitle = data.fullTitle;
	if(this.ID != "Search"){
		this.depth = data.depth;
		this.hasDirectReleases = data.hasReleases; // whether there are direct child releases
		this.hasReleases = this.hasDirectReleases; // whether they are child releases at any depth
		if (this.hasReleases && parentCat){
			parentCat.setHasReleases();
		}
		fieldNames = fieldNames || manager.catFields;
		for (i=0;i<fieldNames.length;i++){
			this[fieldNames[i]] = data[fieldNames[i]];
		}
		customFieldNames = customFieldNames || manager.catCustomFields;
		if (customFieldNames && data.customData){
			for (i=0;i<data.customData.length;i++){
				// If we have a displayTitle, override the title property
				if (data.customData[i].title=="DisplayTitle"){
					if (data.customData[i].value.length>0){
						this.title = data.customData[i].value;
					}
				}else{
					this[cwp.removeSpaces(data.customData[i].title)] = data.customData[i].value;
				}
			}
		}
	}else{
		this.title = "Search";
		this.hasDirectReleases = true;
		this.hasReleases = true;
	}
	this.playlistContainer = false;
	this.mode = cwp.UNRENDERED;
	this.jsonRequested = false;
	this.isSelected = false;
	this.currentPageNum = 1;
	this.clipsPerPage = manager.clipsPerPage;
	this.rendered = false;
	this.currentSortingCriteria = manager.currentSortingCriteria;
	this.sortDescending = manager.Descending[manager.currentSortingCriteria];
	this.rel = [];
	this.totalReleases = 0;
	this.totalPagesLoaded = 0;
	this.totalPages = 0;
	this.firstTimeRender = true;
	

	// Methods
	if(this.ID != "Search"){
		this.click = function(callback){cwpEventProto(this.cwpTarget,"click",callback); return false;};
		this.breadcrumbClick = function(callback){cwpEventProto(this.cwpTarget,"breadcrumbClick",callback); return false;};
		
		this.mouseover = function(callback){
			cwpEventProto(this.cwpTarget,"mouseover",callback);
		};
		this.mouseout = function(callback){
			cwpEventProto(this.cwpTarget,"mouseout",callback);
		};
		this.select = function(callback){
			if (callback){
				cwpEventProto(this,"select",callback);
			}else if (!this.isSelected){
				this.isSelected = true;
				this.manager.setSelected(this,true);
				cwpEventProto(this,"select",callback);
				if (this.element){
					this.element.addClass(cwp.CLASS_SEL);
				}
			}else{
				cwp.logDebug("Cannot double select category: " + this.title);
			}
		};
		this.unselect = function(callback){
			if (callback){
				cwpEventProto(this,"select",callback);
			}else if (this.isSelected){
				this.isSelected = false;
				this.manager.setSelected(this,false);
				cwpEventProto(this,"unselect",callback);
				if (this.element){
					this.element.removeClass(cwp.CLASS_SEL);
				}
			}else{
				cwp.logDebug("Cannot double unselect category: " + this.title);
			}
		};
		this.onChildrenShow = function(callback){cwpEventProto(this,"onChildrenShow",callback);};
	}else{
		this.searchCounter = this.manager.searchCounter;;
		
		this.renderSearchResult= function(userinput, searchQuery, season, episode, callback){
			this.searchQuery = searchQuery;
			this.userinput = userinput;
			this.constructedJSON = null;
			var constructedSearchJSON = this.getConstructedJSON() +  "&query=CommonSearch\|" + escape(searchQuery) + "*";
			if(season){
				constructedSearchJSON += "&query=ContentCustomText\|Season\|" + season;
				this.season = season;
			}
			if(episode){
				constructedSearchJSON += "&query=ContentCustomText\|Episode\|" + episode;
				this.episode = episode;
			}
			this.constructedJSON = constructedSearchJSON;
			this.rel = [];
			this.totalPages = this.totalReleases = this.totalPagesLoaded = 0;
			if (typeof(callback)=="function"){callback();}
		}
		
		this.select = function(callback){
			//if this is a old search
			if(this.searchCounter < this.manager.searchCounter) return;
			if(!this.isSelect){
				this.isSelect = true;
				this.manager.setSelected(this, true);
				this.fullTitle =  this.manager.previousSelectedCat.fullTitle + "/search/" + this.userinput;
			}
			this.renderRelease();
			if (typeof(callback)=="function"){callback();}
		}
		
		this.unselect= function(callback){
			if (callback){
				cwpEventProto(this,"select",callback);
			}else if (this.isSelected){
				this.isSelected = false;
			}
		}
		
		this.updateMenuBreadcrumb = function(menuContainer, targetContainer, searchQuery){
			this.breadcrumb = this.manager.makeSearchBreadcrumb(targetContainer, searchQuery);
		}
		
	}
}
// cwpCategory constants
cwpCategory.prototype.type = cwp.CATEGORY;
// cwpCategory methods
cwpCategory.prototype.getRenderObj = function(){
	var renderObj = this.manager.catRendering[this.depth];
	var renderDepth = this.depth;
	while (!renderObj){
		renderDepth--;
		if (renderDepth<0){return cwp.logError("Cannot render Category.  No rendering methods defined.");}
		renderObj = this.manager.catRendering[renderDepth];
	}
	return renderObj;
};
	
cwpCategory.prototype.setUnrendered = function(setRootUnrendered){
	if (setRootUnrendered){
		this.element=null;
		this.mode=cwp.UNRENDERED;
		this.isSelected=false;
	}
	$.each(this.children, function(i,obj){
		obj.setUnrendered(true);
	});
};

cwpCategory.prototype.getCategory = function(key,value){
	if (this[key]==value){return this;}
	var retVal = null;
	for (var i=0;i<this.children.length;i++){
		retVal = this.children[i].getCategory(key,value);
		if (retVal){break;}
	}
	return retVal;
};

cwpCategory.prototype.getCategoryByFullTitle = function(targetTitle,conversion,mustHaveReleases){
	var retVal = null;
	if (conversion(this.fullTitle)==targetTitle && (!mustHaveReleases || this.hasReleases)){
		retVal = this;
	}else{
		for (var i=0;i<this.children.length;i++){
			retVal = this.children[i].getCategoryByFullTitle(targetTitle,conversion,mustHaveReleases);
			if (retVal!==null){break;}
		}
	}
	return retVal;
};

cwpCategory.prototype.getReleaseCategoryFullTitle = function(categoryIDs){
	var fullTitle = null;
	if(this.hasDirectReleases && this.depth != 0) {
		for(var j=0;j<categoryIDs.length;j++){
			if(categoryIDs[j] == this.ID) fullTitle = this.fullTitle;
		}
	}else{
		for (var i=0;i<this.children.length;i++){
			if(this.children[i].type == cwp.CATEGORY){
				fullTitle = this.children[i].getReleaseCategoryFullTitle(categoryIDs);
				if(fullTitle!=null){break;}
			}
		}
	}
	return fullTitle;
};

cwpCategory.prototype.getRelease = function(key,value){
	var retVal = null;
	for (var i=0;i<this.children.length;i++){
		retVal = this.children[i].getRelease(key,value);
		if (retVal){break;}
	}
	return retVal;
};

cwpCategory.prototype.getInheritedProperty = function(key){
	var retVal = this[key];
	// If we don't have defined value at this level, inherit from the parent, if there is one
	if (!this[key] && this[key]!="inherit" && this.parentCat){
		retVal = this.parentCat.getInheritedProperty(key);
	}
	return retVal;
};

cwpCategory.prototype.getFirstRelease = function(doRecursive){
	doRecursive = (doRecursive!==false);
	var rel = null;
	for(var i=0;i<this.children.length;i++){
		if (this.children[i].type==cwp.RELEASE){
			rel = this.children[i];
		}else if (doRecursive){
			rel = this.children[i].getFirstRelease();
		}
		if (rel!==null){break;}
	}
	return rel;
};

cwpCategory.prototype.getParentAtDepth = function(targetDepth){
	if (targetDepth>this.depth){
		return null;
	}else if (this.depth > targetDepth && this.parentCat){
		return this.parentCat.getParentAtDepth(targetDepth); 
	}else{
		return this;
	}
};

cwpCategory.prototype.getFirstParentCategory = function(rel){
	var i,retVal = null;
	if (this.hasDirectReleases){
		for (i=0;i<rel.categoryIDs.length;i++){
			if (rel.categoryIDs[i]==this.ID){
				retVal = this;
				break;
			}
		}
	}
	if (!retVal){
		for (i=0;i<this.children.length;i++){
			if (this.children[i].type == cwp.CATEGORY){
				retVal = this.children[i].getFirstParentCategory(rel);
				break;
			}
		}
	}
	return retVal;
};

// Used to propagate hasReleases upwards, so we know when a category has releases at any depth below
cwpCategory.prototype.setHasReleases = function(){
	if (!this.hasReleases){
		this.hasReleases = true;
		if (this.parentCat){
			this.parentCat.setHasReleases();
		}
	}
};

cwpCategory.prototype.checkReleasesLoaded = function(){
	if (this.releasesLoaded){return true;}
	else if (this.hasDirectReleases){return false;} // if releasesLoaded is false and we have direct child releases, we need to load them

	if (this.children.length>0){
		for (var i=0;i<this.children.length;i++){
			// If we find a child category without releases loaded
			if (this.children[i].type==cwp.CATEGORY && this.children[i].checkReleasesLoaded()===false){
				this.releasesLoaded = false;
				break;
			}
		}
	// Else if we are at a leaf node with no releases
	}else{
		this.releasesLoaded = true;
	}
	return this.releasesLoaded;
};

cwpCategory.prototype.renderHTML = function(targetContainer,startDepth,endDepth,withReleases){
	if (this.manager.hideEmptyCats && !this.hasReleases){return;}
	endDepth = (typeof(endDepth)=="number")?endDepth:-1;
	startDepth = (typeof(startDepth)=="number")?startDepth:-1;
	withReleases = (typeof(withReleases)=="boolean")?withReleases:false;
	
	var renderObj = null;
	if (this.mode == cwp.UNRENDERED && this.depth>=startDepth){
		// We want to use the rendering methods for the depth of the element, but can default to rendering for higher depths
		//   this way is we want nested <ul> tags, we can just define one rendering that cascades down
		
		// The top level element is just the container for the list item and sublists.  The first child is the actual element we want
		this.element = this.manager.makeContent(targetContainer,this);
		this.element[0].cwpTarget = this;
		this.element.click(this.click);
		this.element.mouseout(this.mouseout);
		this.element.mouseover(this.mouseover);
		//this.mode = cwp.RENDERED;
	}
	// If we have children that have not yet been rendered
	if (this.children.length > 0 && this.children[0].mode == cwp.UNRENDERED){
		// If the children respect the endDepth
		if (endDepth == -1 || this.children[0].depth <= endDepth){
			var nextTarget = targetContainer;
			for(var i=0;i<this.children.length;i++){
				this.children[i].renderHTML(nextTarget,startDepth,endDepth,withReleases);
			}
			this.onChildrenShow();
		}
	}
};

cwpCategory.prototype.getCatIDList = function(){ // Used to query for all releases in a category node and all its children
	var catIDList = this.ID;
	if (this.children.length>0){
		$.each(this.children,function(i,childObj){
			if (childObj.type == cwp.CATEGORY){
				catIDList += "," + childObj.getCatIDList();
			}
		});
	}
	return catIDList;
};

cwpCategory.prototype.makeBreadcrumb = function(targetContainer,withAnchor){
	if(withAnchor){
		this.breadcrumb = this.manager.makeBreadcrumb(targetContainer, this, true);
		if (this.breadcrumb !== null){ // Don't try to attach events if the breadcrumb was suppressed in the template
			this.breadcrumb[0].cwpTarget = this;
			this.breadcrumb.click(this.breadcrumbClick);
		}
	}else{
		this.breadcrumb = this.manager.makeBreadcrumb(targetContainer, this, false);
	}
	if(this.depth > 0){
		this.parentCat.makeBreadcrumb(targetContainer,true);
	}
};

cwpCategory.prototype.getConstructedJSON = function(fieldNames,customReleaseCall,jsonURL){
	if (!this.constructedJSON){
		var customFieldNames = null;
		jsonURL = jsonURL || this.manager.jsonURL;
		customReleaseCall = customReleaseCall || this.manager.releaseCall;
		if (fieldNames === undefined || fieldNames === null){
			fieldNames = this.manager.relFields;
			customFieldNames = this.manager.relCustomFields;
		}else if (typeof fieldNames === 'string'){
			var tempFields = fieldNames.split(";");
			fieldNames = tempFields[0].split(",");
				if (tempFields.length>1){
				customFieldNames = tempFields[1].split(",");
			}
		}

		if (this.hasReleases){
			jsonURL += customReleaseCall;
			if (this.ID !== cwp.PLACEHOLDER_ID && this.ID!== "Search"){
				if(this.hasDirectReleases){
					jsonURL += "&query=CategoryIDs|" + this.ID;
				}else{
					jsonURL += "&query=CategoryIDs|" + this.getCatIDList();
				}
			}
		
			$.each(fieldNames, function(i,field){
				jsonURL += "&field=" + field;
			});
			if (customFieldNames){
				$.each(customFieldNames, function(i,field){
					jsonURL += "&contentCustomField=" + field;
				});
			}
			this.constructedJSON = jsonURL;
		}else{
			this.constructedJSON = null;
		}
	}
	return this.constructedJSON;
};


cwpCategory.prototype.updatePageNum = function(){
	this.manager.currentPageNum = this.currentPageNum;
	this.manager.totalPages = this.totalPages;
	this.manager.updatePageNum();
};

// Returns the index of a release given a page number an the index of that clip on a page
//  Will return the total number of releases is the requested index is beyond the end of the clip list
cwpCategory.prototype.releaseIndex = function(page,num){
	return (page-1)*this.clipsPerPage+num;
};

cwpCategory.prototype.isPageLoaded = function(page){
	return (typeof(this.rel[this.releaseIndex(page,1)])=="object");
};
	
cwpCategory.prototype.isPageRendered = function(page){
	var rel = this.rel[this.releaseIndex(page,1)];
	return (typeof(rel)=="object" && rel.mode==cwp.RENDERED && $("#cwpChildren_" + page).length);
};

cwpCategory.prototype.createPageContainer = function(page,isVisible){
	if($("#cwpChildren_" + page).length) $("#cwpChildren_" + page).remove();
	var divParams = {
		id: "cwpChildren_" + page,
		"class":cwp.CLASS_CwpChildrens
	};
	if (isVisible!==true){divParams.css={"display":"none"};}
	var newDiv = $("<div/>",divParams);
	$("<div/>",{"class":"animFrame"}).appendTo(newDiv);
	newDiv.appendTo("#cwpChildren");
	return newDiv;
};

cwpCategory.prototype.downloadRelease = function(page,showFirst){	
	this.renderPage(this.createPageContainer(page,showFirst),page,$.proxy(function(){
		this.updatePageNum();
		if(this.releaseFound && this.firstTimeRender){
			this.firstTimeRender = false;
		}else{
			if (page+1<=this.totalPages){
				this.renderPage(this.createPageContainer(page+1), page+1);
			}
			if (page-1>0){
				this.renderPage(this.createPageContainer(page-1), page-1);
			}
		}
	},this));
};

cwpCategory.prototype.fetchPageContent = function(targetPage,callback){
	if (this.isPageLoaded(targetPage)){
		if (typeof(callback)=="function"){callback();}
	}else{
		var startIndex = this.releaseIndex(targetPage,1),
				endIndex = startIndex + this.clipsPerPage-1;
		var url =  this.getConstructedJSON() + "&startIndex=" + startIndex + "&endIndex=" + endIndex  + "&sortField=" + this.currentSortingCriteria + "&sortDescending=" +this.sortDescending;
		$.ajaxSetup({cache:"true"});
		$.getJSON(url,
			$.proxy(function(data){	
				if(this.ID == "Search" && this.searchCounter < this.manager.searchCounter) return;
				
				if (typeof data == 'string'){
					alert("Error fetching feed");
				}else{
					if (this.totalPagesLoaded===0){
						this.totalReleases = data.listInfo.totalCount;
						this.totalPages = Math.ceil(this.totalReleases / this.clipsPerPage);
					}
					this.totalPagesLoaded++;
					if(this.ID == "Search" && data.listInfo.totalCount == 0){
						this.totalPages = 1;
						if (typeof(callback)=="function"){callback();}
					}
					for(var i=0; i<data.items.length; i++){
						this.rel[i+startIndex] = new cwpRelease(this.manager,data.items[i],this);
						if(this.manager.selectedRel && typeof(this.manager.selectedRel)=="object"){
							if(this.manager.selectedRel.PID == data.items[i].PID)
								this.rel[i+startIndex].isSelected = true;
						}
					}
					if (typeof(callback)=="function"){callback();}
				}
			},this));
		$.ajaxSetup({cache:"false"});
	}
};

cwpCategory.prototype.renderPage = function(targetContainer,targetPage,callback){
	if(this.ID == "Search" && this.searchCounter < this.manager.searchCounter) return;
	if (!this.isPageLoaded(targetPage)){
		this.fetchPageContent(targetPage, $.proxy(function(){
			if(this.ID == "Search" && this.totalReleases == 0 && this.totalPagesLoaded == 1 && this.totalPages == 1){
				targetContainer.empty();
				targetContainer.append("<div id=\"ContentBrowser-LowerContent-SearchMessage\">Your search - " + this.userinput +" - did not match any video.</div>");
			}
			else{
				this.renderPage(targetContainer,targetPage,callback);
			}
		},this));
	}else{
		targetContainer.empty();
		for(var i=this.releaseIndex(targetPage,1); i<this.releaseIndex(targetPage,this.clipsPerPage+1)&&i<=this.totalReleases; i++){
			this.rel[i].page = targetPage;
			this.rel[i].renderHTML(targetContainer);
		}
		if (typeof(callback)=="function"){callback();}
	}
};

cwpCategory.prototype.loadUntilReleaseFound = function(targetRelease, targetPage, dir, callback){
	this.fetchPageContent(targetPage, $.proxy(function(){
		if (!this.releaseFound){
			if (this.totalReleases===0){
				
			}else{
				for (var i=this.releaseIndex(targetPage,1);i<this.releaseIndex(targetPage,this.clipsPerPage+1)&&i<=this.totalReleases;i++){
					if (this.rel[i].PID == targetRelease.pid){
						this.releaseFound = true;
						if (typeof(callback)=="function"){callback(this.rel[i],targetPage);}
						if (targetPage+1<=this.totalPages){
							this.renderPage(this.createPageContainer(targetPage+1), targetPage+1);
						}
						if (targetPage-1>1){
							this.renderPage(this.createPageContainer(targetPage-1), targetPage-1);
						}
						break;
					}
				}
			}
			if (!this.releaseFound){
				//  We either have a direction of -1, 0, 1.  Since the matching page is likely to be close to the target page, we start with 0
				//   implying that the search should continue in both directions until a release is found
				if (dir>=0 && targetPage+1<=this.totalPages){
					this.loadUntilReleaseFound(targetRelease, targetPage+1,1,callback);
				}
				if (dir<=0 && targetPage-1>0){
					this.loadUntilReleaseFound(targetRelease, targetPage-1,-1,callback);
				}
			}
		}
	},this));
};

cwpCategory.prototype.renderRelease = function(){
	if(this.ID == "Search" && this.searchCounter < this.manager.searchCounter) return;
	this.currentPageNum = 1;
	$('#cwpChildren').empty();
	if(((this.currentSortingCriteria != this.manager.currentSortingCriteria) || 
		(this.sortDescending != this.manager.Descending[this.manager.currentSortingCriteria])) && 
		this.totalPagesLoaded !== 0){
		this.reloadCurrentPageBaseSorting(this.manager.currentSortingCriteria, this.manager.Descending[this.manager.currentSortingCriteria]);
	}else{
		this.downloadRelease(this.currentPageNum,true);
	}
};
	
cwpCategory.prototype.showNextPage = function(){
	var selPage = this.currentPageNum + 1;
	if(selPage <= this.totalPages){
		if (!this.isPageRendered(selPage)){
			this.renderPage(this.createPageContainer(selPage),selPage);
		}
		this.showSelectedPage(selPage, this.currentPageNum);
		var nextPage = selPage + 1;
		if((nextPage <= this.totalPages) && !this.isPageLoaded(nextPage)){
			this.renderPage(this.createPageContainer(nextPage),nextPage);
		}
	}
};

cwpCategory.prototype.showPreviousPage = function(){
	var selPage = this.currentPageNum - 1;
	if(selPage >= 1){
		if (!this.isPageRendered(selPage)){
			this.renderPage(this.createPageContainer(selPage),selPage);
		}
		this.showSelectedPage(selPage, this.currentPageNum);
		var nextPage = selPage - 1;
		if((nextPage > 0) && !this.isPageLoaded(nextPage)){
			this.renderPage(this.createPageContainer(nextPage),nextPage);
		}
	}
};

cwpCategory.prototype.showUserInputPage = function(selPage){
	if(selPage>=1 && selPage<=this.totalPages){
		this.showSelectedPage(selPage, this.currentPageNum);
		var nextPage = selPage + 1;
		if((nextPage <= this.totalPages) && !this.isPageRendered(nextPage)){
			this.renderPage(this.createPageContainer(nextPage),nextPage);
		}
		var prevPage = selPage - 1;
		if((prevPage > 0) && !this.isPageRendered(prevPage)){
			this.renderPage(this.createPageContainer(prevPage),prevPage);
		}
	}
};
	
cwpCategory.prototype.showSelectedPage = function(selPage, prevPage, withoutAnimation){
	if (!this.isPageRendered(selPage)){
		this.renderPage(this.createPageContainer(selPage),selPage);
	}
	if(withoutAnimation){
		$("#cwpChildren_" + prevPage).hide();
		$("#cwpChildren_" + selPage).show();
	}
	else{
		var preWidth = parseInt($("#cwpChildren_" + prevPage).width(),10);
		var selPosition = (prevPage<selPage)?preWidth:-preWidth;
		$("#cwpChildren_" + selPage).css({'left':selPosition + "px",'display':'block'});
		$("#cwpChildren_" + prevPage).animate({"left": -selPosition +"px"}, "slow", function(){ $("#cwpChildren_" + prevPage).hide();});
		$("#cwpChildren_" + selPage).animate({"left": "0px"}, "slow");
	}
	this.currentPageNum = selPage;
	this.updatePageNum();
};
	
cwpCategory.prototype.reloadCurrentPageBaseSorting = function(sortingCriteria, sortingDescending){
	if((this.totalPagesLoaded == this.totalPages) && (this.rel.length < 30)){
		this.localsorting(sortingCriteria,sortingDescending);
	}else{
		this.currentSortingCriteria = sortingCriteria;
		this.sortDescending = sortingDescending;
		this.rel = [];
		this.totalPages = this.totalReleases = this.totalPagesLoaded = 0;
		$("#cwpChildren").empty();
		this.downloadRelease(this.currentPageNum,true);
	}
};

cwpCategory.prototype.localsorting = function(sortingCriteria, sortingDescending){
	var i,j,tempRel,total = this.rel.length;
	for (i=1;i<total;i++){this.rel[i].mode=cwp.UNRENDERED;}
	if(this.currentSortingCriteria == sortingCriteria){
		for(i=1; i< Math.floor(total/2); i++){
			tempRel = this.rel[i];
			this.rel[i] = this.rel[total-i];
			this.rel[total-i] = tempRel;
		}
	}else if(sortingDescending){
		for(i=1;i<total;i++){
			for(j=1;j<total-i;j++){
				if(this.rel[j].getProperty(sortingCriteria)< this.rel[j+1].getProperty(sortingCriteria)){
					tempRel=this.rel[j];
					this.rel[j]=this.rel[j+1];
					this.rel[j+1]= tempRel;
				}
			}
		}
	}else{
		for(i=1;i<total;i++){
			for(j=1;j<total-i;j++){
				if(this.rel[j].getProperty(sortingCriteria) > this.rel[j+1].getProperty(sortingCriteria)){
					tempRel=this.rel[j];
					this.rel[j]=this.rel[j+1];
					this.rel[j+1]= tempRel;
				}
			}
		}
	}
	this.currentSortingCriteria = sortingCriteria;
	this.sortDescending = sortingDescending;
	$("#cwpChildren").empty();
	this.downloadRelease(this.currentPageNum,true);
};

cwpCategory.prototype.findCategoryByFullTitle = function(fullTitleArray){
	var targetCat = null;
	if (fullTitleArray.length==this.depth+1 && fullTitleArray.join("/")==this.fullTitle){
		targetCat = this;
	}else if (fullTitleArray.length>this.depth+1 && fullTitleArray.slice(0,this.depth+1).join("/")==this.fullTitle){
		for (var i=0;i<this.children.length;i++){
			targetCat = this.children[i].findCategoryByFullTitle(fullTitleArray);
			if (targetCat){break;}
		}
	}
	return targetCat;
};

function catRender(makeContent, makeChildContainer){
	this.makeContent = makeContent;
	this.makeChildContainer = makeChildContainer;
}

function cwpManagerObj(data){
	var qs = new cwpQS();
	if (qs.contains("cwpDebug")){
		$("body").append("<div id='debugOutput'>Canwest Player version: " + cwp.version + "</div>");
	}
	if (!data.PID){
		return cwp.logError("You must provide a pid.");
	}
	if (!data.playerTag && !data.pidList){
		return cwp.logError("You must provide a playerTag or pidList to initialize the this.");
	}
	// Constants
	this.PID = data.PID;
	this.playerTag = data.playerTag;
	this.pidList = data.pidList;
	this.clipsPerPage = data.clipsPerPage;
	this.relThumbnailWidth = data.relThumbnailWidth;
	this.relThumbnailHeight = data.relThumbnailHeight;
	this.catPlayerTag = data.catPlayerTag || data.playerTag;
	this.preloadFirstRelease = data.preloadFirstRelease || false;
	this.categoryCall = "getCategoryList?callback=?&field=ID&field=depth&field=hasReleases&field=fullTitle&PID=" + this.PID;
	this.categoryCall += "&query=CustomText|PlayerTag|" + this.catPlayerTag;
	this.releaseCall = "getReleaseList?callback=?&field=ID&field=contentID&field=PID&field=URL&field=categoryIDs&query=BitrateEqualOrGreaterThan|400000&query=BitrateLessThan|601000&field=length&field=airdate&field=requestCount&PID=" + this.PID +
		"&contentCustomField=Show&contentCustomField=Episode&contentCustomField=Network&contentCustomField=Season&contentCustomField=Zone&contentCustomField=Subject"; // these are ad fields
	if (this.playerTag){
		this.releaseCall += "&query=Categories|" + this.playerTag;
	}
	this.site = data.site;
	if (data.site){
		this.releaseCall += "&param=Site|"+data.site;
	}
	this.defaultZone = "NOZONE";
	if (data.siteZone){
		this.defaultZone = data.siteZone;
		this.releaseCall += "&param=Zone|"+this.defaultZone;
	}
	this.hideEmptyCats = (data.hideEmptyCats!==false);
	this.totalPages = 1;
	this.currentPageNum = 1;
	this.currentSortingCriteria = "airdate";
	this.Descending = {
		airdate: true,
		title: false,
		requestCount: true
	};
	this.portal = data.portal || "Showcase";
	
	// Incorporate ad parameters from in-page javascript object
	this.extraParamCount = 0;
	if (data.adParameters) {
		this.adParameters = data.adParameters;
		var extraParamCount = 0;
		var extraReleaseParams = "";
		for (var key in data.adParameters){
			if (data.adParameters.hasOwnProperty(key)){
				for (var i=0;i<data.adParameters[key].length;i++){
					extraReleaseParams += "&param=k"+(extraParamCount)+"|"+cwp.cleanAdParams(key);
					extraReleaseParams += "&param=v"+(extraParamCount)+"|"+cwp.cleanAdParams(data.adParameters[key][i]);	
					extraParamCount++;
				}
			}
		}
		this.releaseCall += extraReleaseParams;
		this.extraParamCount = extraParamCount;
	}

	// Properties
	this.relRendering = null;
	this.catRendering = [];
	data.categoryFields = data.categoryFields || "";
	// If a user wants to retrieve the title, retrieve displayTitle as well to make sure we give them the right string
	if (data.categoryFields.indexOf("title")!=-1 && data.categoryFields.indexOf("DisplayTitle")==-1){
		data.categoryFields += ((data.categoryFields.indexOf(";")==-1)?";":",") + "DisplayTitle";
	}
	var tempFields;
	if (!data.categoryFields){
		this.catFields = [];
		this.customCatFields = [];
	}else{
		tempFields = data.categoryFields.split(";");
		this.catFields = tempFields[0].split(",");
		if (tempFields.length>1){ this.catCustomFields = tempFields[1].split(","); }
	}
	if (!data.releaseFields){
		this.relFields = [];
		this.customRelFields = [];
	}else{
		tempFields = data.releaseFields.split(";");
		this.relFields = tempFields[0].split(",");
		if (tempFields.length>1){ this.relCustomFields = tempFields[1].split(","); }
	}
	this.catList = [];
	this.targetPlaylistContainer = null;
	if (!data.jsonURL){ // optional property
		this.jsonURL = "http://feeds.theplatform.com/ps/JSON/PortalService/2.2/";
	}else{
		this.jsonURL = data.jsonURL;
	}
	this.currentPlay = null;
	this.previousPlay = null;
	this.playlistRootCat = null;
	this.jsonRequested = false;
	this.selectedCat = null;
	this.selectedRel = null;
	// Unless otherwise defined, we will play only one video at a time
	this.playBlocked = false;
	this.disableOmniture = (data.disableOmniture===true);

	this.cat = [];
	
	var searchCatData = {
		ID: "Search",
		fullTitle: "Search"
	};
	this.searchCounter = 0;
	this.searchCat = null;
	var managerObj = this;
	this.OnPlayBlocked = null;
	this.catExists = function(depth){
		var curCat = this.cat[depth];
		if (!curCat) {
			curCat = {};
			curCat.depth = depth;
			if (depth>0){
				this.catExists(depth-1); // Make sure all previous levels exist
				curCat.click = function(obj){managerObj.cat[depth-1].click(obj);};
				curCat.breadcrumbClick = function(obj){managerObj.cat[depth-1].breadcrumbClick(obj);};
				curCat.mouseover = function(obj){managerObj.cat[depth-1].mouseover(obj);};
				curCat.mouseout = function(obj){managerObj.cat[depth-1].mouseout(obj);};
				curCat.onChildrenShow = function(obj){managerObj.cat[depth-1].onChildrenShow(obj);};
				curCat.select = function(obj){managerObj.cat[depth-1].select(obj);};
				curCat.unselect = function(obj){managerObj.cat[depth-1].unselect(obj);};
			}else{
				curCat.click = function(obj){obj.select();}; // click selects the category by default
				curCat.mouseover = function(obj){};
				curCat.mouseout = function(obj){};
				curCat.onChildrenShow = function(obj){};
				curCat.select = function(obj){};
				curCat.unselect = function(obj){};
			}
			this.cat[depth] = curCat;
		}
		return curCat;
	};
	this.catExists(0);
	
	this.evtProto = function(name,depth,callback){this.catExists(depth)[name] = function(obj){callback(obj);};};
	this.catClick = function(depth,callback){this.evtProto("click",depth,callback);};
	this.catBreadcrumbClick = function(depth,callback){this.evtProto("breadcrumbClick",depth,callback);};
	this.catMouseover = function(depth,callback){this.evtProto("mouseover",depth,callback);};
	this.catMouseout = function(depth,callback){this.evtProto("mouseout",depth,callback);};
	this.catSelect = function(depth,callback){this.evtProto("select",depth,callback);};
	this.catUnselect = function(depth,callback){this.evtProto("unselect",depth,callback);};
	this.catOnChildrenShow = function(depth,callback){this.evtProto("onChildrenShow",depth,callback);};

	this.rel = {};
	this.relClick = function(callback){this.rel.click = callback;};
	this.relMouseover = function(callback){this.rel.mouseover = callback;};
	this.relMouseout = function(callback){this.rel.mouseout = callback;};
	this.relMouseenter = function(callback){this.rel.mouseenter = callback;};
	this.relMouseleave = function(callback){this.rel.mouseleave = callback;};
	this.relSelect = function(callback){this.rel.select = callback;};
	this.relUnselect = function(callback){this.rel.unselect = callback;};
	this.relOnVideoStart = function(callback){this.rel.onVideoStart = callback;};
	this.relOnVideoEnd = function(callback){this.rel.onVideoEnd = callback;};
	this.relOnVideoComplete = function(callback){this.rel.onVideoComplete = callback;};
	this.relOnVideoPause = function(callback){this.rel.onVideoPause = callback;};
	this.relOnVideoUnpause = function(callback){this.rel.onVideoUnpause = callback;};
	this.rel.click = function(obj){}; // click selects and plays a release by default

	this.createSearchCat = function(){
		this.searchCat == null;
		this.searchCounter ++;
		this.searchCat = new cwpCategory(this, searchCatData, null, this.catFields, this.catCustomFields);
		return this.searchCat;
	}
	
	this.toggleDevMode = function(devMode){
		if (this.playerTag){ // If there is no player tag, we won't do anything
			this.releaseCall = this.releaseCall.replace(/&query=Categories[|][^&]*/,"");
			if (!devMode){
				this.releaseCall += "&query=Categories|" + this.playerTag;
			}
		}
	};

	this.getCategory = function(key,value){
		var retVal = null;
		for (var i=0;i<this.catList.length;i++){
			retVal = this.catList[i].getCategory(key,value);
			if (retVal){break;}
		}
		return retVal;
	};

	this.getRelease = function(key,value){
		var retVal = null;
		for (var i=0;i<this.catList.length;i++){
			retVal = this.catList[i].getRelease(key,value);
			if (retVal){break;}
		}
		return retVal;
	};
	
	this.getFirstParentCategory = function(rel){
		var retVal = null;
		for (var i=0;i<this.catList.length;i++){
			retVal = this.catList[i].getFirstParentCategory(rel);
			if (retVal){break;}
		}
		return retVal;
	};

	this.setPlayingRelease = function(rel){
		if (this.currentPlay != rel){
			this.previousPlay = this.currentPlay;
			this.currentPlay = rel;
		}
	};

	this.setSelected = function(obj,isSelect){
		// If we are selecting something, call unselect on the previously selected item at the same level
		if (obj.type==cwp.CATEGORY){
			if (isSelect){
				if (this.selectedCat){
					if(this.selectedCat.ID != "Search" && obj.ID == "Search")
						this.previousSelectedCat = this.selectedCat;
					this.selectedCat.unselect();
				}
				this.selectedCat = obj;
			}else{
				this.selectedCat=null;
			}
		}else if (obj.type==cwp.RELEASE){
			if (isSelect){
				if (this.selectedRel && this.selectedRel != obj){
					this.selectedRel.unselect();
				}
				this.selectedRel = obj;
				// We have selected a release, update the page title if necessary
			}else if (this.selectedRel == obj){
				this.selectedRel = null;
			}
		}
	};
	
	this.removeSearch = function(){
		if(this.selectedCat.ID == "Search"){
			if(this.previousSelectedCat)
				this.previousSelectedCat.select();
			else
				this.catList[0].select();
		}
	};
	
	this.handleCategoryFeed = function(data,callback){
		if (typeof(data)== 'string'){
			return cwp.logError("Category JSON request failed: " + data);
		}
		if (data.items.length===0){
			if (typeof(this.OnNoCategories)=="function"){
				this.OnNoCategories();
			}
			return;
		}
		var currentDepth = 0;
		var rootCatLevel = this.catList;
		var currentCatLevel = rootCatLevel;
		var parentCat = null;

		$.each(data.items,$.proxy(function(i,catData){
			if (currentDepth < catData.depth){
				if (currentCatLevel.length===0){
					cwp.logDebug("We skipped a level of depth (expected " + currentDepth + " and got " + catData.depth + ").");
				}else{
					parentCat = currentCatLevel[currentCatLevel.length-1]; // the last category we added
					currentCatLevel = parentCat.children;
				}
				currentDepth = catData.depth;
			}else if (currentDepth > catData.depth){
				do{
					parentCat = parentCat.parentCat;
					if (parentCat===null){
						currentCatLevel = rootCatLevel;
					}else{
						currentCatLevel = parentCat.children;
					}
				}while (currentCatLevel[0].depth > catData.depth);
				currentDepth = catData.depth;
			}
			currentCatLevel.push(new cwpCategory(this, catData, parentCat, this.catFields, this.catCustomFields));
		},this));
		cwp.logDebug("Processed JSON category data.");
		if (typeof(callback)=="function"){callback();}
	};

	// Methods
	this.getCategories = function(callback){
				
		var constructedJSON = this.jsonURL + this.categoryCall;
		$.each(this.catFields, function(i,field){
			constructedJSON += "&field=" + field;
		});
		if (this.catCustomFields){
			$.each(this.catCustomFields, function(i,field){
				constructedJSON += "&customField=" + field;
			});
		}
		
		this.jsonRequested = true;
		cwp.logDebug("Requested category feed: " + constructedJSON);
		$.ajaxSetup({cache:"true"});
		$.getJSON(constructedJSON, $.proxy(function(data){
			this.jsonRequested = false;
			cwp.logDebug("Retrieved JSON category data.");
			this.handleCategoryFeed(data,callback);
		},this));
		$.ajaxSetup({cache:"false"});
	};

	this.renderCategories = function(targetContainer, startDepth, endDepth, withReleases){
		cwp.logDebug("Begin rendering top level categories.");
		endDepth = (typeof(endDepth)=="number")?endDepth:-1;
		startDepth = (typeof(startDepth)=="number")?startDepth:-1;
		withReleases = (typeof(withReleases)=="boolean")?withReleases:false;
		if (this.catList.length>0){
			$.each(this.catList, function(i,cat){
				cat.renderHTML(targetContainer, startDepth, endDepth, withReleases);
			});
		}
		cwp.logDebug("End rendering top level categories.");
	};

	this.getAdParamsForReleaseURL = function(cntParams){
		var i,retVal = "";
		// We repurpose the code from initialization, but since we need the params in a different format, we don't use the same code
		var extraParamCount = 0;
		if (typeof(this.adParameters)=="object") {
			for (var key in this.adParameters){
				if (this.adParameters.hasOwnProperty(key)){
					for (i=0;i<this.adParameters[key].length;i++){
						retVal += "&k"+(extraParamCount)+"="+cwp.cleanAdParams(key);
						retVal += "&v"+(extraParamCount)+"="+cwp.cleanAdParams(this.adParameters[key][i]);
						extraParamCount++;
					}
				}
			}
		}
		if (cntParams){
			for (i=0;i<cntParams.length;i++){
				retVal += "&k"+(extraParamCount)+"=cnt&v"+(extraParamCount)+"="+cwp.cleanAdParams(cntParams[i]);
			}
		}
		return retVal;
	};

	this.constructReleaseURLForTargetRelease = function(targetRelease){
		// Calculate the correct zone
		var newZone = "&Zone=";
		if (targetRelease.zone){
			newZone += targetRelease.zone;
		}else if (targetRelease.network||targetRelease.show){
			newZone += cwp.cleanStr(targetRelease.network+targetRelease.show);
		}else{
			newZone += this.defaultZone;
		}
		return "http://release.theplatform.com/content.select?pid=" + targetRelease.pid + "&mbr=true&UserName=Unknown&Embedded=True&Portal=" + this.portal + "&Site=" + this.site + "&TrackBrowser=True&Tracking=True&TrackLocation=True&format=SMIL&show=" + cwp.cleanAdParams(targetRelease.show) + "&episode=" + cwp.cleanAdParams(targetRelease.episode) + "&network=" + cwp.cleanAdParams(targetRelease.network) + "&season=" + cwp.cleanAdParams(targetRelease.season) + "&clipLength=" + (targetRelease.length>120000?"long":"short") + newZone + this.getAdParamsForReleaseURL((targetRelease.subject)?targetRelease.subject.split(","):null) + (targetRelease.initialTime?("&t=" + (targetRelease.initialTime+1) + "-"):"");
	};

	this.constructReleaseURL = function(pid,zone){
		// Try not including the portal and seeing where we end up
		return "http://release.theplatform.com/content.select?pid=" + pid + "&UserName=Unknown&Embedded=True&Portal=" + this.portal + "&Site=" + this.site + "&TrackBrowser=True&Tracking=True&TrackLocation=True" + this.getAdParamsForReleaseURL;
	};

	this.getReleaseFromPid = function(pid,zone,parentCat){
		var relData = {"categoryIDs":[cwp.PLACEHOLDER_ID],"contentCustomData":[{"title":"Show","value":""},{"title":"Episode","value":""},{"title":"Network","value":""},{"title":"Season","value":""},{"title":"Zone","value":zone}],"contentID":cwp.PLACEHOLDER_ID,"ID":cwp.PLACEHOLDER_ID,"length":120000,"PID":pid,"URL":this.constructedReleaseURL(pid,zone)};
		
		return new cwpRelease(this, relData, parentCat);
	};

	this.getReleasesFromPidList = function(zone,pidList,fetchMetadata,callback){
		var i;
		pidList = pidList || this.pidList; // If we don't override the pidList, use the default one from initialization
		// Don't bother fetching data unless we have an array of pids
		if (pidList && pidList.length>0){
			// We use "story" as the fullTitle, as that is the bucket used for Omniture tracking
			var catData = {"depth":0,"fullTitle":"story","hasReleases":true,"ID":cwp.PLACEHOLDER_ID,"title":"FAKECAT"};
			this.catList[0] = new cwpCategory(this, catData);
			
			// If we should try to fetch the releases with corresponding metadata
			if (fetchMetadata){
				this.jsonRequested = true;
				var cat = this.catList[0];
				this.loadingPlaylistCat = cat;
				var customReleaseCall = this.releaseCall + "&query=PIDs|";
				for (i=0;i<pidList.length;i++){
					customReleaseCall += (i===0)?pidList[i]:","+pidList[i];
				}
				cat.fetchReleases($.proxy(function(releaseList){
					this.jsonRequested = false;
					this.releasesLoaded();
					if (typeof(callback)=="function"){callback();}
				},this),null,customReleaseCall);
			}else{
				for (i=0;i<pidList.length;i++){
					this.catList[0].children.push(this.getReleaseFromPid(pidList[i],zone,this.catList[0]));
				}
				if (pidList.length>0){
					this.catList[0].releasesLoaded = true;
					this.releasesLoaded();
				}
			}
		}
	};

	this.findCategoryByFullTitle = function(fullTitle){
		var targetCat=null,
				fullTitleArray = fullTitle.split("/");
		for (var i=0;i<this.catList.length;i++){
			targetCat = this.catList[i].findCategoryByFullTitle(fullTitleArray);
			if (targetCat){break;}
		}
		return targetCat;
	};
	
	this.onAdStartCallback = null;
	this.onAdStart = $.proxy(function(evt){
		if (typeof(evt)=="function"){
			this.onAdStartCallback = evt;
		}else{

			cwp.logDebug("Received onAdStart event.");
			if (typeof(this.onAdStartCallback)=="function"){this.onAdStartCallback(evt);}
		}
	},this);

	this.onAdEndCallback = null;
	this.onAdEnd = $.proxy(function(evt){
		if (typeof(evt)=="function"){
			this.onAdEndCallback = evt;
		}else{
			cwp.logDebug("Received onEndStart event.");
			if (typeof(this.onAdEndCallback)=="function"){this.onAdEndCallback(evt);}
		}
	},this);

	// Event listeners
	this.onMediaStart = $.proxy(function(evt){
		cwp.logDebug("Received onMediaStart event for: " + evt.data.baseClip.contentID);
		if (evt.data.baseClip.isAd){this.onAdStart(evt);}
		if (!this.currentPlay){cwp.logError("Received a start event without an active release.");}
		this.currentPlay.onVideoStart(evt);
		//cwp.insertComScoreBeacon(this);
	},this);

	this.onMediaEnd = $.proxy(function(evt){
		cwp.logDebug("Received onMediaEnd event for: " + evt.data.baseClip.contentID);
		if (!this.currentPlay){cwp.logDebug("Received an end event without an active release.");}
		if (evt.data.baseClip.isAd){
			this.onAdEnd(evt);
		}else	if (this.currentPlay && this.currentPlay.contentID == evt.data.baseClip.contentID){
			this.currentPlay.onVideoEnd(evt);
		}else if (this.previousPlay && this.previousPlay.contentID == evt.data.baseClip.contentID){
			this.previousPlay.onVideoEnd(evt);
		}
	},this);

	this.onReleaseEnd = $.proxy(function(evt){
		cwp.logDebug("Received onReleaseEnd event for: " + this.currentPlay.ID);
		if (this.currentPlay.ID==cwp.PLACEHOLDER_ID || this.currentPlay.ID==evt.data.playlistID){
			this.currentPlay.onVideoComplete(evt);
		}
	},this);

	this.onMediaPause = $.proxy(function(evt){
		cwp.logDebug("Received onMediaPause event for: " + evt.data.baseClip.contentID);
		if (!this.currentPlay){cwp.logError("Received a pause event without an active release.");}
		this.currentPlay.onVideoPause(evt);
	},this);

	this.onMediaUnpause = $.proxy(function(evt){
		cwp.logDebug("Received onMediaUnpause event for: " + evt.data.baseClip.contentID);
		if (!this.currentPlay){cwp.logError("Received an unpause event without an active release.");}
		this.currentPlay.onVideoUnpause(evt);
	},this);

	this.onPlayerLoaded = $.proxy(function(){
		cwp.logDebug("*** Player has loaded ***");
		// We no longer need to take any action
	},this);

	this.OnNoCategories = null;

	// Add event listeners
	if (tpController){
		tpController.addEventListener("OnMediaStart","cwp.manager.onMediaStart");
		tpController.addEventListener("OnMediaEnd","cwp.manager.onMediaEnd");
		tpController.addEventListener("OnReleaseEnd","cwp.manager.onReleaseEnd");
		tpController.addEventListener("OnMediaPause","cwp.manager.onMediaPause");
		tpController.addEventListener("OnMediaUnpause","cwp.manager.onMediaUnpause");
		tpController.addEventListener("OnPlayerLoaded", "cwp.manager.onPlayerLoaded");
	}

	// Set currently playing release if we have loaded the page with a targetRelease
	if (data.targetRelease!==null){
		this.currentPlay = $.proxy(function(targetRelease){
			// We will create a temporary release from the targetRelease to handle incoming events
			var relData = {"categoryIDs":[cwp.PLACEHOLDER_ID],"contentCustomData":[{"title":"Show","value":targetRelease.show},{"title":"Episode","value":targetRelease.episode},{"title":"Network","value":targetRelease.network},{"title":"Season","value":targetRelease.season}],"contentID":cwp.PLACEHOLDER_ID,"ID":cwp.PLACEHOLDER_ID,"length":targetRelease.length,"PID":targetRelease.pid,"URL":this.constructReleaseURLForTargetRelease(targetRelease)};
			return new cwpRelease(this, relData, null);
		},this)(data.targetRelease);
	}

}

// This needs to be moved to the template file
function startWaitingAnimation(pageJId){
	 $(pageJId).html("<div class='animFrame'></div>");
}

cwp.init = function(data){
	cwp.manager = new cwpManagerObj(data);
};

