/*******************************************************************************
*                                SPLat Controls                                *
*                          Product Development Group                           *
*                            Melbourne,  AUSTRALIA                             *
*                            http://www.splatco.com                            *
********************************************************************************
   DESCRIPTION:
   This search module uses Bing's RESTful interface to obtain search results
   within the SPLat web site.  The results are then displayed within the home
   page.
   Ref:
      http://msdn.microsoft.com/en-us/library/dd250846.aspx

   Bing restrictions:
      - Search results are limited to the first 1,000 results. This means that
      the total of WebRequest.Count plus WebRequest.Offset should not be greater
      than 1,000.

   DEPENDANCIES:
   - jquery
   - jquery jsonp plugin for cross domain queries because jquery's
   "datatype:'jsonp'" doesn't handle error conditions (dns, timeout etc),
   ref http://code.google.com/p/jquery-jsonp/
   - jquery.url_toolbox.js
   - Bing (better than You.Suck.Google in that Bing's RESTful API actually works)

********************************************************************************
*           Copyright (c) 2010 SPLat Controls. All rights reserved.            *
*******************************************************************************/

//nof results per page
var gNofItems = 4;

//current anchor where results are displayed
var gContainerID = '';

//current query string
var gQuery = '';

//preload the search images
$( PrepSearch );


/*------------------------------------------------------------------------------
DESCRIPTION:
 This function is called when the page has loaded.  It appends img tags so all
 the images used by the search module are preloaded and ready to display and
 attaches the DoSearch() function to the hashchange event.
PARAMETERS:
  -> nil
RETURNS:
 <-  nil
------------------------------------------------------------------------------*/
function PrepSearch()
{
   $( 'body' ).append( '<div style="display:none;">\
   <img src="/images/loadinfo.net.gif">\
   <img src="/images/attention64x64.gif">\
   <img src="/images/error64x64.gif">\
   <img src="/images/bing59x24.gif">\
   </div>' ); //"

   //bind the DoAnchorNav() function to the window.onhashchange function that, when the hash changes, shows the matching container.
   $( window ).bind( 'hashchange', function()
   {
      DoSearch( location.hash );
   });

   //Now the page has loaded and since the event is only triggered when the hash changes, we need to trigger the event to handle the hash the page may have loaded with.
   $( window ).trigger( 'hashchange' );
}


/*------------------------------------------------------------------------------
DESCRIPTION:
 This function is called from the search form to initiate a search.  The most
 important feature of this function is it's use of location.assign to store the
 search request in the browser's history.
PARAMETERS:
  -> search string
  -> id of the container that will display the search results
RETURNS:
 <-  nil
------------------------------------------------------------------------------*/
function KickOffSearch( search, containerID )
{
   var url;

   //make the query URL
   url = MakeSearchAnchor( containerID, search, 0 );

   //kick off the search by "jumping" to the anchor with the search query embedded
   location.assign( url );

   //return false to prevent automatic navigation
   return( false );
}


/*------------------------------------------------------------------------------
DESCRIPTION:
 Called when the user clicks search.  Constructs the new query and requests the
 specified page of results.
PARAMETERS:
  -> Search hash, consisting of the container id (with an #href-" prefix, the
  search string and starting index, eg:
  "#href-tab6&q=fdiv&p=4"
RETURNS:
 <-  nil
------------------------------------------------------------------------------*/
function DoSearch( hash )
{
   var i;
   var name;
   var val;
   var query = '';
   var page = 0;

   //find a '&' char in the hash
   i = hash.indexOf( '&' );
   //was a '&' not found?
   if( i == -1 )
      //not found, so abort as there's no search query
      return;

   //figure the container id by removing the 'href-' prefix
   gContainerID = hash.slice( 6, i );

   //yes, so extract the text following the '&'
   hash = hash.slice( i + 1 );

   //loop over the parameters
   while( hash.length )
   {
      //extract the name from the pair
      i = hash.indexOf( '=' );
      name = hash.slice( 0, i );
      hash = hash.slice( i + 1 );

      //extract the value from the pair
      i = hash.indexOf( '&' );
      if( i == -1 )
         i = hash.length; //use the remaining length if no '&' separator found
      val = unescape( hash.slice( 0, i ) );
      hash = hash.slice( i + 1 );

      //store the value in it's place
      switch( name )
      {
      case 'q':
         query = val;
         break;

      case 'p':
         page = parseInt( val );
         break;
      }
   }
   
   //is the query under 2 chars in length?
   if( query.length < 2 )
   {
      //yes, so ignore it & show no results
      HandleSearchResp( null, null );
      return;
   }

   //save the query
   gQuery = query;

   //get the search input box and put the query there (there may be multiple search boxes on the page)
   $('input[name="q"]').val( query );

   //request the page
   RequestPage( page );
}


/*------------------------------------------------------------------------------
DESCRIPTION:
 This function is called to request a specific page of results from google.
 KickOffSearch() must have been called before this function to create the query
 string.
PARAMETERS:
  -> page to get
RETURNS:
 <-  nil
------------------------------------------------------------------------------*/
function RequestPage( offset )
{
   var callback;

   //show a distraction
   $( '#' + gContainerID ).html( '<div style="text-align:center;"><img src="/images/loadinfo.net.gif" width="48" height="48"><br><h3>Searching...</h3></div>' );

   //create a randomly named callback "cbNNNNNN"
   callback = 'cb' + Math.floor( Math.random() * 999999 );

   //send the request using the json padding plugin which, unlike native jquery jsonp, supports error reporting
   $.jsonp( {
      cache: false,
      callback: callback, //inform the jsonp plugin of the callback name
      data: {
         AppId: 'A3F7067643A6DFC8CC43A6CD87204B8A0D435F8D',
         JsonCallback: callback, //give Bing the callback name
         JsonType: 'callback', //ask bing for the result in javascript callback format
         Query: gQuery + ' site:splatco.com', ////append the site restriction (JQuery will automatically URI escape the string) - we always search via splatco.com as other domains may not have been indexed
         Sources: 'web',
         Version: '2.2',
         'Web.Count': gNofItems,
         'Web.Offset': offset
      },
      error: HandleSearchError,
      success: HandleSearchResp,
      timeout: 5000, //timeout after 5 seconds
      url: 'http://api.bing.net/json.aspx'
   } );

   //track search requests
   DoAnalytics( 'Search - ' + gQuery );

   //return false to prevent automatic navigation
   return( false );
}


/*------------------------------------------------------------------------------
DESCRIPTION:
 This function is called to handle the search results.
PARAMETERS:
  -> JSON data from the google server
  -> status
RETURNS:
 <-  nil
------------------------------------------------------------------------------*/
function HandleSearchResp( data, textStatus )
{
   var a;
   var i;
   var dispUrl;
   var offset;
   var page;
   var container;
   var result;
   var textContainer;
   var url;

   //fetch the container
   container = $( '#' + gContainerID );

   //did bing return an error?
   if( data && data.SearchResponse.Errors )
   {
      //yes, so display the generic error message
      HandleSearchError( null, "error" );
      return;
   }

   //erase the content and show a heading
   container.html( '<h1 style="text-align:center;">SPLat Search Results</h1>' );

   //did we NOT get any results?
   if( !data || !data.SearchResponse.Web || !data.SearchResponse.Web.Total )
   {
      container.append( '<img src="/images/notfound.jpg" width="177" height="199" style="float:left;margin-right:20px;" alt="Not found">\
      <br><h3>Search Problem</h3>Sorry, but we could not find any pages containing your <b>"' + gQuery + '"</b> search term.  Please amend your query and try again.<br><br>'
      + '<form name="search2" method="get" action="#" onsubmit="return( KickOffSearch( this.q.value, \'' + gContainerID + '\' ) );">'
         + '<div style="margin-bottom:10px;">'
         + '<input type="text" name="q" size="25" class="search' + (gQuery ? '' : ' searchimg') + '" value="' + gQuery + '" onkeyup="vSrchKeyUp(this)">'
         + '<input type="image" src="/images/search-btn.gif" class="searchimg">'
         + '</div>'
      + '</form>'

      + GetBingLogoMarkup() ); //"
      return;
   }

   //get a hyperlink element that we can use to manipulate URLs
   //ref http://msdn.microsoft.com/en-us/library/ms536389%28VS.85%29.aspx & http://msdn.microsoft.com/en-us/library/ms535173%28v=VS.85%29.aspx
   a = document.createElement('a');

   //loop through the answers
   for( i = 0, textContainer = ''; i < data.SearchResponse.Web.Results.length; i++ )
   {
      //fetch the result
      result = data.SearchResponse.Web.Results[i];

      //convert the result domain to the page domain
      a.href = result.Url; //load the URL
      dispUrl = result.DisplayUrl.replace( a.hostname, location.host ); //convert the display URL domain
      a.host = location.host; //change the result domain to that of the current page
      url = a.href; //fetch the new URL

      //construct the markup
      textContainer += '<h3><a href="' + url + '" style="color:blue;">' + result.Title + '</a></h3>';
      textContainer += ' ' + result.Description + '<br><a href="' + url + '" style="color:green;">' + dispUrl + '</a><br><br>';
   }

   //has the offset exceeded the total (Bing's initial total numbers are sometimes optomistic)
   if( data.SearchResponse.Web.Offset > data.SearchResponse.Web.Total )
      //yes, so use the total number (adjust because the offset is a multiple of 4)
      data.SearchResponse.Web.Offset = data.SearchResponse.Web.Total - data.SearchResponse.Web.Total % gNofItems;

   //show the result count
   textContainer += 'Results ' + (data.SearchResponse.Web.Offset + 1) + ' to ' + (i + data.SearchResponse.Web.Offset)
   + ' of around ' + data.SearchResponse.Web.Total + ' for <b>' + gQuery + '</b>.<br>';

   //is there more than 1 page?
   if( data.SearchResponse.Web.Total > gNofItems )
   {
      //yes, so create a container
      textContainer += '<div style="display:block; text-align:center;">';

      //figure the starting page (based on 8 pages @ 4 results per page)
      page = parseInt( data.SearchResponse.Web.Offset / 8 / gNofItems ) * 8;

      //figure the starting offset (based on 4 results per page)
      offset = page * gNofItems;

      //start the pages at 1
      page++;

      //are there previous pages?
      if( page > 8 )
         //yes, so add the "prev" link
         textContainer += '<a href="' + MakeSearchAnchor( gContainerID, gQuery, offset - gNofItems ) + '" class="padboth10" style="color:#211D70;">...Prev</a>';

      //show the pages
      for( i = 0; i < 8 && offset < data.SearchResponse.Web.Total; i++, offset += gNofItems, page++ )
      {
         //is this the current page?
         if( offset == data.SearchResponse.Web.Offset )
            //yes, so enlarge this page number
            textContainer += '<span class="padboth10" style="color:#B11116; font-size:140%; font-weight:bolder;">' + page + '</span>';
         else
            //not current, display standard size link
            textContainer += '<a href="' + MakeSearchAnchor( gContainerID, gQuery, offset ) + '" class="padboth10" style="color:#211D70;">' + page + '</a>';
      }

      //are there more pages?
      if( offset < data.SearchResponse.Web.Total )
         //yes, so add the "prev" link
         textContainer += '<a href="' + MakeSearchAnchor( gContainerID, gQuery, offset ) + '" class="padboth10" style="color:#211D70;">More...</a>';

      //terminate the container
      textContainer += '</div>';
   }

   //add the bing logo
   textContainer += GetBingLogoMarkup();

   //place the results on the page
   container.append( textContainer );
}


/*------------------------------------------------------------------------------
DESCRIPTION:
 Will be called if there was an error making the request.
PARAMETERS:
  -> request options, or null
  -> error message
RETURNS:
 <-  nil
------------------------------------------------------------------------------*/
function HandleSearchError( xOptions, textStatus )
{
   var containerText;
   var tryAgain;
   var offset;

   //make the heading
   containerText = '<img src="/images/error64x64.gif" width="64" height="64" style="float:left; margin-right:10px;"><br><h3>Search Error</h3>';

   //get the offset if it exists, oherwise use 0
   offset = xOptions ? xOptions.data.start : 0;

   //make the try again link
   tryAgain = '<a onclick="return(RequestPage(' + offset + '))">try again</a>';

   //did we encounter a timeout error?
   if( textStatus == "timeout" )
      //yes, so construct the timeout message with a "try again" option
      containerText += 'The server took too long to respond.  Would you like to ' + tryAgain + '?';
   else
      //otherwise, make a generic fail message
      containerText += 'An error occurred while performing your search.  We\'re not sure what went wrong, but you may like to ' + tryAgain + '.';

   containerText += GetBingLogoMarkup();

   //insert the error message into the page
   $( '#' + gContainerID ).html( containerText );
}


/*------------------------------------------------------------------------------
DESCRIPTION:
 Returns the mark-up for displaying the Bing logo in the bottom right corner of
 the parent container.  The parent container must have a CSS position attribute
 for this to work, eg style="position:relative;" is enough, no dimensions are
 required.
PARAMETERS:
  -> nil
RETURNS:
 <-  HTML markup
------------------------------------------------------------------------------*/
function GetBingLogoMarkup()
{
   return( '<div style="position:absolute; bottom:4px; right:6px;">\
   <a href="http://www.bing.com" target="_blank" style="color:#0492E0; text-decoration:none; background-image:none;">\
   <img src="/images/bing59x24.gif" height="24" width="59" style="bottom:-10px; position:relative;"><br>\
   <b style="position:relative; right:0px;">search</b>\
   </a>\
   </div>' ); //'
}


/*------------------------------------------------------------------------------
DESCRIPTION:
 This function is called to make a string that can be embedded in a hyperlink
 href attribute suitable for initiating a search.  The string will look
 something like:
  "#href-results&q=fdiv&p=4"
PARAMETERS:
  -> container id without any leading '#'
  -> search string
  -> page offset
RETURNS:
 <-  nil
------------------------------------------------------------------------------*/
function MakeSearchAnchor( containerID, search, page )
{
   var query;
   var page;

   query = 'q=' + escape( search );
   page = 'p=' + page;

   return( '#href-' + containerID + '&' + query + '&' + page );
}



