function PathManager()
{
    var _this = this;
    
    _this.currentPath = null;   
}

PathManager.prototype = {
    setCurrentPath: function(setCurrentPath)
    {
        var _this = this;
        
        _this.currentPath = setCurrentPath;
        $(document).trigger('Core.pathManager.setCurrentPath', _this.currentPath);
    },
    
    getCurrentPath: function()
    {
        var _this = this;
        
        return _this.currentPath;
    }
};
function LongPollManager()
{
    var _this = this;
    
    _this.callbacks = [];
    _this.callbackIndex = 0;
    _this.frameReady = false;
    _this.docReady = false;
}

LongPollManager.prototype = {

    registerCallback: function(resourceIdString, eventName, callback, queueUpdates)
    {
                // long poll server not configured
        return;
                
        var _this = this;
        
        // default queueUpdates to false
        queueUpdates = typeof(queueUpdates) != 'undefined' ? queueUpdates : false;
     
        var callbackId = ++_this.callbackIndex;
        var resourceId = resourceIdString.split('_');
        var resource = ResourceManager.get(resourceIdString);

        _this.callbacks[callbackId] = new LongPollCallback(callbackId, resource, resourceId[0], resourceId[1], eventName, callback, queueUpdates);
        
        if (_this.frameReady && _this.docReady) {
            // start or restart polling with this new callback
            _this.startPolling();
        }
    },
    
    onFrameReady: function()
    {
        var _this = this;
                
        // polling iframe is ready to go
        _this.frameReady = true;
        
        if (_this.docReady) {
            _this.startPolling();
        }
    },
    
    startPolling: function()
    {
        var _this = this;
                
        _this.docReady = true;
        
        // if the iframe is ready to go
        if (_this.frameReady) {
            
            // construct the xml to send out
            var xml = '<callbacks>';
            for (var callbackId in _this.callbacks) {
                xml += _this.callbacks[callbackId].getXml();
            }
            xml += '</callbacks>';
            
            window.frames['lp360'].WaitForEvents(xml);
        }
    },

    handleEvents: function(events)
    {
        var _this = this;
        
        // loop through each event that fired
        $('event', events).each(function()
        {
            var $event = $(this);
            
            // grab the callback ids this is going back to
            var ids = $event.attr('ids').split(',');
                        
            // data is a list of name value pairs as element name and value            
            var data = {};
            var $dataNode = $('data', $event);
            $dataNode.children().each(function()
            {
                data[this.tagName] = $(this).text();
            });
                        
            // make callbacks
            for (var index in ids) {
            
                // put in try just in case callback no longer exists
                try {
                    var longPollCallback = _this.callbacks[ids[index]];
                    longPollCallback.callback.call(longPollCallback.resource, data);
                }
                catch(e) {}
            }
        });
    }
};


function LongPollCallback(setId, setResource, setResourceIdUpper, setResourceIdLower, setEventName, setCallback, queueUpdates)
{
    var _this = this;
    
    _this.id = setId;
    _this.resource = setResource;
    _this.resourceIdUpper = setResourceIdUpper;
    _this.resourceIdLower = setResourceIdLower;
    _this.eventName = setEventName;
    _this.callback = setCallback;
    _this.queueUpdates = queueUpdates;
}

LongPollCallback.prototype = {
    getXml: function()
    {
        var _this = this;
        
        var xml = ''
            + '<callback id="' + _this.id + '">'
            + '<event resourceIdUpper="' + _this.resourceIdUpper
                + '" resourceIdLower="' + _this.resourceIdLower
                + '" queueUpdates="' + (_this.queueUpdates ? '1' : '0')
                + '">' + _this.eventName
            + '</event>'
            + '</callback>';
            
        return xml;
    }
}


// set the doc domain so we can communicate across subdomains to iframes.
// document.domain = 'graceinauburn.com';

/**
 * The core Javascript brain for our site
 * This will  be in charge of dispatching events that are imporant enough.
 */
var Core = {
    /**
     * The current mode the sites in, for now ('' or 'admin')
     */
    mode: '',
    
    /**
     * Used for keeping track of the path
     */
    pathManager: new PathManager(),
    
    /**
     * Manages long polling registration and events
     */
    longPollManager: new LongPollManager(),
    
    /**
     * Used for holding the objects relavent to the admin mode
     */
    adminMode: null,

    /**
     * Used for holding the manage mode object
     */
    manageMode: null,
    
    /**
     * The resource id of the root pageblock
     */
    rootPageBlockResourceId: '1500_1114112',
    
    /**
     * The resource of the root pageblock
     */
    rootPageBlock: null,

    /**
     * Keep track of the latest context
     */
    domContextNum: 0,

    /**
     * Called when the DOM is ready
     */
    ready: function()
    {
    
        Core.rootPageBlock = ResourceManager.get(Core.rootPageBlockResourceId);
    
            },
    
    // Deal with some document.write issues
    
    
    /**
     * Overwrites the default document.write functions, injects code into the
     * latest DOM context. Gives us basic protection against document overwrites
     */
    safeDocumentWrite: function(text)
    {
        // Grab the latest DOM context
        $latestDomContext = $('.Core_DomContext_' + Core.domContextNum);
        if ($latestDomContext.length > 0) {
            $latestDomContext.before(text);
            console.log('found dom context, inserting before');
        } else {
            $('body').append(text);
            console.log('no dom context, document.writing at the end of body');
        }
    },

    updateDomContext: function()
    {
        // Increment dom context num
        Core.domContextNum++;

        // Grab the new Core_DomContext
        $domContextTemp = $($('.Core_DomContext_Temp').get(0));
        $domContextTemp.removeClass('Core_DomContext_Temp').addClass('Core_DomContext_' + Core.domContextNum);
    }
};



document.write = Core.safeDocumentWrite;


var originalDraggable = $.fn.draggable;
$.fn.draggable = function(options) {
    
    if (options == null) {
        var options = { };
    }
    
    var originalStart = options.start;
    var originalStop = options.stop;
    
    // Over write the start function to disable selection
    options.start = function(event, ui) {
        Utilities.disableSelection();
        
        if (originalStart) {
            originalStart.call(this, event, ui);
        }
    }
    
    // Turn selection back on when u stop
    options.stop = function (event, ui) {
        Utilities.enableSelection();
        
        if (originalStop) {
            originalStop.call(this, event, ui);
        }
    }
    
    originalDraggable.call(this, options);
}

var originalSortable = $.fn.sortable;
$.fn.sortable = function(options) {
    
    if (options == null) {
        var options = { };
    }
    
    var originalStart = options.start;
    var originalStop = options.stop;
    
    // Over write the start function to disable selection
    options.start = function(event, ui) {
        Utilities.disableSelection();
        
        if (originalStart) {
            originalStart.call(this, event, ui);
        }
    }
    
    // Turn selection back on when u stop
    options.stop = function (event, ui) {
        Utilities.enableSelection();
        
        if (originalStop) {
            originalStop.call(this, event, ui);
        }
    }
    
    originalSortable.call(this, options);
}

// Make sure calling console.log on crap browsers does not cause fatal error
if (typeof(console) == 'undefined') {
    console = {
        log: function() {
        }
    }
}

$(document).ready(function() {
    ResourceManager.loadResources();
    Core.ready();
    ResourceManager.construct();
});

var Utilities = {
    xmlEncode: function(inString)
    {
        return inString.replace(/&/g,'&amp;')
            .replace(/</g,'&lt;')
            .replace(/>/g,'&gt;')
            .replace(/'/g,'&apos;');
    },

    xmlDecode: function(inString)
    {
        return inString.replace(/&amp;/g,"&")
            .replace(/&lt;/g,"<")
            .replace(/&gt;/g,">")
            .replace(/&apos;/g,"'");
    },
    
    jsonToXml: function(input)
    {
        if (!input) { return ''; }

        var xml = '';
        
        switch (typeof(input)) {
            case 'string':
                xml += Utilities.xmlEncode(input);
                break;
            case 'number':
                xml += input;
                break;
            case 'boolean':
                xml += input ? 'true' : 'false';
                break;
            case 'object':
                // If its an object, we need to descend into its children
                
                // if object is an array
                if ($.isArray(input)) {
                    for (index in input) {
                        if (index == "") {
                            // empty is invalid as an xml element. this is converted back at the server.
                            index = "empty___index";
                        }
                        xml += '<' + index + '>';
                        xml += Utilities.jsonToXml(input[index]);
                        xml += '</' + index + '>';
                    }
                }
                else {
                    $.each(input, function(index, value) {
                        if (index == "") {
                            // handle empty case
                            index = "empty___index";
                        }
                        xml += '<' + index + '>';
                        xml += Utilities.jsonToXml(value);
                        xml += '</' + index + '>';
                    });
                }
                break;
        }
        
        return xml;
    },
    
    debugOut: function(text)
    {
        $('<div>' + text + '</div>').appendTo('body');
    },
    
    /* stylesheet manipulation - heavily inspired by http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript */
    
    
    styleSheets_getCSSRule: function(ruleName, deleteFlag, doc)
    {
        // If no delete flag, assume not deleting
        if (deleteFlag == null) { deleteFlag = false; }
    
        // If no other doc provided, use default document
        if (doc == null) { doc = document; }
    
        // Convert ruleName to lower case for browser consistency
        ruleName = ruleName.toLowerCase(); 
        
        // Make sure we can even do this
        if (doc.styleSheets) {
            // Loop through the stylesheets
            
            for (var i = 0; i < doc.styleSheets.length; i++) {
                
                var styleSheet = doc.styleSheets[i];

                if (styleSheet.href) { continue; }
                
                // This sheets not ready
                if (!styleSheet.cssRules && !styleSheet.rules) { continue; }

                var j = 0;
                while (true) {
                    
                    // Grab the css rule
                    var cssRule = styleSheet.cssRules ? styleSheet.cssRules[j] : styleSheet.rules[j];

                    // Ruh roh, we ran out of css rules
                    if (!cssRule) { break; }
                                        
                    // Check for a match
                    if (cssRule.selectorText.toLowerCase() == ruleName) {
                    
                        // Found a match, delete?
                        if (deleteFlag == true) {
                            styleSheet.cssRules ? styleSheet.deleteRule(j) : styleSheet.removeRule(j);
                            return true;
                        }
                                                       
                        // Return the found rule
                        return cssRule;
                    }
                    
                    j++;
                }
                
            }
            
            return false;
        }
                
        return false;
    },
    
    styleSheets_deleteCSSRule: function(ruleName, doc)
    {
        // If no other doc provided, use default document
        if (doc == null) { doc = document; }
    
        return Utilities.styleSheets_getCSSRule(ruleName, true, doc);
    },
    
    styleSheets_getStylesheet: function(doc, styleSheetId)
    {
        if (doc == null) { doc = document; }
        
        // Check if we have access to the styleSheets
        if (!doc.styleSheets || doc.styleSheets.length == 0) { return null; }
        
        var styleSheet = null;
        
        if (styleSheetId == null) {
            // Grab any old stylesheet if no id was given
            styleSheet = doc.styleSheets[0];
        } else {
            // Grab a specific stylesheet based on the id of the owning node
            for (var i = 0; i < doc.styleSheets.length; i++) {
                
                if (doc.styleSheets[i].ownerNode) {
                    var styleSheetNode = doc.styleSheets[i].ownerNode;
                } else {
                    var styleSheetNode = doc.styleSheets[i].owningElement;
                }
                
                // Check to see if that
                if (styleSheetNode.id == styleSheetId) {
                    styleSheet = doc.styleSheets[i];
                    break;
                }
            }
        }
        
        // Hmmm, firefox seems to require an additional check for readyness
        if (!styleSheet.cssRules && !styleSheet.rules) {
            console.log('stylsheet object found, but not quite ready');
            return null;
        }
        
        return styleSheet;
    },
    
    styleSheets_addCSSRule: function(ruleName, doc, styleSheetId)
    {
        // If no other doc provided, use default document
        if (doc == null) { doc = document; }
        var styleSheet = Utilities.styleSheets_getStylesheet(doc, styleSheetId);
        
        // If we couldnt find the stylesheet, we cant do anything
        if (styleSheet == null) { return; }
        
        var ruleCheck = Utilities.styleSheets_getCSSRule(ruleName, false, doc, styleSheet);
        // Rule already exists silly
        if (ruleCheck != false) { return ruleCheck; }
        
        if (styleSheet.addRule) { 
            // IE
            styleSheet.addRule(ruleName, null, 0);
        } else {
            // REST OF THE WORLD
            styleSheet.insertRule(ruleName+' { }', 0);
        }
                    
        // Return the new rule
        return Utilities.styleSheets_getCSSRule(ruleName, false, doc);
    },

    dialog: function(dialogContents, dialogOptions)
    {
        // Create the container for the contents
        var $container = $('<div />');
        
        // Convert to a jQuery obj
        var $dialogContents = $(dialogContents);

        // Remove any scripts and take the first element
        var $dialogScripts = $dialogContents.filter('script');
        $dialogContents = $dialogContents.not('script');

        // Append the scrubbed dialog contents
        $container.append($dialogContents);

        // Make a dialog out of the container
        $container.dialog(dialogOptions);

        // Add the scripts to the head
        $dialogScripts.appendTo('head');
        
        // compile contents
        $container.compile();

        // Return a reference to the container so we can hook into it
        return $container;
    },
    
    enableSelection: function()
    {
        $('body').removeClass('unselectable');
    },
    
    disableSelection: function()
    {
        $('body').addClass('unselectable');
    }
};

// TODO: Move this into its own manager
var AjaxSockets = [];
var AjaxSocketIndex = 0;

/**
 * A class for handling communication between serverside and client side resources
 */
function AjaxSocket()
{
    var self = this;
    
    self.requests = [];
    self.responseQueue = [];
    self.successCallbacks = [];
    
    // Set ourselves to be the newsest AjaxSocketIndex
    self.socketIndex = ++AjaxSocketIndex;
    // Create a new globally referencable instance of this socket
    AjaxSockets[self.socketIndex] = self;
}

AjaxSocket.prototype = {
    /**
     * Add a request to this socket. Requests will be executed in order.
     *
     * @param String resourceIdString The resourceIdString "<upper>_<lower>" of the resource we want to trigger an api action on
     * @param String action The api action we want to run
     * @param Object data Any data that we need to send to the action
     * @param Function callback Optional - if you want a callback with the response to this request then add it here
     */
    addRequest: function(resourceIdString, action, data, callback)
    {
        var self = this;
        
        var resourceId = resourceIdString.split('_');
        
        // Create the new request and push it onto our list of requests
        self.requests.push(new AjaxSocketRequest(self.requests.length, resourceId[0], resourceId[1], action, data, callback));
        
        // Return the current socket to allow for chaining
        return self;
    },
    
    addSuccessCallback: function(callback)
    {
        var self = this;
        self.successCallbacks.push(callback);
        
        // Return the current socket to allow for chaining
        return self;
    },
    
    /**
     * Fire off the ajax request to handle the sockets
     */
    fire: function(asyncRequest)
    {
        var self = this;
        
        if(asyncRequest == null){
			asyncRequest = true;
        }
        
        // Construct the xml to send out
        var xml = '<requests>';
        $.each(self.requests, function()
        {
            xml += this.getXml(); 
        });
        xml += '</requests>';
                
        // Configure the ajax request
        var ajaxOptions = {
			async: asyncRequest,
            url: '/ajax360',
            type: 'post',
            data: {
                xml: xml   
            },
            dataType: 'xml',
            success: function(xml)
            {
                // Parse the responses
                self.parseResponses(xml);
                
                // Run the success callbacks
                while (self.successCallbacks.length > 0) {
                    var callback = self.successCallbacks.shift();
                    callback.call();
                }
            },
            error: function(XMLHttpRequest, textStatus, errorThrown)
            {
                                alert('AjaxSocket Error: ' + textStatus + ' -- ' + errorThrown);
                            }
        }
         
        $.ajax(ajaxOptions);
    },
    
    parseResponses: function(xml)
    {
        var self = this;
        
        // Loop through the return for each request
        $('request', xml).each(function()
        {
            var $request = $(this);
            
            // Get the resource
            var resourceId = $request.attr('resourceIdUpper') + '_' + $request.attr('resourceIdLower');
            var resource = ResourceManager.get(resourceId);
            
            // Grab the id
            var id = $request.attr('id');
            
            // Grab the action if it exists
            var action = $('action', $request).length > 0 ? $('action', $request).text() : '';
            
            // Convert parameters to array. If no parameter nodes then pass data content as text.
            var $dataNode = $('data', $request);
            var $dataParameters = $dataNode.children();
            var data = {};
            if ($dataParameters.size() == 0) {
                data = $dataNode.text();
            }
            else {
                $dataParameters.each(function()
                {
                    data[this.tagName] = $(this).text();
                });
            }
            
            self.responseQueue.push({resource: resource, id: id, action: action, data: data});
        });
        
        self.consumeResponseQueue();
    },
    
    
    consumeResponseQueue: function()
    {
        var self = this;
        
        if (self.responseQueue.length == 0) { return; }
        
        var response = self.responseQueue.shift();
        
        var resource = response.resource;
        var id = response.id;
        var action = response.action;
        var data = response.data;
        var haltConsumption = false;
        
        // This is a direct response to a request with a callback
        if (id != undefined && typeof(self.requests[parseInt(id)].callback) == 'function') {
            // Send back the response data item to this callback

            // Use timeout zero to ensure we continue synchronously 
            setTimeout(function() {
                haltConsumption = self.requests[parseInt(id)].callback.call(resource, data.response, self);
            }, 0);
        }
        
        
        // This is a request by the server for the client to perform some action
        else if (action.length > 0) {
            
            var functionName = 'api' + action.substr(0, 1).toUpperCase() + action.substr(1);
            
            if (resource != null && typeof(resource[functionName]) == 'function') {
                // Use timeout zero to ensure we continue synchronously 
                setTimeout(function() {
                    haltConsumption = resource[functionName].call(resource, data, self);
                }, 0);
            }
        }
        
        // use settimeout zero to prevent execution of this until after the previous settimeout
        if (haltConsumption == null || !haltConsumption) {
            // We don't need to wait for this response to do something that happens out of
            // this thread, we can just continue.
            self.consumeResponseQueue();
        }
    },
    
    getEvalStringReference: function()
    {
        var self = this;
        return 'AjaxSockets[' + self.socketIndex + ']';
    }
};

/**
 * The class for each request of an AjaxSocket, basically just a data holder
 */
function AjaxSocketRequest(setId, setResourceIdUpper, setResourceIdLower, setAction, setData, setCallback)
{
    var self = this;
    
    self.id = setId;
    self.resourceIdUpper = setResourceIdUpper;
    self.resourceIdLower = setResourceIdLower;
    self.action = setAction;
    self.data = setData;
    self.callback = setCallback;
}

AjaxSocketRequest.prototype = {
    /**
     * Returns the xml representation of this request
     *
     * @return An XML string of the request
     */
    getXml: function()
    {
        var self = this;
        
        var xml = ''
            + '<request id="' + self.id + '" resourceIdUpper="' + self.resourceIdUpper + '" resourceIdLower="' + self.resourceIdLower + '">'
            + '<action>' + self.action + '</action>'
            + '<data>'
            + Utilities.jsonToXml(self.data)
            + '</data>'
            + '</request>';
        
        return xml;
    }
};
// Make sure we DO NOT recreate the resource manager from an AJAX call
if (typeof(ResourceManager) == 'undefined') {
    
    /**
     * A javascript object acting as a namespace / static function container
     * This will be used to keep track of our javascript resources
     */
    var ResourceManager = {
        /**
         * The object for storing our resources
         */
        resources: { },
        
        /**
         * Flags to track what resource have been contructed
         */
        isConstructed: { },
        
        constructParams: { },
        
        /**
         * Compiles and constructs the whole document
         */
        construct: function()
        {
            if (ResourceManager.isConstructed.length > 0) {
                console.log('Skipping ResourceManager.construct, already constructed body');
            }
            else {
            
                // compile the doc body
                $('body').compile();
            }
        },

        loadResources: function(context)
        {
            $('.Resource', context).each(function() {        
                var resourceId = $(this).attr('rel');
                var params = ResourceManager.constructParams[resourceId]; // eval($(this).children('.Resource_construct:first').html());
                
                // check if we need to create the resource
                if (!(resourceId in ResourceManager.resources)) {
                    var newResource = new window[params.type](resourceId, 'http://cache.graceinauburn.com/ajax360/' + resourceId.replace('_', '-') + '/');
                    newResource.addClassName(params.type);
                    ResourceManager.setOnce(resourceId, newResource);
                }
            });
        },
        
        constructResources: function(context)
        {
            $('.Resource', context).each(function() {
                var resourceId = $(this).attr('rel');
                var params = ResourceManager.constructParams[resourceId]; // eval($(this).children('.Resource_construct:first').html());
                        
                if (typeof(ResourceManager.resources[resourceId].widgetConstruct) != 'undefined') {
                    ResourceManager.resources[resourceId].widgetConstruct(params);
                }
                if (typeof(ResourceManager.resources[resourceId].construct) != 'undefined') {
                    ResourceManager.resources[resourceId].construct(params);
                }
                ResourceManager.isConstructed[resourceId] = true;
            });
        },
        
        /**
         * Set a resource. If it already exists, do not reset
         *
         * @param String resourceId The id of the resource that we want to set
         * @param Object resource The resource that we want to store
         *
         * @return Object The ResourceManagers record for that resource id
         */
        setOnce: function(resourceId, resource)
        {
            // Check if the resource is already created
            if (!(resourceId in ResourceManager.resources)) {
                ResourceManager.resources[resourceId] = resource;
            }
        
            return ResourceManager.resources[resourceId];
        },
    
        /**
         * Retrives a resource object by a given id. If it does not exist, return null
         *
         * @param String resourceId - The id of the resource object we want
         * @return Object|null 
         */
        get: function(resourceId)
        {
            // Make sure the resource exists
            if (resourceId in ResourceManager.resources) {
                return ResourceManager.resources[resourceId];
            } else {
                return null;
            }
        },
    
        /**
         * Checks if a given resource id is set yet or not
         *
         * @param String resourceId The resource id we want to check
         * @return Boolean If the resource is set or not
         */
        isset: function(resourceId)
        {
            return (resourceId in ResourceManager.resources);
        }
    };    
}

/* jquery extension for compiling html content that may contain resources */
$.fn.compile = function() {
    
    // if this is not the body element we are compiling
    if ($(this).get(0).nodeName.toLowerCase() != 'body') {
        
        console.log('Compiling content');
        
        // wrap so root elements of content are included in selects
        var wrapper = $(this).wrap('<div>').parent();
        
        // load and construct from wrapper
        ResourceManager.loadResources(wrapper);
        ResourceManager.constructResources(wrapper);
        
        // unwrap
        $(this).unwrap();
    }
    
    // if we are compiling the body element
    else {
    
        console.log('Compiling body');
        
        ResourceManager.loadResources(this);
        ResourceManager.constructResources(this);
    }
}



ControllerBase.prototype = {
    // Javascripts instanceof is very unreliable, so make our own
    addClassName:function(className)
    {
        var self = this;
        self.classNames.push(className)
    },
    
    isInstanceOf:function(className)
    {
        var self = this;
        var found = false;
        
        $.each(self.classNames, function()
        {
            if (this == className) {
                found = true;
                return false;
            }
        });
        
        return found;
    }
};

// javascript class representation of ControllerBase
function ControllerBase(setResourceId, setAjaxUrl)
{
    var self = this;

    self.resourceId = setResourceId;
    self.ajaxUrl = setAjaxUrl;
    
    self.classNames = [];
    self.addClassName('ControllerBase');
}


Widget.prototype = new ControllerBase();

function Widget() {
    ControllerBase.apply(this, arguments);
    this.addClassName('Widget');
    this.widget_admin_globallyBound = false;
}

$.extend(Widget.prototype, {
    
    widgetConstruct:function(params)
    {
        var self = this;
                
        // Set reference to parent widget
        if (params.parent && params.parent != 'null') {
            self.parentWidget = ResourceManager.get(params.parent);
        }
        
        self.isVisible = params.visible;
        self.name = params.name;
        
        // Grab our custom widget bindings
        self.$widget_container = $('.Resource_' + self.resourceId);
        self.$widget_content_container = self.$widget_container;
        self.$widget_content = self.$widget_content_container;
        
        console.log('WIDGET CONSTRUCT: ' + self.classNames[self.classNames.length-1]);
        
        // Create the modal overlay
        if (!self.modalOverlay && self.$widget_container.length > 0 && self.isVisible) {
            self.modalOverlay = new ModalOverlay(self.$widget_container);
        } else if (self.modalOverlay) {
            self.modalOverlay.setContent(self.$widget_container);
        }
                
        // Create the live selector for this widget
        if (!self.liveSelector && Core.rootPageBlock == self) {
            // This is the root block
            self.liveSelector = new LiveSelector(self.$widget_container, self.name, $('body'));
            self.configureLiveSelector();
            self.liveSelector.disable();
        } else if (!self.liveSelector && self.parentWidget && self.$widget_container.length > 0 && self.isVisible) {
            // A regular old pageblock or widget
            self.liveSelector = new LiveSelector(self.$widget_container, self.name, self.parentWidget.liveSelector);
            self.configureLiveSelector();
        } else if (self.liveSelector) {
            // We need to update the live selector with the new information so it doesn't destroy itself
            self.liveSelector.setOrigin(self.$widget_container);
        }
        
        if (params.admin) {
            self.widget_admin_init();
        }
    },

    // Any extra things we need to initialize with a widget if we are in admin mode
    widget_admin_init:function()
    {
        var self = this;
                
        // Start out assuming we can highlight
        self.canHighlight = true;

        // Grab some extra dom handles that will be imporant
        self.$widget_editHighlight = $('.{WCLASS}_editHighlight:first', self.$widget_container);
        self.$widget_editIcon = $('.{WCLASS}_editIcon:first', self.$widget_container);
        self.$widget_editLabel = $('.{WCLASS}_editLabel:first', self.$widget_container);

        // Global Resource events
        if (!self.widget_admin_globallyBound) {
            // We only want to bind these to the global handler once
            // We still need to aquire the DOM references above though
            $(document).bind(self.resourceId + '_highlight', {'self':self}, self.widget_admin_highlight);
            $(document).bind(self.resourceId + '_unhighlight', {'self':self}, self.widget_admin_unhighlight);

            $(document).bind(self.resourceId + '_socketAdd_edit', {'self':self}, self.socketAdd_getEditPane);
            $(document).bind(self.resourceId + '_socketAdd_view', {'self':self}, self.socketAdd_getViewPane);

            self.widget_admin_globallyBound = true;
        }
        
        // If we are in admin mode, we want to bind the clicking of a resource to the edit mode
        if (self.liveSelector && !self.liveSelectorInitialized) {
            
            self.liveSelectorInitialized = true;
            
            // Hackish stuff going on here.
            self.liveSelector.allowInvisibleHovering = true;
            
            $(self.liveSelector).bind('click', function() {
                Core.adminMode.editResource(self.resourceId);
            });
            
            self.liveSelector.setColor('#AC5A5A');
        }

    },
    
    /**
     * Base function for getting the live selector, will be overridden where it needs to
     */
    configureLiveSelector: function()
    {
        var self = this;
        
        self.liveSelector.addTag('widget');
        
        return self.liveSelector;
    },

    // shorthand for adding widget context to a select
    $:function(selection)
    {
        return $(selection, this.$widget_container);
    },
    
    // shorthand for binding dom element events in this widget to methods
    bind:function(selection, event, method)
    {
        if (typeof selection === "object") {
            selection.bind(event, $.proxy(this, method));
        }
        else {
            $(selection, this.$widget_container).bind(event, $.proxy(this, method));
        }
    },

    // shorthand for live binding dom element events in this widget to methods
    live:function(selection, event, method)
    {
        if (typeof selection === "object") {
            selection.live(event, $.proxy(this, method));
        }
        else {
            $(selection, this.$widget_container).live(event, $.proxy(this, method));
        }
    },

    apiReloadWidget:function(newSource)
    {
        var self = this;

        // replace widget with new source
        self.$widget_container.replaceWith(newSource);
        
        // recompile widget
        self.$widget_container.compile();
    },

    /* ********** HIGHLIGHTING FUNCTIONS ********** */

    // Triggered when we mouse over a widget
    widget_admin_mouseOver:function(e)
    {
        var self = e.data.self;

        // Check for no live event editing
        if (!self.isLiveSelectionOn()) { return; }

        // Check if we can highlight
        if (!self.canHighlight) { return; }

        // we are taking the event so stop further propagation
        e.stopPropagation();

        // Show the edit icon and label
        //self.$widget_editIcon.show();
        //self.$widget_editLabel.show();

        // Trigger the highlight event on this resource
        //$(document).trigger(self.resourceId + '_highlight');
    },

    // Trigger when we mouse out a widget
    widget_admin_mouseOut:function(e)
    {
        // Stop the event bubbling
        // e.stopPropagation();

        var self = e.data.self;

        // Dont show the highlight or edit icon
        self.$widget_editIcon.hide();
        self.$widget_editLabel.hide();

        // Trigger the unhighlight event on this resource
        $(document).trigger(self.resourceId + '_unhighlight');
    },

    // Highlight the widget, needs to be a funciton since envExplorer will call it
    widget_admin_highlight:function(e)
    {
        var self = e.data.self;
        
        if (self.liveSelector) {
            $(self.liveSelector).trigger('hover');
        }
    },

    // Unhighlight the widget, needs to be a funciton since envExplorer will call it
    widget_admin_unhighlight:function(e)
    {
       var self = e.data.self;
       
       if (self.liveSelector) {
           $(self.liveSelector).trigger('unhover');
       }
    },


    widget_admin_editIconClick:function(e)
    {
        var self = e.data.self;
        // Tell the core adminmode that we want to edit this widget
        Core.adminMode.editResource(self.resourceId);
    },

    callServerObject:function(apiFunction, data, callback, async)
    {
        var self = this;

        var socket = new AjaxSocket();
        socket.addRequest(self.resourceId, apiFunction, data, callback);
        socket.fire(async);
    },

    /**
     * Add a request to load the widgets edit mode to the socket given
     *
     * @param Event e The event that triggered this listener
     * @param AjaxSocket socket the AjaxSocket we should add to
     */
    socketAdd_getEditPane: function(e, socket)
    {
        var self = e.data.self;

        // We don't want to highlight ourselves anymore
        self.canHighlight = false;

        // Go into loading mode ourselves
        self.widget_admin_loading();

        // Populate the data that the server side controller needs
        var data = {
            url: Core.pathManager.getCurrentPath()
        };

        // Add the request to the socket
        socket.addRequest(self.resourceId, 'getEditPane', data);
    },

    /**
     * A client action that gets called from the server to set the edit pane
     *
     * @param Object data The data the server sent
     */
    apiSetEditPane: function(data, socket)
    {
        var self = this;

        // We don't want to highlight ourselves anymore
        self.canHighlight = false;

        var finalContent = data['content'] +
            '<script type="text/javascript">$(document).ready(function() {' +
            socket.getEvalStringReference() + '.consumeResponseQueue();' +
            '});</script>';

        // Set the widget content to the returned data
        self.$('.Widget_content_container_admining:first').html(finalContent)
        self.$widget_container.compile();

        // Remove the loading overlay
        self.widget_admin_doneLoading();
        
        // Enable modal overlay
        Core.adminMode.setModalOverlay(self.modalOverlay);
        
        // Tell the ajax socket to halt consumption of other requests until we have finished loading this one
        // We do this by returning true
        return true;
    },


    /**
     * Add a request to load the widgets view mode to the socket given
     *
     * @param Event e The event that triggered this listener
     * @param AjaxSocket socket the AjaxSocket we should add to
     */
    socketAdd_getViewPane: function(e, socket)
    {
        if (e) {
            var self = e.data.self;
        } else {
            var self = this;
        }

        // We can highlight this again
        self.canHighlight = true;

        // Go into loading mode ourselves
        self.widget_admin_loading();

        // Populate the data that the server side controller needs
        var data = {
            url: Core.pathManager.getCurrentPath()
        };

        // Add the request to the socket
        socket.addRequest(self.resourceId, 'getViewPane', data);
    },

    /**
     * A client action that gets called from the server to set the view pane
     *
     * @param Object data The data the server sent
     */
    apiSetViewPane: function(data, socket)
    {
        var self = this;
        
        // We can highlight this again
        self.canHighlight = true;

        var finalContent = data['content'] +
            '<script type="text/javascript">$(document).ready(function() {' +
            socket.getEvalStringReference() + '.consumeResponseQueue();' +
            '});</script>';

        // Set the widget content to the returned data
        self.$('.Widget_content_container_admining:first').html(finalContent)        
        self.$widget_container.compile();

        // Remove the loading overlay
        self.widget_admin_doneLoading();
        
        // Tell the ajax socket to halt consumption of other requests until we have finished loading this one
        // We do this by returning true
        return true;
    },



    // Go into loading mode
    widget_admin_loading:function()
    {
        var self = this;
        
        if (self.liveSelector) {
            self.liveSelector.$selector.addClass('{WCLASS}_loading');
            $(self.liveSelector).trigger('hover');
        }
    },

    // Finished loading, remove the overlay
    widget_admin_doneLoading:function()
    {
        var self = this;

        
        if (self.liveSelector) {
            $(self.liveSelector).trigger('unhover');
            self.liveSelector.$selector.removeClass('{WCLASS}_loading');
        }
    },

    /******************************
     * CSS EDITOR FUNCTIONS
     ******************************/
    widget_admin_cssEditor_init:function()
    {
        var self = this;

        // Get DOM references
        self.$widget_admin_cssEditor_css = $('.cssEditor_css');
        self.$widget_admin_cssEditor_save = $('.cssEditor_save');

        self.$widget_admin_cssEditor_images = $('.cssEditor_images');
        self.$widget_admin_cssEditor_image_upload_form = $('.cssEditor_image_upload_form');

        // Bind local events
        self.$widget_admin_cssEditor_save.bind('click', {'self':self}, self.widget_admin_cssEditor_save);

        // Setup image uploader ajaxForm
        self.$widget_admin_cssEditor_image_upload_form.ajaxForm({
            url: self.ajaxUrl+'?action=cssEditorUploadImage',
            success:function(data)
            {
                self.$widget_admin_cssEditor_images.html(data);
            }
        });
    },

    widget_admin_cssEditor_save:function(e)
    {
        var self = e.data.self;

        var socket = new AjaxSocket();
        var data = {
            css: self.$widget_admin_cssEditor_css.val()
        };
        socket.addRequest(self.resourceId, 'cssEditorSave', data, function() {
                // Trigger the thing to renter edit to get css changes
                // TODO: Integrate this action into the original add request
                var socket2 = new AjaxSocket();

	            // Reload the site preview
	            $(document).trigger(self.resourceId + '_socketAdd_edit', socket2);

	            socket2.fire();
            });

        socket.fire();
    }
});


PageBlock.prototype = new Widget();

function PageBlock() {
    Widget.apply(this, arguments);
}

$.extend(PageBlock.prototype, {

    construct:function(params)
    {
        var self = this;
        
        if (params.admin) {
            // make live selector color different for layers
            self.liveSelector.setColor('#666666');
        }
    },
    
    edit_init:function()
    {
        var _this = this;
                
        // Get any DOM references we need
        _this.$widgetDropZones = $('.WidgetDropZone_editing', _this.$widget_container);
                
        // Just in case this finished loading after the extra tools, tell the widgets the lists are available
        $('.PageBlock_ExtraTools_widget_drag').draggable('option', 'connectToSortable', '.WidgetDropZone_sortable');
        
        // Bind any global listeners, only once though
        if (!_this.edit_globallyBound) {

            // Bind any drop zone telling us we should save
            $(document).bind(_this.resourceId + '_edit_save', {'_this':_this}, _this.edit_save);

            _this.edit_globallyBound = true;
        }
    },
    
    edit_save:function(e)
    {
        var _this = e.data._this;
        
        // Get a list of drop zones and their contained resource ids in order
        var dropZones = { };
        
        // Loop through the drop zones, setting the first index as the dz's resource id
        _this.$widgetDropZones.each(function()
        {
            var $this = $(this);
            var dropZoneResourceId = $this.attr('rel');
            // Create a new array in that location to store the items of the drop zone
            dropZones['dz_' + dropZoneResourceId] = [];
            // Loop through the widgets in this drop zone
                        
            $this.children().children('li').each(function()
            {
                var $itemThis = $(this);
                if ($itemThis.attr('class') == 'WidgetDropZone_item') {
                    var id = $itemThis.find('.Widget_dropZone_toolbar').attr('rel');
                } else {
                    var id = $itemThis.find('.PageBlock_ExtraTools_widget').attr('rel');
                }
                
                // Add the widget resource id into this array
                dropZones['dz_' + dropZoneResourceId].push(id);
            });
            // Join these together as a nice string
            dropZones['dz_' + dropZoneResourceId] = dropZones['dz_' + dropZoneResourceId].join(',');
        });
        
        dropZones['url'] = Core.pathManager.getCurrentPath();
        
        // Create a new socket to do our bidding
        var socket = new AjaxSocket();
        
        // Add the request to save the drop zone configs
        socket.addRequest(_this.resourceId, 'editSave', dropZones, function(data)
        {
            var data = eval('(' + data + ')');
            
            // If we have new widgets, lets replace their temporary holders
            $.each(data, function()
            {
                var type = this.type;
                var html = this.html;
                var rid = this.rid;
                                    
                // Find the first temp widget matching this description
                $(".PageBlock_ExtraTools_widget[rel='new_" + type + "']", _this.$widget_container).parent().replaceWith('<li class="WidgetDropZone_item" rel="' + rid + '">' + html + '</li>');                
            });
            
            // Tell the drop zones to rebind remove
            _this.$widgetDropZones.each(function()
            {
                var $this = $(this);
                var dropZoneResourceId = $this.attr('rel');
                ResourceManager.get(dropZoneResourceId).edit_bindRemoveIcons();
                ResourceManager.get(dropZoneResourceId).edit_bindEditIcons();
            });
        });
        
        // Add a request to reload the env explorer block
        $(document).trigger(_this.resourceId + '_envExplorer_socket_reload', socket);
        
        socket.fire();
    },
    
    /**
     * Initialize this resources extra tools panel
     */
    extraTools_init:function()
    {
        var _this = this;
                
        _this.$extraTools_widgets = $('.PageBlock_ExtraTools_widget_drag');
        _this.$extraTools_widgets.draggable({
            connectToSortable: '.WidgetDropZone_sortable',
            helper: 'clone',
            revert: 'invalid',
            zIndex: 10000,
            appendTo: 'body',
            start: function()
            {
                $('.WidgetDropZone_sortable').sortable( 'refreshPositions');
            }
            
        });
    },
    
    /**
     * Initialize this resources layout tools tab
     */
    layoutTools_init:function()
    {
        var _this = this;
        
        // Get our DOM references
        _this.$extraTools_layout_form = $('.PageBlock_ExtraTools_layout_form');
        _this.$extraTools_layoutControls_form = $('.PageBlock_ExtraTools_layoutControls_form');
        
        _this.$extraTools_layout_form.ajaxForm({
            url: _this.ajaxUrl + '?action=extraToolsLoadLayout',
            success:function(data)
            {
                _this.extraTools_layoutLoaded(data);
            }
        });

        _this.$extraTools_layoutControls_form.ajaxForm({
            url: _this.ajaxUrl + '?action=extraToolsSaveLayoutControls',
            success:function(data)
            {
                _this.extraTools_layoutControlsSaved(data);
            }
        });
    },
    
    /** 
     * Called after our ajaxForm tries to load our new layout file
     *
     * @param data Any data the submission returned
     */
    extraTools_layoutLoaded:function(data)
    {
        var _this = this;
        
        // We have some things to refresh from the server
        var socket = new AjaxSocket();
        
        // Reload the toolbox
        Core.adminMode.toolbox.socketAdd_reload(socket);
        
        // Reload the site preview
        $(document).trigger(_this.resourceId + '_socketAdd_edit', socket);
        
        // Reload the environment explorer block
        $(document).trigger(_this.resourceId + '_envExplorer_socket_reload', socket);

        socket.fire();
    },
    
    extraTools_layoutControlsSaved:function(data)
    {
        var _this = this;
        
        var socket = new AjaxSocket();

        // Reload the site preview
        $(document).trigger(_this.resourceId + '_socketAdd_edit', socket);
        
        socket.fire();
    },
    
    /**
     * Override to make the layer selector different
     */
    configureLiveSelector: function()
    {
        var _this = this;
        _this.liveSelector.addTag('layer');        
        return _this.liveSelector;
    }
});


WidgetDropZone.prototype = new Widget();

function WidgetDropZone() {
    Widget.apply(this, arguments);
}

$.extend(WidgetDropZone.prototype, {

    construct:function(params)
    {
        var self = this;
        
        if (params.pageBlockEditing) {
            self.edit_init(params.parent);
        }
    },
    
    /**
     * Initialize the drop zone for action
     */
    edit_init:function(parentResourceId)
    {        
        var self = this;
        
        self.parentResourceId = parentResourceId;
        
        // Grab our DOM references
        self.$widgetDropZone = $('.WidgetDropZone_' + self.resourceId);
        self.$widgetDropZone_sortable = $('.WidgetDropZone_sortable', self.$widgetDropZone);
        
        // Make the list sortable
        self.$widgetDropZone_sortable.sortable({
            handle: '.PageBlock_ExtraTools_widget,.Widget_dropZone_toolbar',
            connectWith: '.WidgetDropZone_sortable',
            placeholder: 'WidgetDropZone_drag_placeHolder',
            forcePlaceholderSize: true,
            appendTo: 'body',
            zIndex: 10000,
            stop: function()
            {                                
                // Trigger the parent page block to save the locations of the widgets
                $(document).trigger(self.parentResourceId + '_edit_save');   
            } 
        });
        
        self.edit_bindRemoveIcons();
        self.edit_bindEditIcons();
    },
    
    edit_bindRemoveIcons:function()
    {
        var self = this;
        
        // Bind all of the remove icons to the remove widget handler
        self.$widgetDropZone.find('.Widget_dropZone_remove').unbind().bind('click', {'self':self}, self.edit_removeWidget);  
    },
    
    edit_bindEditIcons:function()
    {
        var self = this;
        
        self.$widgetDropZone.find('.Widget_dropZone_edit').unbind().bind('click', {'self':self}, self.edit_editWidget);
    },
    
    edit_removeWidget:function(e)
    {
        var self = e.data.self;
        
        // Make sure we really want to do this
        if (!confirm('Are you sure you want to remove this widget?')) { return; }
        
        // Grab the drop zone list element we need to remove
        var $dzItem = $(this).parents('li.WidgetDropZone_item');
        
        // Animate the item away
        $dzItem.slideUp('normal', function()
        {
            $dzItem.remove();
            
            // Tell the parent block we want to remove this widget, 
            $(document).trigger(self.parentResourceId + '_edit_save');
        });
    },
    
    edit_editWidget:function(e)
    {
        varself = e.data.self;
        
        // Grab the drop zone list element we need to edit
        var $dzItem = $(this).parents('li.WidgetDropZone_item');
        
        // Trigger edit
        Core.adminMode.editResource($dzItem.attr("rel"));
    }
    
});
Widget.prototype = new ControllerBase();

function Widget() {
    ControllerBase.apply(this, arguments);
    this.addClassName('Widget');
    this.widget_admin_globallyBound = false;
}

$.extend(Widget.prototype, {
    
    widgetConstruct:function(params)
    {
        var self = this;
                
        // Set reference to parent widget
        if (params.parent && params.parent != 'null') {
            self.parentWidget = ResourceManager.get(params.parent);
        }
        
        self.isVisible = params.visible;
        self.name = params.name;
        
        // Grab our custom widget bindings
        self.$widget_container = $('.Resource_' + self.resourceId);
        self.$widget_content_container = self.$widget_container;
        self.$widget_content = self.$widget_content_container;
        
        console.log('WIDGET CONSTRUCT: ' + self.classNames[self.classNames.length-1]);
        
        // Create the modal overlay
        if (!self.modalOverlay && self.$widget_container.length > 0 && self.isVisible) {
            self.modalOverlay = new ModalOverlay(self.$widget_container);
        } else if (self.modalOverlay) {
            self.modalOverlay.setContent(self.$widget_container);
        }
                
        // Create the live selector for this widget
        if (!self.liveSelector && Core.rootPageBlock == self) {
            // This is the root block
            self.liveSelector = new LiveSelector(self.$widget_container, self.name, $('body'));
            self.configureLiveSelector();
            self.liveSelector.disable();
        } else if (!self.liveSelector && self.parentWidget && self.$widget_container.length > 0 && self.isVisible) {
            // A regular old pageblock or widget
            self.liveSelector = new LiveSelector(self.$widget_container, self.name, self.parentWidget.liveSelector);
            self.configureLiveSelector();
        } else if (self.liveSelector) {
            // We need to update the live selector with the new information so it doesn't destroy itself
            self.liveSelector.setOrigin(self.$widget_container);
        }
        
        if (params.admin) {
            self.widget_admin_init();
        }
    },

    // Any extra things we need to initialize with a widget if we are in admin mode
    widget_admin_init:function()
    {
        var self = this;
                
        // Start out assuming we can highlight
        self.canHighlight = true;

        // Grab some extra dom handles that will be imporant
        self.$widget_editHighlight = $('.Widget_editHighlight:first', self.$widget_container);
        self.$widget_editIcon = $('.Widget_editIcon:first', self.$widget_container);
        self.$widget_editLabel = $('.Widget_editLabel:first', self.$widget_container);

        // Global Resource events
        if (!self.widget_admin_globallyBound) {
            // We only want to bind these to the global handler once
            // We still need to aquire the DOM references above though
            $(document).bind(self.resourceId + '_highlight', {'self':self}, self.widget_admin_highlight);
            $(document).bind(self.resourceId + '_unhighlight', {'self':self}, self.widget_admin_unhighlight);

            $(document).bind(self.resourceId + '_socketAdd_edit', {'self':self}, self.socketAdd_getEditPane);
            $(document).bind(self.resourceId + '_socketAdd_view', {'self':self}, self.socketAdd_getViewPane);

            self.widget_admin_globallyBound = true;
        }
        
        // If we are in admin mode, we want to bind the clicking of a resource to the edit mode
        if (self.liveSelector && !self.liveSelectorInitialized) {
            
            self.liveSelectorInitialized = true;
            
            // Hackish stuff going on here.
            self.liveSelector.allowInvisibleHovering = true;
            
            $(self.liveSelector).bind('click', function() {
                Core.adminMode.editResource(self.resourceId);
            });
            
            self.liveSelector.setColor('#AC5A5A');
        }

    },
    
    /**
     * Base function for getting the live selector, will be overridden where it needs to
     */
    configureLiveSelector: function()
    {
        var self = this;
        
        self.liveSelector.addTag('widget');
        
        return self.liveSelector;
    },

    // shorthand for adding widget context to a select
    $:function(selection)
    {
        return $(selection, this.$widget_container);
    },
    
    // shorthand for binding dom element events in this widget to methods
    bind:function(selection, event, method)
    {
        if (typeof selection === "object") {
            selection.bind(event, $.proxy(this, method));
        }
        else {
            $(selection, this.$widget_container).bind(event, $.proxy(this, method));
        }
    },

    // shorthand for live binding dom element events in this widget to methods
    live:function(selection, event, method)
    {
        if (typeof selection === "object") {
            selection.live(event, $.proxy(this, method));
        }
        else {
            $(selection, this.$widget_container).live(event, $.proxy(this, method));
        }
    },

    apiReloadWidget:function(newSource)
    {
        var self = this;

        // replace widget with new source
        self.$widget_container.replaceWith(newSource);
        
        // recompile widget
        self.$widget_container.compile();
    },

    /* ********** HIGHLIGHTING FUNCTIONS ********** */

    // Triggered when we mouse over a widget
    widget_admin_mouseOver:function(e)
    {
        var self = e.data.self;

        // Check for no live event editing
        if (!self.isLiveSelectionOn()) { return; }

        // Check if we can highlight
        if (!self.canHighlight) { return; }

        // we are taking the event so stop further propagation
        e.stopPropagation();

        // Show the edit icon and label
        //self.$widget_editIcon.show();
        //self.$widget_editLabel.show();

        // Trigger the highlight event on this resource
        //$(document).trigger(self.resourceId + '_highlight');
    },

    // Trigger when we mouse out a widget
    widget_admin_mouseOut:function(e)
    {
        // Stop the event bubbling
        // e.stopPropagation();

        var self = e.data.self;

        // Dont show the highlight or edit icon
        self.$widget_editIcon.hide();
        self.$widget_editLabel.hide();

        // Trigger the unhighlight event on this resource
        $(document).trigger(self.resourceId + '_unhighlight');
    },

    // Highlight the widget, needs to be a funciton since envExplorer will call it
    widget_admin_highlight:function(e)
    {
        var self = e.data.self;
        
        if (self.liveSelector) {
            $(self.liveSelector).trigger('hover');
        }
    },

    // Unhighlight the widget, needs to be a funciton since envExplorer will call it
    widget_admin_unhighlight:function(e)
    {
       var self = e.data.self;
       
       if (self.liveSelector) {
           $(self.liveSelector).trigger('unhover');
       }
    },


    widget_admin_editIconClick:function(e)
    {
        var self = e.data.self;
        // Tell the core adminmode that we want to edit this widget
        Core.adminMode.editResource(self.resourceId);
    },

    callServerObject:function(apiFunction, data, callback, async)
    {
        var self = this;

        var socket = new AjaxSocket();
        socket.addRequest(self.resourceId, apiFunction, data, callback);
        socket.fire(async);
    },

    /**
     * Add a request to load the widgets edit mode to the socket given
     *
     * @param Event e The event that triggered this listener
     * @param AjaxSocket socket the AjaxSocket we should add to
     */
    socketAdd_getEditPane: function(e, socket)
    {
        var self = e.data.self;

        // We don't want to highlight ourselves anymore
        self.canHighlight = false;

        // Go into loading mode ourselves
        self.widget_admin_loading();

        // Populate the data that the server side controller needs
        var data = {
            url: Core.pathManager.getCurrentPath()
        };

        // Add the request to the socket
        socket.addRequest(self.resourceId, 'getEditPane', data);
    },

    /**
     * A client action that gets called from the server to set the edit pane
     *
     * @param Object data The data the server sent
     */
    apiSetEditPane: function(data, socket)
    {
        var self = this;

        // We don't want to highlight ourselves anymore
        self.canHighlight = false;

        var finalContent = data['content'] +
            '<script type="text/javascript">$(document).ready(function() {' +
            socket.getEvalStringReference() + '.consumeResponseQueue();' +
            '});</script>';

        // Set the widget content to the returned data
        self.$('.Widget_content_container_admining:first').html(finalContent)
        self.$widget_container.compile();

        // Remove the loading overlay
        self.widget_admin_doneLoading();
        
        // Enable modal overlay
        Core.adminMode.setModalOverlay(self.modalOverlay);
        
        // Tell the ajax socket to halt consumption of other requests until we have finished loading this one
        // We do this by returning true
        return true;
    },


    /**
     * Add a request to load the widgets view mode to the socket given
     *
     * @param Event e The event that triggered this listener
     * @param AjaxSocket socket the AjaxSocket we should add to
     */
    socketAdd_getViewPane: function(e, socket)
    {
        if (e) {
            var self = e.data.self;
        } else {
            var self = this;
        }

        // We can highlight this again
        self.canHighlight = true;

        // Go into loading mode ourselves
        self.widget_admin_loading();

        // Populate the data that the server side controller needs
        var data = {
            url: Core.pathManager.getCurrentPath()
        };

        // Add the request to the socket
        socket.addRequest(self.resourceId, 'getViewPane', data);
    },

    /**
     * A client action that gets called from the server to set the view pane
     *
     * @param Object data The data the server sent
     */
    apiSetViewPane: function(data, socket)
    {
        var self = this;
        
        // We can highlight this again
        self.canHighlight = true;

        var finalContent = data['content'] +
            '<script type="text/javascript">$(document).ready(function() {' +
            socket.getEvalStringReference() + '.consumeResponseQueue();' +
            '});</script>';

        // Set the widget content to the returned data
        self.$('.Widget_content_container_admining:first').html(finalContent)        
        self.$widget_container.compile();

        // Remove the loading overlay
        self.widget_admin_doneLoading();
        
        // Tell the ajax socket to halt consumption of other requests until we have finished loading this one
        // We do this by returning true
        return true;
    },



    // Go into loading mode
    widget_admin_loading:function()
    {
        var self = this;
        
        if (self.liveSelector) {
            self.liveSelector.$selector.addClass('Widget_loading');
            $(self.liveSelector).trigger('hover');
        }
    },

    // Finished loading, remove the overlay
    widget_admin_doneLoading:function()
    {
        var self = this;

        
        if (self.liveSelector) {
            $(self.liveSelector).trigger('unhover');
            self.liveSelector.$selector.removeClass('Widget_loading');
        }
    },

    /******************************
     * CSS EDITOR FUNCTIONS
     ******************************/
    widget_admin_cssEditor_init:function()
    {
        var self = this;

        // Get DOM references
        self.$widget_admin_cssEditor_css = $('.cssEditor_css');
        self.$widget_admin_cssEditor_save = $('.cssEditor_save');

        self.$widget_admin_cssEditor_images = $('.cssEditor_images');
        self.$widget_admin_cssEditor_image_upload_form = $('.cssEditor_image_upload_form');

        // Bind local events
        self.$widget_admin_cssEditor_save.bind('click', {'self':self}, self.widget_admin_cssEditor_save);

        // Setup image uploader ajaxForm
        self.$widget_admin_cssEditor_image_upload_form.ajaxForm({
            url: self.ajaxUrl+'?action=cssEditorUploadImage',
            success:function(data)
            {
                self.$widget_admin_cssEditor_images.html(data);
            }
        });
    },

    widget_admin_cssEditor_save:function(e)
    {
        var self = e.data.self;

        var socket = new AjaxSocket();
        var data = {
            css: self.$widget_admin_cssEditor_css.val()
        };
        socket.addRequest(self.resourceId, 'cssEditorSave', data, function() {
                // Trigger the thing to renter edit to get css changes
                // TODO: Integrate this action into the original add request
                var socket2 = new AjaxSocket();

	            // Reload the site preview
	            $(document).trigger(self.resourceId + '_socketAdd_edit', socket2);

	            socket2.fire();
            });

        socket.fire();
    }
});

MetaContentTag.prototype = new Widget();

function MetaContentTag() {
    Widget.apply(this, arguments);
}

PageBlockCollection.prototype = new Widget();

function PageBlockCollection() {
    Widget.apply(this, arguments);
}

$.extend(PageBlockCollection.prototype, {
    
    construct:function(params)
    {
        var _this = this;
                        
       //if (!_this.collection_globallyBound) {
            // TODO - Investigate - $(document).bind(_this.resourceId + '_loadSelection', {'_this':_this}, _this.loadSelection);
        //}
    },
    
    apiExtraToolsInit:function()
    {
        var _this = this;
                
        // Grab our DOM references
        _this.$extraTools_defaultPageBlock = $('.PageBlockCollection_ExtraTools_defaultPageBlock');
        _this.$extraTools_defaultPageBlockSave = $('.PageBlockCollection_ExtraTools_defaultPageBlockSave');
        _this.$extraTools_defaultPageBlockSpinner = $('.PageBlockCollection_ExtraTools_defaultPageBlockSpinner');
        
        _this.$extraTools_notFoundPageBlock = $('.PageBlockCollection_ExtraTools_notFoundPageBlock');
        _this.$extraTools_notFoundPageBlockSpinner = $('.PageBlockCollection_ExtraTools_notFoundPageBlockSpinner');
        _this.$extraTools_notFoundPageBlockSave = $('.PageBlockCollection_ExtraTools_notFoundPageBlockSave');
        
        _this.$extraTools_add_name = $('.PageBlockCollection_ExtraTools_add_name');
        _this.$extraTools_add = $('.PageBlockCollection_ExtraTools_add');
        
        _this.$extraTools_PageBlock_edits = $('.PageBlockCollection_ExtraTools_PageBlock_edit');
        _this.$extraTools_edit_container = $('.PageBlockCollection_ExtraTools_edit_container');
        _this.$extraTools_edit_newName = $('.PageBlockCollection_ExtraTools_edit_newName');
        _this.$extraTools_edit_oldName = $('.PageBlockCollection_ExtraTools_edit_oldName');
        _this.$extraTools_edit_errorMessage = $('.PageBlockCollection_ExtraTools_edit_errorMessage');
        _this.$extraTools_edit = $('.PageBlockCollection_ExtraTools_edit');
        
        // Bind any locally triggered listeners
        _this.$extraTools_defaultPageBlockSave.bind('click', {_this:_this}, _this.extraTools_defaultPageBlockSave);
        _this.$extraTools_notFoundPageBlockSave.bind('click', {_this:_this}, _this.extraTools_notFoundPageBlockSave);
        
        _this.$extraTools_add.bind('click', {'_this':_this}, _this.extraTools_add);
        _this.$extraTools_PageBlock_edits.bind('click', {'_this':_this}, _this.extraTools_edit);
        _this.$extraTools_edit.bind('click', {'_this':_this}, _this.extraTools_saveEdit);
  
    },

    /**
     * Called by dynamic list when an item is removed from the list
     */
    onDynamicListRemoveItem: function(list, item) {
        var _this = this;
        
        _this.extraTools_removed();
    },
    
    /**
     * Save the default page block
     */
    extraTools_defaultPageBlockSave: function(e)
    {
        var _this = e.data._this;
        
        // Show the spinner
        _this.$extraTools_defaultPageBlockSpinner.show();
        
        // Create a new ajax socket to save the default page block
        var socket = new AjaxSocket();
        var data = {
            pageBlockResourceId: _this.$extraTools_defaultPageBlock.val()
        };
        
        // Add the api request
        socket.addRequest(_this.resourceId, 'saveDefaultPageBlock', data, function(data)
        {
            _this.$extraTools_defaultPageBlockSpinner.hide();
        });
        
        // Reload the site preview
        $(document).trigger(_this.resourceId + '_socketAdd_edit', socket);
        
        // Reload the environment explorer block
        $(document).trigger(_this.resourceId + '_envExplorer_socket_reload', socket);
        
        // Fire the socket
        socket.fire();
    },
    
    /**
     * Save the not found page block
     */
    extraTools_notFoundPageBlockSave: function(e)
    {
        var _this = e.data._this;
        
        // Show the spinner
        _this.$extraTools_notFoundPageBlockSpinner.show();
        
        // Create a new ajax socket to save the default page block
        var socket = new AjaxSocket();
        var data = {
            pageBlockResourceId: _this.$extraTools_notFoundPageBlock.val()
        };
        
        // Add the api request
        socket.addRequest(_this.resourceId, 'saveNotFoundPageBlock', data, function(data)
        {
            _this.$extraTools_notFoundPageBlockSpinner.hide();
        });
        
        // Reload the site preview
        $(document).trigger(_this.resourceId + '_socketAdd_edit', socket);
        
        // Reload the environment explorer block
        $(document).trigger(_this.resourceId + '_envExplorer_socket_reload', socket);
        
        // Fire the socket
        socket.fire();
    },
    
    /**
     * Add the new pageblock into the collection
     *
     * @param Event e The event that triggered this listener
     */
    extraTools_add: function(e)
    {
        var _this = e.data._this;
        
        // Get the widget and drop zone we are using
        var name = _this.$extraTools_add_name.val();
        
        // Create the socket for our serverside requests
        var socket = new AjaxSocket();
        
        // Send the request to actually add the page block
        socket.addRequest(
            _this.resourceId, 
            'addPageBlock', 
            {
                name: name
            },
            function(data)
            {
                data = eval('(' + data + ')');
                if (!data.success) {
                    alert(data.message);
                }   
            }
        );
        
        // Add the request to reload the toolbox
        Core.adminMode.toolbox.socketAdd_reload(socket);
        
        // Reload the site preview
        $(document).trigger(_this.resourceId + '_socketAdd_edit', socket);
        
        // Reload the environment explorer block
        $(document).trigger(_this.resourceId + '_envExplorer_socket_reload', socket);
        
        // Execute all these things
        socket.fire();
    },
    
    /**
     * Edit an existing PageBlock in the collection
     *
     * @param Event e The event that triggered this listener
     */
    extraTools_edit: function(e)
    {
        var _this = e.data._this;
        _this.extraTools_showEditForm($(this).attr('rel'));
    },
    
    /**
     * Show the edit form with the name we prove
     *
     * @param The name of the PageBlock we want to edit
     */
    extraTools_showEditForm: function(name)
    {
        var _this = this;
        
        _this.$extraTools_edit_container.hide();
        
        // Set the name
        _this.$extraTools_edit_oldName.val(name);
        _this.$extraTools_edit_newName.val(name);
        
        // Slide the thing down
        _this.$extraTools_edit_container.slideDown();
    },
    
    extraTools_saveEdit: function(e)
    {
        var _this = e.data._this;
                
        var ajaxOptions = {
            url: _this.ajaxUrl,
            type: 'post',
            data: {
                action: 'editPageBlock',
                oldName: _this.$extraTools_edit_oldName.val(),
                newName: _this.$extraTools_edit_newName.val()   
            },
            dataType: 'json',
            success: function(data)
            {
                if (data.success) {
                    
                    // Everything went well so lets refresh some things
                    var socket = new AjaxSocket();
                    
                    // Refresh the toolbox
                    Core.adminMode.toolbox.socketAdd_reload(socket);
                    
                    // Refresh the site preview
                    $(document).trigger(_this.resourceId + '_socketAdd_edit', socket);
                    
                    // Refresh the environment explorer
                    $(document).trigger(_this.resourceId + '_envExplorer_socket_reload', socket);
                    
                    socket.fire();
                    
                } else {                    
                    // Something went wrong, display what in the edit_errorMessage
                    _this.$extraTools_edit_errorMessage.html(data.message);
                    _this.$extraTools_edit_errorMessage.slideDown('normal', function()
                    {
                        setTimeout(function()
                        {
                            _this.$extraTools_edit_errorMessage.slideUp();
                        }, 5000); 
                    });   
                }
            }
        };
        
        $.ajax(ajaxOptions);
    },
    
    /**
     * Triggered as a callback from the DynamicList when a pageblock is removed
     */
    extraTools_removed: function(e)
    {
        var _this = this;        
        
        var socket = new AjaxSocket();
        
        // Reload the site preview
        $(document).trigger(_this.resourceId + '_socketAdd_edit', socket);
        
        // Reload the environment explorer block
        $(document).trigger(_this.resourceId + '_envExplorer_socket_reload', socket);
        
        // Execute all these things
        socket.fire();   
    }
    
    
});


AdminMode_Manager.prototype = new Widget();

function AdminMode_Manager() {
    Widget.apply(this, arguments);
}

$.extend(AdminMode_Manager.prototype, {
    
    construct: function(params)
    {
        var self = this;

        self.activeCategory = '';
        self.activeSubCategory = '';
        self.activeWidgetResource = null;
        self.lastExitMode = null;
        self.lastExitModeArg = null;
        self.continuingExit = false;
        
        self.contentPusher_enabled = false;
        
        self.setupBindings(self.$widget_container);
        
        self.checkForMissingWidgets();
        
        // set core reference
        Core.manageMode = self;

        if (params.activeCategory != undefined && params.activeCategory != '') {
            self.loadCategory(params.activeCategory);
        }
        if (params.activeSubCategory != undefined && params.activeSubCategory != '') {
            self.loadSubCategory(params.activeSubCategory);
        }
    },
    
    setupBindings: function(context)
    {
        var self = this;
        
        // create parent div for context to include context dom element in the scope
        var fullContext = context.wrap('<div>').parent();

        self.bind($('.AdminMode_Manager_tab', fullContext), 'click', 'onTabClick');
        self.bind($('.AdminMode_Manager_pulldownOption', fullContext), 'click', 'onCategoryClick');
        self.bind($('.AdminMode_Manager_categorySelected', fullContext), 'click', 'onDropdownClick');
        self.bind($('.AdminMode_Manager_categorySelectOption', fullContext), 'click', 'onChangeCategory');
        self.bind($('.AdminMode_Manager_categoryBarSelect', fullContext), 'mouseleave', 'onDropdownLeave');
        self.bind($('.AdminMode_Manager_subCategoryOption', fullContext), 'click', 'onSubCategoryClick');
        self.bind($('.AdminMode_Manager_subCategoryWidget', fullContext), 'click', 'onWidgetClick');
        self.bind($('.AdminMode_Manager_subCategoryWidget', fullContext), 'mouseover', 'onWidgetMouseover');
        self.bind($('.AdminMode_Manager_subCategoryWidget', fullContext), 'mouseout', 'onWidgetMouseout');
        self.bind($('.AdminMode_Manager_lov', fullContext), 'click', 'onToggleLoggedOutView');
        self.bind($('.AdminMode_Manager_exit', fullContext), 'click', 'onExit');
        self.bind($('.AdminMode_Manager_categoryExitOption', fullContext), 'click', 'onExit');
        
        // remove temporary context wrapper
        context.unwrap();
    },
    
    checkForMissingWidgets: function()
    {
        var self = this;
        var missingList = [];
        
        self.$('.AdminMode_Manager_subCategoryWidget').each(function() {
            var widget = ResourceManager.get($(this).attr('widgetid'));
            
            if (widget == null) {
                missingList.push($(this).attr('widgetid'));
            }
        });
        
        // remove missing widgets from the list
        $.each(missingList, function() {
            self.$('.AdminMode_Manager_subCategoryWidget[widgetid="' + this + '"]').remove();
        });
    },
    
    onTabClick: function(e)
    {
        var self = this;
        var target = $(e.currentTarget);
        
        // toggle categories
        var categories = target.prevAll('.AdminMode_Manager_pulldownCategories:first');
        if (categories.is(':visible')) {
            categories.slideUp(100);
        }
        else {
            categories.slideDown(100);
        }
    },
    
    onDropdownClick: function(e)
    {
        var self = this;
        var target = $(e.currentTarget);
        
        // toggle show dropdown
        var categories = target.nextAll('.AdminMode_Manager_categorySelectOptions:first');
        if (categories.is(':visible')) {
            categories.slideUp(100);
            
            // remove clone from body
            $('.AdminMode_Manager_dropdownClone').remove();
        }
        else {
            // clone dropdown out to body tag so it can cover elements outside the manage tab dom
            var dropdown = target.parent().clone(true);
            dropdown.addClass('AdminMode_Manager_dropdownClone');
            dropdown.appendTo('body');

            categories = dropdown.find('.AdminMode_Manager_categorySelectOptions:first');
            categories.slideDown(100);
        }
    },
    
    onDropdownLeave: function(e)
    {
        var self = this;
        var target = $(e.currentTarget);
        
        var dropdown = target.find('.AdminMode_Manager_categorySelectOptions:first');
        if (dropdown.is(':visible')) {
            dropdown.slideUp(100);

            // remove clone from body
            $('.AdminMode_Manager_dropdownClone').remove();
        }
    },
        
    onCategoryClick: function(e)
    {
        var self = this;
        var target = $(e.currentTarget);

        // close and hide manage tab
        self.$('.AdminMode_Manager_pulldownCategories').hide();
        self.$('.AdminMode_Manager_pulldown').hide();
        
        // hide site builder tab if there is one
        $('#Cms_tab').hide();
        
        // load category
        var categoryName = target.html();
        self.loadCategory(categoryName);
    },
    
    onChangeCategory: function(e)
    {
        var self = this;
        var target = $(e.currentTarget);

        var dropdown = target.parents('.AdminMode_Manager_categorySelectOptions:first');
        if (dropdown.is(':visible')) {
            dropdown.slideUp(100);
            
            // remove clone from body
            $('.AdminMode_Manager_dropdownClone').remove();            
        }
        
        var categoryName = target.html();
        self.loadCategory(categoryName);
    },
    
    loadCategory: function(categoryName)
    {
        var self = this;
        
        // just in case we are currently in edit mode
        if (!self.exitEdit()) {
            
            self.lastExitMode = 'loadCategory';
            self.lastExitModeArg = categoryName;
            return false;
        }

        // hide all categories
        self.$('.AdminMode_Manager_category').hide();
        
        // show category
        self.$('.AdminMode_Manager_category[rel="' + categoryName + '"]').show();
        
        self.activeCategory = categoryName;
        self.activeSubCategory = '';
        self.saveCategorySelection();
        
        self.showLiveSelect();
        
        self.contentPusher_start();
        
        return true;
    },
    
    onSubCategoryClick: function(e)
    {
        var self = this;
        var target = $(e.currentTarget);
        
        // make sure we can exit edit mode if we are in edit mode
        if (!self.exitEdit()) {

            self.lastExitMode = 'onSubCategoryClick';
            self.lastExitModeArg = e;
            return false;
        }
        
        var doClose = false;
        if (target.hasClass('AdminMode_Manager_subCategorySelected')) {
            
            // selecting item that is already selected
            doClose = true;
        }
        
        // clear previous selection
        self.$('.AdminMode_Manager_subCategorySelected').removeClass('AdminMode_Manager_subCategorySelected');

        if (!doClose) {

            // load subcategory
            var subCategoryName = target.html();
            self.loadSubCategory(subCategoryName);
        }
        else {
        
            // we no longer have an active sub category
            self.activeSubCategory = '';
        
            // hide widget select bar
            self.$('.AdminMode_Manager_widgetSelectBar').hide();
            
            // refresh live select positioning
            self.refreshLiveSelect();
            self.contentPusher_push(false);
        }
    },    

    loadSubCategory: function(subCategoryName)
    {
        var self = this;
        
        // just in case we are currently in edit mode
        if (!self.exitEdit()) {
            
            self.lastExitMode = 'loadSubCategory';
            self.lastExitModeArg = subCategoryName;
            return false;
        }

        // get current category dom object
        var category = self.$('.AdminMode_Manager_category[rel="' + self.activeCategory + '"]:first');
        
        // hide context bar
        self.hideContextBar();
        
        // hide all subcategories
        $('.AdminMode_Manager_subCategoryWidgets', category).hide();
        
        // unselect all sub category options
        self.$('.AdminMode_Manager_subCategorySelected').removeClass('AdminMode_Manager_subCategorySelected');

        // select sub category option
        self.$('.AdminMode_Manager_subCategoryOption[rel="' + subCategoryName + '"]:first').addClass('AdminMode_Manager_subCategorySelected');
        
        var subCategory = $('.AdminMode_Manager_subCategoryWidgets[rel="' + subCategoryName + '"]:first', category);
        var quickedit = false;
        
        // if there is only one widget in the sub category
        var widget = subCategory.find('.AdminMode_Manager_subCategoryWidget');
        if (widget.length == 1) {
            
            // if quick edit is enabled on the one widget
            if (widget.attr('quickedit') == 'true') {
                quickedit = true;
            }
        }
                
        self.activeSubCategory = subCategoryName;
        self.saveCategorySelection();

        if (quickedit) {
            // hide category widget select bar
            $('.AdminMode_Manager_widgetSelectBar', category).hide();

            self.editWidget(widget.attr('widgetId'));
        }
        else {
        
            // show sub category within category
            subCategory.show();

            // show category widget select bar
            $('.AdminMode_Manager_widgetSelectBar', category).show();
            
            // refresh live select positioning
            self.refreshLiveSelect();
            self.contentPusher_push(false);
        }
        
        return true;
    },
    
    onWidgetClick: function(e)
    {
        var self = this;
        var widgetId = $(e.currentTarget).attr('widgetId');
        
        if (self.activeWidgetResource !== null) {
            if (widgetId == self.activeWidgetResource.resourceId) {
                
                // clicked on widget that is already in edit mode so exit edit
                if (!self.exitEdit()) {
                    self.lastExitMode = 'exitEdit';
                    self.lastExitModeArg = null;
                }
                return;
            }
        }
        self.editWidget(widgetId);
    },
    
    onWidgetMouseover: function(e)
    {
        var self = this;
        var widgetId = $(e.currentTarget).attr('widgetId');
        var widget = ResourceManager.get(widgetId);
        $(widget.liveSelector).trigger('hover');
    },
    
    onWidgetMouseout: function(e)
    {
        var self = this;
        var widgetId = $(e.currentTarget).attr('widgetId');
        var widget = ResourceManager.get(widgetId);
        $(widget.liveSelector).trigger('unhover');
    },
    
    editWidget: function(widgetId)
    {
        var self = this;
        
        // need a recursive block since the call to load sub category from this method
        // could lead to re-entry for widgets in quick edit mode
        if (self.inEditWidget) {
            return true;
        }
        self.inEditWidget = true;

        // just in case we are currently in edit mode
        if (!self.exitEdit()) {

            self.lastExitMode = 'editWidget';
            self.lastExitModeArg = widgetId
            self.inEditWidget = false;
            return false;
        }
        
        // hide current context bar if showing
        self.hideContextBar();
        
        // get current category dom object
        var category = self.$('.AdminMode_Manager_category[rel="' + self.activeCategory + '"]:first');
        var subCategory;
        var widget;
        
        var widgetFound = false;
        if (self.activeSubCategory != '') {
            // get current sub category within category
            subCategory = category.find('.AdminMode_Manager_subCategoryWidgets[rel="' + self.activeSubCategory + '"]:first');

            // get widget in sub category
            widget = subCategory.find('.AdminMode_Manager_subCategoryWidget[widgetId="' + widgetId + '"]:first');
            
            if (widget.length != 0) {
                widgetFound = true;
            }
        }

        if (!widgetFound) {
        
            // no sub category selected or widget not found in current sub category so search for first
            // place the widget shows up in the category tree
            widget = category.find('.AdminMode_Manager_subCategoryWidget[widgetId="' + widgetId + '"]:first');
            
            if (widget.length == 0) {
            
                // widget not found, try to find it in another category
                widget = self.$('.AdminMode_Manager_subCategoryWidget[widgetId="' + widgetId + '"]:first');
                
                if (widget.length == 0) {
                
                    // widget not found
                    self.inEditWidget = false;
                    return false;
                }
                else {
                
                    // found widget in another category so switch category
                    var category = widget.parents('.AdminMode_Manager_category:first').attr('rel');
                    
                    // set category
                    self.loadCategory(category);
                }
            }
            
            // get the sub category we found the widget in
            subCategory = widget.parents('.AdminMode_Manager_subCategoryWidgets:first');
            
            // load the sub category. note: this could generate a recursive call for quick edit which is
            // blocked at the beginning of this function
            self.loadSubCategory(subCategory.attr('rel'));
        }
                
        // highlight sub category widget select
        widget.addClass('AdminMode_Manager_widgetSelected');
        
        // get widget resource object
        var widgetResource = ResourceManager.get(widgetId);

        self.hideLiveSelect();
        
        // if widget has a custom view then go get it
        var callback = widget.attr('callback');
        if (callback != '') {
            widgetResource.callServerObject(callback, {
                category: self.activeCategory,
                subCategory: self.activeSubCategory,
                name: widget.html()
                },
                function(response) {
                    
                    widgetView = $(response);

                    self.showContextBar(widgetView);
                    
                    // Push the site content down after a short delay
                    // For some reason the styles do not take immediately so we can get
                    // a height calculation based on unstyled content if we do this immediately
                    setTimeout(function() {
                        self.contentPusher_push(false);
                    }, 50);
                    
                    // Modal overlay
                    self.overlay = widgetResource.modalOverlay;
                    if (self.overlay) {
                        self.overlay.show();
                        $(self.overlay).unbind('click');
                        $(self.overlay).bind('click', function() {
                            self.exitEdit();
                        });
                    }
                    
                    // tell widget we are entering manage mode
                    if (typeof(widgetResource.manage_onEnter) != 'undefined') {
                        widgetResource.manage_onEnter(widgetView, self.activeCategory, self.activeSubCategory, $.trim(widget.html()));
                    }
                }
            );
        }
        else {
        
            // Modal overlay
            self.overlay = widgetResource.modalOverlay;
            if (self.overlay) {
                self.overlay.show();
                $(self.overlay).unbind('click');
                $(self.overlay).bind('click', function() {
                    self.exitEdit();
                });
            }
        
            // tell widget we are entering manage mode
            if (typeof(widgetResource.manage_onEnter) != 'undefined') {
                widgetResource.manage_onEnter(null, self.activeCategory, self.activeSubCategory, $.trim(widget.html()));
            }
        }
                
        self.activeWidgetResource = widgetResource;
        self.inEditWidget = false;
        
        return true;
    },
    
    deleteWidget: function(widgetId)
    {
        var self = this;
        
        // check every category widget bar
        self.$('.AdminMode_Manager_subCategoryWidgets').each(function() {
            var subCategory = $(this);
            
            // delete all instances of the widget from widgets
            subCategory.find('.AdminMode_Manager_subCategoryWidget[widgetid="' + widgetId + '"]').remove();
            
            // if no widgets left in sub category
            if (subCategory.find('.AdminMode_Manager_subCategoryWidget').length == 0) {
                
                // get subcategory name and parent category
                var subCategoryName = subCategory.attr('rel');
                var category = subCategory.parents('.AdminMode_Manager_category:first');
                
                // remove sub category and sub category option
                category.find('.AdminMode_Manager_subCategoryOption[rel="' + subCategoryName + '"]').remove();
                subCategory.remove();
            }
        });
    },
    
    // Renames the widget title set in the widget select bar in every category and sub category
    // You must specify the old name to prevent accidentally changing custom names created by the widget
    renameWidget: function(widgetId, oldName, newName)
    {
        var self = this;
        
        // rename every matching instance
        self.$('.{CLASS_subCategoryWidget[widgetid="' + widgetId + '"]').each(function() {
            var widget = $(this);
            
            if ($.trim(widget.html()).toLowerCase() == oldName.toLowerCase()) {
                widget.html(newName);
            }
        });
    },
    
    showContextBar: function(content)
    {
        var self = this;
        
        var contextBar = self.$('.AdminMode_Manager_contextBar');
        contextBar.html(content);
        contextBar.show();
    },
    
    hideContextBar: function()
    {
        var self = this;
        
        self.$('.AdminMode_Manager_contextBar').hide();
        
        // also deselect all widget selections
        self.$('.AdminMode_Manager_subCategoryWidget').removeClass('AdminMode_Manager_widgetSelected');
    },
    
    hideAll: function()
    {
        var self = this;
        
        // hide all categories
        self.$('.AdminMode_Manager_category').hide();

        // hide all subcategory widget select bars
        self.$('.AdminMode_Manager_widgetSelectBar').hide();
        
        // hide all subcategories
        self.$('.AdminMode_Manager_subCategoryWidgets').hide();
        
        // deselect sub categories and widgets
        self.$('.AdminMode_Manager_subCategorySelected').removeClass('AdminMode_Manager_subCategorySelected');
        self.$('.AdminMode_Manager_widgetSelected').removeClass('AdminMode_Manager_widgetSelected');
        
        // hide context bar
        self.hideContextBar();        
    },

    exitEdit: function()
    {
        var self = this;
        
        // make sure now dropdown clones hanging around
        $('.AdminMode_Manager_dropdownClone').remove();            
        
        if (self.activeWidgetResource != null) {

            // tell widget we are exiting manage mode
            if (!self.continuingExit && typeof(self.activeWidgetResource.manage_onExit) != 'undefined') {
                if (self.activeWidgetResource.manage_onExit() === false) {
                    
                    // widget wants to cancel exit
                    return false;
                }
            }
        }
        
        if (self.overlay) {
            self.overlay.hide();
        }
        
        // hide context view
        self.hideContextBar();
        
        // unselect widget
        self.$('.AdminMode_Manager_widgetSelected').removeClass('AdminMode_Manager_widgetSelected');
        
        self.activeWidgetResource = null;
        self.exitMode = null;
        
        self.lastExitMode = null;
        self.lastExitModeArg = null;

        self.showLiveSelect();
        
        return true;
    },
    
    // Used to resume exiting after returning false from manage_onExit. This is necessary
    // when the widget manage_onExit needs to use a non-blocking means (like a dialog) to determine if
    // the widget should cancel the exit.
    continueExit: function()
    {
        var self = this;
        
        self.continuingExit = true;
        if (self.lastExitMode !== null) {
            self[self.lastExitMode].call(self, self.lastExitModeArg);
        }
        else {
            // no last exit mode so just exit
            self.exitEdit();
        }
        self.continuingExit = false;
    },
    
    onExit: function(e)
    {
        var self = this;
        
        if (!self.exitEdit()) {
        
            self.lastExitMode = 'onExit';
            self.lastExitModeArg = e;
            return false;
        }
        
        self.contentPusher_end();
        
        self.hideLiveSelect();
        self.hideAll();
        
        // make sure dropdowns are all collapsed
        self.$('.AdminMode_Manager_categorySelectOptions').hide();
        
        self.activeCategory = '';
        self.activeSubCategory = '';
        self.saveCategorySelection();
        
        // show manage tab
        self.$('.AdminMode_Manager_pulldown').show();

        // show site builder tab if there is one
        $('#Cms_tab').show();
    },
    
    onToggleLoggedOutView: function(e)
    {
        var self = this;
        
        self.callServerObject('toggleLoggedOutView', {}, function() {
        
            // reload the page
            location.href = location.href;
        });
    },
    
    showLiveSelect: function()
    {
        var self = this;
        var rootSelector = Core.rootPageBlock.liveSelector;
                
        // Loop through the categories
        self.$('.AdminMode_Manager_category').each(function() {
            var $category = $(this);
            var categoryName = $category.attr('rel');
            var categoryTag = LiveSelector.taggify(categoryName);
                        
            // Loop through the sub categories
            $category.find('.AdminMode_Manager_subCategoryWidgets').each(function() {
                var $subCategory = $(this);
                var subCategoryName = $subCategory.attr('rel');
                var subCategoryTag = LiveSelector.taggify(categoryName, subCategoryName);
                                
                // Loop through the widgets in the sub category
                $subCategory.find('.AdminMode_Manager_subCategoryWidget').each(function() {
                    var widget = ResourceManager.get($(this).attr('widgetid'));
                    
                    if (widget != null && widget.liveSelector) {
                        // Add in the tags
                        widget.liveSelector.clearTags();
                        widget.liveSelector.addTag(categoryTag);
                        widget.liveSelector.addTag(subCategoryTag);
                        
                        // Bind to edit widget
                        $(widget.liveSelector).unbind();
                        $(widget.liveSelector).bind('click', function() {
                            Core.manageMode.editWidget(widget.resourceId);
                        });
                    }
                });
            });
        });
        
        rootSelector.refreshDisplay();
        rootSelector.clearFilter();
        rootSelector.addFilter(LiveSelector.taggify(self.activeCategory));
        rootSelector.highlightCandidates();
        rootSelector.enable();
    },
    
    hideLiveSelect: function()
    {
        var self = this;
        var rootSelector = Core.rootPageBlock.liveSelector;
        rootSelector.disable();
    },
    
    refreshLiveSelect: function()
    {
        var self = this;
        var rootSelector = Core.rootPageBlock.liveSelector;
        rootSelector.refreshDisplay();
    },
    
    saveCategorySelection: function()
    {
        var self = this;

        // save category selection as cookies
        document.cookie = "amm_catSel=" + self.activeCategory + "; path=/";
        document.cookie = "amm_subCatSel=" + self.activeSubCategory + "; path=/";
    },
    
    contentPusher_start: function()
    {
        var self = this;
        
        // We dont want to enable the content pusher more than once
        if (self.contentPusher_enabled) { return ;}
        self.contentPusher_enabled = true;
        
        self.bodyTopMargin = parseInt($('body').css('margin-top'));
        
        // Do a content push and then schedule the next one
        self.contentPusher_push(true);
    },
    
    contentPusher_end: function()
    {
        var self = this;
        self.contentPusher_enabled = false;
        $('body').css('margin-top', self.bodyTopMargin);
    
        // Force explicit reflow (IE7)
        document.body.className = document.body.className;
    },
    
    contentPusher_push: function(scheduleNextUpdate)
    {
        var self = this;
        
        var height = parseInt($('.AdminMode_Manager_managerContainer').height());
        var newMargin = self.bodyTopMargin + height;
        var oldMargin = parseInt($('body').css('margin-top'));
                
        if (oldMargin != newMargin) {
        
            // set new margin
            $('body').css('margin-top', newMargin);
            
            // get current browser scroll position so we can counter to keep things in the same place as much as possible
            var scroll = $('html').scrollTop();
            if (scroll == 0) {
            
                // some browsers work with body instead of html element
                scroll = $('body').scrollTop();
            }

            // calculate new scroll position
            scroll = scroll + newMargin - oldMargin;
            
            // 1 is the minimum happy number
            if (scroll < 1) { scroll = 1; }
            
            $('html').scrollTop(scroll);
            if ($('html').scrollTop() != scroll) {
            
                // try setting it on body element
                $('body').scrollTop(scroll);
            }

            // tell overlay to update if it exists
            if (self.overlay) {
                self.overlay.refreshDisplay();
            }
            
            // Refresh the live selectors
            self.refreshLiveSelect();

            // Force an explicity reflow because IE7 is teh suck
            document.body.className = document.body.className;
        }

        if (scheduleNextUpdate) {
            setTimeout(function() {
                self.contentPusher_push(true);
            }, 500);
        }
    },
    
    apiLoadNewManageModes: function(render)
    {
        var self = this;
        var newAdditions = $(render);
        
        // check each category
        newAdditions.find('.AdminMode_Manager_category').each(function() {
            var newCategory = $(this);
            var categoryName = newCategory.attr('rel');
            
            // get category from current manage mode dom
            var category = self.$('.AdminMode_Manager_category[rel="' + categoryName + '"]');
            
            // if category doesn't exist already
            if (category.length == 0) {
                
                // add new category to manage area
                self.$('.AdminMode_Manager_category:last').after(newCategory);
                
                // add new category pulldown option to manage tab
                var categoryOption = newAdditions.find('.AdminMode_Manager_pulldownOption[rel="' + categoryName + '"]');
                self.$('.AdminMode_Manager_pulldownOption:last').after(categoryOption);
                
                // bind events on new dom elements added
                self.setupBindings(newCategory);
                self.setupBindings(categoryOption);
                
                // update category select options in each category
                self.$('.AdminMode_Manager_categorySelectOptions').each(function() {
                    var options = $(this);
                    var categoryName = options.parents('.AdminMode_Manager_category:first').attr('rel');
                    
                    // comprehensive check of all other category options out there
                    self.$('.AdminMode_Manager_categorySelectOption').each(function() {
                        var categoryOption = $(this);
                        var optionName = categoryOption.attr('rel');
                        
                        // check if category option is not part of current category
                        if (options.find('.AdminMode_Manager_categorySelectOption[rel="' + optionName + '"]').length == 0) {
                                                    
                            // add option to list
                            options.find('.AdminMode_Manager_categorySelectOption:last').after(categoryOption.clone(true));
                        }
                    });
                });
            }
            
            // if category already exists
            else {
            
                // check each sub category
                newCategory.find('.AdminMode_Manager_subCategoryWidgets').each(function() {
                    var newSubCategory = $(this);
                    var subCategoryName = newSubCategory.attr('rel');
                    
                    // get sub category from current manage mode category
                    var subCategory = category.find('.AdminMode_Manager_subCategoryWidgets[rel="' + subCategoryName + '"]');
                    
                    // if sub category doesn't exist already
                    if (subCategory.length == 0) {
                    
                        // add new sub category to category widget select bar
                        category.find('.AdminMode_Manager_widgetSelectBar').append(newSubCategory);
                        
                        // add new sub category to sub category options
                        var newSubCategoryOption = newCategory.find('.AdminMode_Manager_subCategoryOption[rel="' + subCategoryName + '"]');
                        category.find('.AdminMode_Manager_subCategories').append(newSubCategoryOption);
                        
                        // bind events on new dom elements added
                        self.setupBindings(newSubCategory);
                        self.setupBindings(newSubCategoryOption);
                    }
                    
                    // if sub category already exists
                    else {
                    
                        // check each widget
                        newSubCategory.find('.AdminMode_Manager_subCategoryWidget').each(function() {
                            var newWidget = $(this);
                            var newWidgetId = newWidget.attr('widgetid');
                            
                            // get widget from current manage mode sub category
                            var widget = subCategory.find('.AdminMode_Manager_subCategoryWidget[widgetid="' + newWidgetId + '"]');
                            
                            // if widget doesn't exist already
                            if (widget.length == 0) {
                                
                                // add new widget to sub category
                                subCategory.append(newWidget);
                                
                                // bind events to new widget
                                self.setupBindings(newWidget);
                            }
                        });
                    }
                });
            }
        });
        
        self.checkForMissingWidgets();
    }
});

