(function($) {

    /**
     * Abgeleitet von
     * autor: CTAPbIu_MABP
     * email: ctapbiumabp@gmail.com
     * site: http://mabp.kiev.ua/content/2008/04/08/autocomplete/
     * license: MIT & GPL
     * last update: 19.02.2009
     * version: 1.3
     *
     * Style Biespiel
       .ac_results {position:absolute; border: 1px solid; background-color:#fff; cursor:pointer; display:none; z-index:9999;}
       .ac_results div {width: 226px; padding:2px;}
       .ac_over, .ac_over.ac_even {background-color:#ddd;}
       .ac_match {font-weight:bold;}
       .ac_even {background-color:#f9f9f9;}
       .ac_label {cursor:default;background-color:#999; color:#fff; font-weight:bold;}
     */

    var ac = function(c, o) {
        this.cache = {}; // main chache {mask:[text]}
        this.store = {}; // secondary cache {mask:strind}
        this.init(c, o);
    };

    ac.prototype = {

        // html elements
        ac : null, // main input
        ul : null, // autocomplete list

        // timeouts
        close : null, // ac hide
        timeout : null, // ac search

        // system definitons
        chars : 0, // previous search string lenght

        // user definitons
        url : null, // url for ajax request
        minchar : null, // minchars
        delay : 250, // for search
        type : 'xml', // ajax data type
        width : 200, // width

        // events, please use 'self' instead of 'this'
        onSelect : function () {
        },
        onSetup : function () {
        },
        onKeyPress : function () {
        },
        onSuggest : function () {
        },
        onError : function () {
        },
        onSuccess : function () {
        },
        onDisplay : function () {
        },

        init : function (ac, options) {
            var self = $.extend(this, options);

            self.ac = $(ac)
                .attr({autocomplete:'off'})
                .bind('blur', function() {
                    clearTimeout(self.close);
                    self.close = setTimeout(function() {
                        self.ul.hide();
                    }, 300);
                }) // IE bug self.ul[.hide()] = undefined

            self.ul = $('<div/>')
                .addClass('ac_results')
                .appendTo('body')
                .bind("click", function() {
                    self.select();
                    self.ac.focus();
                });

            $(window).bind('resize load', function() {
                self.ul.css({
                    width:self.width,
                    top:(self.ac.offset().top + self.ac.height() + 1),
                    left:(self.ac.offset().left)
                });
            });

            if ($.browser.mozilla)
                self.ac.bind('keypress', self, self.process);
            else
                self.ac.bind('keydown', self, self.process);

            self.onSetup.apply(self,arguments);
        },

        process : function (e) {
            var self = e.data, len = self.ac.val().length;
            self.onKeyPress.apply(self,arguments);

            if (/27$|38$|40$|13$/.test(e.keyCode) && self.ul.is(':visible')) {
                e.preventDefault();
                e.stopPropagation();
                switch (e.keyCode) {
                    case 38: // up
                        self.prev();
                        break;
                    case 40: // down
                        self.next();
                        break;
                    case 13: // return
                        mask = self.ac.val();
                        if (self.get().text().length)
                          self.select();
                        else
                          self.ac.val(mask);
                        self.ul.hide();
                        self.ac[0].form.submit();
                        break;
                    case 27: // escape
                        self.ul.hide();
                        break;
                }
            } else if (len != self.chars || !len) {
                self.chars = len;
                if (self.timeout)
                    clearTimeout(self.timeout);
                self.timeout = setTimeout(function() {
                    self.suggest();
                }, self.delay);
            }
        },

        get : function() {
            var self = this;
            return self.ul.find('.ac_over');
        },

        prev : function () {
            var self = this, current = self.get(), prev = current.prev();
            if (prev.hasClass('ac_label')) prev = current.prev().prev()
            if (current.length) {
                current.removeClass('ac_over');
                if (prev.text())
                    prev.addClass('ac_over');
                }
            if (!current.length || !prev.text()){
                self.ul.children(':last').addClass('ac_over');
            }
            self.scroll();
        },

        next : function () {
            var self = this, current = self.get(), next = current.next();
            if (next.hasClass('ac_label')) next = current.next().next()
            if (current.length) {
                current.removeClass('ac_over');
                if (next.text())
                    next.addClass('ac_over');
            }
            if (!current.length || !next.text()) {
                if (self.ul.children(':first').hasClass('ac_label'))            
                    self.ul.children(':first').next().addClass('ac_over');
                else
                    self.ul.children(':first').addClass('ac_over');
            }
            self.scroll();
        },

        scroll : function(){
            var self = this, current = self.get();
            if (!current.length)
                return; // quick return
            var el = current.get(0), list = self.ul.get(0); // dont scroll after click on document :(
            if(el.offsetTop + el.offsetHeight > list.scrollTop + list.clientHeight)
                list.scrollTop = el.offsetTop + el.offsetHeight - list.clientHeight;
            else if(el.offsetTop < list.scrollTop)
                list.scrollTop = el.offsetTop;
        },

        select : function () {
            var self = this, current = self.get();
            if (current) {
                self.ac.val(current.text());
                self.ul.hide();
                self.onSelect.apply(self,arguments);
            }
        },

        suggest : function () {
            var self = this, mask = self.ac.val();
            self.ul.hide();
            if (mask.length >= self.minchar) {
                self.onSuggest.apply(self,arguments);
                if (self.cache[mask])
                    self.prepare(self.cache[mask],mask);
                else if (self.url) // use ajax
                    $.ajax({type: "GET", url:self.url, data:{value:mask},
                        success:function(xml) {
                            self.onSuccess.apply(self,arguments);
                            self.prepare(xml,mask);
                        },
                        error:function(){
                            self.onError.apply(self,arguments);
                        },
                        dataType:self.type
                    });
           }
        },

        prepare : function(xml, mask){
            var self = this, list = [], map = [], optiongroups = $('optiongroup', xml);
            if (!self.store[mask]){
                if(optiongroups.length) {
                   optiongroups.each(function(i, m) {
                     list.push(self.label($(m).attr('value')));
                     options = $('option', m);
                     if(options.length)
                        options.each(function(j, n) {
                          var t = $(n).text();
                          map.push(t);
                          list.push(self.mark(t,mask));
				                  self.cache[mask] = map;
				                  self.store[mask] = list.join('');
                      });
                  });
                } else {
                  self.ul.empty(); 
	                self.cache[mask] = map;
	                self.store[mask] = list.join('');
                }
            }
            return self.display(self.store[mask]);
        },

        mark : function(text, mask){
            if (new RegExp('^' + mask, 'ig').test(text))
                return '<div>' + text.replace(new RegExp('^' + mask, 'ig'), function(mask) {
                    return '<span class="ac_match">' + mask + '</span>';
                }) + '</div>';
        },
        
        label : function(title) {
            return '<div class="ac_label">' + title + '</div>';
        },

        display : function (list) {
            var self = this;
            self.onDisplay.apply(self,arguments);
            if (!list || list == "")
                return self.ul.hide();
            self.ul.empty().append(list);
            self.ul.find('div:not(.ac_label)').mouseover(function() {
                    $(this).siblings().removeClass('ac_over').end().addClass('ac_over');
                });
            self.ul.find('div:not(.ac_label)').filter(":even").addClass('ac_even');
            return self.ul.show();
        }
    };

    $.fn.solrautocomplete = function(options) {
        this.each(function() {
            new ac(this, options);
        });
        return this;
    };

})(jQuery);