new Class('LiveSelector').With({
    
    /**
     * The constructor for this class
     * @param $origin The DOM element we are selecting for
     * @param label The label to display on hover
     * @param parentOrContext The parent LiveSelector, or the context we should embed ourselves in
     */
    __construct: function($origin, label, parentOrContext)
    {
        var self = this;
        
        self.$origin = $origin;
        self.label = label;
        self.children = [];
        self.allowInvisibleHovering = false;
        
        // This is the root selector if the parent given was not a LiveSelector
        if (parentOrContext instanceof LiveSelector) {
            self.parent = parentOrContext;
            self.parent.children.push(self);
            self.$context = self.parent.$container;
        } else {
            self.parent = null;
            self.$context = $('<div class="LiveSelection"></div>').prependTo(parentOrContext);;
        }
        
        /* ***** TAGS AND FILTERING ***** */
        
        self.tags = { };
        self.filter =  { };
        
        /* ***** DOM NODES ***** */
        
        self.$container = $('<div class="LiveSelectorContainer"></div>').appendTo(self.$context);
        self.$selector = $('<div class="LiveSelector"></div>').appendTo(self.$container);
        
        /* ***** TOOLTIPS ***** */
        
        // Cache the tooltip reference and create if necessary
        self.$tooltip = $('.LiveSelector_tooltip');
        if (self.$tooltip.length == 0) {
            self.$tooltip = $('<div class="LiveSelector_tooltip"></div>').appendTo('body');
        }
        
        /* ***** HOVERING ***** */
        
        // Internal
        
        self.$container.bind('mouseover', function(e) {
            self.hover(e, true);
        });
        
        self.$container.bind('mouseout', function(e) {
            self.unhover(e, true);
        });
        
        // External
        
        $(self).bind('hover', function(e) {
            self.hover(e, false);
        });
        
        $(self).bind('unhover', function(e) {
            self.unhover(e, false);
        });
        
        
        /* ***** CLICKING ***** */
        
        self.$container.click(function() {
            if (self.passesFilter()) {
                $(self).trigger('click');
                return false;
            }
        });
        
        /* ***** ROOT LEVEL RESPONSIBILITIES ***** */
        
        if (self.isRoot()) {
            
            self.$container.addClass('LiveSelectorContainer_root');
            
            // We should do a refresh after the page has finished loading and all images are in
            $(window).bind('load', function() {
                self.refreshDisplay();
            });

            // Also update the live selection sized on resize
            $(window).bind('resize', function() {
                self.refreshDisplay();
            });
            
            // Move the tooltip around
            $(self.$container).bind('mousemove', function(e) {
                self.moveTooltip(e);
            });
        }
        
    },
    
    isRoot: function()
    {
        var self = this;
        return self.parent == null;
    },
    
    setOrigin: function($origin)
    {
        var self = this;
        self.$origin = $origin;
        self.refreshDisplay(false, false);
    },
    
    setLabel: function(label)
    {
        var self = this;
        self.label = label;
    },
    
    setColor: function(newColor)
    {
        var self = this;
        self.$selector.css('background', newColor);
    },
    
    /**********************************************
     * TAGGING
     **********************************************/
    
    addTag: function(tag)
    {
        var self = this;
        self.tags[tag] = true;
    },
    
    removeTag: function(tag)
    {
        var self = this;
        self.tags[tag] = null;
    },
    
    clearTags: function()
    {
        var self = this;
        self.tags = { };
    },
    
    /**********************************************
     * FILTER
     **********************************************/
    
    addFilter: function(tag)
    {
        var self = this;
        self.filter[tag] = true;
    },
    
    removeFilter: function(tag)
    {
        var self = this;
        delete self.filter[tag];
    },
    
    clearFilter: function()
    {
        var self = this;
        self.filter = { };
    },
    
    /**
     * Does this pass the filter test?
     */
    passesFilter: function()
    {
        var self = this;
        
        var passes = false;
        var emptyFilter = true;
        
        var current = self;
        
        while (current) {
                        
            // Loop through the filter tags
            $.each(current.filter, function(tag) {
                
                // There was some filter
                emptyFilter = false;
                
                if (self.tags[tag]) {
                    
                    passes = true;
                    return false;
                }
            });
            
            // Check next levels filter if we have not passed yet
            current = passes ? null : current.parent;
        }
        
        // Return true if it was an empty filter all the way
        // or if a tag matches a filter we found
        return emptyFilter || passes;
    },
    
    /**********************************************
     * HIGHLIGHTING
     **********************************************/
    
    /**
     * A recursive call that goes through each live selector in the tree
     * and highlights them if appropriate
     */
    highlightCandidates: function()
    {
        var self = this;
                
        // If we match, highlight ourselves
        if (self.passesFilter()) {
            self.highlight();
        } else {
            self.unhighlight();
        }
        
        // Go through each child and check
        $.each(self.children, function() {
            var child = this;
            child.highlightCandidates();
        });
    },
    
    /**
     * Unhighlight all candidates. We cheat here a bit in the name of efficiency :)
     * We might have to not cheat at some point.
     */
    unhighlightCandidates: function()
    {
        var self = this;
        self.$container.find('.LiveSelector_highlighted').removeClass('.LiveSelector_highlighted');
    },
    
    highlight: function()
    {
        var self = this;
        self.$selector.addClass('LiveSelector_highlighted');
        self.$selector.fadeTo('normal', 0.5);
    },
    
    unhighlight: function()
    {
        var self = this;
        self.$selector.removeClass('LiveSelector_highlighted');
    },
    
    /**********************************************
     * HOVERING
     **********************************************/
    
    /**
     * Called when we begin hovering over this selector
     */
    hover: function(e, showTooltip)
    {
        var self = this;
        
        if (!self.passesFilter()) { return; }
        
        if (showTooltip) {
            self.showTooltip(self.label);
        }
                
        self.$selector.addClass('LiveSelector_hovering');        
        e.stopPropagation();
    },
    
    /**
     * Called when we stop hovering over this selector
     */
    unhover: function(e)
    {
        var self = this;
        self.hideTooltip();
        self.$selector.removeClass('LiveSelector_hovering');
    },
    
    /**********************************************
     * TOOLTIP
     **********************************************/
    
    showTooltip: function(title)
    {
        var self = this;
        self.$tooltip.html(title).show();
    },
    
    hideTooltip: function()
    {
        var self = this;
        self.$tooltip.hide();
    },
    
    moveTooltip: function(e)
    {
        var self = this;
        self.$tooltip.css('top', e.pageY + 10).css('left', e.pageX + 20);
    },
    
    /**********************************************
     * ENABLE AND DISABLE
     **********************************************/
    
    enable: function()
    {
        var self = this;
        
        // Remove the disabled marker
        self.$container.removeClass('LiveSelectorContainer_disabled');
        
        // Start the display refreshing
        self.refreshDisplay(true);
    },
    
    disable: function()
    {
        var self = this;
        self.$container.addClass('LiveSelectorContainer_disabled');
    },
    
    isEnabled: function()
    {
        var self = this;
        return !self.$container.hasClass('LiveSelectorContainer_disabled');
    },
    
    clearClickBindings: function()
    {
        var self = this;
        $(self).unbind();
        
        // Unbind children
        $.each(self.children, function() {
            var child = this;
            child.clearClickBindings();
        });
    },
    
    pause: function()
    {
        var self = this;
        self.$container.addClass('LiveSelectorContainer_paused');
    },
    
    unpause: function()
    {
        var self = this;
        self.$container.removeClass('LiveSelectorContainer_paused');
    },
    
    /**
     * Refresh the live selector based on the positions and dimensions of the origin
     */
    refreshDisplay: function(scheduleFutureRefresh, refreshChildren)
    {
        var self = this;
        
        if (refreshChildren == null) { refreshChildren = true; }
        
        // If we are root and not enabled, don't even bother
        if (self.isRoot() && !self.isEnabled()) {
            return;
        }
        
        // Check if the node we are representing no longer exists in the primary DOM tree
        if (self.$origin.parents('body').length == 0) {
            self.destroy();
            return;
        }
        
        
        // Compute our current offset
        var offset = self.$origin.offset();
        
        // Subtract the parent offset if we have one
        if (self.parent) {
            var parentOffset = self.parent.$origin.offset();
            offset.top -= parentOffset.top;
            offset.left -= parentOffset.left;
        }
        
        
        if (self.isRoot()) {
            // Subtract off the top position of the liveselection container
            // This was added to make the animating in of margins all nice looking
            offset.top -= $('.LiveSelection').offset().top;
        }
        
        // Update the info
        self.$selector.css('width', parseInt(self.$origin.css('width')) - 4);
        self.$selector.css('height', parseInt(self.$origin.css('height')) - 4);
        
        self.$container.css('top', offset.top).css('left', offset.left);
        if (self.allowInvisibleHovering) {
            self.$container.css('width', parseInt(self.$origin.css('width')) - 4);
            self.$container.css('height', parseInt(self.$origin.css('height')) - 4);
        }
        

        
        if (scheduleFutureRefresh) {
            // Refresh the display again in 2 seconds
            setTimeout(function() {
                self.refreshDisplay(true);
            }, 2000);
        }
        
        // Tell the children to refresh their displays now as well
        if (refreshChildren) {
            for (var i = 0; i < self.children.length; i++) {
                var child = self.children[i];
                child.refreshDisplay();
            }
        }
    },
    
    /**
     * Destroy this live selector
     */
    destroy: function()
    {
        var self = this;
        self.$container.remove();
        
        // Remove from parents list of children
        if (self.parent) {
            var index = self.parent.children.indexOf(self);
            self.parent.children.splice(index, 1);
        }
    }
    
});

LiveSelector.taggify = function()
{
    var out = '';
    
    for (var i = 0; i < arguments.length; i++) {
        if (i > 0) {
            out += '_';
        }
        
        out += arguments[i].replace(/[^a-zA-Z]/g, '');
    }
    
    return out;
}

