

//////////////////////////////////////
// Various defined and default values
//////////////////////////////////////
DEFAULT_SEARCH_QUERYSTR = ''
DEFAULT_SEARCH_START = 0
DEFAULT_SEARCH_NUMRESULTS = 10

// google returns results in two size formats :
//   rsz=large -> 8 results per page
//   rsz=small -> 4 results per page
SEARCH_NUM_RESULTS_PER_PAGE = 8 ;
SEARCH_WEB_MAX_START = 56 ; // google current maximum start for ajax web search

// HTTP codes
HTTP_OK = 200 ;




// json method callbacks
//    sadly Google doesn't allow complex callbacks like: 'getgvar("video_result_builder").handle_results', so we must have global functions and call them as shortcuts
function handle_web_ajax_search_results ( p_context, p_json, p_status, p_details, p_unused ) {
	 getgvar("web_result_builder").handle_results( p_context, p_json, p_status, p_details, p_unused ) ;
}
function handle_video_ajax_search_results ( json ) {
	 getgvar("video_result_builder").handle_results( json ) ;
}





// manager for pagination of the various result_builders
function ajax_cursor_manager () {

	 /////////////////////////////////////////////
	 // Defaults
	 /////////////////////////////////////////////

	 // default directory search filetype is music
	 this.DEFAULT_DIRSEARCH_FILETYPE = 'm' ;

	 // default filetypes string for music ( which is the default ) dirsearch filetype
	 this.DEFAULT_FILETYPES_STR = 'mp3|ogg|wav|wma|' ;

	 
	 /////////////////////////////////////////////
	 // Variables
	 /////////////////////////////////////////////

	 // search page root url ( like 'search' )
	 this.rooturl = '' ;

	 // link target for pagination links ( like '_self' )
	 this.link_target = '_self' ;

	 // full pagination root url ( like search/bananas?t=m&ft=mp3|ogg|wav&r=dir )
	 //    the key url parameter that wont be there will be 'start', as that's added later
	 this.pagination_rooturl = '' ;

	 // the type of results for pagination
	 //   'vid' -> video results
	 //   'dir' -> open directory results
	 this.results_type = '' ;


	 // list of cursors returned for each search
	 this.cursors = new Array() ;

	 // the number of cursors to wait for before generating and adding the paging information
	 this.numcursors = 0 ;


	 // the pagination parent id
	 this.parentid = '' ;

	 // the paginatio parent element
	 this.parent_ele = null ;


	 // handle new result function that will be called, but shouldn't do anything because the cursor manager doesn't care about individual results
	 this.handle_new_result = function ( p_result, p_isvalid, p_resbuilder ) {
		  // do nothing
	 }

	 // function thats called by each of the result builders when they have finished getting all of the results
	 // this function checks to see if all result_builder.after_results have been called, and if so generates and handles pagination
	 // Parameters :
	 //    p_querystr -> the query string
	 //    p_start -> index of the first result
	 //    p_num_requested_results -> user requested number of results per page
	 //    p_dirsearch_filetype -> the directory search type of file
	 //    p_filetypes_str -> the filetypes string
	 //
	 //    p_num_valid_results -> the total number of VALID results
	 //    p_totaltime -> the total time ( in milliseconds ) it took to get these results
	 //    p_cursor -> array of pagination cursors returned from search results
	 this.after_results = function ( p_querystr, p_start, p_num_requested_results, p_dirsearch_filetype, p_filetypes_str, p_num_valid_results, p_totaltime, p_cursor ) {
		  // set up the parent element if it hasn't been set up already
		  if ( this.parent_ele == null )
				this.parent_ele = document.getElementById( this.parentid ) ;

		  // construct the pagination_rooturl if it hasnt been built already
		  if ( this.pagination_rooturl == '' )  {
				// the trailing url argument string
				var urlargs = '' ;
				if ( p_dirsearch_filetype != '' && p_dirsearch_filetype != this.DEFAULT_DIRSEARCH_FILETYPE )
					 urlargs += '&t=' + p_dirsearch_filetype ;
				if ( p_filetypes_str != '' && p_filetypes_str != this.DEFAULT_FILETYPES_STR )
					 urlargs += '&ft=' + p_filetypes_str ;
				if ( this.results_type != '' && this.results_type != 'vid' ) // note that the default search type is video, or 'vid', so if its videos we dont need to specify it
					 urlargs += '&r=' + this.results_type ;

				// replace the first '&' with a '?'
				if ( urlargs.length > 0 )
					 urlargs = '?' + urlargs.substr( 1 ) ;

				this.pagination_rooturl = this.rooturl + '/' + escape( p_querystr ) + urlargs ;
		  }
				
		  // add this cursor to the array of cursors
		  this.cursors[ this.cursors.length ] = p_cursor ;

		  // if we've received a cursor for every search were waiting on, handle pagination now
		  if ( this.cursors.length == this.numcursors )
				this.handle_pagination( p_start, p_num_requested_results ) ;
	 }

	 // handle pagination and add it to the bottom of the page
	 this.handle_pagination = function ( p_start, p_num_requested_results ) {
		  // get the current page
		  var current_page =  Math.floor( p_start / p_num_requested_results ) ;

		  // get the maximum start out of _all_ the cursors
		  var maxstart = -1 ; 
		  for ( var i in this.cursors )
				// if the 'pages' element of the cursor exists ( it won't if the search had zero results )
				if ( this.cursors[ i ][ 'pages' ] != undefined )
					 for ( var j in this.cursors[ i ][ 'pages' ] )
						  if ( parseInt( this.cursors[ i ][ 'pages' ][ j ][ 'start' ] ) > maxstart )
								maxstart = parseInt( this.cursors[ i ][ 'pages' ][ j ][ 'start' ] ) ;
		  
		  // assume the last page is a full page of results, so the last page has SEARCH_NUM_RESULTS_PER_PAGE results in it
		  var last_page = 0 ;
		  if ( maxstart != -1 )
				var last_page = Math.floor( ( maxstart + SEARCH_NUM_RESULTS_PER_PAGE ) / p_num_requested_results ) ;


		  // remove any currently existing pagination elements
		  this.remove_children( this.parent_ele ) ;


		  // if there is a previous page
		  var cursor_link = null ;
		  var cursor_class = 'page-cursor' ;
		  if ( current_page > 0 )
				this.parent_ele.appendChild( this.create_page_link( '&laquo;&nbsp;Previous',
																					 (current_page - 1) * p_num_requested_results,
																					 cursor_class ) ) ;

		  // now loop over the pages and print them
		  for ( var i = 0; i <= last_page; i++ ) {
				if ( i == current_page )
					 cursor_class += ' page-cursor-current' ; // add the current page css
				else
					 cursor_class = ' page-cursor' ; // regular page

				// now create and add this cursor link
				this.parent_ele.appendChild( this.create_page_link( i + 1,
																					 ( i * p_num_requested_results ),
																					 cursor_class ) ) ;
		  }
		  
		  // print a next link if there is a next page
		  if ( current_page < last_page )
				this.parent_ele.appendChild( this.create_page_link( 'Next&nbsp;&raquo',
																					 ( current_page + 1 ) * p_num_requested_results,
																					 cursor_class ) ) ;
	 }

	 // create a link with certain options
	 // Parameters :
	 //    p_text -> the text inside the link, like '1'
	 //    p_start -> the start index of this link, like 8
	 //    p_classname -> the css classname string to use for this element
	 //       default is ''
	 this.create_page_link = function ( p_text, p_start, p_classname ) {
		  // default classname is ''
		  if ( p_classname == undefined || p_classname == null )
				p_classname = '' ;
		  
		  // construct the full url
		  var url = url_setparam( this.pagination_rooturl, 'start', p_start ) ; // add the 'start' parameter
		  
		  // create and configure this anchor element
		  var el = document.createElement('a') ;
		  el.href = url ;
		  el.innerHTML = p_text ;
		  el.className = p_classname ;
		  el.target = this.link_target ;

		  // return the element
		  return el;
	 }

	 // remove all children of an element
	 this.remove_children = function ( p_ele ) {
		  while ( p_ele.firstChild )
				p_ele.removeChild ( p_ele.firstChild ) ;
	 }
}


// factory to make ajax_cursor_manager objects
function ajax_cursor_manager_factory () {

	 // setup an ajax_cursor_manager with the given parameters
	 this.get_cursor_manager = function ( p_numcursors, p_results_type, p_pagination_parentid, p_rooturl ) {
		  // cursor manager to return
		  var cursormgr = new ajax_cursor_manager() ;

		  // set the number of cursors to wait for
		  cursormgr.numcursors = p_numcursors ;

		  // set the type of the requested results
		  cursormgr.results_type = p_results_type ;

		  // set the pagination parent id
		  cursormgr.parentid = p_pagination_parentid ;

		  // set the root url
		  cursormgr.rooturl = p_rooturl ;

		  // return this cursor manager
		  return cursormgr ;
	 }

}






// object that builds and manages displaying search results
function ajax_result_display () {

	 // the type of this result display, in plaintext, like 'video' or 'web'
	 this.result_type = '' ;

	 // parent element id for the results
	 this.results_parentid = '' ;

	 // parent object under which to add the divs of results
	 //    this is set with the first call to handle_new_results by using: document.getElementById( this.results_parentid ) ;
	 this.results_parent_ele = null ;

	 // header DOM element id
	 this.header_parentid = '' ;

	 // array of DOM result elements
	 this.domresults = new Array() ;



	 // set the result parent id
	 this.set_results_parentid = function ( p_parentid ) {
		  this.results_parentid = p_parentid ;
	 }

	 // set the header parent id
	 this.set_header_parentid = function ( p_parentid ) {
		  this.header_parentid = p_parentid ;
	 }

	 // removes all children from the parent
	 this.clear_results = function () {
		  // make sure to reset the set_2str set object ( you can use set_2str.erase() )
	 }

	 // initialize DOM element pointers
	 this.init_dom_pointers = function () {
		  // if we haven't set the parent elements yet, do that now
		  if ( this.results_parent_ele == null )
				this.results_parent_ele = document.getElementById( this.results_parentid ) ;
		  if ( this.header_parent_ele == null )
				this.header_parent_ele = document.getElementById( this.header_parentid ) ;
	 }


	 // function that gets called whenever a new result comes in
	 //    p_result -> a json result object
	 //    p_isvalid -> boolean ( [true,false] ) whether or not this result passed the result_builder filter or not 
	 //    p_resbuilder -> the result builder object that was used to get this result
	 this.handle_new_result = function ( p_result, p_isvalid, p_resbuilder ) {
		  // make sure the dom element pointers have been setup
		  this.init_dom_pointers() ;

		  // handle the result, and pass whether or not its a valid result to this.create_result_div as the 2nd parameter
		  var new_result_div = this.create_result_div( p_result, p_isvalid, p_resbuilder.querystr ) ;
		  if ( new_result_div != null )
				this.results_parent_ele.appendChild( new_result_div ) ;

		  // add this result div to the list of results
		  this.domresults[ this.domresults.length ] = new_result_div ;
	 }

	 // remove all children of an element
	 this.remove_children = function ( p_ele ) {
		  while ( p_ele.firstChild )
				p_ele.removeChild ( p_ele.firstChild ) ;
	 }

	 // creates the base header DOM element
	 // Parameters:
	 //    p_start_text -> result text to display as the type of result
	 //    p_querystr -> the query string
	 //    p_start -> index of the first result
	 //    p_num_valid -> number of valid results on the page
	 //    p_timestr -> load time respresented as a string, like '1.53'
	 this.create_base_header = function ( p_result_text, p_querystr, p_start, p_num_valid_results, p_timestr ) {

		  var bold_text = document.createElement ( "b" ) ;
		  var last_index = parseInt(p_start) + parseInt(p_num_valid_results) ;
		  if ( last_index == 0 )
				last_index = 1 ;
		  bold_text.appendChild ( document.createTextNode ( ( parseInt(p_start) + 1 ) + " - " + ( last_index ) ) ) ;

		  var show_all_stats = document.createElement ( "div" ) ;
		  if ( p_result_text != '' )
				show_all_stats.appendChild ( document.createTextNode ( p_result_text + ' Results ' ) ) ;
		  else
				show_all_stats.appendChild ( document.createTextNode ( "Results " ) ) ;
		  show_all_stats.appendChild ( bold_text ) ;
		  show_all_stats.appendChild ( document.createTextNode ( " for " ) ) ;

		  bold_text = document.createElement ( "b" ) ;
		  bold_text.appendChild ( document.createTextNode ( p_querystr ) ) ;

		  show_all_stats.appendChild ( bold_text ) ;
		  show_all_stats.appendChild ( document.createTextNode ( " (" + p_timestr + " seconds)" ) ) ;

		  return show_all_stats ;
	 }

	 // create a 'no results' div thats used when a query didnt produce any results
	 // Parameters :
	 //    p_querystr -> user's query string
	 //    p_results_type -> type of results, like 'Video' or 'Web'
	 //       default: ''
	 this.create_no_results_div = function ( p_querystr, p_results_type ) {
		  if ( p_results_type == undefined || p_results_type == null )
				p_results_type = '' ;

		  var bold_text = document.createElement ( 'b' ) ;
		  bold_text.appendChild ( document.createTextNode ( p_querystr ) ) ;

		  var ele = document.createElement ( 'div' ) ;
		  ele.className = 'no-results' ;
		  ele.appendChild( document.createTextNode ( 'Your ' + ((p_results_type!='')?p_results_type+' ':'') + 'search for' ) ) ;
		  ele.appendChild( document.createElement( 'br' ) ) ;
		  ele.appendChild( document.createElement( 'br' ) ) ;
		  ele.appendChild( bold_text ) ;
		  ele.appendChild( document.createElement( 'br' ) ) ;
		  ele.appendChild( document.createElement( 'br' ) ) ;
		  ele.appendChild( document.createTextNode ( ' did not match any results.' ) ) ;
		  return ele ;
	 }

	 // finishes up work involving the header and results after all of the valid results have been parsed
	 // Parameters :
	 //    p_header_ele -> DOM element to set as the only header child
	 //    p_results_ele -> DOM element to set as the only results child ( does nothing if null, which is default )
	 this.finalize_results_display = function ( p_header_ele, p_results_ele ) {
		  // make sure the dom element pointers have been setup
		  this.init_dom_pointers() ;


		  // delete the current header
		  this.remove_children( this.header_parent_ele ) ;

		  // now add the header as the sole child
		  this.header_parent_ele.appendChild( p_header_ele ) ;


		  // if there weren't any valid results, add a 'no results' div to the results list
		  if ( p_results_ele != undefined && p_results_ele != null ) {
				// remove all children of the results div ( in case theres anything there currently )
				this.remove_children( this.results_parent_ele ) ;

				// set the content to be the 'no results' div
				this.results_parent_ele.appendChild( p_results_ele ) ;
		  }

	 }

}



// factory that builds ajax_result_display objects
function ajax_result_display_factory () {

	 // builds a web result displayer
	 // Parameters :
	 //    p_parentid -> the DOM id of the parent element for all of the results
	 this.get_web_result_display = function ( p_header_parentid, p_results_parentid ) {

		  // ajax result display object to return
		  var resdisplay = new ajax_result_display() ;

		  // set the type of this result display
		  resdisplay.result_type = 'web' ;


		  // set the header parentid
		  resdisplay.set_header_parentid( p_header_parentid ) ;

		  // set diplays parent id ( to be looked up in the document later )
		  resdisplay.set_results_parentid( p_results_parentid ) ;


		  // creates a result div from a json result object
		  // Parameters :
		  //    p_result -> json result object
		  //    p_validresult -> boolean ( [true,false] ), true if the result is a 'valid one' ( deemed by this.filter_result ). false otherwise
		  //    p_searchquery -> the search query associated with this result object
		  resdisplay.create_result_div = function ( p_result, p_validresult, p_searchquery ) {
				// if p_validresult wasn't passed in, defaults to true
				if ( p_validresult == undefined )
					 p_validresult = true ;

				// div element representing this result
				var resele = document.createElement ( "div" ) ;

				// classname string ( depends on whether or not this result is a valid one or not, detirmined by p_validresult )
				var classnamestr = "webdir-webResult webdir-result" ;
				if ( p_validresult == false )
					 classnamestr += " webdir-invalid-hidden" ;
				resele.className = classnamestr ;
				
				// title
				var title = document.createElement ( "div" ) ;
				title.className = "webdir-title" ;
				var anchor = document.createElement ( "a" ) ;
				anchor.className = "webdir-title" ;
				anchor.href = p_result[ 'unescapedUrl' ] ;
				anchor.target = "_self" ;
				anchor.appendChild ( document.createTextNode ( p_result[ 'titleNoFormatting' ] ) ) ;
				title.appendChild ( anchor ) ;
				resele.appendChild ( title ) ;

				// snippet
				var snippetstr = p_result[ 'content' ].substring( 0, 203 ) ;
				if ( snippetstr.length < p_result[ 'content' ].length )
					 snippetstr += '\u003cb\u003e...\u003c/b\u003e' ; // bold '...', or the same as '<b>...</b>'
				var snippet = this.boldify_div ( snippetstr, "webdir-snippet" ) ;
				resele.appendChild ( snippet ) ;

				// domains
				var domain = document.createElement ( "div" ) ;
				domain.className = "webdir-visibleUrl webdir-visibleUrl-short" ;
				domain.appendChild ( document.createTextNode ( p_result[ 'visibleUrl' ] ) ) ;
				resele.appendChild ( domain ) ;
				var unescaped_domain = document.createElement ( "div" ) ;
				unescaped_domain.className = "webdir-visibleUrl webdir-visibleUrl-long" ;
				unescaped_domain.appendChild ( document.createTextNode ( p_result[ 'unescapedUrl' ] ) ) ;
				resele.appendChild ( unescaped_domain ) ;

				// return this element now
				return resele ;
		  }

		  // function that returns a div containing intermixed bold and regular text with the specified classname
		  resdisplay.boldify_div = function ( p_text, p_classname ) {
				// element to be returned
				var retele = document.createElement ( "div" ) ;
				retele.className = p_classname ;
				
				// now loop over the string and add text and bold elements, according to the text
				while ( p_text.length > 0 ) {
					 // find an index of a bold opening element ( "<b>" )
					 var start_loc = p_text.indexOf ( "\u003cb\u003e" ) ;

					 // add all of the text before that as a text element
					 if ( start_loc >= 0 ) {
						  retele.appendChild ( document.createTextNode ( p_text.substring ( 0, start_loc ) ) ) ;
						  
						  // now remove this text that has been added from p_text
						  p_text = p_text.substring ( start_loc ) ;

						  // now find the ending bold ( "</b>"
						  var end_loc = p_text.indexOf ( "\u003c/b\u003e" ) ;

						  // add this text as bold
						  var bold_ele = document.createElement ( "b" ) ;
						  bold_ele.appendChild ( document.createTextNode ( p_text.substring ( 3, end_loc ) ) ) ;
						  retele.appendChild ( bold_ele ) ;

						  // remove the bold text that was just added
						  p_text = p_text.substring ( end_loc + 4 ) ;
					 }
					 // otherwise just add all of the text b/c no bold text was found
					 else { 
						  retele.appendChild ( document.createTextNode ( p_text ) )  ;

						  // break the loop so we don't loop forever
						  break ;
					 }
				}

				return retele ;
		  }



		  // function that gets called after all of the results for this query have been returned
		  // Parameters :
		  //    p_querystr -> the query string
		  //    p_start -> index of the first result
		  //    p_num_requested_results -> user requested number of results per page
		  //    p_dirsearch_filetype -> the type of search
		  //    p_filetypes_str -> the filetypes string
		  //
		  //    p_num_valid_results -> the total number of VALID results
		  //    p_totaltime -> the total time ( in milliseconds ) it took to get these results
		  //    p_cursor -> array of pagination cursors returned from search results
		  resdisplay.after_results = function ( p_querystr, p_start, p_num_requested_results, p_dirsearch_filetype, p_filetypes_str, p_num_valid_results, p_totaltime, p_cursor ) {
				// get the time string to display, like '1.21s'
				var timestr = Math.floor ( p_totaltime / 1000 ) + "." + ( ( p_totaltime / 10 ) | 0 )

				// create the base text, like "Results 1 - 10 in 1.53s"
				var base_header_text_ele = this.create_base_header( this.result_type.substring(0,1).toUpperCase() + this.result_type.substring(1), p_querystr, p_start, p_num_valid_results, timestr ) ;

				// DOM element to be the results child ( null if there are results )
				//    this is only used if we didn't get any results back
				var results_ele = null ;
				if ( p_num_valid_results == 0 )
					 results_ele = this.create_no_results_div( p_querystr, this.result_type ) ;

				// set the header and results body if necessary
				this.finalize_results_display( base_header_text_ele, results_ele ) ;

		  }
		  // hide invalid results ( default used in resdisplay.after_results(...) )
		  resdisplay.hide_invalid_results = function ( eleid_to_hide, eleid_to_show ) {
				// hide and show the respective elements
				document.getElementById( eleid_to_hide ).style.display = 'none' ;
				document.getElementById( eleid_to_show ).style.display = 'block' ;

				// change the css of all the hidden invalid elements to show them
				var eles = this.getElementsByClassName( this.results_parent_ele, "div", "webdir-webResult webdir-result webdir-invalid-shown" ) ;
				for ( var i in eles )
					 eles[ i ].className = "webdir-webResult webdir-result webdir-invalid-hidden" ;
		  }

		  // show invalid results
		  resdisplay.show_invalid_results = function ( eleid_to_hide, eleid_to_show ) {
				// hide and show the respective elements
				document.getElementById( eleid_to_hide ).style.display = 'none' ;
				document.getElementById( eleid_to_show ).style.display = 'block' ;

				// change the css of all the hidden invalid elements to hide them
				var eles = this.getElementsByClassName( this.results_parent_ele, "div", "webdir-webResult webdir-result webdir-invalid-hidden" ) ;
				for ( var i in eles )
					 eles[ i ].className = "webdir-webResult webdir-result webdir-invalid-shown" ;
		  }
		  // function to get elements with a certain css class
		  //    taken and modified from http://www.robertnyman.com/2005/11/07/the-ultimate-getelementsbyclassname/
		  resdisplay.getElementsByClassName = function ( oElm, strTagName, strClassName ) {
				var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
				var arrReturnElements = new Array();
				strClassName = strClassName.replace(/\-/g, "\\-");
				var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
				var oElement;
				for(var i=0; i<arrElements.length; i++){
					 oElement = arrElements[i];
					 if(oRegExp.test(oElement.className)){
						  arrReturnElements.push(oElement);
					 }
				}
				return (arrReturnElements)
		  }

		  // return this display
		  return resdisplay ;
	 }

	 // builds a video result displayer
	 this.get_video_result_display = function ( p_header_parentid, p_results_parentid ) {
		  // ajax result display object to return
		  var resdisplay = new ajax_result_display() ;

		  // set the type of this result display
		  resdisplay.result_type = 'video' ;

		  // set the header parentid
		  resdisplay.set_header_parentid( p_header_parentid ) ;

		  // set diplays parent id ( to be looked up in the document later )
		  resdisplay.set_results_parentid( p_results_parentid ) ;


		  // creates a result div from a json result object
		  // Parameters :
		  //    result -> json result object
		  //    validresult -> boolean ( [true,false] ), true if the result is a 'valid one' ( deemed by this.filter_result ). false otherwise
		  //    searchquery -> the search query associated with this result object
		  //
		  // handy html to dom site: http://rick.measham.id.au/paste/html2dom.htm
		  resdisplay.create_result_div = function ( result, isvalidresult, searchquery ) {
				// if isvalidresult wasn't passed in, defaults to true
				if ( isvalidresult == undefined )
					 isvalidresult = true ;

				// we don't want to display invalid video links at all, so just skip over this video
				if ( isvalidresult == false )
					 return null ;


				// initialize a video data formatter to use in formatting the video length and relative timestamp strings
				var vidformatter = new video_data_formatter() ;

				// extract the information we need from this youtube result
				var videoid = result[ 'id' ][ '$t' ].split( '/' ).pop() ; // extract from: "http://gdata.youtube.com/feeds/api/videos/GIVCjLALwQk"
				var author = result[ 'author' ][ 0 ][ 'name' ][ '$t' ] ;
				var title = result[ 'title' ][ '$t' ] ;
				var description = result[ 'content' ][ '$t' ] ;
				var duration = parseInt( result[ 'media$group' ][ 'yt$duration' ][ 'seconds' ] ) ;
				var published = result[ 'published' ][ '$t' ] ;
				var views = 0 ;
				if ( result[ 'yt$statistics' ] && result[ 'yt$statistics' ][ 'viewCount' ] )
					 views = result[ 'yt$statistics' ][ 'viewCount' ] ;
				var rating = 0 ;
				if ( result[ 'gd$rating' ] && result[ 'gd$rating' ][ 'average' ] )
					 rating = result[ 'gd$rating' ][ 'average' ] ;
				var restricted = false ; // true or false, whether this video is 'restricted' for some reason or not
				if ( result[ 'app$control' ] && result[ 'app$control' ][ 'yt$state' ] &&  result[ 'app$control' ][ 'yt$state' ][ 'name' ] )
					 restricted = ( result[ 'app$control' ][ 'yt$state' ][ 'name' ].toLowerCase() == 'restricted' ) ? true : false ;

				// construct associated urls
				var studio_url = this.construct_studio_url( videoid, searchquery ) ;
				var tburl = this.construct_thumbnail_url( videoid ) ;

				// if the description is too long, truncate it
				var truncated_description = this.construct_truncated_str( description, 200 ) ;



				// create the div to be returned
				var div_0 = document.createElement('div');
				div_0.className = "video-result";

				var img_0 = document.createElement('img');
				img_0.className = "video-result-thumbnail";
				img_0.border = 0 ;
				img_0.src = tburl ;
				img_0.width = 120 ;
				img_0.height = 90 ;

				var a_1 = document.createElement('a') ;
				a_1.href = studio_url ;
				a_1.appendChild( img_0 ) ;

				div_0.appendChild( a_1 );


				var div_2 = document.createElement('div');
				div_2.className = "video-result-body";

				var div_3 = document.createElement('div');
				div_3.className = "video-result-title";

				var a_0 = document.createElement('a');
            a_0.href = studio_url ;
            a_0.appendChild( document.createTextNode( this.construct_truncated_str( title, 170 ) ) );
				div_3.appendChild( a_0 );

				div_2.appendChild( div_3 );


				var div_4 = document.createElement('div');
				div_4.className = "video-result-text";
				// only display the first x characters of the description
				div_4.appendChild( document.createTextNode( truncated_description ) ) ;
				div_2.appendChild( div_4 );


				var div_5 = document.createElement('div');
				div_5.className = "video-result-metadata";

				var span_0 = document.createElement('span');
            span_0.className = "video-result-metadata-length";
            span_0.appendChild( document.createTextNode( vidformatter.seconds_to_length_str( duration ) ) ) ;
				div_5.appendChild( span_0 );


				var span_1 = document.createElement('span');
            span_1.className = "video-result-metadata-rating";
				var ratingstr = ( rating + '' ).substring( 0, 3 ) ;
				if ( ratingstr == "-1" )
					 span_1.appendChild( document.createTextNode( 'not rated' ) ) ;
				else
					 span_1.appendChild( document.createTextNode( ratingstr + " of 5 stars") );
				div_5.appendChild( span_1 );


				var span_2 = document.createElement('span');
            span_2.className = "video-result-metadata-published";
            span_2.appendChild( document.createTextNode( vidformatter.timestr_to_relative_timestr( published ) + " ago") ) ;
				div_5.appendChild( span_2 );

				div_2.appendChild( div_5 );
				div_0.appendChild( div_2 );

				// return this element now
				return div_0 ;
		  }
		  // returns a dirpy studio url when given a video result
		  //    ex: returns something like '/studio/9bfWBYNcc7g', etc
		  resdisplay.construct_studio_url = function ( videoid, searchquery ) {
				// return the /studio/*videoid*/ base with the appropriate url parameters from the result
				return '/studio/' + videoid + '?q=' + escape( searchquery ) ;
		  }
		  // construct the vidoe thumbnail url from a video's videoid
		  resdisplay.construct_thumbnail_url = function ( videoid ) {
				// return the "http://i.ytimg.com/vi/*videoid*/default.jpg" base with the appropriate videoid from the result
				return 'http://i.ytimg.com/vi/' + videoid + '/default.jpg' ;
		  }
		  // return the description string to display on the page
		  //   if it's too long it's truncated and ends with '...'
		  resdisplay.construct_truncated_str = function ( description, maxlength ) {
				if ( description.length > maxlength )
					 description = description.substring( 0, maxlength - 3 ) + '...' ;

				return description ;
		  }


		  
		  // function that gets called after all of the results for this query have been returned
		  // Parameters :
		  //    p_querystr -> the query string
		  //    p_start -> index of the first result
		  //    p_num_requested_results -> user requested number of results per page
		  //    p_dirsearch_filetype -> the type of search
		  //    p_filetypes_str -> the filetypes string
		  //
		  //    p_num_valid_results -> the total number of VALID results
		  //    p_totaltime -> the total time ( in milliseconds ) it took to get these results
		  //    p_cursor -> array of pagination cursors returned from search results
		  resdisplay.after_results = function ( p_querystr, p_start, p_num_requested_results, p_dirsearch_filetype, p_filetypes_str, p_num_valid_results, p_totaltime, p_cursor ) {

				// get the time string to display, like '1.21s'
				var timestr = Math.floor ( p_totaltime / 1000 ) + "." + ( ( p_totaltime / 10 ) | 0 )
				
				// create the base text, like "Results 1 - 10 in 1.53s"
				var base_header_text_ele = this.create_base_header( this.result_type.substring(0,1).toUpperCase() + this.result_type.substring(1), p_querystr, p_start, p_num_valid_results, timestr ) ;

				var results_ele = null ;
				if ( p_num_valid_results == 0 )
					 results_ele = this.create_no_results_div( p_querystr, this.result_type ) ;

				// set the header and results body if necessary
				this.finalize_results_display( base_header_text_ele, results_ele ) ;
		  }


		  // return this display
		  return resdisplay ;
	 }

}








// result builder object
function ajax_result_builder () {

	 // user's query string
	 this.querystr = '' ;

	 // user's original requested starting result number
	 this.user_start = -1 ;

	 // user's requested number of results
	 this.numresults = -1 ;

	 // user's search type
	 this.search_type = '' ;

	 // user's search filetypes string ( like 'mp3|ogg' )
	 this.filetypes_str = '' ;


	 // the start used in the last query
	 this.start = -1 ;

	 // estimated number of total results there are 
	 //    this is set after the first ajax query if this data exists, -1 otherwise
	 this.estimatedresults = -1 ;

	 // total array of all cumulative results for this query
	 this.results = new Array() ;

	 // cursor object contains pagination data for this search
	 this.cursor = new Object() ;
	 

	 // array of 'valid' filtered results ( results that passed through the filter function this.filter_result(...) )
	 this.filteredresults = new Array() ;

	 // search object to use for executing the ajax queries
	 this.searchobj = null ;
	 
	 // list of result listeners that will be called after every group of results is received
	 this.listeners = new Array() ;

	 // timer used to time the number of milliseconds it takes to load all of the requested results
	 this.timer = null ;

	 

	 // add a listener object ( that has handle_new_result(...) defined )
	 this.add_listener = function ( p_listener ) {
		  this.listeners[ this.listeners.length ] = p_listener ;
	 }

	 // makes an ajax search call out and lets the callback function handle the results returned
	 //    this function should NOT be called directly except by this.get_results(...) because it needs a new p_searchobj for every set of parameters
	 // Parameters:
	 //   p_searchobj -> the ajax search object to use with the query
	 //   p_querystr -> search query string to use
	 //   p_start -> index of the first result
	 //   p_num   -> number of results
	 this.collect_results = function ( p_searchobj, p_querystr, p_start, p_num ) {
		  
		  // start the timer
		  this.timer = new Date() ;

		  // validate the search parameters
		  if ( p_start < 0 )
				p_start = DEFAULT_SEARCH_START ;
		  if ( p_num < 0 )
				p_num = DEFAULT_SEARCH_NUMRESULTS ;

		  // store the query string
		  this.querystr = p_querystr ;

		  // prepare the query to be submitted
		  p_querystr = this.prepare_query( p_querystr ) ;

		  // perform the json request
		  json_perform_request( p_searchobj.construct_url( p_querystr, p_start ) ) ;

	 }

	 // clear all of the results
	 this.clear_results = function () {
		  // make sure to reset the set_2str object for web results!!!

		  // call clear_results() on all of the listeners
	 }

	 // let all of the listeners know that we've gotten all of the results
	 this.inform_listeners_after_results = function () {
		  // calculate the time it took to load these results
		  this.timer =  new Date() - this.timer ;

		  // let all of the listeners know we've gotten all of the requested results
		  for ( var i in this.listeners )
				// Parameters :
				//    p_querystr -> the query string
				//    p_start -> index of the first result
				//    p_num_requested_results -> user requested number of results per page
				//    p_dirsearch_filetype -> the type of search
				//    p_filetypes_str -> the filetypes string
				//
				//    p_num_valid_results -> the total number of VALID results
				//    p_totaltime -> the total time ( in milliseconds ) it took to get these results
				//    p_cursor -> array of pagination cursors returned from search results
				this.listeners[ i ].after_results( this.querystr, 
															  this.user_start, 
															  this.numresults, 
															  this.search_type, 
															  this.filetypes_str, 

															  this.filteredresults.length, 
															  this.timer, 
															  this.cursor ) ;
	 }

}




// result builder factory
function result_builder_factory () {
	 
	 // web results
	 this.get_web_result_builder = function () {

		  // ajax result builder to return
		  var resbuilder = new ajax_result_builder() ;

		  // set the type
		  resbuilder.type = 'web' ;

		  
		  // filter the web results
		  // 
		  // add a set_2str object to detect duplicates
		  resbuilder.unique_result_set = new set_2str() ;
		  resbuilder.filter_result = function ( p_result ) {
				// if this link is a valid open directory and we havent already added this [title, url ] pair
				if ( is_valid_html_dir_title( p_result.titleNoFormatting ) &&
					  this.unique_result_set.insert( p_result.titleNoFormatting, highest_order_url_dir( p_result.unescapedUrl ) ) )
					 return true ;

				// invalid result
				return false ;
		  }

		  // prepare the query by adding text to search for open directories
		  resbuilder.prepare_query = function ( p_query ) {
				return '-intitle intitle:"index of " + ' + this.querystr + ' + (' + 
					 this.filetypes_str.substring( 0, this.filetypes_str.length - 1 ).replace( '|', ' OR ' ) + ')' ; // remove the last '|' of the filetypes string, and then replace every '|' character with ' OR '
		  }


		  // build a cur

		  // creates an ajax_search_object and then calls its collect_results(...) function
		  // Parameters:
		  //    p_query -> search query to use
		  //    p_start -> index of the first result
		  //    p_num   -> number of results
		  //    p_dirsearch_filetype  -> the type of search results, like 'm' for music, etc
		  //    p_filetypes_str -> filetypes string, like 'mp3|ogg'
		  //    p_results_display_ele -> the element id to be made visible to display the results
		  resbuilder.get_results = function ( p_query, p_start, p_num, p_dirsearch_filetype, p_filetypes_str, p_results_display_ele ) {

				// set the user's requested start and number of results
				if ( this.start == -1 ) // if this.start hasnt been initialized yet, initialize it now
					 this.start = p_start ;
				this.numresults = p_num ;
				this.search_type = p_dirsearch_filetype ;
				this.filetypes_str = p_filetypes_str ;

				// only set user_start on the first query
				if ( this.user_start < 0 )
					 this.user_start = p_start ;					 

				// get an ajax search object to use for the queries
				var searchobj_factory = new ajax_search_object_factory() ;
				var searchobj = searchobj_factory.get_web_search_object() ;

				// call the collect_results() function
				this.collect_results( searchobj, p_query, p_start, p_num ) ;

				// display the results
				if ( p_results_display_ele != null && p_results_display_ele != undefined )
					 p_results_display_ele.style.display = 'block' ;

		  }



		  // handle returned results
		  //    this is the callback function used when the data is returned from get_results()
		  // Parameters :
		  //    p_context -> the number of results to skip first in the result set
		  //    p_json -> the json results
		  //    p_status -> the http status of the ajax query
		  //    p_details -> any miscellaneous details from the query
		  //    p_unused -> currently unused
		  resbuilder.handle_results = function ( p_context, p_json, p_status, p_details, p_unused ) {
				// make sure we got a valid response
				if ( p_status != HTTP_OK ) {
					 this.inform_listeners_after_results() ;
					 return ;
				}
				
				// collect the json results ( which is an array ) for this last ajax query
				var results = p_json[ 'results' ] ;

				// store the cursor for these elements
				this.cursor = p_json[ 'cursor' ] ;

				// store the estimated result size
				this.estimatedresults = this.cursor[ 'estimatedResultCount' ] ;

				// if we didn't get any results, let all of the listeners know that and return
				if ( results == null || results.length == 0 ) {
					 this.inform_listeners_after_results() ;
					 return ;
				}

				// loop over the results ( and skip the first p_context results ) and call each listeners handle_new_result(...) method with this new result
				for ( var i = p_context; i < results.length && this.filteredresults.length < this.numresults; i++ ) {
					 // see if this result passes through the filter or not
					 var valid_result = this.filter_result( results[ i ] ) ;

					 // if this result is 'valid' and passes through the filter
					 if ( valid_result )
						  // add this result to the list of filtered results
						  this.filteredresults[ this.filteredresults.length ] = results[ i ] ;

					 // add this result to the total results list
					 this.results[ this.results.length ] = results[ i ] ;

					 // call all result listeners with this new result, whether it passed the result_builder filter or not, and with this result builder object
					 for ( var j in this.listeners )
						  this.listeners[ j ].handle_new_result( results[ i ], valid_result, this ) ;
				}

				// if we need more results, query for them now using (this.results.size + 1) as the new start
				// calculate the new start
				var newstart = parseInt(this.start) + parseInt(this.results.length) ; // for some reason we need to cast these to ints for + to act like integer + instead of str +
				//alert( this.filteredresults.length < this.numresults && newstart < this.estimatedresults && newstart < SEARCH_WEB_MAX_START )
				if ( this.filteredresults.length < this.numresults && newstart < this.estimatedresults && newstart < ( SEARCH_WEB_MAX_START + SEARCH_NUM_RESULTS_PER_PAGE ) )
					 this.get_results( this.querystr, newstart, this.numresults, this.search_type, this.filetypes_str ) ;
				// otherwise we're done, and we can let all of the display listeners know that
				else
					 this.inform_listeners_after_results() ;
		  }

		  // return this result builder
		  return resbuilder ;
	 }


	 // youtube results
	 this.get_youtube_result_builder = function () {

		  // ajax result builder to return
		  var resbuilder = new ajax_result_builder() ;


		  // filter out any videos that we dont want to appear in the search results
		  resbuilder.filter_result = function ( result ) {

				// if we want to filter vevo videos
				// if the last 4 letters of the author string lowercased is 'vevo', then its a vevo video
				/*
				var author = result[ 'author' ][ 0 ][ 'name' ][ '$t' ] ;
				if ( author.substring( author.length - 4 ).toLowerCase() == 'vevo' )
					 return false ;
				return true ;
            */

				// filter to block 'restricted' videos
				//   which videos are 'restricted' and why?
				/*
				var restricted = false ; // true or false, whether this video is 'restricted' for some reason or not
				if ( result[ 'app$control' ] && result[ 'app$control' ][ 'yt$state' ] &&  result[ 'app$control' ][ 'yt$state' ][ 'name' ] )
					 restricted = ( result[ 'app$control' ][ 'yt$state' ][ 'name' ].toLowerCase() == 'restricted' ) ? true : false ;
				if ( restricted == true )
					 return false ;
            */


				// if we've reached here it's a valid result => return true
				return true ;

		  }

		  // no preperation is needed for the youtube query
		  resbuilder.prepare_query = function ( p_query ) {
				return p_query ;
		  }

		  // creates an ajax_search_object and then calls its collect_results(...) function
		  // Parameters:
		  //    p_query -> search query to use
		  //    p_start -> index of the first result
		  //    p_num   -> number of results
		  //    p_type  -> the type of search results, like 'm' for music, etc
		  //    p_filetypes_str -> filetypes string, like 'mp3|ogg'
		  //    p_results_display_ele -> the element id to be made visible to display the results
		  resbuilder.get_results = function ( p_query, p_start, p_num, p_type, p_filetypes_str, p_results_display_ele ) {

				// set the user's requested start and number of results
				if ( this.start == -1 ) // if this.start hasnt been initialized yet, initialize it now
					 this.start = p_start ;
				this.numresults = p_num ;
				this.search_type = p_type ;
				this.filetypes_str = p_filetypes_str

				// only set user_start on the first query
				if ( this.user_start < 0 )
					 this.user_start = p_start ;					 

				// get an ajax search object to use for the queries
				var searchobj_factory = new ajax_search_object_factory() ;
				var searchobj = searchobj_factory.get_youtube_search_object() ;

				// call the collect_results() function
				this.collect_results( searchobj, p_query, p_start, p_num ) ;

				// display the results
				if ( p_results_display_ele != null && p_results_display_ele != undefined )
					 p_results_display_ele.style.display = 'block' ;

		  }

		  // build a cursor object for the video results similar to the cursor object returned by the google data search
		  //   IMPORTANT: in the future this cursor object should be independent and build independently from each type of search results 
		  //              ( rather than copied from the google web search results, etc )
		  //
		  // cursor object looks like ( for now, again later change it to be independent of the returned json data )
		  //
/*
    "estimatedResultCount": "306", 
    "pages": [
      {
        "label": 1, 
        "start": "0"
      }, 
      {
        "label": 2, 
        "start": "8"
      }, 
      {
        "label": 3, 
        "start": "16"
      }, 
      {
        "label": 4, 
        "start": "24"
      }, 
      {
        "label": 5, 
        "start": "32"
      }, 
      {
        "label": 6, 
        "start": "40"
      }, 
      {
        "label": 7, 
        "start": "48"
      }, 
      {
        "label": 8, 
        "start": "56"
      }
    ]
  }
*/
		  resbuilder.build_cursor = function ( json ) {
				// cursor object to return
				var cursor = new Object() ;

				// estimated total number of results for this query
				cursor[ 'estimatedResultCount' ] = json[ 'feed' ][ 'openSearch$totalResults' ][ '$t' ] ;

				// build the pages
				//
				// IMPORTANT: for now only a maximum of 8 pages are built
				//
				// number of pages ( as most 8, with 10 results per page )
				var numpages = cursor[ 'estimatedResultCount' ] / DEFAULT_SEARCH_NUMRESULTS ;
				if ( numpages > 7 )
					 numpages = 7 ;
				cursor[ 'pages' ] = new Array() ;
				for ( var i = 0; i < numpages; i++ ) {
					 // create and fill this page object
					 var page = new Object() ;
					 page[ 'label' ] = i ; // label is the integer page number, starting with 1
					 page[ 'start' ] = ( i * DEFAULT_SEARCH_NUMRESULTS ) + '' ;	// string of the starting index value, starting with 0

					 // add the page object to the pages array
					 cursor[ 'pages' ].push( page ) ;
				}

				return cursor ;

		  }

		  // handle returned results
		  //    this is the callback function used when the data is returned from get_results()
		  // Parameters :
		  //    json -> the json results string
		  resbuilder.handle_results = function ( json ) {
				// make sure we got a valid response
				if ( json == null || json == '' ) {
					 this.inform_listeners_after_results() ;
					 return ;
				}
				
				// collect the json results ( which is an array ) for this last ajax query
				var results = json[ 'feed' ][ 'entry' ] ;

				// build and store the pagination cursor for these results
				this.cursor = this.build_cursor( json ) ;

				// store the estimated result size
				this.estimatedresults = this.cursor[ 'estimatedResultCount' ] ;

				// if we didn't get any results, let all of the listeners know that and return
				if ( results == null || results.length == 0 ) {
					 this.inform_listeners_after_results() ;
					 return ;
				}

				// loop over the results ( and skip the first p_context results ) and call each listeners handle_new_result(...) method with this new result
				for ( var i = 0; i < results.length && this.filteredresults.length < this.numresults; i++ ) {
					 // see if this result passes through the filter or not
					 var valid_result = this.filter_result( results[ i ] ) ;

					 // if this result is 'valid' and passes through the filter
					 if ( valid_result )
						  // add this result to the list of filtered results
						  this.filteredresults[ this.filteredresults.length ] = results[ i ] ;

					 // add this result to the total results list
					 this.results[ this.results.length ] = results[ i ] ;

					 // call all result listeners with this new result, whether it passed the result_builder filter or not, and with this result builder object
					 for ( var j in this.listeners )
						  this.listeners[ j ].handle_new_result( results[ i ], valid_result, this ) ;
				}

				// if we need more results, query for them now using (this.results.size + 1) as the new start
				// calculate the new start
				var newstart = parseInt(this.start) + parseInt(this.results.length) ; // for some reason we need to cast these to ints for + to act like integer + instead of str +
				//alert( this.filteredresults.length < this.numresults && newstart < this.estimatedresults && newstart < SEARCH_WEB_MAX_START )
				if ( this.filteredresults.length < this.numresults && newstart < this.estimatedresults && newstart < ( SEARCH_WEB_MAX_START + SEARCH_NUM_RESULTS_PER_PAGE ) )
					 this.get_results( this.querystr, newstart, this.numresults, this.search_type, this.filetypes_str ) ;
				// otherwise we're done, and we can let all of the display listeners know that
				else
					 this.inform_listeners_after_results() ;
		  }


		  // return this result builder
		  return resbuilder ;
	 }

}
















// ajax search object
//    contains all of the information necessary for executing an ajax search
function ajax_search_object () {
	 // everything in here is set in the factory depending on this ajax_search_object's application

	 // get a start divisible by SEARCH_NUM_RESULTS_PER_PAGE
	 //    for now google requires the specified start value to be a multiple of SEARCH_NUM_RESULTS_PER_PAGE, like: 0, 8, 16, ... etc
	 // 
	 this.get_search_start = function ( p_requested_start ) {
		  // get the first multiple of SEARCH_NUM_RESULTS_PER_PAGE before p_requested_start
		  return ( Math.floor( p_requested_start / SEARCH_NUM_RESULTS_PER_PAGE ) ) * SEARCH_NUM_RESULTS_PER_PAGE ;
	 }
}


// ajax search object factory 
function ajax_search_object_factory () {

	 // create a google configured ajax search object
	 this.get_web_search_object = function () {

		  // new ajax_search_object to return
		  var searchobj = new ajax_search_object() ;

		  // configure this search object for google ajax search
		  searchobj.baseurl = "http://www.google.com/uds/GwebSearch?" ;   // base url
		  searchobj.key = "ABQIAAAAQ5XZ_cxcHgDguKaVSDLBfhRgBOSJ0hqj7-juAgU7TuPANuqN1BT7jAZpHARB0b5LSiN60InWpurZGw" ;   // google developer search key
		  searchobj.output_format = "json" ;	 // output format
		  searchobj.callback = 'handle_web_ajax_search_results' ;	 // callback. See a description for why this is necessary at the function definition
		  searchobj.default_numresults = 8 ;	 // default number of results returned
		  searchobj.max_numresults = 8 ;	 // maximum number of results returned per page of results
		  searchobj.max_start = 32 ;	 // maximum result index
		  searchobj.default_start = 0 ;	 // default starting index ( starts at 0 )
		  searchobj.default_context = "0" ;	 // default context
		  searchobj.link_target = "_self" ;	 // link target for new links
		  searchobj.custom_engine = "000697466513142665673:5gng6r91xxm" ;	 // custom search engine

		  // construct the full url
		  searchobj.construct_url = function ( p_query, p_start ) {

				// calculate the proper start position
				var search_start = this.get_search_start( p_start ) ;

				// build the full url and return it
				return this.baseurl + 
					 "callback=" + this.callback +
					 "&context=" + parseInt( p_start - search_start ) + // set the context as the number of results to skip over ( the number of results between p_start and valid_start )
					 "&lstkp=0&rsz=large&hl=en&gss=.com&sig=5713ab50c580233c8f2c7a065e0248ed" + // other stuff that needs to be included
					 "&v=1.0" + // version
					 "&cx=" + this.custom_engine +
					 "&key=" + this.key + 
					 "&start=" + search_start +
					 // "&num=" + p_num // this is taken care of by &rsz=large in the baseurl
					 "&q=" + p_query ;				

		  }

		  // return this configured ajax search object
		  return searchobj ;

	 }

	 // create a youtube configured ajax search object
	 this.get_youtube_search_object = function () {

		  // new ajax_search_object to return
		  var searchobj = new ajax_search_object() ;

		  // configure this search object for google ajax search
		  searchobj.baseurl = "http://gdata.youtube.com/feeds/api/videos?"	 // base url
		  searchobj.key = "AI39si4LvwqOTGjh_suAJKmSWZBQ-YOytfAZS5F1isxnf-ouaqq5N5njOdez-xubocvjezv-tfP9ibuht9xUYuZV_BbE_9tlGg" ;   // youtube developer search key
		  searchobj.output_format = "json-in-script" ;	 // output format
		  searchobj.callback = 'handle_video_ajax_search_results' ;	 // callback. See a description for why this is necessary at the function definition
	 	  searchobj.default_numresults = 8 ;	 // default number of results returned
		  searchobj.numresults = 10 ;	 // maximum number of results returned per page of results
		  searchobj.max_start = 32 ;	 // maximum result index
		  searchobj.default_start = 0 ;	 // default starting index ( starts at 0 )
		  searchobj.default_context = "0" ;	 // default context
		  searchobj.link_target = "_self" ;	 // link target for new links

		  // construct the full url
		  searchobj.construct_url = function ( p_query, p_start ) {

				// calculate the proper start position
				//var search_start = this.get_search_start( p_start ) + 1 ; // not the +1 is needed b/c youtube is 1 indexed ( i.e. the first element is 1, second is 2, etc )
				var search_start = parseInt( p_start ) + 1 ;

				// build the full url and return it
				return this.baseurl + 
					 "callback=" + this.callback +
					 // no context is needed to specify the number of results to skip over because the youtube data feed lets specify any starting value
					 //"&context=" + parseInt( p_start - search_start ) + // set the context as the number of results to skip over ( the number of results between p_start and valid_start )
					 "&alt=json-in-script" + // other stuff that needs to be included
					 "&v=1" + // version
					 //"&cx=" + g_search.custom_engine +
					 "&key=" + this.key + 
					 "&start-index=" + search_start +
					 "&max-results=" + this.numresults +
					 "&q=" + p_query ;

		  }

		  // return this configured ajax search object
		  return searchobj ;

	 }

}





// std::set like class to make sure a pair of strings is unique
//    that is ["a","b"],["a","c"],["b","a"] will return false when trying to add ["a","b"] 
function set_2str () {
	 // first index object, stores the first string
	 var d_set = new Object () ;
	 
	 // function that inserts a pair
	 // returns true if the pair was successfully added, false if that pair already exists in the set
	 this.insert = function ( p_index, p_value ) {

		  // if this index already exists
		  if ( d_set.hasOwnProperty ( p_index ) == true ) {

				// if the value also already exists then return false
				if ( d_set[ p_index ].hasOwnProperty ( p_value ) == true )
					 return false ;
				// otherwise the value does not exist and we can the value to the array
				else {
					 d_set[ p_index ][ p_value ] = true ;

					 return true ;
				}
		  }
		  // otherwise the index does not exist and we can add it
		  else {
				d_set[ p_index ] = new Object () ;
				d_set[ p_index ][ p_value ] = true ;

				return true ;
		  }
	 }

	 // erase all of the pairs
	 this.erase = function () {
		  d_set = new Object () ;
	 }
}

