/**
 * Extending jQuery with autocomplete
 * Version: 1.0
 */
(function($) {

 var RETURN = 13;
 var TAB = 9;
 var ESC = 27;
 var ARRUP = 38;
 var ARRDN = 40;

function getCaretPosition(obj){
  var start = -1;
  var end = -1;
  if(typeof obj.selectionStart != "undefined"){
    start = obj.selectionStart;
    end = obj.selectionEnd;
  }
  else if(document.selection&&document.selection.createRange){
    var M=document.selection.createRange();
    var Lp;
    try{
      Lp = M.duplicate();
      Lp.moveToElementText(obj);
    }catch(e){
      Lp=obj.createTextRange();
    }
    Lp.setEndPoint("EndToStart",M);
    start=Lp.text.length;
    if(start>obj.value.length)
      start = -1;
    
    Lp.setEndPoint("EndToStart",M);
    end=Lp.text.length;
    if(end>obj.value.length)
      end = -1;
  }
  return {'start':start,'end':end};
}
function setCaret(obj,l){
  obj.focus();
  if (obj.setSelectionRange){
    obj.setSelectionRange(l,l);
  }
  else if(obj.createTextRange){
    m = obj.createTextRange();      
    m.moveStart('character',l);
    m.collapse();
    m.select();
  }
}

$.fn.autocomplete =
  function(options){
    if(!options && (!$.isFunction(options.get) || !options.ajax_get)){
       return $(this);
    }
    // options
    options = $.extend({ 
                         delay     : 500 ,
                         timeout   : 5000 ,
                         minchars  : 3 ,
                         multi     : false ,
                         cache     : true , 
                         height    : 150 ,
                         noresults : 'No results'
                         },
                       options);
    // take me
    var me = $(this);
    var me_this = $(this).get(0);
    // bind key events
    me.keypress(function(ev){
      var key = ev.which;
      switch(key){
        case RETURN:
          if(!current_highlight) getSuggestions(getUserInput());
          else setHighlightedValue();
          return false;
        case ESC:
          clearSuggestions();
          return false;
      }
      return true;
    });
    
    me.keyup(function(ev){
      var key = ev.which;
      // set responses to keydown events in the field
      // this allows the user to use the arrow keys to scroll through the results
      switch(key){
        case ESC: case RETURN: return false;
        case ARRUP:
          changeHighlight(key);
          return false;
        case ARRDN:
          changeHighlight(key);
          return false;
        default:
          getSuggestions(getUserInput());
      }
      return true;
    });

    // no autocomplete!
    me.attr("autocomplete","off");

    // init variables
    var user_input = "";
    var input_chars_size  = 0;
    var suggestions = [];
    var current_highlight = 0;
    var suggestions_menu = null;
    var suggestions_list = null;
    var clearSuggestionsTimer = false;
    var ajaxTimer = false;

    // get user input
    function getUserInput(){
      var val = me.val();
      if(options.multi){
        var pos = getCaretPosition(me_this);
        var start = pos.start;
        for(;start>0 && val.charAt(start-1) != ',';start--){}
        var end = pos.start;
        for(;end<val.length && val.charAt(end) != ',';end++){}
        var val = val.substr(start,pos.start-start);
        $('#info').text("start "+start+" end "+end+" pos "+pos.start+" "+val);
      }
      return val;
    }
    // set suggestion
    function setSuggestion(val){
      user_input = val;
      if(options.multi){
        var orig = me.val();
        var pos = getCaretPosition(me_this);
        var start = pos.start;
        for(;start>0 && orig.charAt(start-1) != ',';start--){}
        var end = pos.start;
        for(;end<orig.length && orig.charAt(end) != ',';end++){}
        var new_val = orig.substr(0,start) + val + orig.substr(end);
        me.val(new_val);
        $('#info').text("set "+(start+val.length));
        setCaret(me_this,start+val.length);
      }
      else{
        me_this.focus();
        me.val(val);
      }
    }
    // get suggestions
    function getSuggestions(val){
        if (val == user_input) return false;
        // input length is less than the min required to trigger a request
        // reset input string
        // do nothing
        if (val.length < options.minchars){
            clearSuggestions();
            user_input = false;
            return false;
        }
        // if caching enabled, and user is typing (ie. length of input is increasing)
        // filter results out of suggestions from last request
        if (val.length > input_chars_size && suggestions.length && options.cache){
            var arr = [];
            for (var i=0;i<suggestions.length;i++){
                if(suggestions[i].value.substr(0,val.length).toLowerCase() == val.toLowerCase())
                    arr.push( suggestions[i] );
            }

            user_input = val;
            input_chars_size = val.length;
            suggestions = arr;
            createList(suggestions);
            return false;
        }
        else{
            // do new request
            user_input = val;
            input_chars_size = val.length;
            clearTimeout(ajaxTimer);
            ajaxTimer = setTimeout( 
                function(){ 
                  //try{
                  suggestions = [];
                  // call pre callback, if exists
                  if($.isFunction(options.pre_callback))
                      options.pre_callback();
                  // call get
                  if($.isFunction(options.get)){
                    var jsondata = options.get(val);
                    for(var i=0;i<jsondata.length;i++){
                       suggestions.push(jsondata[i]);
                    }
                    createList(suggestions);
                  }
                  if($.isFunction(options.ajax_get)){
                     options.ajax_get(val,function(jsondata){
                       for(var i=0;i<jsondata.length;i++){
                         suggestions.push(jsondata[i]);
                       }
                       createList(suggestions);
                     });
                  }
                  
                //}catch(e){
                //   alert('Error when AJAX call: '+e);
                //}
              },
              options.delay );
        }
        return false;
    };
    // create suggestions list
    function createList(arr){
        // get rid of old list
        // and clear the list removal timeout
        if(suggestions_menu) $(suggestions_menu).remove();
        killTimeout();
	
	// create holding div
	suggestions_menu = $('<div class="jqac-menu"></div>').get(0);
	
	// ovveride some necessary CSS properties 
	$(suggestions_menu).css('position','absolute');
	$(suggestions_menu).css('max-height',options.height+'px');
	$(suggestions_menu).css('overflow-y','auto');
	
	// create and populate ul
	suggestions_list = $('<ul></ul>').get(0);
	
	// loop throught arr of suggestions
	// creating an LI element for each suggestion
	for (var i=0;i<arr.length;i++){
            // format output with the input enclosed in a EM element
            // (as HTML, not DOM)
            var val = new String(arr[i].value);
            // using RE
            var re = new RegExp("("+user_input+")",'ig');
            var output = val.replace(re,'<em>$1</em>');
            // using substr
            //var st = val.toLowerCase().indexOf( user_input.toLowerCase() );
            //var len = user_input.length;
            //var output = val.substring(0,st)+"<em>"+val.substring(st,st+len)+"</em>"+val.substring(st+len);

            var span = $('<span class="jqac-link">'+output+'</span>').get(0);
            if (arr[i].info != ""){
                $(span).append($('<div class="jqac-info">'+arr[i].info+'</div>'));
            }

            $(span).attr('name',i+1);
            $(span).click(function () { setHighlightedValue(); });
            $(span).mouseover(function () { setHighlight($(this).attr('name')); });

            var li = $('<li></li>').get(0);
            $(li).append(span);

            $(suggestions_list).append(li);
        }


        // no results
        //
        if (arr.length == 0){
            $(suggestions_list).append('<li class="jqac-warning">'+options.noresults+'</li>');
        }

        $(suggestions_menu).append(suggestions_list);

	// get position of target textfield
	// position holding div below it
	// set width of holding div to width of field
	var pos = me.offset();
	
	$(suggestions_menu).css('left', pos.left + "px");
    $(suggestions_menu).css('top', ( pos.top + me.height() + 2 ) + "px");
    $(suggestions_menu).width(me.width());
	
	// set mouseover functions for div
	// when mouse pointer leaves div, set a timeout to remove the list after an interval
	// when mouse enters div, kill the timeout so the list won't be removed
	$(suggestions_menu).mouseover(function(){ killTimeout() });
	$(suggestions_menu).mouseout(function(){ resetTimeout() });
	$(suggestions_menu).ready(function(){ 
      
    });

	// add DIV to document
	$('body').append(suggestions_menu);
	
	// adjust height
	if($(suggestions_menu).height() > options.height){
       $(suggestions_menu).height(options.height);
    }
	
	// currently no item is highlighted
	current_highlight = 0;
	
	// remove list after an interval
	clearSuggestionsTimer = setTimeout(function () { clearSuggestions() }, options.timeout);
    };
    // set highlighted value
    function setHighlightedValue(){
        if(current_highlight && suggestions[current_highlight-1]){
            setSuggestion(suggestions[ current_highlight-1 ].value);
            // pass selected object to callback function, if exists
            if ($.isFunction(options.callback))
              options.callback( suggestions[current_highlight-1] );

            clearSuggestions();
        }
    };
    // change highlight according to key
    function changeHighlight(key){	
        if(!suggestions_list) return false;
        var n;
        if (key == ARRDN)
            n = current_highlight + 1;
        else if (key == ARRUP)
            n = current_highlight - 1;

        if (n > $(suggestions_list).children().size())
            n = 1;
        if (n < 1)
            n = $(suggestions_list).children().size();
        setHighlight(n);
    };
    // change highlight
    function setHighlight(n){
        if (!suggestions_list) return false;
        if (current_highlight > 0) clearHighlight();
        current_highlight = Number(n);
        var li = $(suggestions_list).children().get(current_highlight-1);
        li.className = 'jqac-highlight';
        adjustScroll(li);
        killTimeout();
    };
    // clear highlight
    function clearHighlight(){
        if (!suggestions_list)return false;
        if (current_highlight > 0){
            $(suggestions_list).children().get(current_highlight-1).className = '';
            current_highlight = 0;
        }
    };
    // clear suggestions list
    function clearSuggestions(){
        killTimeout();
        if (suggestions_menu){
          $(suggestions_menu).remove();
          suggestions_menu = null;
          current_highlight = 0;
          user_input = false;
        }
    };
    // set scroll
    function adjustScroll(el){
      if(!suggestions_menu) return false;
      var viewportHeight = suggestions_menu.clientHeight;        
      var wholeHeight = suggestions_menu.scrollHeight;
      var scrolled = suggestions_menu.scrollTop;
      var elTop = el.offsetTop;
      var elBottom = elTop + el.offsetHeight
      if(elBottom > scrolled + viewportHeight){
        suggestions_menu.scrollTop = elBottom - viewportHeight;
      }
      else if(elTop < scrolled){
        suggestions_menu.scrollTop = elTop;
      }
      return true; 
    }
    // timeout funcs
    function killTimeout(){
        clearTimeout(clearSuggestionsTimer);
    };
    function resetTimeout(){
        clearTimeout(clearSuggestionsTimer);
        clearSuggestionsTimer = setTimeout(function () { clearSuggestions() }, 1000);
    };
  
    return $(this);

  };

});
