(function ($) {
    $.fn.customSelect = function (settings) {
        settings = $.extend({}, $.fn.customSelect.defaults, settings);
        
        return this.each(function () {
            var select = $(this).hide();
            var selectValue = select.val();
            var selectText = $('option[value='+selectValue+']', select).text();
            
            var toggle = $(settings.toggle.replace('%text', selectText)).insertAfter(select).customSelectFind(settings.toggleSelector).click(toggleMenu);
            var items = '';
            $('option', select).each(function () {
                items += settings.item.replace('%value', $(this).val()).replace('%text', $(this).text());
            });
            var menu = $(settings.menu.replace('%items', items)).hide().insertAfter(toggle).customSelectFind(settings.menuSelector).click(selectMenu);
            
            $(document).click(function (e) {
                if (menu.is(':visible') && !menu.is(':animated')) {
                    toggleMenu(e);
                }
            });
            
            // If the initialization callback has been defined call it
            settings.initCallback && settings.initCallback(toggle, menu, select);
            
            function toggleMenu(e) {
                // If the open or close callback has been defined and if it returns FALSE, don't toggle the menu
                if (false !== (
                    menu.is(':visible') ?
                        (settings.openCallback && settings.openCallback(toggle, menu, e)) :
                        (settings.closeCallback && settings.closeCallback(toggle, menu, e))
                ) && !menu.is(':animated')) {
                    toggle.toggleClass('select-toggle-open');
                    menu.slideToggle('fast');
                }
                return false;
            }
            
            function selectMenu(e) {
                var item = $(e.target); // Event delegation
                if (item.is(settings.itemSelector) && !menu.is(':animated')) {
                    var itemData = settings.getItemData(item);
                    // If the select callback has been defined and if it returns FALSE, don't select the clicked item
                    if (false !== (settings.selectCallback && settings.selectCallback(toggle, menu, item, itemData, e))) {
                        select.val(itemData.value);
                        menu.customSelectFind(settings.itemSelector).removeClass('selected');
                        item.addClass('selected');
                        settings.setToggle(toggle, itemData);
                        toggleMenu(e);
                        // If the select callback has been defined call it
                        settings.selectedCallback && settings.selectedCallback(toggle, menu, item, itemData, e);
                    }
                }
                return false;
            }
        });
    };
    
    // Searches for the given selector amon the children as well as the current element
    $.fn.customSelectFind = function (selector) {
        return this.is(selector) ? this : this.find(selector);
    };
    
    $.fn.customSelect.defaults = {
        toggleSelector: '.select-toggle',
        menuSelector: '.select-menu',
        itemSelector: '.select-item',
        selectedClass: 'selected',
        toggleOpenClass: 'select-toggle-open',
        
        toggle: '<a class="select-toggle" href="#">%text</a>',
        menu: '<ul class="select-menu">%items</ul>',
        item: '<li><a class="select-item" href="#%value">%text</a></li>',
        
        getItemData: function (item) { return { value: item.attr('href').replace(/.*#/,''), text: item.text() }; },
        setToggle: function (toggle, itemData) { toggle.text(itemData.text); },
        
        initCallback: null,
        selectCallback: null,
        selectedCallback: null,
        openCallback: null,
        closeCallback: null
    };
})(jQuery);