/*
 * jQuery 
 * version: 1.0 (2008/11/13)
 * @requires jQuery v1.2.6
 * @todo: Test it with previous jQuery versions
 * @author: sebastien rannou - http://www.aimxhaisse.com
 *
 * licensed under the MIT: http://www.opensource.org/licenses/mit-license.php
 *
 * Revision: 1
 */

jQuery.fn.james = function (url_to_call, options) {
  var that = jQuery(this),
  results_set = [],
  current_hovered_rank = 0,
  element_selected = false,
  keyEvents = [
  {
    keycode: 38,
    action: function () {
      keyEventKeyUp();
    }
    },

    {
    keycode: 40,
    action: function () {
      keyEventKeyDown();
    }
    },

    {
    keycode: 13,
    action: function () {
      keyEventEnter();
    }
    },

    {
    keycode: 27,
    action: function () {
      keyEventEsc();
    }
    }
  ],
  ul_element = false,
  o = jQuery.extend({
    onKeystroke:    function (data) {
      return data;
    },
    onSelect:       function (dom_value, json_obj) {
      that.attr("value", results_set[current_hovered_rank].text);
    },
    keydelay:       300,
    minlength:      3,
    method:         "get",
    varname:        "input_content",
    params:         ""
  },  options || {});
    
  /*
     * This method performs DOM initialization
     * Creates a UL with an Unique ID and push it to DOM
     * It's called only once
     */
  (function initDOM() {
    var ul_id = false;
    var ul_node = document.createElement("ul");

    // Performs generation of an unique ID
    var genUniqueId = function () {
      var result = "ul_james_" + Math.round(Math.random() * 424242);

      if (jQuery("#" + result).length > 0)
      {
        result = genUniqueId();
      }
      return result;
    };

    ul_id = genUniqueId();

    jQuery(ul_node).attr("id", ul_id).addClass("ul_james");
    that.after(ul_node);
    // Creating a shortcut
    ul_element = jQuery("#" + ul_id);
    ul_element.hide();
  })();
    
  /*
	 * This method performs CSS initialization
     * It sets position's <ul> (especially for IE6)
     * And sets result's width to input's width
     * Because offset can be changed, it's called each time
     * the dom is modified
     */
  var initCSS = function initCSS() {
    //var input_offset = that.offset();
    var input_offset = that.position();

    ul_element.css({
      top:        input_offset.top + that.outerHeight(),
      width:      that.outerWidth(),
      left:       input_offset.left,
      position:   "absolute"
    });
  }
    
  /*
     * This is used to avoid form to be submit
     * when the user press Enter to make his choice
     * @TODO: When user has already made his choice, submit it
     */
  that.keydown(function (event) {
    if (event.keyCode === 13)
    {
      return false;
    }
  });
    
  /*
     * This method performs Keyboard Events
     * @TODO: Build actions for more key events (CTRL? ALT?)
     * or recognize ASCII codes?
     */
  //Timer's ID of next AJAX call
  var keyevent_current_timer = false;
    
  that.keyup(function(event) {
    var is_specific_action = false;
    // Check if a specific action is linked to the keycode
    for (var i = 0; keyEvents[i]; i++)
    {
      if (event.keyCode === keyEvents[i].keycode)
      {
        is_specific_action = true;
        keyEvents[i].action();
        break;
      }
    }
    // If it's not a specific action
    if (is_specific_action === false)
    {
      // Unset last timeout if it was defined
      if (keyevent_current_timer !== false)
      {
        window.clearTimeout(keyevent_current_timer);
        keyevent_current_timer = false;
      }
      // Set a now timeout with an AJAX call inside
      keyevent_current_timer = window.setTimeout(function () {
        ajaxUpdate();
      }, o.keydelay);
    }
  });

  /*
     * This method performs AJAX calls
     */
  var ajaxUpdate = function () {
    var value_to_send = that.attr("value");
    // Check length of input's value
    if (value_to_send.length > 0 &&
      (o.minlength === false ||
        value_to_send.length >= o.minlength))
        {
      $.ajax({
        type:       o.method,
        // @TODO: Would be great if params could be an object
        data:       o.varname + "=" + value_to_send + "&" + o.params,
        url:        url_to_call,
        dataType:   "json",
        success:    function (data) {
          var arr = o.onKeystroke(data);
          results_set = [];
          current_hovered = 0;
          for (var i in arr)
          {
            if (arr[i] !== null)
            {
              if (typeof(arr[i].json) === "undefined")
              {
                results_set.push({
                  text: arr[i],
                  json: {}
                });
              }
              else
              {
                results_set
                .push({
                  text: arr[i].text,
                  json: arr[i].json
                  });
              }
            }
          }
          updateDom();
        }
      });
    }
    else
    {
      cleanResults();
    }
  }
    
  /*
     * This method performs the display of the results set
     * Basically called when an event has been made
     */
  var updateDom = function() {
    jQuery(ul_element).empty();
    var is_empty = true;

    initCSS();
    for (var i in results_set)
    {
      if (results_set[i] !== null)
      {
        var li_elem = document.createElement("li");

        jQuery(li_elem).addClass("li_james");
        if (i == (current_hovered_rank % results_set.length))
        {
          jQuery(li_elem).addClass("li_james_hovered");
        }
        jQuery(li_elem).append(results_set[i].text);
        jQuery(ul_element).append(li_elem);
        bind_elem_mouse_hover(li_elem, i);
        is_empty = false;
      }
    }
    if (is_empty)
    {
      jQuery(ul_element).hide();
    }
    else
    {
      jQuery(ul_element).show();
    }
  }
    
  /*
     * This method performs the ability to
     * select a result with mouse
     */
  var bind_elem_mouse_hover = function (elem, i) {
    jQuery(elem).hover(function() {
      jQuery(ul_element)
      .find(".li_james_hovered")
      .removeClass("li_james_hovered");
      jQuery(elem).addClass("li_james_hovered");
      current_hovered_rank = i;
      element_selected = true;
    }, function() {
      jQuery(elem).removeClass("li_james_hovered");
      current_hovered_rank = 0;
      element_selected = false;
    });
    jQuery(elem).click(function() {
      keyEventEnter();
    });
  }
    
  /*
     * This method clears results in DOM & JS
     */
  var cleanResults = function () {
    jQuery(ul_element).empty();
    jQuery(ul_element).hide();
    results_set = [];
    current_hovered_rank = 0;
  }
    
  /*
     * Key event actions
     */
    
  // Moving up into results set
  var keyEventKeyUp = function () {
    if (current_hovered_rank > 0)
    {
      current_hovered_rank--;
    }
    else if (results_set.length)
    {
      current_hovered_rank = results_set.length - 1;
    }
    updateDom();
  }
    
  // Moving down into resuls set
  var keyEventKeyDown = function () {
    if (current_hovered_rank < (results_set.length - 1))
    {
      current_hovered_rank++;
    }
    else
    {
      current_hovered_rank = 0;
    }
    updateDom();
  }
    
  // Selecting a set (onSelect function is called there)
  var keyEventEnter = function () {
    if (results_set.length > 0)
    {
      that.attr("value",
        o.onSelect(results_set[current_hovered_rank].text,
          results_set[current_hovered_rank].json));
    }
    cleanResults();
  }
    
  // Removing results set
  var keyEventEsc = function () {
    that.attr("value", "");
    cleanResults();
  }

  that.blur(function() {
    if (element_selected == null || !element_selected) {
      cleanResults();
    }
    return;
  });

}
