/**
	@Class
	A JSON version of the MuseStorm SearchManager.<br>
	Allows developers to retrieve data from various sources by submitting a 
	search term and then requesting batches of results.<br>
	<i>Type</i> flags are used to specify what type of search you are 
	interested in (for example, for news search, use SEARCH_NEWS).<br>
	<i>Source</i> flags are used to indicate the data source for the search
	(for example, to specify that the source for news search should be Yahoo, 
	use YAHOO). <br>All search types have source defaults, so if you leave the
	source empty, the server will use the default source for the data type 
	(for example, the default for news search is GOOGLE).
**/
function SearchManager(){
	//search type flags
	
	this.SEARCH_WEB=0;		//Type flag for Web Search	
	this.SEARCH_IMAGES=1;	//Type flag for Image Search	
	this.SEARCH_NEWS=2;		//Type flag for News Search	
	this.SEARCH_BOOKS=3;	//Type flag for Book Search	
	this.SEARCH_AUCTIONS=4;	//Type flag for Auction Search	
	this.SEARCH_DEFINITIONS=5;	//Type flag for Definition Search	
	this.SEARCH_PHOTOS=6;	//Type flag for Photo Search	
	this.SEARCH_FAVORITES=7;	//Type flag for Favorites Search 
	this.SEARCH_VIDEOS=8;	//Type flag for Video Search 
	this.SEARCH_BLOGS=9;	//Type flag for Blog Search 
	this.GOOGLE="GOOGLE";	//Source flag. Applicable for Web search, News search
	this.YAHOO="YAHOO";		//Source flag. Applicable for Web search, News search
	this.DELICIOUS="DELICIOUS";	//Source flag. Applicable for Favorites search
	this.SIMPY="SIMPY";		//Source flag. Applicable for Favorites search

	//???????????????????? TEMP ??????????????????????????????????
	this.SEARCH_FINANCIAL_NEWS=10;	//Type flag for financial news
	this.SEEKINGALPHA="SEEKINGALPHA";
	
	
	this.NODE_TYPE_TEXT=3;
	
	//musemanager
	this.manager=null;
	//callback handler
	this.handler=null;
	//session ID
	this.sessionID=null;

	//max results to retrieve from server, enforced on server as well
	this.numMaxCount=20;
	//size of result buffer to maintain
	this.numBuffer=40;
	//hold result objects
	this.arrTypes;
	//number of different result types
	this.numResultTypes=11;
	//number of different sources
	this.sourceArray=["GOOGLE", "YAHOO", "AMAZON","EBAY","FLICKR","DELICIOUS","YOUTUBE","TECHNORATI","SIMPY", "SEEKINGALPHA"];
	this.numSources=10;
	//number of results to fetch each time
	this.numResultCount=20;
	//SearchManager version
	this.version="1.1.1";
}


/**
	@Description
	Initialize the SearchManager

	@Parameters	
	mng = reference to the MuseManager
	refName = name of the SearchManager instance (string) 
	handler = reference to the event handler for the manager
**/
SearchManager.prototype.init=function(mng, refName, handler){
	this.manager=mng;
	this.ref=refName;
	this.handler=handler;
}
			

/**
	@Description
	Start a new search.
	
	@Parameters
	term = search term	
**/
SearchManager.prototype.newSearch=function(term){
	delete this.arrTypes;
	this.arrTypes=null;
	//create data matrix
	this.arrTypes=new Array();		//create the search type array
	for (var i=0;i<this.numResultTypes;i++){
		this.arrTypes[i]=new Array();	//create the source array for the type
		for (var j=0;j<this.numSources;j++){	//init each source data struct
			this.arrTypes[i][j]=new ResultsObject();
		}
	}		
	//create request
	var strTemp="opCode=search&searchTerm="+term+"&isCategory=false&isHTMLReply=false&sessionID="+this.sessionID;
	this.manager.submit(this.ref+".onNewSearch", strTemp);
}

/**
	@Description
	To use the SearchManager your handler must implement this function.<br>
	Function is called by the SearchManager to notify that server is ready to serve results.
**/
SearchManager.prototype.onNewSearch=function(jsonRes){
	var res=jsonRes.json;
	//remove tag from DOM
	this.manager.removeScriptTag(res.tid);
	var newres=new Object();
	newres.status=res.status;
	if (res.status=="OK")
		this.sessionID=res.sessionID;
	else
		newres.errorMessage=res.errorMessage;
	//notify GUI
	this.handler["onNewSearch"](newres);
}

/**
	@Description
	Get the first batch of results of specified type for the search term. <br>
	Example: (if your SearchManager instance is called sm) sm.getFirstResults(sm.SEARCH_WEB, 10, sm.GOOGLE);
	
	@Parameters
	numType = type of results to return (one of the global parameter flags)	
	numCount = number of results to return
	txtSource = source of data. Pass null to get default source or use one of the source flags
	subType = additional parameter for use with some data sources. Usually you should pass 'null'
**/
SearchManager.prototype.getFirstResults=function(numType, numCount, txtSource, subType){
	//map source
	var source=this.mapSource(numType, txtSource);
	if (source==null)
		return;
	//don't fetch more then max results
	if (numCount>this.numMaxCount)
		numCount=this.numMaxCount;
			
	//get results from rds
	var arr=this.arrTypes[numType][source].rds.getBatch(0, numCount);		
	
	//enough results, or end of results
	if (arr.length==numCount || this.arrTypes[numType][source].boolEnd==true){			
		this.arrTypes[numType][source].numLast=arr.length;
		//according to request type - callback to storm
		this.returnToStorm(numType, arr);
	}
	else { //not enough results, go get more
		this.arrTypes[numType][source].numStart=0; 
		this.arrTypes[numType][source].numCount=numCount;	
		var stype= subType==null ? "" : "&subType="+subType;
		var strTemp="opCode=getSearchResults&startIndex=0&resultsNum=" + this.numResultCount + "&searchType=" + this.convert(numType) + "&sessionID=" + this.sessionID + "&source=" + this.sourceArray[source] + stype;
		this.manager.submit(this.ref+".onSearchResults", strTemp);	
		
	}			
}

/*
	@Description
	callback to storm according to type of result
	
	@Parameters
	numType = type of results
	arr = array of results
*/
SearchManager.prototype.returnToStorm=function(numType, arr){	
	var stat=new Object();
	stat.status="OK";
	this.handler[this.typeToFunc(numType)](arr,stat);	
}

/*
	@Description
	take enumeration ID and return API string
	
	@Parameters
	num = enum ID
*/
SearchManager.prototype.convert=function(num){
	switch (num) {
		case this.SEARCH_WEB: return "STANDARD";
		case this.SEARCH_NEWS: return "NEWS";
		case this.SEARCH_IMAGES: return "IMAGES";
		case this.SEARCH_BOOKS: return "BOOKS";
		case this.SEARCH_AUCTIONS: return "AUCTIONS";	
		case this.SEARCH_DEFINITIONS: return "DEFINITIONS";
		case this.SEARCH_PHOTOS: return "PHOTOS";
		case this.SEARCH_FAVORITES: return "FAVORITES";
		case this.SEARCH_VIDEOS: return "VIDEOS";
		case this.SEARCH_BLOGS: return "BLOGS";
		//?????????????????? TEMP ??????????????????????
		case this.SEARCH_FINANCIAL_NEWS: return "FINANCIAL_NEWS";
		default: return null;
	}	
}

/*
	@Description
	take enumeration ID and return API string
	
	@Parameters
	num = enum ID
*/
SearchManager.prototype.typeToFunc=function(num){
	switch (num) {
		case this.SEARCH_WEB: return "onWebResults";
		case this.SEARCH_NEWS: return "onNewsSearchResults";
		case this.SEARCH_IMAGES: return "onImagesSearchResults";
		case this.SEARCH_BOOKS: return "onBooksSearchResults";
		case this.SEARCH_AUCTIONS: return "onAuctionsSearchResults";	
		case this.SEARCH_DEFINITIONS: return "onDefinitionSearchResults";
		case this.SEARCH_PHOTOS: return "onPhotosSearchResults";
		case this.SEARCH_FAVORITES: return "onFavoritesSearchResults";
		case this.SEARCH_VIDEOS: return "onVideoSearchResults";
		case this.SEARCH_BLOGS: return "onBlogsSearchResults";
		//?????????????????? TEMP ??????????????????????
		case this.SEARCH_FINANCIAL_NEWS: return "onFinancialNewsResults";
		default: return null;
	}		
}
/**
	@Description
	Implement this function for your handler to receive Technorati blog search data.
	
	@Parameters
	arr = array of BlogsResult objects
	status = status object
**/
SearchManager.prototype.onBlogsSearchResults=function(arr,status){}
/**
	@Description
	Implement this function for your handler to receive YouTube video data.
	
	@Parameters
	arr = array of VideosResult objects
	status = status object
**/
SearchManager.prototype.onVideoSearchResults=function(arr,status){}
/**
	@Description
	Implement this function for your handler to receive del.icio.us bookmarks data.
	
	@Parameters
	arr = array of FavoritesResult objects
	status = status object
**/
SearchManager.prototype.onFavoritesSearchResults=function(arr,status){}
/**
	@Description
	Implement this function for your handler to receive Flickr photos data.
	
	@Parameters
	arr = array of PhotoResult objects
	status = status object
**/
SearchManager.prototype.onPhotosSearchResults=function(arr,status){}
/**
	@Description
	Implement this function for your handler to receive Google Definition data.
	
	@Parameters
	arr = array of DefinitionResult objects
	status = status object
**/
SearchManager.prototype.onDefinitionSearchResults=function(arr,status){}
/**
	@Description
	Implement this function for your handler to receive EBay Auctions data.
	
	@Parameters
	arr = array of AuctionResult objects
	status = status object
**/
SearchManager.prototype.onAuctionsSearchResults=function(arr,status){}
/**
	@Description
	Implement this function for your handler to receive Google Web search results data.
	
	@Parameters
	arr = array of WebResult objects
	status = status object
**/
SearchManager.prototype.onWebResults=function(arr,status){}
/**
	@Description
	Implement this function for your handler to receive Google News search results data.
	
	@Parameters
	arr = array of NewsResult objects
	status = status object
**/
SearchManager.prototype.onNewsSearchResults=function(arr,status){}
/**
	@Description
	Implement this function for your handler to receive Google Image search results data.
	
	@Parameters
	arr = array of ImageResult objects
	status = status object
**/
SearchManager.prototype.onImagesSearchResults=function(arr,status){}
/**
	@Description
	Implement this function for your handler to receive Amazon books data.
	
	@Parameters
	arr = array of BookResult objects
	status = status object
**/
SearchManager.prototype.onBooksSearchResults=function(arr,status){}


/*
	@Description
	parse server response, return results to Storm
	
	@Parameters
	jsonRes = JSON-formatted server response
*/
SearchManager.prototype.onSearchResults=function(jsonRes){
	var res=jsonRes.json;	
	//remove tag from DOM
	this.manager.removeScriptTag(res.tid);
	var newres=new Object();
	//check if error occured while requesting data 
	if (res.status!="OK"){
		newres.status=res.status;
		newres.errorMessage=res.errorMessage;
		this.handler[typeToFunc(mapType(res.type))](newres);
		return;
	}
	//response ok, parse it...
	newres.status=res.status;
	newres.errorMessage="";
	//get total count attribute
	var totalCount=res.totalCount;
	//create array of correct objects
	var arr=new Array();
	//save type of results
	newres.type= this.mapType(res.type);	
	newres.source=res.source;
	//extract all objects
	for (var i=0;i<res.results.length;i++){
		var _obj=this.mapObject(res.type);
		arr.push(_obj);
		_obj.totalCount=totalCount;
		for (var attr in res.results[i]){
			var source=res.results[i];			
			if (typeof source[attr] == "object"){
				_obj[attr]=new Object();
				this.buildObject(_obj[attr], source[attr]);
			}
			else{
				_obj[attr]=unescape(source[attr]);
			}
		} //for attr
	}	//for i		
	newres.data=arr;
	this.returnResults(newres);
}

SearchManager.prototype.buildObject=function(dest, source){
	for (var attr in source){
		if (typeof source[attr] == "object"){
			dest[attr]=new Object();
			this.buildObject(dest[attr], source[attr]);
		}
		else
			dest[attr]=unescape(source[attr]);
	}
}

SearchManager.prototype.mapObject=function(type){
	switch (type) {
		case "WEB": return new WebResult();
		case "IMAGES": return new ImageResult();
		case "PHOTOS": return new PhotoResult();
		case "AUCTIONS": return new AuctionResult();
		case "BLOGS": return new BlogsResult();
		case "NEWS": return new NewsResult();
		case "BOOKS": return new BookResult();
		case "DEFINITIONS": return new DefinitionResult();
		case "FAVORITES": return new FavoritesResult();
		case "VIDEOS": return new VideosResult();
		//?????????????????? TEMP ??????????????????????
		case "FINANCIAL_NEWS": return new FinancialNewsResult();
		default: return null;
	}
}			

SearchManager.prototype.mapType=function(type){	
	if (type==null)
		return null;		
	//map type
	switch (type){
		case "WEB": return this.SEARCH_WEB; 
		case "IMAGES": return this.SEARCH_IMAGES; 
		case "AUCTIONS": return this.SEARCH_AUCTIONS; 
		case "BLOGS": return this.SEARCH_BLOGS; 
		case "BOOKS": return this.SEARCH_BOOKS; 
		case "DEFINITIONS": return this.SEARCH_DEFINITIONS; 
		case "FAVORITES": return this.SEARCH_FAVORITES; 
		case "NEWS": return this.SEARCH_NEWS; 
		case "PHOTOS": return this.SEARCH_PHOTOS; 
		case "VIDEOS": return this.SEARCH_VIDEOS; 
		case "AUCTIONS": return this.SEARCH_AUCTIONS; 
		//?????????????????? TEMP ??????????????????????
		case "FINANCIAL_NEWS": return this.SEARCH_FINANCIAL_NEWS; 
	}
	return null;
}

//map a source name to a number to lookup in the matrix
SearchManager.prototype.mapSource=function(type,source){
	//???????????? the '10' at the end is seekingalpha - temp only ??????????????
	var defs=[0,1,0,2,3,0,4,5,6,7,10];
	if (source==null)		//use default if source is null
		return defs[type];
	for (var i=0;i<this.sourceArray.length;i++){
		if (source.toLowerCase()==this.sourceArray[i].toLowerCase())
			return i;
	}
	return defs[type];
}

SearchManager.prototype.returnResults=function(res){
	//map source
	var source=this.mapSource(res.type,res.source);
	if (source==null)
		return;
	//create the status object
	var statObj=new Object();
	statObj.status=res.status;
	statObj.errorMessage=res.errorMessage;
	var arrResults=res.data;
	//if error occured and main is waiting for the results
	if (arrResults==null && this.arrTypes[res.type][source].numStart!=-1){
		this.handler[this.typeToFunc(res.type)](null, statObj);
		this.arrTypes[res.type][source].numStart=-1;  this.arrTypes[res.type][source].numCount=-1;
		return;
	}
	
	//no results for term
	if (arrResults.length==0 && this.arrTypes[res.type][source].getArrayLength()==0){
		this.arrTypes[res.type][source].boolEnd=true;
		this.arrTypes[res.type][source].numStart=-1;  this.arrTypes[res.type][source].numCount=-1;
		this.arrTypes[res.type][source].numLast=-1;
		this.handler[this.typeToFunc(res.type)](arrResults, statObj);
		return;
	}
		
	//if server returned less results then asked for - flag to stop requesting
	if (arrResults.length < this.numResultCount){
		this.arrTypes[res.type].boolEnd=true;			
	}		
	
	this.arrTypes[res.type][source].rds.setTotalResults(arrResults[0].getTotalCount());
	var flag=this.arrTypes[res.type][source].rds.appendResults(arrResults);		
	delete arrResults;			
	//if main is waiting for results
	if (this.arrTypes[res.type][source].numStart!=-1 && flag==true){
		this.arrTypes[res.type][source].numLast=this.arrTypes[res.type][source].numStart+this.arrTypes[res.type][source].numCount;			
		this.handler[this.typeToFunc(res.type)](this.arrTypes[res.type][source].rds.getBatch(this.arrTypes[res.type][source].numStart, this.arrTypes[res.type][source].numCount), statObj);			
		this.arrTypes[res.type][source].numStart=-1;  this.arrTypes[res.type][source].numCount=-1;			
	}
}

/**
	@Description
	Get the next batch of results of specified type for the search term.<br>
	Uses a callback.
	
	@Parameters
	numType = type of results to get (one of the global parameters flags)
	numCount = number of results to fetch
	txtSource = source of data. Pass null to get default source or use one of the source flags
**/
SearchManager.prototype.getNextResults=function(numType, numCount, txtSource){		
	//map source
	var source=this.mapSource(numType,txtSource);
	if (source==null)
		return;
	//don't fetch more then max results
	if (numCount>this.numMaxCount)
		numCount=this.numMaxCount;
	//get results from rds		
	var arr=this.arrTypes[numType][source].rds.getBatch(this.arrTypes[numType][source].numLast, numCount);
	//enough results, or end of results and some results exist
	if (arr && (arr.length==numCount || (this.arrTypes[numType][source].boolEnd==true && arr.length>0))){			
		this.arrTypes[numType][source].numLast+=arr.length;
		this.arrTypes[numType][source].numStart=-1;  
		this.arrTypes[numType][source].numCount=-1;
		//callback to storm		
		this.returnToStorm(numType, arr);			
		
		//check if need to buffer more results, (while server did not signal end of results)
		if (((this.arrTypes[numType][source].rds.getIndex()-this.arrTypes[numType][source].numLast)<this.numBuffer) && this.arrTypes[numType][source].boolEnd==false){				
			var count=((this.arrTypes[numType][source].rds.getIndex()+1)/this.numResultCount)*this.numResultCount;			
			var strTemp="opCode=getSearchResults&startIndex="+count+"&resultsNum="+this.numResultCount+"&searchType="+this.convert(numType)+"&sessionID="+this.sessionID+"&source="+this.sourceArray[source];
			this.manager.submit(this.ref+".onSearchResults", strTemp);
		}
	}
	//not enough results, go get more
	else if (!this.arrTypes[numType][source].boolEnd){					
		this.arrTypes[numType][source].numStart=this.arrTypes[numType][source].numLast;		
		this.arrTypes[numType][source].numCount=numCount;
		var count=((this.arrTypes[numType][source].rds.getIndex()+1)/this.numResultCount)*this.numResultCount;
		var strTemp="opCode=getSearchResults&startIndex="+count+"&resultsNum="+this.numResultCount+"&searchType="+this.convert(numType)+"&sessionID="+this.sessionID+"&source="+this.sourceArray[source];
		this.manager.submit(this.ref+".onSearchResults", strTemp);
	}
	else{ //server signaled end of results, no more results to return to user		
		//callback to storm
		this.returnToStorm(numType, null);
	}			
}

/**
	@Description
	Get the previous results of specified type for the search term.<br>
	Uses a callback.

	@Parameters
	numType = type of results to get (one of the global parameters flags)	
	numCount = number of results to return
	txtSource = source of data. Pass null to get default source or use one of the source flags
**/
SearchManager.prototype.getPrevResults=function(numType, numCount, txtSource){	
	//map source
	var source=this.mapSource(numType, txtSource);
	if (source==null)
		return;
	//don't fetch more then max results
	if (numCount>this.numMaxCount)
		numCount=this.numMaxCount;
	var arr;
	//make sure request is in bounds
	if (this.arrTypes[numType][source].numLast-numCount < 0){
		arr=this.arrTypes[numType][source].rds.getBatch(0,this.arrTypes[numType][source].numLast);
		this.arrTypes[numType][source].numLast=0;
	}
	else{	//request inbounds, get results from rds
		//first - round off to nearest whole div
		var mod=this.arrTypes[numType][source].numLast%numCount;			
		if (mod==0)	//no need to round off
			this.arrTypes[numType][source].numLast-=numCount;
		else
			this.arrTypes[numType][source].numLast-=mod;
		arr=this.arrTypes[numType][source].rds.getBatch(this.arrTypes[numType][source].numLast-numCount,numCount);			
	}
	//callback to storm
	this.returnToStorm(numType, arr);
}

/**
	@Description
	Map a MuseStorm result ID into a real URL

	@Parameters
	MuseStormID = the result's MuseStorm ID
**/
SearchManager.prototype.openResult=function(MuseStormID){
	var str=this.manager.loadPage("opCode=linkClicked&siteID="+MuseStormID+"&sessionID="+this.sessionID);
	return str;
}
//----------------------------------- ResultsObject ----------------------------------

function ResultsObject(){
	//hold results
	this.rds=new ResultDataStructure();
	//mark pending request
	this.numStart=-1;
	this.numCount=-1;
	//mark last result returned
	this.numLast=-1;
	//number of batches requested from server
	this.numBatch=0;
	//flag to mark end of requests
	this.boolEnd=false;
}

ResultsObject.prototype.reset=function(){
	if (this.rds!=null)
		delete rds;
	this.rds=null;
	this.rds=new ResultDataStructure();
	this.numStart=-1; 
	this.numCount=-1;
	this.numLast=-1;
	this.numBatch=0;
	this.boolEnd=false;
}

ResultsObject.prototype.getArrayLength=function(){
	return this.rds.getArrayLength();
}

//----------------------------------- ResultsDataStructure ----------------------------------

function ResultDataStructure(){
	this.sessionID=null;
	this.totalResults=0;
	this.index=-1;
	this.resultsArray=new Array();
}
	
ResultDataStructure.prototype.getSessionID=function(){return this.sessionID;}
ResultDataStructure.prototype.setSessionID=function(s){this.sessionID=s;}
ResultDataStructure.prototype.getTotalResults=function(){return this.totalResults;}
ResultDataStructure.prototype.setTotalResults=function(n){this.totalResults=n;}
ResultDataStructure.prototype.getIndex=function(){return this.index;}
ResultDataStructure.prototype.getArrayLength=function(){return this.resultsArray.length}
/*	
	@Description
	return a batch of n results, starting at index i in the list.
	
	@Parameters
	i = index of first result to return
	n = number of results to return. If there are less then n results available - return only those
*/
ResultDataStructure.prototype.getBatch=function(i,n){
	if (this.resultsArray.length==0){		//no results - return empty array to signal
		return (new Array());
	}
	else if (i>this.index || i<0){		//index out of bounds
		return null;
	}
	//not enough results, return only what exists
	else if(i+n>this.resultsArray.length){
		//update n
		n=this.index+1-i;
	}
	var arr=new Array(n);
	//populate the array
	for (var j=0; j<n; j++){
		arr[j]=this.resultsArray[i+j];			
	}				
	return arr;
}
/*
	@Description
	append new results to the data structure
	
	@Parameters
	arr = array of results
	
	@Returns
	true if results are not duplicate, false else
*/
ResultDataStructure.prototype.appendResults=function(arr){
	//if data structure is empty
	if (this.resultsArray.length==0){			
		this.resultsArray=arr;
		this.index=parseInt(this.resultsArray[this.resultsArray.length-1].getIndex());			
	}
	//data exists, append new results
	else{		
		//check if these results were already appended, and abort if true
		if (this.index>=arr[arr.length-1].getIndex()){				
			return false;
		}			
		this.resultsArray=this.resultsArray.concat(arr);
		this.index=parseInt(this.resultsArray[this.resultsArray.length-1].getIndex());					
	}		
	return true;
}

