/* $Id: autosuggest.js,v 1.11 2008/06/30 17:04:58 nickc Exp $
   (c) 2008 The New York Times Company
*/

// common object for member center ticker alerts
var TickerAlertCommon = { safe: 'result.ticker', preventSubmit: true, selectFirst: true, disableFunds: true, paramName: 'query' }

/**
 * Configuration variables specific to Business Autosuggest
 * @constructor
 */
Business = {
  
  /**
   * @param {Array} Inputs An array of inputs used in autosuggest,
   * an item can either be a string representing the ID or an object
   * containing setup information:
   * { input: id,
   *   placeholder: text (optional)
   *   placeholder_class: class (optional)
   *   safe: evaled js (optional) Place in input field
   *   preventSubmit: boolean (optional) Prevents redirecting to URL on selection
   *   selectFirst: boolean (optional) Selects first item on searching
   *  // Extra: any option defined within Ajax.Autocompleter can go here
   *  // and will be passed on creation
   *  }
   */
  Inputs:   [ // regular page inputs
              { input: 'bsearchQuery', // subnav input
                placeholder: 'News, Stocks, Funds, Companies',
                placeholder_class: 'greyed' },
              { input: 'qsearchQuery', // module input
                placeholder: 'Stocks, ETFs, Funds',
                placeholder_class: 'greyed',
                paramName: 'query' },
                
              // create ticker alert inputs (member center)
              Object.extend(Object.clone(TickerAlertCommon), { input: 'tickername_1' }),
              Object.extend(Object.clone(TickerAlertCommon), { input: 'tickername_2' }),
              Object.extend(Object.clone(TickerAlertCommon), { input: 'tickername_3' }),
              Object.extend(Object.clone(TickerAlertCommon), { input: 'tickername_4' }),
              Object.extend(Object.clone(TickerAlertCommon), { input: 'tickername_5' })
            ],
  
  /**
   * Defines autosuggest URI based on the hostname
   *
   * @param {String} h Supply a hostname instead of
   * basing it off the window hostname
   * @returns {String} Autosuggest Service URI
   */
  SuggestServer: function(h) {
    switch (h || window.location.hostname) {
      // blogs, currently disabled
      case 'shiftingcareers.blogs.nytimes.com':
      case 'norris.blogs.nytimes.com':
      case 'tvdecoder.blogs.nytimes.com':
      case 'dealbook.blogs.nytimes.com':
        return false;
        break;
      // wsod
      case 'smarkets.on.nytimes.com':
      case 'markets.on.nytimes.com':
      case 'nytimes.wsodqa.com':
        return '/services/autocomplete/autocomplete.asp';
        break;
       // debug
      case 'localhost':
        return '_return.php';
        break;
      // default
      default:
        return '/svc/search/business/autosuggest';
    }
  },
  
  /**
   * Returns the funds redirect url
   *
   * @param {String} s Ticker to be appended to url
   * @returns {String} URL for Funds redirect
   */
  FundURL: function(s) {
    return 'http://markets.on.nytimes.com/research/markets/usmarkets/snapshot.asp?symbol=' + s;
  },
  
  /**
   * @param {Template} Template Defines template used for pushing into UL
   */
  Template:           new Template('<li title="#{output_safe}"><span>#{ticker}</span>#{company}</li>')
}

/**
 * Business.Autosuggest is an extension of Ajax.Autocompleter
 * defined by scriptaculous. This extension adds specific
 * support for NYT service-based autocomplete.
 *
 * Assumes results divider is below (next) to the search
 * input.
 *
 * Requires Scriptaculous 1.8.1 or above (1.8.0 has a bug)
 * 
 * @constructor
 * @base Ajax.Autocompleter
 * @requires Business.Config Required for configuration variables
 */
Business.Autosuggest = Class.create();
Object.extend(Object.extend(Business.Autosuggest.prototype, Ajax.Autocompleter.prototype), {
  initialize: function(element, update, url, options) {
    options = options || {};
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.options.method        = 'get';
    this.options.autoSelect    = false; // prevent autoselecting if only one result
    this.options.minChars      = 2;
    this.options.frequency     = 0;
    this.options.selectFirst   = options.selectFirst || false;
    this.options.preventSubmit = options.preventSubmit || false;
    this.options.disableFunds  = options.disableFunds || false;
    this.url                   = url;
    this.urls                  = [];
  },
  
  /**
   * Called on a successfull complete of the service call
   */
  onComplete: function(request) {
    this.processResponse(request.responseText.evalJSON());
  },
  
  /**
   * Overwriting to remove scrolling into view as this breaks
   * functionality. Why was this added?
   * @private
   */
  markPrevious: function() {
    if(this.index > 0) this.index--
      else this.index = this.entryCount-1;
  },

  /**
   * Overwriting to remove scrolling into view as this breaks
   * functionality. Why was this added?
   * @private
   */
  markNext: function() {
    if(this.index < this.entryCount-1) this.index++
      else this.index = 0;
  },
  
  /**
   * Fires when a user selects an entry, this will redirect the
   * user to the page supplied within the URLs array
   */
  selectEntry: function() {
    this.active = false;
    var entry = this.getCurrentEntry();
    if(entry) {
      this.updateElement(entry);
      this.element.parentNode.onsubmit = function() { return false; } // firefox 2 mac bug
      if( !this.options.preventSubmit ) { window.location = this.urls[entry.autocompleteIndex]; }
    } else {
      this.element.parentNode.submit();
    }
  },
  
  /**
   * Updates the search input with the selected value
   *
   * @param {Element} selectedElement DOM element selected by user
   */
  updateElement: function(selectedElement) {  
    this.element.setValue(selectedElement.readAttribute('title'));
    this.element.focus();
  },
  
  /**
   * Processes the JSON response into a properly formatted list
   *
   * @param {Object} json The json object initially returned
   * by the ajax call
   * @requires Business.Config Defines HTML template
   */
  processResponse: function(json) {
    var html = [];
    var keyword = json[0];
    var results = json[1];
    
    this.urls = [];
    this.entryCount = 0;
    html.push('<ul>')
    
    /**
     * Loop through the results array and push to UL
     */
    for(var i = 0; i < results.length; i++ )
    {
      var result = eval('(' + results[i] + ')').results;
      var re = new RegExp('(' + keyword + ')', 'i');
      
      // validate and skip on false
      if ( !this.validateResult(result) ) continue;

      this.urls.push((this.isFund(result)) ? Business.FundURL(result.ticker) : result.url);
      html.push(this.options.template.evaluate({
        ticker:       result.ticker.replace(re, '<strong>$1</strong>'),
        company:      result.company.replace(re, '<strong>$1</strong>'),
        output_safe:  ( this.options.safe ) ?
          eval( this.options.safe ) : result.company
      }));
      this.entryCount++;
    }
    
    html.push('</ul>');
    this.updateChoices(html.join(''));
    if( !this.options.selectFirst ) this.makeInactive();
  },
  
  /**
   * Not so pretty way of not selecting the first result by
   * default; this removes the selected class and sets the
   * index to a negative value negating the selectEntry func
   */
  makeInactive: function() {
    this.update.getElementsByTagName('li')[0].removeClassName('selected');
    this.index = -1;
  },
  
  /**
   * Returns true or false dependent on weather the result
   * is a fund, i.e. contains company and ticker but no url
   *
   * @returns True or false
   */
  isFund: function(result) {
    return ( result.company && result.ticker && !result.url );
  },
  
  /**
   * Returns true or false based on whether the object passes
   * validation or not.
   *
   * @param {Object} result Result object passed to function
   * @returns True or false
   * @type Boolean
   */
  validateResult: function(result) {
    // if entirely empty return false
    if ( !result.company || !result.ticker || ( !this.isFund(result) && !result.url) )
      return false;

    // if the result is a fund, check that we want to display it
    if ( this.isFund(result) && this.options.disableFunds )
      return false;
    
    // defaults to true
    return true;
  }
});

/**
 * Creates a placeholder, or default input text, for the
 * supplied element.
 *
 * @param {Element} el Search input
 * @param {String} text Placeholder Text
 * @param {String} class Placeholder class name
 */
var createPlaceholder = function(el, text, css_class) {
  var searchInput     = $(el);

  // if value is present, quietly exit
  if ( searchInput.present() ) return;
  
  // set default text and class
  if ( css_class ) $(el).addClassName( css_class );
  $(el).setValue( text );

  /**
   * Observe focus, if input value equals placeholder value
   * then clear the input and remove the class
   */
  searchInput.observe('focus', function(){
    if ( this.value == text ) {
      this.clear();
      if ( css_class ) this.removeClassName( css_class );
    }
  });

  /**
   * Observe blur, if input value is empty, replace with default
   * placeholder value and add class back
   */
  searchInput.observe('blur', function(){
    if ( !this.present() ) {
      if ( css_class ) this.addClassName( css_class );
      this.setValue( text );
    }
  });
}

/**
 * Fires on document ready, initializes autosuggest object
 */
document.observe("dom:loaded", function(){
  var page = Business.SuggestServer();
  var template = Business.Template;
  
  // get the inputs, if there is only one, push it into an array
  var inputs = ( typeof Business.Inputs != 'object' ) ?
    [Business.Inputs] : Business.Inputs;
  
  // only create object if on correct host and has defined inputs
  if ( page != false && inputs )
  {    
    // loop through supplied inputs and create autosuggest object
    inputs.each(function(i) {
      
      // using placeholder? if so, get the input from object
      var input = ( typeof i == 'object' ) ? $(i.input) : $(i);
      try { var update = input.next(); } catch(e) { var update = null }
            
      // check for existence & create
      if( input && update ) {
        // deal with options - remove default (not used by autosuggest)
        var options = ( typeof i == 'object' ) ? Object.clone(i) : {};
        options.input = options.placeholder = options.placeholder_class = null;
        options.template = template;
        
        // finally create the object
        new Business.Autosuggest(input, update, page, options);
      }
      
      // create placeholder if defined
      if ( input && typeof i == 'object' && i.placeholder )
        createPlaceholder(input, i.placeholder, i.placeholder_class)
    });
  }
});
