jQuery.fn.extend({
    autoComplete: function(args) {
        return this.each(function() {
            new jQuery.autoComplete(this, args);
        });
    }
});

jQuery.autoComplete = function(text, args) {
    var $text     = $(text);
    var $offset   = $text.offset();
    var pos       = { top : $offset.top + $text.outerHeight() + 1, left : $offset.left, width : $text.width(), height : $text.height() };
    var matches   = new Array();
    var current_selected = null;

    if (!args || !args.url) {
        alert('The source is required!');
        return false;
    }
    args.loaded = false;
    if (!args.adjustLeft) args.adjustLeft = 0; 
    if (!args.adjustTop) args.adjustTop = 0;
    if (!args.adjustWidth) args.adjustWidth = 0;

// Create the box
    if ($('body .auto-popup').length == 0) $('body').append('<div class="auto-popup"></div>');

    var num_suggest   = args.suggests || 3;
    var previous_word = '';
    var paging        = { max_hits: 0, current_nh: 0, max_hits : 10 };

// set autocomplete to off
    $text.attr({ autocomplete : 'off' });

    var popDown = function() {
        args.loaded = false;
        $('.auto-popup').slideUp('normal');
        $('.loading').remove(); 
        $text.stopTime('loading');
        $text.stopTime('keyup-timer');
    };

    var popUp = function(e) {
        var c = e.keyCode;
        if (c == 0) c = e.charCode;
        switch (c) {
            case 38: // Up
                e.returnValue = false;
                //if (e.preventDefault) e.preventDefault();
                break;
            case 40: // Down
                e.returnValue = false;
                //if (e.preventDefault) e.preventDefault();
                break;
            case 13: // Enter
                //selectRange();
                popDown();

                e.returnValue = false;
                //if (e.preventDefault) e.preventDefault();
                break;
            case 27: //ESC
                popDown();
                e.returnValue = false;
                //if (e.preventDefault) e.preventDefault();
                break;
            case 9: //TAB
                //selectRange();
                popDown();
                e.returnValue = false;
                //if (e.preventDefault) e.preventDefault();
                break;
            case 8: //BACKSPACE
                if ($text.val().length > 0) {
                    loadSource($text.val());
                }
                else {
                    popDown();
                }
                break;
            default: 
                if ($text.val().length >= num_suggest) {
                    loadSource($text.val());
                }
                else {
                    popDown();
                }
                break;
        }
    };

    var sourceLoading = function() {
        $('body').append('<div class="loading" style="z-index: 10000"></div>');
        $('.loading').css({ top : $offset.top + 3 + 'px', left: pos.left + pos.width - 50 + 'px', height : pos.height }).fadeIn('slow');
    };
    
    var hidePopup = function(e) {
        var popup = $('.auto-popup');
        if (popup.length == 0 || !args.loaded) return;

        var offset  = popup.offset();
        var nheight = popup.outerHeight();
        var nwidth  = popup.outerWidth();

        if (e.pageX <= offset.left + 10 || e.pageX >= nwidth + offset.left + 10 ||
            e.pageY <= offset.top + 5 || e.pageY >= nheight + offset.top + 5) popDown();
    }

    $(document).bind('mousedown', function(e) { hidePopup(e) });

    var orgValue = '';
    $text.bind('keydown', function(e) {
        var code = e.keyCode;
        if (code == 0) code = e.charCode;

        if (code == 9) {
          popDown();
          return;
        }
        else if (code != 38 && code != 40) hidePopup();

        var popup   = $('.auto-popup');
        var curItem = popup.find('ul li.selected');
        var gotoItem;

        if (code == 38) { // up arrow
            if (curItem.length == 0) {
                gotoItem = popup.find('ul li:last');
                if (gotoItem.hasClass('popup-paging')) gotoItem = gotoItem.prev();
            }
            else if (curItem.length > 0) {
                gotoItem = curItem.removeClass('selected').prev();
            }
        }
        else if (code == 40) { // down arrow
            if (curItem.length == 0) {
                gotoItem = popup.find('ul li:first');
            }
            else if (curItem.length > 0) {
                gotoItem = curItem.removeClass('selected').next();
            }
        }

        if (typeof(gotoItem) == 'undefined') return;

        if (gotoItem.length == 0 || gotoItem.hasClass('popup-paging')) {
            $text.val(orgValue);
        }
        else {
            $text.val(gotoItem.addClass('selected').find('a[rel]').attr('rel'));
        }
    });

    $text.bind('keyup', function(e) {
        var code = e.keyCode;
        if (code == 0) code = e.charCode;
        if (code == 38 || code == 40) {
            $(this).stopTime('keyup-timer');
            e.returnValue = false;
            return;
        }

        /* Update offset, in case it was invisible */
        $offset = $text.offset();
        pos     = { top : $offset.top + $text.outerHeight() + 1, left : $offset.left, width : $text.width(), height : $text.height() };

        var k = $text.val();

        /* Filter the previous results. If nothing found, then will send a request to the server */
        if (args.load_catch && previous_word.length < k.length && matches.length > 0) {
            var subset = new Array();
            for (var i=0; i<matches.length; i++) {
                if (matches[i].indexOf(k) >= 0) {
                    subset.push(matches[i]);
                }
            }
            matches = subset;

            if (matches.length > 0) {
                createPopup(k);
                return;
            }
        }
        else {
            matches = new Array();
        }

        /* Now sending a request to server if nothing was found in the previous results */
        $text.stopTime('keyup-timer').oneTime(250, 'keyup-timer', function() { 
            $(this).stopTime('keyup-timer');
            popUp(e);
        });
    });

    function loadSource(k, nh) {
        if (k.length < num_suggest) {
            popDown();
            return;
        }

        if (typeof(nh) == 'undefined') nh = 1;

        $text.oneTime(200, 'loading', function() { sourceLoading(); });

        var url = args.url + k;
        if (args.opts) {
          var opts = args.opts;
          for (var k in opts) {
            url += ';' + k + '=' + opts[k].attr('selectedIndex');
          }
        }
        url += ';nh=' + nh;

        $.ajax({
            type : 'GET',
            url : url,
            success : function(req) {
                var words  = req.getElementsByTagName('word');
                var p      = req.getElementsByTagName('paging');
                if (p.length > 0) {
                    paging.num_hits   = parseInt(p[0].getElementsByTagName('num_hits')[0].firstChild.nodeValue);
                    paging.max_hits   = parseInt(p[0].getElementsByTagName('max_hits')[0].firstChild.nodeValue);
                    paging.current_nh = parseInt(p[0].getElementsByTagName('current_nh')[0].firstChild.nodeValue);
                }

                matches = new Array();
                if (words.length > 0) {
                    for (var i=0; i<words.length; i++) {
                        var val = words[i].firstChild.nodeValue;
                        matches.push(val);
                    }
                    createPopup(k);
                }
                else {
                    matches = new Array();
                    popDown();
                }
                previous_word = k;
            }
        });
    }

    function createPopup(k) {
        var html  = '<ul>';
        k = k.replace(/\s+$/, '');
        for (var i=0; i<matches.length; i++) {
            if (i == paging.max_hits) break;

            var val = matches[i];
            var name = val.replace(new RegExp('(' + k + ')', 'ig'), "<strong>$1<\/strong>");
            html += '<li><a href="#" rel="' + val + '">' + name + '<\/a><\/li>\n';
        }
        html += '</ul><div class="clear popup-paging" style="width: ' + pos.width + 'px;"><a href="#" rel="close" class="close" style="padding-right: 0">close<\/a>';
        
        // Handle paging
        if (paging.num_hits > 0) {
            var num_pages = parseInt(paging.num_hits / paging.max_hits);
            if (paging.num_hits % paging.max_hits > 0) num_pages++;
            if (num_pages > paging.current_nh ) {
                html += '<a href="#" rel="paging">more...<\/a>';
            }
        }
        html += '<\/div>';

        orgValue = $text.val();
        var $popup = $('.auto-popup');
        $popup.empty().html(html);

        $popup.find('ul a[rel]').each(function() {
            $(this).mouseover(function() {
//                $popup.find('.selected').removeClass('selected');
                $(this).parent().addClass('selected');
            }).mouseout(function() {
                $(this).parent().removeClass('selected')
            });
            $(this).click(function() {
                var val = htmlUnEscape($(this).attr('rel'));
                val = val.replace(' (Region)', '');
                previous_word = val;
                $text.val(val);
                popDown();
                   // selectRange();
                return false;
            });
        });

        $popup.find('.popup-paging a[rel="paging"]').click(function() {
            loadSource($text.val(), paging.current_nh + 1);
            return false;
        });

        $popup.find('.popup-paging a[rel="close"]').click(function() {
            popDown();
            return false;
        });

        $popup.css({ top : pos.top + args.adjustTop + 'px', left : pos.left + args.adjustLeft + 'px', width: $text.outerWidth() + args.adjustWidth  - 1 + 'px' }).slideDown('normal', function() {
            // Remove loading image and stop timer if visible
            $('.loading').remove(); 
            args.loaded = true;
            $text.stopTime('loading');
        });
        $text.focus();
    }

    function selectRange(from, to) {
        if (from == to) {
            $text.focus();
            return;
        }

        if (text.createTextRange) {
            var range = text.createTextRange();
            range.moveStart('character', from);
            range.select();
        }
        else {
            text.setSelectionRange(from, to);
        }
    }

    function htmlUnEscape(text) {
        text = text.replace(/&quot;/gi, '"');
        text = text.replace(/&gt;/gi, '>');
        text = text.replace(/&lt;/gi, '<');
        text = text.replace(/&amp;/gi, '&');
        return text;
    }
}
