/**
@author Chloé Desoutter <chloe.desoutter@gmail.com>
@description Ask Google, Yahoo! BOSS and Bing for search terms.
**/

/**
Format for results of a WebSearch:
{
	"searchType": "Web,
	"title": "My search phrase",
	"totalResultCount": "My total result count",
	"adultFilter": "value of the adult filter",
	"language": "Canonical identifier of the search language",
	"country": "Canonical identifier of the search country",
	"google":
	{
		"more": "view more results on the original site",
		"hits": "number of hits for google",
		"results":
		[
			"title": "title of the website",
			"description": "description of the link",
			"clickLink": "link for href field",
			"displayLink": "link for display",
		]
	},
	"bing":
	{
		// same as google
	},
	"boss":
	{
		// same as google and bing
	}
}
**/
var WebSearch = Class.create(
{	
	initialize: function(config)
	{
		/// initialize to empty strings the API keys for the different services
		this.bossKey = '';
		this.bingKey = '';
		this.goglKey = '';
		/// services base URL. Useful for Bing and BOSS.
		this.bingBaseURL = 'http://api.bing.net/json.aspx?';
		this.bossBaseURL = 'http://boss.yahooapis.com/ysearch/web/v1/{query}?appid={BOSS_APP_ID}&count={MAX_RESULT_COUNT}';
		this.searchPhrase = '';
		this.ajaxProxyPath = '/index.php?ajaxproxy=1';
		/// these are the results as they are output'ed by the search APIs
		this.googleSearcher = null;
		this.bingSearcher = null;
		this.bossSearcher = null;
		/// these are arrays of results
		this.googleArray = new Array();
		this.bossArray = new Array();
		/// these are the re-parsed results. They only contain the information common to the three search engines
		this.googleResult = null;
		this.bingResult = null;
		this.bossResult = null;
		/// this next var is the field that will contain all results as documented above
		this.globalResult = null;
		// this is the default callback and its context for default result
		this.resultAvailableCallback = function(json){alert('CDSearchAPIWeb=>default callback: ' + json);};
		this.resultAvailableCallbackContext = this;
		/// default parameters / options
		this.safeSearch = 'strict'; // by default, filter out all hate and porn
		this.numResults = 64; // the default (and maximal for google) results count is 64
		this.searchLanguage = Languages.en_US; // the default language is English
		this.searchCountry = Countries.en_US; // the default country is USA
		this.parseConfig(config);
		this.initAPIs();
	},
	parseConfig: function(config){
		if ('googleKey' in config)
		{
			this.goglKey = config.googleKey;
		}
		if ('bingKey' in config)
		{
			this.bingKey = config.bingKey;
		}
		if ('bossKey' in config)
		{
			this.bossKey = config.bossKey;
		}
		if ('numResults' in config)
		{
			this.numResults = config.numResults;
		}
		if ('safeSearch' in config)
		{
			this.safeSearch = config.safeSearch;
		}
		if ('ajaxProxyPath' in config)
		{
			this.ajaxProxyPath = config.ajaxProxyPath;
		}
		if ('resultCallback' in config)
		{	
			this.resultAvailableCallback = config.resultCallback;
		}
		if ('resultCallbackContext' in config)
		{
			this.resultAvailableCallbackContext = config.resultCallbackContext;
			this.resultAvailableCallback = this.resultAvailableCallback.bind(this.resultAvailableCallbackContext);
		}
		if ('searchLanguage' in config)
		{
			this.searchLanguage = Languages[config.searchLanguage];
			this.searchCountry = Countries[config.searchLanguage];

		}
	},
	
	initAPIs: function(config)
	{
		// initialize google
		google.load('search', '1', {"callback":function(){}});
		numResults = Math.min(50, this.numResults);
		this.bingBaseURL += 'AppId='+this.bingKey+'&Version=2.0&Web.Count='+numResults+'&Sources=Web&';
		this.bossBaseURL = this.bossBaseURL.replace('{BOSS_APP_ID}', this.bossKey);
		this.bossBaseURL = this.bossBaseURL.replace('{MAX_RESULT_COUNT}', numResults);
	},

	/**
	* @description This parses the results of BOSS search engine to a common format
	**/
        resultBOSS: function(t)
        {
                this.bossSearcher = t.responseText.evalJSON();
		var response = this.bossSearcher.ysearchresponse;
        	this.bossArray = this.bossArray.concat(response.resultset_web);
		// check if we have all the results we want
		var goAhead = false;
		if (response == undefined)
		{
			response = {"start":0, "count":0, resultset_web: []};
			goAhead = true;
		}
		if (!goAhead && parseInt(response.start) + parseInt(response.count) < parseInt(this.numResults))
		{
			opt = this.searchOptions;
			opt.start = response.start + response.count;
			this.searchBOSS(this.searchPhrase, opt);
		}
		else
		{
			// we have all our results so we build our JSON standard object
                        var numHits = parseInt(response.start) + parseInt(response.count);
                        var moreResults = 'http://'+this.searchCountry.BOSS+'.search.yahoo.com/search?p='+encodeURIComponent(this.searchPhrase)+'&ei=UTF-8&b=61';
                        var results = new Array();
			resp = '';
                        for (var i = 0; i < this.bossArray.length; i++)
                        {
				resp = this.bossArray[i];
                                results.push(
                                {
                                        "title": resp.title,
                                        "description": resp.abstract,
                                        "clickLink": resp.clickurl,
                                        "displayLink": resp.dispurl
                                });
                        }
                        var jsonOut =
                        {
                                "more": moreResults,
                                "hits": numHits,
                                "results":
                                [
                                        results
                                ]
                        };
                        this.bossResult = jsonOut;
			this.bossSearchComplete = true;
			this.generateWholeResult();
		}
	},
	/**
	* @description This parses the results of Bing search engine to a common format
	**/
        resultBing: function(t)
        {
                this.bingSearcher = t.responseText.evalJSON();
                var response = this.bingSearcher.SearchResponse;
		var goAhead = true;
                if (response.Web == undefined)
                {
                        response.Web = {'Results': [], 'Offset': 0};
                        goAhead = true;
                }
                this.bingArray = this.bingArray.concat(response.Web.Results);
		var numResults = response.Web.Results.length;
		var offset = parseInt(response.Web.Offset);
                // check if we have all the results we want
                if (response.Web.Results.length!=0 && numResults + offset < parseInt(this.numResults))
                {
                        opt = this.searchOptions;
                        opt.start = numResults + offset;
                        this.searchBing(this.searchPhrase, opt);
			goAhead = false;
			return;
                }
                if (goAhead)
                {
		
                        // we have all our results so we build our JSON standard object
                        var numHits = parseInt(response.Web.Offset) + parseInt(response.Web.Results.length);
			var moreResults = 'http://bing.net/search?q='+encodeURIComponent(this.searchPhrase)+'&go=&form=QBLH&filt=all&first=61&mkt='+this.searchLanguage.Bing;
                        var results = new Array();
                        var resp = '';
			for (var i = 0; i < this.bingArray.length; i++)
                        {
				resp = this.bingArray[i];
                                results.push(
                                {
                                        "title": resp.Title,
                                        "description": resp.Description,
                                        "clickLink": resp.Url,
                                        "displayLink": resp.DisplayUrl
                                });
                        }
                        var jsonOut =
                        {
                                "more": moreResults,
                                "hits": numHits,
                                "results":
                                [
                                	results
                                ]
                        };
                        this.bingResult = jsonOut;
			this.bingSearchComplete = true;
			this.generateWholeResult();
                }
		
        },
	/**
	* @description This parses the results of Google Ajax search api to a common format
	**/
        resultGoogle: function()
        {
		var searcher = this.googleSearcher;
		var numHits = 0;
		var moreResults = '';
		this.googleArray = this.googleArray.concat(searcher.results);
		var goAhead = false;
		if (searcher.cursor == undefined)
		{
			goAhead = true;
			
			this.googleArray = [];
			searcher.cursor = {"moreResultsUrl": "http://www.google."+this.searchCountry.BOSS+"/search?q="+encodeURIComponent(this.searchPhrase)+"&lr="+this.searchLanguage.Google};
		}

		if (!goAhead && searcher.cursor.currentPageIndex < searcher.cursor.pages.length - 1)
		{
			searcher.gotoPage(searcher.cursor.currentPageIndex + 1);
		
			return; 
		}	
		else // we have gone through the first results. Now we convert the data
		{
			numHits = this.googleArray.length;
			moreResults = searcher.cursor.moreResultsUrl;
			results = new Array();
			var resp = '';
			for (i = 0; i < this.googleArray.length; i++)
			{
				resp = this.googleArray[i];
				results.push(
				{ 
					"title": resp.titleNoFormatting,
					"description": resp.content,
					"clickLink": resp.url,
					"displayLink": resp.url
				});
			}
			var jsonOut = 
			{
				"more": moreResults,
				"hits": numHits,
				"results":
				[
					results
				]		
			};
			this.googleResult = jsonOut;	
			this.googleSearchComplete = true;
			this.generateWholeResult();
		}
        },
	/**
	* @description Generate the whole JSON result field that will be used for display
 	**/
	generateWholeResult: function()
	{
		if (!(this.googleSearchComplete	&& this.bingSearchComplete && this.bossSearchComplete)) return;
		var totalResultCount = parseInt(this.googleResult.hits) + parseInt(this.bingResult.hits) + parseInt(this.bossResult.hits);
		var jsonout = 
		{
			"searchType": "Web",
			"title": this.searchPhrase,
			"totalResultCount": totalResultCount,
			"adultFilter": this.safeSearch,
			"language": this.searchLanguage.Canonical,
			"country": this.searchCountry.Canonical,
			"google": this.googleResult,
			"bing": this.bingResult,
			"boss": this.bossResult
		};
		this.globalResult = jsonout;
		this.resultAvailableCallback(this.globalResult);
	},

	searchGoogle: function(phrase, options)
	{
		this.searchPhrase = phrase;
		this.searchOptions = options;
		this.googleSearchComplete = false;
		this.googleArray = new Array();
		this.googleSearcher = new google.search.WebSearch();
		this.googleSearcher.setSearchCompleteCallback(this, WebSearch.prototype.resultGoogle);
		this.googleSearcher.setResultSetSize(google.search.Search.LARGE_RESULTSET);
			
		// filter out "porn" and "hate" contents if the options tell us to do so
		var safeSear = this.safeSearch=='none'?google.search.Search.SAFESEARCH_OFF:this.safeSearch=='moderate'?google.search.Search.SAFESEARCH_MODERATE:google.search.Search.SAFESEARCH_STRICT;

		this.googleSearcher.setRestriction(google.search.Search.RESTRICT_SAFESEARCH, safeSear);
		this.googleSearcher.setRestriction(google.search.Search.RESTRICT_EXTENDED_ARGS, { "lr": this.searchLanguage.Google });
		this.googleSearcher.execute(phrase);
	},

	searchBOSS: function(phrase, options)
	{
		var start='&start=0';
		var that = this;
		this.searchOptions = options;
		if ('start' in options)
		{
			start='&start='+options.start;
		}
		else
		{
			this.bossArray = new Array();
			this.bossSearchComplete = false;
		}
		this.searchPhrase = phrase;
		var bossURL = this.bossBaseURL;
		var bossCallback = this.resultBOSS;
		bossURL = bossURL.replace('{query}', encodeURIComponent(phrase));
		bossURL += '&lang='+this.searchLanguage.BOSS;
		bossURL += '&region='+this.searchCountry.BOSS;
		bossURL += start;
		new Ajax.Request(
		this.ajaxProxyPath,
		{
			method: 'post',
			parameters: 
			{
				url: bossURL
			},
			onSuccess: bossCallback.bind(that)
		})
		
	},


	searchBing: function(phrase, options)
	{
		var that = this;
		this.searchPhrase = phrase;
		this.searchOptions = options;
                var startPage = '';
		if ('start' in options)
		{
			startPage = '&Web.offset='+options.start
		}
                else if ('page' in options)
		{
                        startPage = '&Web.offset='+(this.numResults * options.page);
                }
		else
		{
			this.bingArray = new Array();
			this.bingSearchComplete = false;
		}
		var safeSear = this.safeSearch=='none'?'Off':this.safeSearch=='moderate'?'Moderate':'Strict';
		var bingURL = this.bingBaseURL;
		var bingCallback = this.resultBing;
		bingURL += "Query="+encodeURIComponent(phrase);
		bingURL += "&Market="+this.searchCountry.Bing;
		bingURL += "&Adult="+safeSear;
		bingURL += startPage;
		new Ajax.Request(
		this.ajaxProxyPath,
		{
			method: 'post',
			parameters: 
			{
				url: bingURL
			},
			onSuccess: bingCallback.bind(that)
		}
		);
	},

	searchAll: function(phrase, options)
	{
		this.parseConfig(options);
		this.searchGoogle(phrase, options);
		this.searchBOSS(phrase, options);
		this.searchBing(phrase, options);
	}

}
);
