/*
**  Searcher class will manage the livesearch system.
*/
WF.Searcher = Class.$extend({
    FADE_SPEED: 100,
    NO_RESULTS: 'No results matched your search.',
    RESULTS_CONTAINER: [
        '<div class="search-container">',
            '<div class="search-top"></div>',
            '<div class="search-center">',
                '<ul class="search-results clearfix"></ul>',
            '</div>',
            '<div class="search-bottom"></div>',
        '</div>'],
    
    __init__: function(slug, options) {
        // Bind to the textbox, and create some items.
        var self = this;
        
        self.slug = slug;
        
        self.defaults = {
            items: [],
            field: 'q',
            name_attr: 'n',
            desc_attr: 'd',
            link_attr: 'p',
            select_attr: 'id',
            searchitem: WF.SearchItem
        };
        
        self.options = jQuery.extend(self.defaults, options);
        
        self.hidden_field = $('#id_'+slug);
        
        self.textbox = $('<input type="text" id="id_faux_'+slug+'" />');
        self.textbox.attr('autocomplete', 'off');
        self.textbox.parent().css('position', 'relative');
        
        self.hidden_field.after(self.textbox);
        
        self.field_wrapper = self.hidden_field.parent();
        
        // Results & Results Container
        self.container = $(self.RESULTS_CONTAINER.join(""));
        self.results = self.container.find('ul.search-results');
        $('body').prepend(self.container);
        
        self.search_field = self.options.field;
        
        if ((self.options.items[0] === '/')) {
            self.is_ajax = true;
            self.ajax_url = self.options.items;
        } else {
            self.quick_items = self.generateQuickItems(self.options.items);
            self.items = self.generateSearchItems(self.options.items);
        }
        
        if (self.hidden_field.val() !== '') {
            self.setInitialValue(self.hidden_field.data('label'));
        }
        
        self.textbox.bind('keydown', function(event) {
            if (event.which == 38 || event.which == 40) {
                event.preventDefault();
            }
        });
        
        self.textbox.bind('keyup', function(event) {
            var keycode = event.which,
                input = $(this).val().toLowerCase();
            
            if (keycode in oc(40, 38, 13)) {
                self.navigateEvent(keycode);
            } else if (input.length && (input !== "")) {
                clearTimeout(self.search_timer);
                
                if (self.is_ajax) {
                    self.search_timer = setTimeout(function() {
                        self.searchEvent(input);
                    }, 300);
                } else {
                    self.searchEvent(input);
                }
            } else {
                self.closeMenu();
            }
        });
        
        self.textbox.bind('focus', function(event) {
            var input = $(this).val().toLowerCase();
            if (input && (input !== "")) {
                self.searchEvent(input);
            }
        }).bind('keydown', function(event) {
            if (event.which === 13) { return false; }
        }).bind('blur', function(event) {
            self.closeMenu();
        });
        
    },
    
    getNameAttr: function() {
        var self = this;
        return self.options.name_attr || '';
    },
    
    getDescAttr: function() {
        var self = this;
        return self.options.desc_attr || '';
    },
    
    getSelectAttr: function() {
        var self = this;
        return self.options.select_attr || '';
    },
    
    generateQuickItems: function(items){
        var self = this;
        
        return $.map(items, function(item, i) {
            return item[self.search_field].toString().toLowerCase();
        });
    },
    
    generateSearchItems: function(items) {
        var self = this,
            _items = {};
            
        for (var i=0; i < items.length; i++) {
            var item = items[i],
                key = item[self.search_field].toString().toLowerCase();

            _items[key] = new self.options.searchitem(self, item);
        };
        return _items;
    },
    
    searchEvent: function(value) {
        var self = this;
        
        if (self.is_ajax) {
            if (self.item_request){
                self.item_request.abort();
            }
            
            // Fetch the results from the server
            self.item_request = $.ajax({
                url: self.ajax_url,
                data: { 'q': value },
                dataType: 'json',
                success: function(payload) {
                    self.quick_items = self.generateQuickItems(payload.extra.items);
                    self.items = self.generateSearchItems(payload.extra.items);
                    
                    self.searchItems(value);
                }
            });
        } else {
            self.searchItems(value);
        }
        
        self.container.css({
            'top': self.textbox.offset().top + self.textbox.height()+25,
            'left': self.textbox.offset().left - ((self.container.width() - self.textbox.width())/2) + 5,
        });
    },
    
    searchItems: function(value) {
        var self = this,
            scores = [],
            items = [];
        
        
        $(self.quick_items).each(function(i) {
            var score = this.score(value);
            if (score > 0) { scores.push([score, i]); }
        });
        
        jQuery.each(scores.sort(function(a, b) { return b[0] - a[0]; }), function() {
            items.push(self.quick_items[this[1]]);
        });
        
        self.setCurrentItems(items);
    },
    
    navigateEvent: function(keycode) {
        var self = this;
        
        switch (keycode) {
            case 38: // Up Arrow
                self.moveUp();
                break;
            case 40: // Down Arrow
                self.moveDown();
                break;
            case 13: // Enter Key
                self.openSelectedItem();
                break;
            default:
                break;
        }
    },
    
    // Deselect current item, and then select item above it (unless it's the first one)
    moveUp: function() {
        var self = this;
        
        if (self.currently_selected === 0) { return; }
        
        var current = self.current_items[self.currently_selected],
            target = self.current_items[self.currently_selected-1];
        
        current.unselect();
        target.select();
        
        self.currently_selected--;
        
    },
    
    // Deselect current item, and then select item below it (unless it's the last one)
    moveDown: function() {
        var self = this;
        
        if (self.currently_selected == self.currently_selected.length-1) { return; }
        
        var current = self.current_items[self.currently_selected],
            target = self.current_items[self.currently_selected+1];
        
        current.unselect();
        target.select();
        
        self.currently_selected++;
        
    },
    
    // Call the click event of the currently selected item
    openSelectedItem: function() {
        var self = this;
        
        self.current_items[self.currently_selected].open();
        
        self.textbox.blur();
        
        // self.closeMenu();
    },
    
    closeMenu: function(immediately) {
        var self = this;
        
        self.container.hide();
    },
    
    setCurrentItems: function(items) {
        var self = this;
        
        self.current_items = $.map(items, function(item, index) {
            return self.items[item];
        });
        
        // Render the list with ze new itemz!
        self.renderList();
    },
    
    renderList: function() {
        var self = this;

        self.results.empty();
        
        self.rendered = $.map(self.current_items, function(item, index) {
            return item.render();
        });
        
        if (self.rendered.length === 0) {
            self.noResults();
        } else {
            $.each(self.rendered.splice(0, 10), function(index) {
                self.results.append(this);
            });
            
            self.currently_selected = 0;
            self.current_items[self.currently_selected].select();
            self.container.fadeIn(self.FADE_SPEED);
        }
    },
    
    noResults: function() {
        var self = this;
        
        self.results.append(self.showMessage(self.NO_RESULTS));
        self.container.fadeIn(self.FADE_SPEED);
    },
    
    showMessage: function(message) {
        var self = this;
        
        return $('<li/>').addClass('message').text(message);
    },
    
    // Called when an item is opened.
    onOpen: function(item) {
        var iv = setInterval(function() {
            if (item.row.hasClass('selected')) {
                item.unselect();
            } else {
                item.select();
            }
        }, 80);
    },
    
    setInitialValue: function(object_json) {
        var self = this;
        
        item = new self.options.searchitem(self, object_json);
        
        self.onOpen(item);
    },
    
    reSetValue: function(item) {
        var self = this;
        
        if (self.selected_item) {
            self.selectedHTML.remove();
            self.field_wrapper.show().children('input').focus();
        }
        self.onOpen(item);
        self.closeMenu();
    }
    
});

function isArray(o) {
    return Object.prototype.toString.call(o) === '[object Array]';
}

function oc(args) {
    var o = {}, a = (isArray(args)) ? args : arguments;
    
    for(var i=0;i<a.length;i++) { o[a[i]]=''; }
    
    return o;
}

// A searchitem
WF.SearchItem = Class.$extend({
    __init__: function(manager, obj) {
        var self = this;
        // Loop the passed params, and make them that of the SearchItem object.
        
        self.manager = manager;
        
        for (val in obj) {
            this[val] = obj[val];
        };
    },
    
    getNameAttr: function() {
        var self = this;
        return self.getAttr(self.manager.getNameAttr());
    },
    
    getDescAttr: function() {
        var self = this;
        return self.getAttr(self.manager.getDescAttr());
    },
    
    getSelectAttr: function() {
        var self = this;
        return self.getAttr(self.manager.getSelectAttr());
    },
    
    getAttr: function(attr) {
        var self = this,
            val;
        
        if (typeof attr == 'function') {
            val = attr.call(self);
        } else {
            val = self[attr];
        };

        return val;
    },
    
    render: function() {
        var self = this;
        
        var content = Array(
            '<span class="leftCap"></span>',
            '<span class="name">'+self.getNameAttr().shorten(25)+'</span>');
        
        if (self.getDescAttr()) {
            content.push('<span class="desc">'+self.getDescAttr().shorten(40)+'</span>');
        }
        
        content.push('<span class="rightCap"></span>');
        
        var li = $('<li/>').addClass('result'),
            a = $('<a/>').attr('href', '#').html(content.join(''));
        
        a.appendTo(li);
        
        li.bind('click', function(event) {
            self.open()
            return false;
        });
        
        if (!self.getDescAttr()) {
            li.addClass('small');
        }
        
        self.button = a;
        self.row = li;
        
        return li;
    },
    
    select: function() {
        var self = this;
        
        self.row.addClass('selected');
    },
    
    unselect: function() {
        var self = this;
        
        self.row.removeClass('selected');
    },
    
    open: function() {
        var self = this;
        
        self.manager.onOpen(self);
    }
    
});

String.prototype.score = function(abbreviation, offset) {
    offset = offset || 0;
    
    abbreviation = abbreviation.replace(/[^0-9]/g, '');
    
    if (abbreviation.length === 0) return 0.9;
    if (abbreviation.length > this.length) return 0.0;
    
    for (var i = abbreviation.length; i > 0; i--) {
        var sub_abbreviation = abbreviation.substring(0, i);
        var index = this.indexOf(sub_abbreviation);
        
        if (index < 0) continue;
        if (index + abbreviation.length > this.length + offset) continue;
        
        var next_string = this.substring(index + sub_abbreviation.length);
        var next_abbreviation = null;
        
        if (i >= abbreviation.length) {
            next_abbreviation = '';
        } else {
            next_abbreviation = abbreviation.substring(i);
        }
        
        var remaining_score = next_string.score(next_abbreviation, offset + index);
        
        if (remaining_score > 0) {
            var score = this.length - next_string.length;
            
            if (index != 0) {
                var j = 0;
                
                var c = this.charCodeAt(index - 1);
                if (c == 32 || c == 9) {
                    for (j = (index - 2); j >= 0; j--) {
                        c = this.charCodeAt(j);
                        score -= ((c == 32 || c == 9) ? 1: 0.15);
                    }
                } else {
                    score -= index;
                }
            }
            
            score += remaining_score * next_string.length;
            score /= this.length;
            return score;
        }
    }
    return 0.0;
};
