

//////////////////////////////////////
// Search type constants
//////////////////////////////////////
var MUSIC_TYPE = 'm' ;
var VIDEO_TYPE = 'v' ;
var ARCHIVE_TYPE = 'a' ;





// 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_json_search_suggestions ( p_response ) {
	 getgvar('search_menu').handle_suggestions( p_response[ 0 ], p_response[ 1 ] ) ;
}

// keyboard and mouse callbacks
function search_menu_keypress_callback ( p_event ) {
	 getgvar('search_menu').handle_keypress( p_event ) ;
}
function search_menu_mouseclick_callback ( p_event ) {
	 getgvar('search_menu').handle_mouseclick( p_event ) ;
}




// search menu object
//    stores relevant information and data for constructing and interacting with a search menu
function search_menu () {

	 // the type of requested results ( like 'vid' for video, 'dir' for web directory, etc )
	 this.results_type = '' ;

	 // the current type of the search
	 //    'm' is music, 'v' is video, 'a' is archive, etc
	 this.dirsearch_filetype = MUSIC_TYPE ; // music is default

	 // the specified search types and their respective filetypes
	 //    THIS IS THE MASTER LIST OF SEARCH TYPES AND THEIR FILETYPES
	 //    if you want to add another search type and filetypes for that search type, add it here!!
	 this.dirsearch_filetypes = new Object() ;
	 this.dirsearch_filetypes[ MUSIC_TYPE ] = new Array( 'mp3','ogg','wav','wma' ) ;
	 this.dirsearch_filetypes[ VIDEO_TYPE ] = new Array( 'avi','mp4','mov','mpeg' ) ;
	 this.dirsearch_filetypes[ ARCHIVE_TYPE ] = new Array( 'zip','gz','rar','bz2' ) ;


	 // filetype strings for each search type
	 //    these are maintained throughout the user session on the page, so at some point music_filetype_str might be 'mp3', and later it might be 'mp3|ogg' after he's checked the 'ogg' checkbox
	 this.filetypes_str = new Object() ;


	 // the base search url to use when the user submits a search query ( set later )
	 //   ex: 'search'
	 this.submit_baseurl = '' ;

	 // the studio base url to use when the user submits a full video url and we want to skip the search and go right to the studio
	 this.studio_baseurl = '/studio/' ;

	 // pointer to the search input box
	 this.search_input_box_ele = null ;


	 // pointer to the search suggestions box
	 this.search_suggestions_ele = null ;

	 // the last query sent off to get suggestions
	 this.search_suggestions_last_query = '' ;

	 // whether the suggestions box has been closed by the user so far
	 this.suggestions_box_closed = false ;

	 // index of the currently selected entry ( -1 if no entry is selected )
	 this.selected_suggestion = -1 ;

	 // temporary input value used when the user is flipping through suggestions to not trigger getting new suggestions using these temporary values
	 this.temp_suggestion_input = '' ;


	 // pointer to a search filetypes menu table element
	 //    this element's children will be changed to offer the correct filetypes for the type of chosen search
	 this.search_filetypes_ele = null ;

	 // pointer to the search type drop down element
	 this.search_type_dropdown_ele = null ;

	 // DOM filetypes checkbox tables
	 this.filetypes_table_ele = new Object() ;
	 
	 // two level map of the search type and extension name -> the checkbox dom element for that extension
	 //    i.e. filetypes_checkbox_eles[ MUSIC_TYPE ][ "mp3" ] == __input_dom_element_for_mp3__
	 this.filetypes_checkbox_eles = new Object() ;


	 // array of type select listeners who will also have typeselect(...) defined and will get their typeselect(...) functions called when the type is changed
	 this.typeselect_listeners = new Array() ;
	 


	 // function that initializes the search menu values
	 //   p_inputbox_id -> the search input box element id
	 //   p_suggestions_id -> the search suggestions div id
	 //   p_filetypes_id -> the search filetypes containing div id
	 //   p_submit_baseurl -> the base url ( like 'search' ) to submit to

	 //   p_results_type -> the type of results being returned on this page, like 'vid' for videos and 'dir' for web directories
	 //   p_dirsearch_filetype -> the search type string, like 'm' or 'v', etc
	 //   p_typeselect_listeners -> array of typeselect listeners
	 this.init = function ( p_inputbox_id, p_suggestions_id, p_filetypes_id, p_submit_baseurl, p_results_type, p_dirsearch_filetype, p_typeselect_listeners) {

		  // set the search input box pointer
		  this.search_input_box_ele = document.getElementById( p_inputbox_id ) ;
		  
		  // if the input box has a value in it, set this as the previously submitted search suggestion query.
		  // that way it wont pop up suggestions if the user types in the box but the query doesnt actually change
		  if ( this.search_input_box_ele.value != '' )
				this.search_suggestions_last_query = this.search_input_box_ele.value ;

		  // focus the search box
		  this.search_input_box_ele.focus() ;


		  // get the pointer tot he suggestions div
		  this.search_suggestions_ele = document.getElementById( p_suggestions_id ) ;

		  // set the submit base url
		  this.submit_baseurl = p_submit_baseurl ;

		  // set the results type
		  this.results_type = p_results_type ;


		  // setup the various data objects for each search type
		  for ( var type in this.dirsearch_filetypes ) {
				// load the filetypes strings passed in from the server
				this.filetypes_str[ type ] = getgvar( "initial_filetypes_str" )[ type ] ;				

				// search type checkbox maps
				this.filetypes_checkbox_eles[ type ] = new Object() ;
		  }

		  // store the typeselect listeners
		  this.typeselect_listeners = p_typeselect_listeners ;

		  // filetypes options
		  this.search_filetypes_ele = document.getElementById( p_filetypes_id ) ;

		  // search type dropdown ( music, video, etc )
		  this.search_type_dropdown_ele = document.getElementById( "type" ) ;

		  // build the filetypes DOM tables to be displayed on screen later
		  this.build_filetypes_tables() ;


		  // select and load all data relating to the given search type
		  this.typeselect( p_dirsearch_filetype ) ;
	 }


	 // select a new search type and changes the filetypes appropriately
	 this.typeselect = function ( p_type ) {

		  // if this.search_filetypes_ele is null, then we don't care about managing filetypes and we can just return
		  if ( this.search_filetypes_ele == null )
				return ;

		  // store the old search type
		  oldtype = this.dirsearch_filetype
		  
		  // set the new type of search selected
		  this.dirsearch_filetype = p_type ;

		  // load the tab options under the search bar
		  this.remove_ele_children( this.search_filetypes_ele ) ;

		  // load the correct filetypes table
		  this.search_filetypes_ele.appendChild( this.filetypes_table_ele[ p_type ] ) ;

		  // initialize every checkbox for this type
		  for ( var ext in this.filetypes_checkbox_eles[ p_type ] )
				this.init_checkbox( p_type, ext ) ;

		  // call the typeselect(...) methods on all of the typeselect listeners
		  for ( var i = 0; i < this.typeselect_listeners.length; i++ )
				this.typeselect_listeners[ i ].typeselect( oldtype, p_type ) ;

	 }


	 // adds and removes filestypes from the global string g_filetypes when checkboxes are clicked or unclicked
	 // Parameters :
	 //    p_type -> search type
	 //    p_ext -> the extension, like 'mp3' or 'avi'
	 this.checkclick = function ( p_type, p_ext ) {
		  // if the checkbox was checked, add the filetype
		  if ( this.filetypes_checkbox_eles[ p_type ][ p_ext ].checked == true ) {
				this.filetypes_str[ p_type ] = this.filetypes_str[ p_type ].replace( p_ext + "|", "" ) ;
				this.filetypes_str[ p_type ] = this.filetypes_str[ p_type ] + p_ext + "|" ;
		  }

		  // if the checkbox was unchecked, remove that filetype from the filetypes string
		  else
				this.filetypes_str[ p_type ] = this.filetypes_str[ p_type ].replace( p_ext + "|", "" ) ;
	 }


	 // function that re-initializes a checkbox whenever it is re-added to the screen
	 // if the passed in filetype exists in it's type's filetypes string (e.g. "mp3" exists in musics's filetypes string ("mp3|ogg|") )
	 // check the checkbox if the filename exists, otherwise uncheck it
	 this.init_checkbox = function ( p_type, p_ext ) {
		  if ( this.filetypes_str[ p_type ].indexOf( p_ext ) != -1 )
				this.filetypes_checkbox_eles[ p_type ][ p_ext ].checked = true ;
		  else
				this.filetypes_checkbox_eles[ p_type ][ p_ext ].checked = false ;
	 }


	 // submit the query to p_baseurl using all of user's entered data on this page
	 this.submit = function () {

		  // clean up the query by removing all leading, trailing, and double or more intermediate white spaces
		  // e.g. "  ab   cd    " to "ab cd"
		  // get the query string
		  var querystr = this.strip_whitespace_groups( url_encode( this.search_input_box_ele.value ) ) ;

		  //  if the querystr is a null string "", reload the page
		  if ( querystr == '' ) {
				window.location.href = window.location.href ;
				return ;
		  }

		  
		  // figure out what kind of input we have and decide how to proceed
		  var newurl = '' ;

		  // if a full youtube watch url was submitted, construct the link to the studio page
		  if ( is_valid_youtube_watch_url( querystr ) )
				// sw // newurl = this.studio_baseurl + get_youtube_videoid( querystr ) ;
				newurl = "results-transcoder.php?search=" + get_youtube_videoid( querystr ) ;

		  // if it was a search query
		  else
				// sw // newurl = this.submit_baseurl + '/' + escape(querystr) + this.construct_search_parameter_str() ;
				newurl = this.submit_baseurl + escape(querystr) + this.construct_search_parameter_str() ;

		  // load this new url
		  window.location.href = newurl ;

	 }


	 // construct a parameter string using the search terms
	 // the function always returns the parameter string that starts with an ampersand and does NOT include the query
	 //    ex: '?t=v&ft=avi|mp4|mov|mpeg|wmv|'
	 // so to easily get rid of the first '&' if needed, just call this.construct_search_parameter_str().substr( 1 )
	 this.construct_search_parameter_str = function ( p_querystr ) {
		  var urlparams = '' ;

		  // the results type ( can be ignored for the default of 'vid' for videos )
		  if ( this.results_type != 'vid' )
				urlparams += '&r=' + this.results_type ;

		  // search type ( only included if it's not the default MUSIC_TYPE )
		  if ( this.dirsearch_filetype != MUSIC_TYPE )
				urlparams += '&t=' + this.dirsearch_filetype ;
			
		  // filetypes ( only included if it's not the default 'mp3|ogg|wav|wma|' )
		  if ( this.filetypes_str[ this.dirsearch_filetype ] != 'mp3|ogg|wav|wma|' )
				urlparams += '&ft=' + this.filetypes_str[ this.dirsearch_filetype ] ;

		  // replace the first '&' with a '?'
		  if ( urlparams.length > 0 )
				urlparams = '?' + urlparams.substr( 1 ) ;

		  return urlparams ;
	 }



	 /////////////////////////////////////////////
	 // Search Suggestions
	 /////////////////////////////////////////////

	 // gets search suggestions
	 // Paramters :
	 //   p_query -> the user's query string used to get suggestions
	 this.get_search_suggestions = function ( p_query ) {
		  // if this input is the same as the temporary suggestion input, then skip it
		  if ( p_query == this.temp_suggestion_input )
				return ;
		  // otherwise reset it because the user changed the value of the input
		  else
				this.temp_suggestion_input = '' ;


		  // if the user has already closed the suggestions box, return and don't get anymore suggestions
		  // or if this isn't a new query, just return
		  if ( this.suggestions_box_closed == true || p_query == this.search_suggestions_last_query )
				return ;
		  // if this query is a null string, then delete the suggestion box
		  else if ( p_query == '' ) {
				this.delete_suggestions_box() ;
				this.search_suggestions_last_query = p_query ;
				return ;
		  }

		  // update the last query used for suggestions
		  this.search_suggestions_last_query = p_query ;

		  // construct the full suggestions url to retrieve the suggestions for this query
		  var url = 'http://suggestqueries.google.com/complete/search?client=suggest&ds=yt' +
				'&jsonp=handle_json_search_suggestions' + // the callback funtion
				'&q=' + p_query ; // the query

		  // submit the url and handle the result with the callback
		  json_perform_request( url ) ;

	 }

	 // handles returned search suggestions
	 this.handle_suggestions = function ( p_query, p_suggestions ) {

		  // if we don't have any suggestions, close the box
		  if ( p_suggestions.length == 0 ) {
				this.delete_suggestions_box() ;
				return ;
		  }

		  // show the suggestions div
		  this.search_suggestions_ele.style.display = 'block' ;
		  if (navigator.userAgent.indexOf("Firefox")==-1)
		  {
			  paginate.style.visibility = "hidden";
			  prepaginate.style.visibility = "hidden";
				catsongs.style.visibility = "hidden";
				//googlediv.style.visibility = "hidden";
				googlediv.style.display = "none";
			}

		  // add the upper right [x] close ( only add it if it doesnt exist already, assuming that the first child will always be xclose )
		  var xclose_ele = this.create_suggestions_close_div( '[x]', 'search-suggestions-xclose' ) ;
		  if ( this.search_suggestions_ele.childNodes.length == 0 )
				this.search_suggestions_ele.appendChild( xclose_ele ) ;
		  else
				this.search_suggestions_ele.replaceChild( xclose_ele, this.search_suggestions_ele.childNodes[ 0 ] ) ;

		  // reset the currently selected suggestion index because we just loaded a new set of suggestions
		  this.selected_suggestion = -1 ;
		  
		  // loop through these suggestions and replace them into the suggestions box ( after the x-close element )
		  for ( var i in p_suggestions ) {
				// add a suggestion entry for this suggestion
				this.add_suggestion_entry( p_suggestions[ i ][ 0 ], i ) ;
		  }

		  // add the final invisible suggestion which is the original query
		  this.add_suggestion_entry( p_query, parseInt( i ) + 1, 'search-suggestions-entry-invisible' ) ;


		  // get the index of the first element AFTER all of the suggestions
		  var after_entries = parseInt( i ) + 2 ;
 
		  // add the final 'close' anchor if the last element isn't ( if the next element isn't already a suggestions close box )
		  var close_ele = this.create_suggestions_close_div( 'close', 'search-suggestions-close' ) ;
		  if ( after_entries + 1 >= this.search_suggestions_ele.childNodes.length )
				this.search_suggestions_ele.appendChild( close_ele ) ;
		  else if ( this.search_suggestions_ele.childNodes[ after_entries + 1 ].classname != 'search-suggestions-close' )
				this.search_suggestions_ele.replaceChild( close_ele, this.search_suggestions_ele.childNodes[ after_entries + 1 ] ) ;
				
		  // remove all trailing elements after the last close element
		  var numchildren = this.search_suggestions_ele.childNodes.length ;
		  for ( var j = after_entries + 2; j < numchildren; j++ )
				this.search_suggestions_ele.removeChild( this.search_suggestions_ele.childNodes[ after_entries + 2 ] ) ;

	 }

	 // adds a suggestion entry to the suggestion list
	 // Parameters :
	 //   p_suggestion -> the suggestion text, like 'Candy Land', etc
	 //   p_index -> the index of this suggestion of the total suggestions, starting with 0 and going up
	 //   p_classname -> an optional extra classname to use for this entry
	 this.add_suggestion_entry = function ( p_suggestion, p_index, p_classname ) {

		  // create the suggestion entry div
		  var newentry = this.create_suggestion_entry( p_suggestion, p_index, p_classname ) ;

		  // adjust the index to accomodate for the first [x]-close element before the first suggestion entry
		  var adjusted_index = parseInt( parseInt( p_index ) + 1 ) ;

		  // replace into the suggestions using this new entry ( i.e. replace it if it already exists, else append it )
		  if ( adjusted_index >= this.search_suggestions_ele.childNodes.length )
				this.search_suggestions_ele.appendChild( newentry ) ;
		  else
				this.search_suggestions_ele.replaceChild( newentry, this.search_suggestions_ele.childNodes[ adjusted_index ] ) ;

	 }

	 // closes the suggestions box ( called by the user )
	 this.close_suggestions_box = function () {
		  // record that the user has closed the suggestions box so it wont be displayed again
		  this.suggestions_box_closed = true ;

		  // close the suggestions box
		  this.delete_suggestions_box() ;
		
		  // re-focus the suggestion box
		  this.search_input_box_ele.focus() ;
	 }


	 // selects a suggestion based on its index ( starting with 0 )
	 // Parameters :
	 //   p_index -> the index ( starting at 0 ) of the suggestion entry
	 //   p_replace_input -> a flag that specifies whether or not this suggestions text should be copied over into the input box when selected
	 //      default: false
	 this.select_suggestion = function ( p_index, p_replace_input ) {

		  // whether or not we copy the input over, by default is false
		  if ( p_replace_input == undefined )
				p_replace_input == false ;

		  // make sure we have a valid index ( childnodes.length - 2 to account for the [x]-close anchor and the 'close' anchor elements that are also children in addition to the search suggestion entries )
		  if ( p_index < 0 || p_index >= ( this.search_suggestions_ele.childNodes.length - 2 ) )
				return ;

		  // child index adjusted for the [x]-close element that preceeds the first entries
		  var adjusted_index = parseInt( parseInt( p_index ) + 1 ) ;

		  // deselect the old selected index
		  this.deselect_suggestion( this.selected_suggestion ) ;

		  // set the selected search index to point to this
		  this.selected_suggestion = p_index ;
		  
		  // add the selected classname ( p_index + 1 to account for the first [x]-close anchor child element before the suggestion entries )
		  css_addclass( this.search_suggestions_ele.childNodes[ adjusted_index ], 'search-suggestions-entry-selected' ) ;

		  // if we need to set the query box's text to this suggestion's text
		  if ( p_replace_input ) {
				// get the text for this node
				var text = this.search_suggestions_ele.childNodes[ adjusted_index ].childNodes[ 0 ].data ;
				
            // set the last suggestion used to this selected query to prevent the page from loading suggestions for this selected query
            this.search_suggestions_last_query = text ;

				// set the temporary input value so new suggestions arent loaded for this text value
				this.temp_suggestion_input = text ;

				// set the input text
				this.search_input_box_ele.value = text ;
		  }

	 }


	 // deselects a search suggestion based on its index
	 this.deselect_suggestion = function ( p_index ) {
		  // child index
		  var adjusted_index = parseInt( parseInt( p_index ) + 1 ) ;

		  // remove the selected css classname ( p_index + 1 to account for the first [x]-close anchor child element before the suggestion entries )
		  css_removeclass( this.search_suggestions_ele.childNodes[ adjusted_index ], 'search-suggestions-entry-selected' ) ;		  

/* NOTE: Is this necessary? Apparently not...
		  // if this index is the same as the currently selected entry, reset the search suggestion index
		  if ( p_index == this.selected_suggestion )
				this.selected_suggestion = -1 ;
*/
	 }


	 // selects a new suggestion given an offset from the old one
	 //   ex: if p_offset = 1, it would advance to the next suggestion
	 //       but if p_offset = -1, it would move to the previous suggestion
	 //   suggestions wrap around
	 this.select_suggestion_offset = function ( p_offset ) {
		  // new base index
		  var newindex = parseInt( this.selected_suggestion ) + parseInt( p_offset ) ;
		  var numentries = this.search_suggestions_ele.childNodes.length - 2 ; // -2 to subtract from the [x] and 'close' elements

		  // if none have been selected, select the first one in the list now
		  if ( this.selected_suggestion < 0 )
				newindex = 0 ;
		  // wrap around over the end back to the start
		  else if ( newindex >= numentries )
				newindex = newindex % numentries ; 
		  // wrap back from the start to the end
		  else if ( newindex < 0 )
				newindex = ( numentries ) - ( ( -1 * newindex ) % ( numentries ) ) ; 

		  // select this new suggestion
		  this.select_suggestion( newindex, true ) ;
	 }

	 // deletes the suggestionb box ( not called directly through the UI )
	 this.delete_suggestions_box = function () {
		  // delete all of the suggestions box children
		  this.remove_ele_children( this.search_suggestions_ele ) ;

		  // hide the search suggestions div
		  this.search_suggestions_ele.style.display = 'none' ;
			if (navigator.userAgent.indexOf("Firefox")==-1)
			{
		  	paginate.style.visibility = "visible";
		  	prepaginate.style.visibility = "visible";
		  	catsongs.style.visibility = "visible";
		  	//googlediv.style.visibility = "visible";
		  	googlediv.style.display = "block";
			}
		  // reset the temporary suggestion, if there was one
		  this.temp_suggestion_input = '' ;

		  // reset the selected suggestion index
		  this.selected_suggestion = -1 ;
	 }

	 // reloads the suggestion box using the current input in the input box as the query
	 this.reload_suggestions_box = function () {
		  // reset the last query so that we can make the next one ( we can't make two identical queries in a row ) 
		  this.search_suggestions_last_query = '' ;

		  // reset the suggestions box closed flag
		  this.suggestions_box_closed = false ;

		  // reload suggestions for this query
		  this.get_search_suggestions( this.search_input_box_ele.value ) ;
	 }

	 // submit a suggestion
	 this.submit_suggestion = function ( p_text ) {
		  // if p_text isn't defined, get it from the currently selected element
		  if ( p_text == undefined || p_text == '' && this.selected_suggestion > 0 )
				p_text = this.search_suggestions_ele[ this.selected_suggestion + 1 ] ; // + 1 because we need to account for the [x] close ele before the first entry
				
		  // set the search box's text and refocus it
		  this.search_input_box_ele.value = p_text ;
		  this.search_input_box_ele.focus() ;

		  // delete teh suggestions box
		  this.delete_suggestions_box() ;

		  // and now submit the form using this suggestion's text
		  this.submit() ;
	 }

	 // handle a key press
	 this.handle_keypress = function ( p_event ) {
		  // key pressed
		  var keynum = -1 ;

		  // IE
		  if ( window.event )
				keynum = window.event.keyCode ;
		  // Netscape/Firefox/Opera
		  else if ( p_event.which )
				keynum = p_event.which ;


		  // down arrow
		  if ( keynum == 40 ) {
				// if there are suggestions, move on to the next one
				if ( this.search_suggestions_ele.childNodes.length > 2 ) // 2 to account for the two close buttons
					 this.select_suggestion_offset( 1 ) ;
				// else if there are no suggestions ( like the user closed the menu, reload the suggestions for this query )
				else if ( this.search_suggestions_ele.childNodes.length == 0 )
					 this.reload_suggestions_box() ;
		  }
		  // up arrow
		  else if ( keynum == 38 ) {
				// if there are suggestions, move back to the previous one
				if ( this.search_suggestions_ele.childNodes.length > 2 ) // 2 to account for the two close buttons
					 this.select_suggestion_offset( -1 ) ;
				// else if there are no suggestions ( like the user closed the menu, reload the suggestions for this query )
				else if ( this.search_suggestions_ele.childNodes.length == 0 )
					 this.reload_suggestions_box() ;
		  }
		  // escape
		  else if ( keynum == 27 ) {
				// go back to the original query string, which is stored in the last suggestion
				this.search_input_box_ele.value = this.search_suggestions_ele.childNodes[ this.search_suggestions_ele.childNodes.length - 2 ].childNodes[ 0 ].data ; 

				// now delete the suggestions box
				this.delete_suggestions_box() ;

				// refocus the input box
				this.search_input_box_ele.focus() ;
		  }

		  // else, do nothing

	 }

	 // handle a mouseclick
	 this.handle_mouseclick = function ( p_event ) {

		  // get the target of the event
		  //   this code taken and modified from: http://www.quirksmode.org/js/events_properties.html
		  var target ;
		  if ( ! p_event ) 
				p_event = window.event ;
		  if ( p_event.target )
				target = p_event.target ;
		  else if ( p_event.srcElement )
				target = p_event.srcElement ;
		  if ( target.nodeType == 3 ) // defeat Safari bug
				target = target.parentNode ;


		  // see if the user clicked outside the search suggestions box, and if so close the box
		  //   the deepest you can be is two levels below 'search-suggestions'
		  if ( ( target.parentNode && 
					target.parentNode == this.search_suggestions_ele ) ||
				 ( target.parentNode && 
					target.parentNode.parentNode &&
					target.parentNode.parentNode == this.search_suggestions_ele ) )
				return ;
		  else
				this.delete_suggestions_box() ;

	 }




	 /////////////////////////////////////////////
	 // DOM Construction
	 /////////////////////////////////////////////

	 // build the DOM filetypes tables
	 this.build_filetypes_tables = function () {
		  // build the different search type filetypes tables
		  for ( var type in this.dirsearch_filetypes )
				this.build_checkbox_table( type, this.dirsearch_filetypes[ type ] ) ;
	 }
	 //
	 // handy hmtl to dom site: http://rick.measham.id.au/paste/html2dom.htm
	 //
	 // creates a search suggestion entry 
	 // Parameters :
	 //   p_suggestion -> the suggestion text, like 'Candy Land', etc
	 //   p_index -> the index of this suggestion of the total suggestions, starting with 0 and going up
	 //   p_classname -> an optional extra classname to use for this entry
	 this.create_suggestion_entry = function ( p_suggestion, p_index, p_classname ) {
		  // crop after too many characters
		  if ( p_suggestion.length > 52 )
				p_suggestion = p_suggestion.substring( 0, 49 ) + '...' ;

		  var div_0 = document.createElement('div');
		  div_0.onclick = function () {
				getgvar( 'search_menu' ).submit_suggestion( p_suggestion ) ;
		  } ;
		  div_0.onmouseover = function () {
				getgvar( 'search_menu' ).select_suggestion( p_index, false ) ;
		  } ;		  
		  div_0.onmouseout = function () {
				getgvar( 'search_menu' ).deselect_suggestion( p_index ) ;
		  } ;		  

		  div_0.className = 'search-suggestions-entry' ;
		  // if we were passed another classname, use it now
		  if ( p_classname != undefined && p_classname != '')
				div_0.className += ' ' + p_classname ;

		  div_0.appendChild( document.createTextNode( p_suggestion ) );
		  return div_0 ;
	 }
	 // creates a close div of the suggestions box
	 this.create_suggestions_close_div = function ( p_text, p_classname ) {
		  var div_0 = document.createElement('div');
		  div_0.className = p_classname ;

		  var a_0 = document.createElement('a');
        a_0.onclick = function(){
				getgvar( 'search_menu' ).close_suggestions_box()
        };
        a_0.appendChild( document.createTextNode(p_text) );
		  div_0.appendChild( a_0 );
		  return div_0 ;
	 }
	 // adds a td element to the object passed in
	 this.ele_addtd = function ( p_ele ) {
		  // create the td element
		  var td_ele = document.createElement( "td" ) ;
		  td_ele.setAttribute( "class", "option_check" ) ;

		  // add the element to the td
		  td_ele.appendChild( p_ele ) ;

		  return td_ele ;
	 }
	 // create the tables that contain the checkboxes for the search criteria
	 // function that returns a <td> element containing a checkbox with the passed in value
	 // returns an object with the following html format: <td class="option_check"> <input type=checkbox value="wma" checked> </td>
	 // p_checked of -1 means not checked, all else means checked (this is so the .indexOf method can be directly sent to the function as a parameter)
	 this.ele_checkbox = function ( p_type, p_ext ) {
		  // create the input element
		  var input_ele = document.createElement( "input" ) ;
		  input_ele.setAttribute( 'class', "hand-checkbox" ) ;
		  input_ele.type = "checkbox" ;
		  input_ele.onclick = function () { getgvar( 'search_menu' ).checkclick( p_type, p_ext ) ; } ;

		  // return the input element
		  return input_ele ;
	 }
	 // adds a checkbox to p_parent_ele and also adds this checkbox's id to the p_filetypes_checkbox_map object
	 // Parameters :
	 //    p_parent_ele -> the parent DOM element under which to add the new checkbox as a child
	 //    p_type -> the search type of this checkbox, like MUSIC_TYPE or VIDEO_TYPE
	 //    p_ext -> filetype extension of the checkbox, like 'mp3'
	 this.add_checkbox = function ( p_parent_ele, p_type, p_ext ) {
		  // create a checkbox
		  var checkbox = this.ele_checkbox( p_type, p_ext ) ;

		  // add this checkbox to the checkbox map
		  this.filetypes_checkbox_eles[ p_type ][ p_ext ] = checkbox ;

		  // add this checkbox to the parent element
		  p_parent_ele.appendChild( this.ele_addtd( checkbox ) ) ;		
		  p_parent_ele.appendChild( this.ele_tdtext( p_ext ) ) ;		

	 }
	 // function that returns a td element with class=sub_spacer
	 // returns: <td class="sub_spacer"></td>
	 this.ele_tdsubspacer = function () {
		  // create the td element
		  var td_ele = document.createElement( "td" ) ;
		  td_ele.setAttribute( "class", "sub_spacer" ) ;

		  return td_ele ;
	 }
	 // function that returns text in a td
	 // returns: <td> blah </td>
	 this.ele_tdtext = function ( p_text ) {
		  // create the td element
		  var td_ele = document.createElement( "td" ) ;
		  td_ele.className = "filetype-text" ;

		  // create the text element
		  var text_ele = document.createTextNode( p_text ) ;

		  // add the text node to the td element
		  td_ele.appendChild( text_ele ) ;

		  // return the td element with the text child
		  return td_ele ;
	 }
	 // builds a DOM checkbox table and then adds those checkboxes to the checkbox map
	 // Parameters :
	 //    p_type -> the search type
	 //    p_extensions -> array of extensions, like: new Array( 'mp3', 'ogg', 'wav' )
	 this.build_checkbox_table = function ( p_type, p_extensions ) {
		  // create the table
		  this.filetypes_table_ele[ p_type ] = document.createElement( "table" ) ;
		  this.filetypes_table_ele[ p_type ].setAttribute( "class", "options" ) ;

		  // create the table framework for the checkboxes
		  var tbody = document.createElement( "tbody" ) ;
		  var row = document.createElement( "tr" ) ;

		  // add the checkboxes
		  for ( var i = 0; i < p_extensions.length; i++ )
				this.add_checkbox( row, p_type, p_extensions[ i ] )

		  // add the children
		  tbody.appendChild( row ) ;
		  this.filetypes_table_ele[ p_type ].appendChild( tbody ) ;
	 }



	 /////////////////////////////////////////
	 // Utility Functions
	 /////////////////////////////////////////
	 
	 // removes all preceding, twin or more intermediate, and trailing whitespace
	 // e.g. "  ab   cd    " to "ab cd"
	 this.strip_whitespace_groups = function ( p_str ) {
		  // new string to be returned
		  retstr = "" ;

		  // remove leading spaces
		  p_str = p_str.replace( new RegExp( /^\s+/ ), "" ) ;

		  // remove trailing spaces
		  p_str = p_str.replace( new RegExp( /\s+$/ ), "" ) ;

		  // loop over the string and remove two or more intermediate whitespaces
		  // binary flag as to whether or not a whitspace has been found
		  var in_whitespace = false ;
		  for ( var i = 0; i < p_str.length; i++ ) {
				// whitespace was found and whitespace flag was false
				if ( p_str.charAt( i ) == ' ' && in_whitespace == false ) {
					 // set in_whitespace to true
					 in_whitespace = true ;

					 // add this character to the string to be returned
					 retstr = retstr + p_str.charAt( i ) ;
				}
				// whitespace found and whitespace flag was true
				else if ( p_str.charAt( i ) != ' ' ) {
					 // set whitespace to true and continue iteration
					 in_whitespace = false ;

					 // add that character to the return string
					 retstr = retstr + p_str.charAt( i ) ;
				}
		  }
        
		  // return the cleaned up string
		  return retstr ;
	 }

	 // removes all of the children from the passed in element p_ele
	 this.remove_ele_children = function ( p_ele ) {
		  // if undefined or null, return
		  if ( p_ele == undefined || p_ele == null )
				return ;

		  // loop over the options child elements and remove them
		  while ( p_ele.hasChildNodes() )
				p_ele.removeChild( p_ele.firstChild ) ;
	 }

}
