    Ext.debug = {};

    (function(){

    var cp;

    function createConsole(){

        var scriptPanel = new Ext.debug.ScriptsPanel();
        var logView = new Ext.debug.LogPanel();
        var tree = new Ext.debug.DomTree();
        var compInspector = new Ext.debug.ComponentInspector();
        var compInfoPanel = new Ext.debug.ComponentInfoPanel();
        var storeInspector = new Ext.debug.StoreInspector();
        var objInspector = new Ext.debug.ObjectInspector();

        var tabs = new Ext.TabPanel({
            activeTab: 0,
            border: false,
            tabPosition: 'bottom',
            items: [{
                title: 'Debug Console',
                layout:'border',
                items: [logView, scriptPanel]
            },{
                title: 'HTML Inspector',
                layout:'border',
                items: [tree]
            },{
                title: 'Component Inspector',
                layout: 'border',
                items: [compInspector,compInfoPanel]
            },{
                title: 'Object Inspector',
                layout: 'border',
                items: [objInspector]
            },{
                title: 'Data Stores',
                layout: 'border',
                items: [storeInspector]
            }]
        });

        cp = new Ext.Panel({
            id: 'x-debug-browser',
            title: 'Console',
            collapsible: true,
            animCollapse: false,
            style: 'position:absolute;left:0;bottom:0;z-index:101',
            height:200,
            logView: logView,
            layout: 'fit',

            tools:[{
                id: 'close',
                handler: function(){
                    cp.destroy();
                    cp = null;
                    Ext.EventManager.removeResizeListener(handleResize);
                }
            }],

            items: tabs
        });

        cp.render(Ext.getBody());

        cp.resizer = new Ext.Resizable(cp.el, {
            minHeight:50,
            handles: "n",
            pinned: true,
            transparent:true,
            resizeElement : function(){
                var box = this.proxy.getBox();
                this.proxy.hide();
                cp.setHeight(box.height);
                return box;
            }
        });

    //     function handleResize(){
    //         cp.setWidth(Ext.getBody().getViewSize().width);
    //     }
    //     Ext.EventManager.onWindowResize(handleResize);
    //
    //     handleResize();

        function handleResize(){
            var b = Ext.getBody()
            var size = b.getViewSize();
            if(size.height < b.dom.scrollHeight) {
                size.width -= 18;
            }
            cp.setWidth(size.width);
        }
        Ext.EventManager.onWindowResize(handleResize);
        handleResize();
    }

    Ext.apply(Ext, {
        log : function(){
            if(!cp){
                createConsole();
            }
            cp.logView.log.apply(cp.logView, arguments);
        },

        logf : function(format, arg1, arg2, etc){
            Ext.log(String.format.apply(String, arguments));
        },

        dump : function(o){
            if(typeof o == 'string' || typeof o == 'number' || typeof o == 'undefined' || Ext.isDate(o)){
                Ext.log(o);
            }else if(!o){
                Ext.log("null");
            }else if(typeof o != "object"){
                Ext.log('Unknown return type');
            }else if(Ext.isArray(o)){
                Ext.log('['+o.join(',')+']');
            }else{
                var b = ["{\n"];
                for(var key in o){
                    var to = typeof o[key];
                    if(to != "function" && to != "object"){
                        b.push(String.format("  {0}: {1},\n", key, o[key]));
                    }
                }
                var s = b.join("");
                if(s.length > 3){
                    s = s.substr(0, s.length-2);
                }
                Ext.log(s + "\n}");
            }
        },

        _timers : {},

        time : function(name){
            name = name || "def";
            Ext._timers[name] = new Date().getTime();
        },

        timeEnd : function(name, printResults){
            var t = new Date().getTime();
            name = name || "def";
            var v = String.format("{0} ms", t-Ext._timers[name]);
            Ext._timers[name] = new Date().getTime();
            if(printResults !== false){
                Ext.log('Timer ' + (name == "def" ? v : name + ": " + v));
            }
            return v;
        }
    });

    })();

    Ext.debug.ScriptsPanel = Ext.extend(Ext.Panel, {
        id:'x-debug-scripts',
        region: 'east',
        minWidth: 200,
        split: true,
        width: 350,
        border: false,
        layout:'anchor',
        style:'border-width:0 0 0 1px;',

        initComponent : function(){

            this.scriptField = new Ext.form.TextArea({
                anchor: '100% -26',
                style:'border-width:0;'
            });

            this.trapBox = new Ext.form.Checkbox({
                id: 'console-trap',
                boxLabel: 'Trap Errors',
                checked: true
            });

            this.toolbar = new Ext.Toolbar([{
                    text: 'Run',
                    scope: this,
                    handler: this.evalScript
                },{
                    text: 'Clear',
                    scope: this,
                    handler: this.clear
                },
                '->',
                this.trapBox,
                ' ', ' '
            ]);

            this.items = [this.toolbar, this.scriptField];

            Ext.debug.ScriptsPanel.superclass.initComponent.call(this);
        },

        evalScript : function(){
            var s = this.scriptField.getValue();
            if(this.trapBox.getValue()){
                try{
                    var rt = eval(s);
                    Ext.dump(rt === undefined? '(no return)' : rt);
                }catch(e){
                    Ext.log(e.message || e.descript);
                }
            }else{
                var rt = eval(s);
                Ext.dump(rt === undefined? '(no return)' : rt);
            }
        },

        clear : function(){
            this.scriptField.setValue('');
            this.scriptField.focus();
        }

    });

    Ext.debug.LogPanel = Ext.extend(Ext.Panel, {
        autoScroll: true,
        region: 'center',
        border: false,
        style:'border-width:0 1px 0 0',

        log : function(){
            var markup = [  '',
                        Ext.util.Format.htmlEncode(Array.prototype.join.call(arguments, ', ')).replace(/\n/g, '
    ').replace(/\s/g, ' '),
                        ''].join(''),
                bd = this.body.dom;

            this.body.insertHtml('beforeend', markup);
            bd.scrollTop = bd.scrollHeight;
        },

        clear : function(){
            this.body.update('');
            this.body.dom.scrollTop = 0;
        }
    });

    Ext.debug.DomTree = Ext.extend(Ext.tree.TreePanel, {
        enableDD:false ,
        lines:false,
        rootVisible:false,
        animate:false,
        hlColor:'ffff9c',
        autoScroll: true,
        region:'center',
        border:false,

        initComponent : function(){

            Ext.debug.DomTree.superclass.initComponent.call(this);

            // tree related stuff
            var styles = false, hnode;
            var nonSpace = /^\s*$/;
            var html = Ext.util.Format.htmlEncode;
            var ellipsis = Ext.util.Format.ellipsis;
            var styleRe = /\s?([a-z\-]*)\:([^;]*)(?:[;\s\n\r]*)/gi;

            function findNode(n){
                if(!n || n.nodeType != 1 || n == document.body || n == document){
                    return false;
                }
                var pn = [n], p = n;
                while((p = p.parentNode) && p.nodeType == 1 && p.tagName.toUpperCase() != 'HTML'){
                    pn.unshift(p);
                }
                var cn = hnode;
                for(var i = 0, len = pn.length; i < len; i++){
                    cn.expand();
                    cn = cn.findChild('htmlNode', pn[i]);
                    if(!cn){ // in this dialog?
                        return false;
                    }
                }
                cn.select();
                var a = cn.ui.anchor;
                this.getTreeEl().dom.scrollTop = Math.max(0 ,a.offsetTop-10);
                //treeEl.dom.scrollLeft = Math.max(0 ,a.offsetLeft-10); no likey
                cn.highlight();
                return true;
            }

            function nodeTitle(n){
                var s = n.tagName;
                if(n.id){
                    s += '#'+n.id;
                }else if(n.className){
                    s += '.'+n.className;
                }
                return s;
            }

            /*
            function onNodeSelect(t, n, last){
                return;
                if(last && last.unframe){
                    last.unframe();
                }
                var props = {};
                if(n && n.htmlNode){
                    if(frameEl.pressed){
                        n.frame();
                    }
                    if(inspecting){
                        return;
                    }
                    addStyle.enable();
                    reload.setDisabled(n.leaf);
                    var dom = n.htmlNode;
                    stylePanel.setTitle(nodeTitle(dom));
                    if(styles && !showAll.pressed){
                        var s = dom.style ? dom.style.cssText : '';
                        if(s){
                            var m;
                            while ((m = styleRe.exec(s)) != null){
                                props[m[1].toLowerCase()] = m[2];
                            }
                        }
                    }else if(styles){
                        var cl = Ext.debug.cssList;
                        var s = dom.style, fly = Ext.fly(dom);
                        if(s){
                            for(var i = 0, len = cl.length; i' + ellipsis(html(String(c.nodeValue)), 35) + '',
                            cls: 'x-tree-noicon'
                        }));
                    }
                }
                cb();
            };

            //tree.getSelectionModel().on('selectionchange', onNodeSelect, null, {buffer:250});

            this.root = this.setRootNode(new Ext.tree.TreeNode('Ext'));

            hnode = this.root.appendChild(new Ext.debug.HtmlNode(
                    document.getElementsByTagName('html')[0]
            ));

        }
    });

    Ext.debug.ComponentNodeUI = Ext.extend(Ext.tree.TreeNodeUI,{
        onOver : function(e){
            Ext.debug.ComponentNodeUI.superclass.onOver.call(this);
            var cmp = this.node.attributes.component;
            if (cmp.el && cmp.el.mask && cmp.id !='x-debug-browser') {
                try { // Oddly bombs on some elements in IE, gets any we care about though
                    cmp.el.mask();
                } catch(e) {}
            }
        },

        onOut : function(e){
            Ext.debug.ComponentNodeUI.superclass.onOut.call(this);
            var cmp = this.node.attributes.component;
            if (cmp.el && cmp.el.unmask && cmp.id !='x-debug-browser') {
                try {
                    cmp.el.unmask();
                } catch(e) {}
            }
        }
    });

    Ext.debug.ComponentInspector = Ext.extend(Ext.tree.TreePanel, {
        enableDD:false ,
        lines:false,
        rootVisible:false,
        animate:false,
        hlColor:'ffff9c',
        autoScroll: true,
        region:'center',
        border:false,

        initComponent : function(){
            this.loader = new Ext.tree.TreeLoader();
            this.bbar = new Ext.Toolbar([{
                text: 'Refresh',
                handler: this.refresh,
                scope: this
            }]);
            Ext.debug.ComponentInspector.superclass.initComponent.call(this);

            this.root = this.setRootNode(new Ext.tree.TreeNode({
                text: 'Ext Components',
                component: Ext.ComponentMgr.all,
                leaf: false
            }));
            this.parseRootNode();

            this.on('click', this.onClick, this);
        },

        createNode: function(n,c) {
            var leaf = (c.items && c.items.length > 0);
            return n.appendChild(new Ext.tree.TreeNode({
                text: c.id + (c.getXType() ? ' [ ' + c.getXType() + ' ]': '' ),
                component: c,
                uiProvider:Ext.debug.ComponentNodeUI,
                leaf: !leaf
            }));
        },

        parseChildItems: function(n) {
            var cn = n.attributes.component.items;
            if (cn) {
                for (var i = 0;i < cn.length; i++) {
                    var c = cn.get(i);
                    if (c.id != this.id && c.id != this.bottomToolbar.id) {
                        var newNode = this.createNode(n,c);
                        if (!newNode.leaf) {
                            this.parseChildItems(newNode)
                        }
                    }
                }
            }
        },

        parseRootNode: function() {
            var n = this.root;
            var cn = n.attributes.component.items;
            for (var i = 0,c;c = cn[i];i++) {
                if (c.id != this.id && c.id != this.bottomToolbar.id) {
                    if (!c.ownerCt) {
                        var newNode = this.createNode(n,c);
                        if (!newNode.leaf) {
                            this.parseChildItems(newNode);
                        }
                    }
                }
            }
        },

        onClick: function(node, e) {
            var oi = Ext.getCmp('x-debug-objinspector');
            oi.refreshNodes(node.attributes.component);
            oi.ownerCt.show();
        },

        refresh: function() {
            while (this.root.firstChild) {
                this.root.removeChild(this.root.firstChild);
            }
            this.parseRootNode();
            var ci = Ext.getCmp('x-debug-compinfo');
            if (ci) {
                ci.message('refreshed component tree - '+Ext.ComponentMgr.all.length)
            }
        }
    });

    Ext.debug.ComponentInfoPanel = Ext.extend(Ext.Panel,{
        id:'x-debug-compinfo',
        region: 'east',
        minWidth: 200,
        split: true,
        width: 350,
        border: false,
        autoScroll: true,
        layout:'anchor',
        style:'border-width:0 0 0 1px;',

        initComponent: function() {
            this.watchBox = new Ext.form.Checkbox({
                id: 'x-debug-watchcomp',
                boxLabel: 'Watch ComponentMgr',
                listeners: {
                    check: function(cb, val) {
                        if (val) {
                            Ext.ComponentMgr.all.on('add', this.onAdd, this);
                            Ext.ComponentMgr.all.on('remove', this.onRemove, this);
                        } else {
                            Ext.ComponentMgr.all.un('add', this.onAdd, this);
                            Ext.ComponentMgr.all.un('remove', this.onRemove, this);
                        }
                    },
                    scope: this
                }
            });

            this.tbar = new Ext.Toolbar([{
                text: 'Clear',
                handler: this.clear,
                scope: this
            },'->',this.watchBox
            ]);
            Ext.debug.ComponentInfoPanel.superclass.initComponent.call(this);
        },

        onAdd: function(i, o, key) {
            var markup = ['',
                        'Added: '+o.id,
                        ''].join('');
            this.insertMarkup(markup);
        },

        onRemove: function(o, key) {
            var markup = ['',
                        'Removed: '+o.id,
                        ''].join('');
            this.insertMarkup(markup);
        },

        message: function(msg) {
            var markup = ['',
                        msg,
                        ''].join('');
            this.insertMarkup(markup);
        },
        insertMarkup: function(markup) {
            this.body.insertHtml('beforeend', markup);
            this.body.scrollTo('top', 100000);
        },
        clear : function(){
            this.body.update('');
            this.body.dom.scrollTop = 0;
        }
    });

    Ext.debug.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
        focus: Ext.emptyFn, // prevent odd scrolling behavior

        renderElements : function(n, a, targetNode, bulkRender){
            this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';

            var t = n.getOwnerTree();
            var cols = t.columns;
            var bw = t.borderWidth;
            var c = cols[0];

            var buf = [
                 '',
                    '',
                        '',this.indentMarkup,"",
                        '',
                        '',
                        '',
                        '', n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"",
                    ""];
             for(var i = 1, len = cols.length; i < len; i++){
                 c = cols[i];

                 buf.push('',
                            '',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"",
                          "");
             }
             buf.push(
                '',
                '',
                "");

            if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
                this.wrap = Ext.DomHelper.insertHtml("beforeBegin",
                                    n.nextSibling.ui.getEl(), buf.join(""));
            }else{
                this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
            }

            this.elNode = this.wrap.childNodes[0];
            this.ctNode = this.wrap.childNodes[1];
            var cs = this.elNode.firstChild.childNodes;
            this.indentNode = cs[0];
            this.ecNode = cs[1];
            this.iconNode = cs[2];
            this.anchor = cs[3];
            this.textNode = cs[3].firstChild;
        }
    });

    Ext.debug.ObjectInspector = Ext.extend(Ext.tree.TreePanel, {
        id: 'x-debug-objinspector',
        enableDD:false ,
        lines:false,
        rootVisible:false,
        animate:false,
        hlColor:'ffff9c',
        autoScroll: true,
        region:'center',
        border:false,
        lines:false,
        borderWidth: Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
        cls:'x-column-tree',

        initComponent : function(){
            this.showFunc = false;
            this.toggleFunc = function() {
                this.showFunc = !this.showFunc;
                this.refreshNodes(this.currentObject);
            }
            this.bbar = new Ext.Toolbar([{
                text: 'Show Functions',
                enableToggle: true,
                pressed: false,
                handler: this.toggleFunc,
                scope: this
            }]);

            Ext.apply(this,{
                title: ' ',
                loader: new Ext.tree.TreeLoader(),
                columns:[{
                    header:'Property',
                    width: 300,
                    dataIndex:'name'
                },{
                    header:'Value',
                    width: 900,
                    dataIndex:'value'
                }]
            });

            Ext.debug.ObjectInspector.superclass.initComponent.call(this);

            this.root = this.setRootNode(new Ext.tree.TreeNode({
                text: 'Dummy Node',
                leaf: false
            }));

            if (this.currentObject) {
                this.parseNodes();
            }
        },

        refreshNodes: function(newObj) {
            this.currentObject = newObj;
            var node = this.root;
            while(node.firstChild){
                node.removeChild(node.firstChild);
            }
            this.parseNodes();
        },

        parseNodes: function() {
            for (var o in this.currentObject) {
                if (!this.showFunc) {
                    if (Ext.isFunction(this.currentObject[o])) {
                        continue;
                    }
                }
                this.createNode(o);
            }
        },

        createNode: function(o) {
            return this.root.appendChild(new Ext.tree.TreeNode({
                name: o,
                value: this.currentObject[o],
                uiProvider:Ext.debug.ColumnNodeUI,
                iconCls: 'x-debug-node',
                leaf: true
            }));
        },

        onRender : function(){
            Ext.debug.ObjectInspector.superclass.onRender.apply(this, arguments);
            this.headers = this.header.createChild({cls:'x-tree-headers'});

            var cols = this.columns, c;
            var totalWidth = 0;

            for(var i = 0, len = cols.length; i < len; i++){
                 c = cols[i];
                 totalWidth += c.width;
                 this.headers.createChild({
                     cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),
                     cn: {
                         cls:'x-tree-hd-text',
                         html: c.header
                     },
                     style:'width:'+(c.width-this.borderWidth)+'px;'
                 });
            }
            this.headers.createChild({cls:'x-clear'});
            // prevent floats from wrapping when clipped
            this.headers.setWidth(totalWidth);
            this.innerCt.setWidth(totalWidth);
        }
    });

    Ext.debug.StoreInspector = Ext.extend(Ext.tree.TreePanel, {
        enableDD:false ,
        lines:false,
        rootVisible:false,
        animate:false,
        hlColor:'ffff9c',
        autoScroll: true,
        region:'center',
        border:false,

        initComponent: function() {
            this.bbar = new Ext.Toolbar([{
                text: 'Refresh',
                handler: this.refresh,
                scope: this
            }]);
            Ext.debug.StoreInspector.superclass.initComponent.call(this);

            this.root = this.setRootNode(new Ext.tree.TreeNode({
                text: 'Data Stores',
                leaf: false
            }));
            this.on('click', this.onClick, this);

            this.parseStores();
        },

        parseStores: function() {
            var cn = Ext.StoreMgr.items;
            for (var i = 0,c;c = cn[i];i++) {
                this.root.appendChild({
                    text: c.storeId + ' - ' + c.totalLength + ' records',
                    component: c,
                    leaf: true
                });
            }
        },

        onClick: function(node, e) {
            var oi = Ext.getCmp('x-debug-objinspector');
            oi.refreshNodes(node.attributes.component);
            oi.ownerCt.show();
        },

        refresh: function() {
            while (this.root.firstChild) {
                this.root.removeChild(this.root.firstChild);
            }
            this.parseStores();
        }
    });

    // highly unusual class declaration
    Ext.debug.HtmlNode = function(){
        var html = Ext.util.Format.htmlEncode;
        var ellipsis = Ext.util.Format.ellipsis;
        var nonSpace = /^\s*$/;

        var attrs = [
            {n: 'id', v: 'id'},
            {n: 'className', v: 'class'},
            {n: 'name', v: 'name'},
            {n: 'type', v: 'type'},
            {n: 'src', v: 'src'},
            {n: 'href', v: 'href'}
        ];

        function hasChild(n){
            for(var i = 0, c; c = n.childNodes[i]; i++){
                if(c.nodeType == 1){
                    return true;
                }
            }
            return false;
        }

        function renderNode(n, leaf){
            var tag = n.tagName.toLowerCase();
            var s = '<' + tag;
            for(var i = 0, len = attrs.length; i < len; i++){
                var a = attrs[i];
                var v = n[a.n];
                if(v && !nonSpace.test(v)){
                    s += ' ' + a.v + '="' + html(v) +'"';
                }
            }
            var style = n.style ? n.style.cssText : '';
            if(style){
                s += ' style="' + html(style.toLowerCase()) +'"';
            }
            if(leaf && n.childNodes.length > 0){
                s+='>' + ellipsis(html(String(n.innerHTML)), 35) + '</'+tag+'>';
            }else if(leaf){
                s += ' />';
            }else{
                s += '>';
            }
            return s;
        }

        var HtmlNode = function(n){
            var leaf = !hasChild(n);
            this.htmlNode = n;
            this.tagName = n.tagName.toLowerCase();
            var attr = {
                text : renderNode(n, leaf),
                leaf : leaf,
                cls: 'x-tree-noicon'
            };
            HtmlNode.superclass.constructor.call(this, attr);
            this.attributes.htmlNode = n; // for searching
            if(!leaf){
                this.on('expand', this.onExpand,  this);
                this.on('collapse', this.onCollapse,  this);
            }
        };

        Ext.extend(HtmlNode, Ext.tree.AsyncTreeNode, {
            cls: 'x-tree-noicon',
            preventHScroll: true,
            refresh : function(highlight){
                var leaf = !hasChild(this.htmlNode);
                this.setText(renderNode(this.htmlNode, leaf));
                if(highlight){
                    Ext.fly(this.ui.textNode).highlight();
                }
            },

            onExpand : function(){
                if(!this.closeNode && this.parentNode){
                    this.closeNode = this.parentNode.insertBefore(new Ext.tree.TreeNode({
                        text:'</' + this.tagName + '>',
                        cls: 'x-tree-noicon'
                    }), this.nextSibling);
                }else if(this.closeNode){
                    this.closeNode.ui.show();
                }
            },

            onCollapse : function(){
                if(this.closeNode){
                    this.closeNode.ui.hide();
                }
            },

            render : function(bulkRender){
                HtmlNode.superclass.render.call(this, bulkRender);
            },

            highlightNode : function(){
                //Ext.fly(this.htmlNode).highlight();
            },

            highlight : function(){
                //Ext.fly(this.ui.textNode).highlight();
            },

            frame : function(){
                this.htmlNode.style.border = '1px solid #0000ff';
                //this.highlightNode();
            },

            unframe : function(){
                //Ext.fly(this.htmlNode).removeClass('x-debug-frame');
                this.htmlNode.style.border = '';
            }
        });

        return HtmlNode;
    }();