Alces.autoSuggest = function(opts) {
    this.options = opts || {};

    this.dom = {
        box: null,
        suggestions: null
    };

    this.data = {
        suggestions: null,
        suggestionsIsCategory: [],
        active: 0,
        term: null,
        disableKeybnav: false,
        hasFocus: false
    };

    this.bindInputEvents = function() {
        var self = this,
            keydelay = null,
            cancel_keyup = false;

        this.options.input.bind({
            keydown: function(e){
                cancel_keyup = false;
                
                if (!self.data.suggestions) {
                    return true;
                }
                
                var arrow_dir = 1;

                switch (e.which) {
                    // key up
                    case 38:
                        // note: flow to next case intentional.
                        arrow_dir = -1;
                    // key down
                    case 40:
                        if (!self.data.disableKeybnav) {
                            cancel_keyup = true;
                            self.setActive(arrow_dir, true);
                        }
                        return false;

                    // enter
                    case 13:
                        // enter key will not be passed to the browser to prevent
                        // the form being accidentally submitted
                        // note: flow to next case intentional.
                        cancel_keyup = true;
                        
                    // tab (and enter ^^)
                    case 9:
                        if (self.data.active != 0) {
                            // focus will be changed later on to the next field,
                            // prevent the event from being handled by the browser
                            self.chooseActive();
                        } else {
                            // notice that we're not setting the cancel here; focus
                            // will be changed by browser if tab caused the event
                            self.clear();
                        }
                        
                        return !cancel_keyup;
                }
                
                return true;
            },
            keyup: function(e){
                if (cancel_keyup) {
                    return false;
                }
                
                if (keydelay != null) {
                    clearTimeout(keydelay);
                    keydelay = null;
                }
               
                keydelay = setTimeout(function(){
                    self.query(self.options.input.val());
                }, 350);
            },
            
            focus: function(){
                self.data.hasFocus = true;
            },
            
            blur: function(){                
                self.data.hasFocus = false;
                
                if (keydelay != null) {
                    clearTimeout(keydelay);
                    keydelay = null;
                }

                if (self.data.suggestions) {
                    self.clear();
                }
            }
        }).attr('autocomplete', 'off');
    };
    
    //set active row
    this.setActive = function(pos, relative){
        var num = this.data.suggestions.length;

        if (relative) {
            var newpos = this.data.active;
            
            do {
                newpos += pos;
                
                if (newpos > num) {
                    newpos = 0;
                } else if (newpos < 0) {
                    newpos = num;
                }
            } while (newpos > 0 && this.data.suggestions[newpos - 1].suggestion == undefined);

            pos = newpos;
        }
        
        this.data.active = pos;
        this.dom.suggestions.removeClass('active');
        
        if (pos > 0) {
            this.dom.suggestions.eq(pos - 1).addClass('active');
        }
    };
    
    this.chooseActive = function(){
        var active = this.data.suggestions[this.data.active - 1];
        var isCategory = this.data.suggestionsIsCategory[this.data.active - 1];
        
        if (active === undefined) {
            return;
        }
        this.options.input.val(active.suggestion);
        
        // make a mark in dom, that this is a category search, not a keyword search.
        if (isCategory && !!this.options.isCategoryInput) {
            this.options.isCategoryInput.val(isCategory).removeAttr('disabled');
        }
        
        // This is for placeholder stuff on IE
        // - needs to be set, otherwise mouse selection doesn't work properly
        this.options.input.data("realval", active.suggestion);
        
        this.clear();
        
        if (typeof this.options.onSelect == 'function') {
            this.options.onSelect();
        }
    };
    
    this.query = function(term){
        if (this.data.term == term) {
            return;
        }
        
        this.data.term = term;
        
        if (term == '') {
            this.clear();
            return;
        }
        
        var self = this;
        
        this.sendQuery(term, function(res){
            if (!self.data.hasFocus) {
                return;
            }
            
            if (res.result != undefined && res.result.length > 0) {
                self.show(res.result);
                return;
            }
            
            self.clear();
        });
    };
    
    this.sendQuery = function(query, callback){
        jQuery.ajax(
            Alces.settings.emapi_autosuggest_uri,
            {
                data: {
                    q: query,
                    pid: 'fonecta',
                    type: this.options.type,
                    locale: 'fi'
                },
                dataType: 'jsonp'
            }
        ).success(function(res){
            callback(res);
        });
    };
    
    this.show = function(sug){
        var self = this;
        var str = '';
        var isCategory = false;
        
        this.data.suggestionsIsCategory = [];
        this.data.suggestions = sug;
        this.data.active = 0;
        
        str += '<ol>';
        
        // categoryMeta tells if the word is a category or a keyword search.
        for (var i = 0, len = sug.length; i < len; i++) {
            if (sug[i].heading != undefined) {
                str += '<li class="heading">' + sug[i].heading + '</li>';
                
                isCategory = (sug[i].heading === 'Hakusanat') ? true : false;
                this.data.suggestionsIsCategory.push(false);
            } else {
                str += '<li class="suggestion">' + sug[i].suggestion + '</li>';
                
                this.data.suggestionsIsCategory.push(isCategory);
            }
        }
        
        str += '</ol>';
        
        this.dom.box.html(str);
        this.dom.suggestions = this.dom.box.find('li');

        this.dom.suggestions.each(function(idx){
            if (self.data.suggestions[idx].suggestion == undefined) {
                return;
            }
            
            jQuery(this).bind({
                mouseover: function(){
                    self.data.disableKeybnav = true;
                    self.setActive(idx + 1);
                },
                
                mouseout: function(){
                    self.data.disableKeybnav = false;
                },
                
                mousedown: function(){
                    self.chooseActive();
                }
            });
        });

        this.dom.box.show();
        
        // Autoposition is not a completely finished functionality, currently it's just good enough for route-search dynamic fields. 
        if ((typeof self.options.autoposition !== "undefined") && (self.options.autoposition == true)) {
            var position = $(self.options.input).offset();
            $(this.dom.box).css({'top':position.top + $(self.options.input).height() + 10, 'width':$(self.options.input).outerWidth() - 7});
        }
    };
    
    this.clear = function(){
        this.data.suggestions = null;
        this.data.active = 0;
        
        if (this.dom.box) {
            this.dom.box.hide();
        }
    };
    
    // constructor
    (function(self){
        self.dom.box = jQuery('<div class="auto-suggest" id="' + self.options.id + '"></div>')
        .hide()
        .appendTo(self.options.container);
        
        self.bindInputEvents();
    })(this);
};

Alces.autoSuggestComposite = function(opts){
    Alces.autoSuggest.call(this, opts);
    
    this.sendQuery = function(query, callback){
        var listen = true;
        var pending = 0;
        var response = {};
        var self = this;

        setTimeout(function(){
            listen = false;
            
            if (pending > 0) {
                callback(self.parseResponse(response));
            }
        }, 2000);
        
        var req = {
            data: {
                q: query,
                pid: 'fonecta',
                locale: 'fi'
            },
            dataType: 'jsonp'
        };
        
        jQuery.each(this.options.types, function(k, v){
            req.data.type = v.type;
            pending++;
            
            jQuery.ajax(
                Alces.settings.emapi_autosuggest_uri,
                req
            ).success(function(res){
                if (!listen) {
                    return false;
                }
                
                pending--;
                
                if (res.result !== undefined && res.result.length > 0) {
                    response[v.type] = res.result;
                }
                if (pending === 0) {
                    callback(self.parseResponse(response, v));
                }
            });
        });
    };
    
    this.parseResponse = function(res, type){
        var result = [];
        var numberOfSuggestions = 5;
        
        jQuery.each(this.options.types, function(k, v){
            if (res[v.type] !== undefined) {
                result.push({heading: v.title});

                for (var i = 0, len = res[v.type].length; i < len && i < numberOfSuggestions; i++) {
                    result.push(res[v.type][i]);
                }
            }
        });
        
        return {result: result};
    };
};
