// ==UserScript==
// @include http://localhost*
// ==/UserScript==


/*
 *
 * Shift-click on an element to view a list of that element's ancestors, including classes, ids and any 
 * presentational attributes. 
 *
 * Click on a tag name in this list to view or edit the CSS properties for that element.
 *
 * View computed styles: enter a CSS property name in the textfield and press enter, or select a group 
 * of CSS properties from the drop down box. To clear the list of styles displayed type the word empty 
 * into the textfield (and then press enter) or choose the "Clear the list of displayed styles" option 
 * from the drop down box.
 *
 * Set styles: type propertyname: value into the textfield and then press enter to set a CSS property.
 *
 */


(function() {

    var props = {};
    props.struct = null;
    props.displayPresentationalAttrs = true;
    props.boxStyle = {
        position: 'absolute',
        background: '#eee',
        border: '5px solid #fff',
        padding: '5px',
        margin: '0px',
        fontSize: '12px',
        fontFamily: 'verdana,arial,helvetica,sans-serif',
        color: '#000',
        maxWidth: '500px',
        textAlign: 'left'
    };
    props.listStyle = {
        margin: '10px 5px 10px 35px',
        padding: '2px',
        listStyleType: 'decimal'
    };

    var climbTree = function(src) {
        var struct = [];
        while (src.nodeName.toLowerCase() != 'html') {
            if (src.nodeType == 1) {
                if (src.hasAttribute('id') && src.getAttribute('id').indexOf('css-checker') != -1) {
                    return src.getAttribute('id');
                }
                struct.push(src);
            }
            src = src.parentNode;
        }
        return struct;
    };

    var addDraggableItem = function(obj) {
        obj.addEventListener('mousedown', startDrag, false);
        obj.addEventListener('mouseup', stopDrag, false);

        document.getElementsByTagName('body')[0].appendChild(obj);
    };

    var removeDraggableItem = function(obj) {
        obj.removeEventListener('mousedown', startDrag, false);
        obj.removeEventListener('mouseup', stopDrag, false);

        obj.parentNode.removeChild(obj);
    };

    var styleElement = function(obj, defaultObj) {
        var styleObj = {};
        for (var x in defaultObj) {
            styleObj[x] = defaultObj[x];
        }

        for (var i = 2; i < arguments.length; i += 2) {
            styleObj[arguments[i]] = arguments[i + 1];
        }
        
        for (var x in styleObj) {
            obj.style[x] = styleObj[x];
        }
    };
  
    var stopEventProp = function(e) {
        e.stopPropagation();
    };

    var convertRGB = function(str) {
        var start = str.indexOf('rgb(');
        var end = str.indexOf(')', start);
        if (start == -1 || end == -1) {
            return str;
        }

        var col = str.slice(start + 4, end).replace(/ +/g, '');
        var rgbArray = col.split(',');
        var colourStr = '#' + (rgbArray[0] << 16 | rgbArray[1] << 8 | rgbArray[2]).toString(16);
        while (colourStr.length < 7) {
            colourStr += '0';
        }
        return str.substr(0, start) + colourStr + str.substring(end + 1);
    };

    var removeEvents = function(parentElement, tagName, ev, fn) {
         var objs = parentElement.getElementsByTagName(tagName);
         var num = objs.length;
         while (num--) {
             objs[num].removeEventListener(ev, fn, false);
         }
    };

    var removeElementList = function(el) {
        if (!el) return;

        removeEvents(el, 'li', 'mousedown', stopEventProp);
        removeEvents(el, 'a', 'click', makeStyleList);
        removeDraggableItem(el);
    };

    var removeStyleList = function(el) {
        if (!el) return;

        removeEvents(el, 'option', 'click', displayStyles);
        removeEvents(el, 'input', 'keyup', displayStyles);
        removeEvents(el, 'li', 'mousedown', removeStyleListItem);
        removeDraggableItem(el);
    };

    var startDrag = function(e) {
        e.target.mouseX = e.pageX - findPosX(e.target);
        e.target.mouseY = e.pageY - findPosY(e.target);
        e.target.addEventListener('mousemove', dragBox, false);
        e.stopPropagation();
    };

    var stopDrag = function(e) {
        e.target.removeEventListener('mousemove', dragBox, false);
    };

    var dragBox = function(e) {
        this.style.left = (e.pageX - this.mouseX) + 'px';
        this.style.top = (e.pageY - this.mouseY) + 'px';
    };

    // findPosX and findPosY basically copied from http://www.quirksmode.org/js/findpos.html
    var findPosX = function(obj) {
        var curleft = 0;
        if (obj.offsetParent) {
            while (obj.offsetParent) {
                curleft += obj.offsetLeft;
                obj = obj.offsetParent;
            }
        }
        return curleft;
    };

    var findPosY = function(obj) {
        var curtop = 0;
        if (obj.offsetParent) {
            while (obj.offsetParent) {
                curtop += obj.offsetTop;
                obj = obj.offsetParent;
            }
        } 
        return curtop;
    };

    var displayStyles = function(e) {
         switch (e.type) {
             case 'click':
                 var s1 = this.getAttribute('style1');
                 var s2 = this.getAttribute('style2');

                 if (!s1) {
                     return clearDisplayedStyles();
                 }

                 s1 = s1.split('|');
                 s2 = s2.split('|');

                 var styleObj = document.defaultView.getComputedStyle(props.struct[this.parentNode.parentNode.getAttribute('elementId')], null);

                 for (var i = 0; i < s1.length; ++i) {
                     var styleStr = s1[i] + ': ';
                     if (s2.length) {
                         for (var j = 0; j < s2.length; ++j) {
                             var elStyle = styleObj.getPropertyValue(s1[i] + s2[j]);
                             if (elStyle) {
                                 styleStr += convertRGB(elStyle) + ' ';
                             } else {
                                 styleStr += 'unset property ';
                             }
                         }
                     } else {
                         var elStyle = styleObj.getPropertyValue(s1[i]);
                         if (elStyle) {
                             styleStr += convertRGB(elStyle);
                         } else {
                             styleStr += 'unset property';
                         }
                     }
                     addStyleListItem(styleStr);
                 }
             break;
             default:
                 if (e.keyCode != 13) {
                     return;
                 }

                 var val = this.value.replace(/^ +|[ ;]+$/g, '').toLowerCase();
                 if (val == 'empty') {
                     return clearDisplayedStyles();
                 }
                 if (val.indexOf(':') != -1) {
                     var parts = val.split(':');
                     var prop = parts[0].replace(/^ +| +$/g, '');
                     var value = parts[1].replace(/^ +| +$/g, '');
                     props.struct[this.parentNode.getAttribute('elementId')].style.setProperty(prop, value, null);
                     addStyleListItem('set ' + prop + ' to ' + value);
                     return;
                 }

                 var styleObj = document.defaultView.getComputedStyle(props.struct[this.parentNode.getAttribute('elementId')], null);
                 var elStyle = styleObj.getPropertyValue(val);
                 if (elStyle) {
                      addStyleListItem(val + ': ' + convertRGB(elStyle));
                 } else {
                      addStyleListItem(val + ': unset property');
                 }

             break;
         }
    };

    var clearDisplayedStyles = function() {
        var lis = document.getElementById('css-checker-style-list').getElementsByTagName('li');
        var num = lis.length;
        if (num == 0) {
            alert('The list is already empty!');
        } 

        while (num--) {
            lis[num].removeEventListener('mousedown', removeStyleListItem, false);
            lis[num].parentNode.removeChild(lis[num]);
        }
        return;
    };

    var addStyleListItem = function(str) {
        var li = document.createElement('li');
        li.setAttribute('title', 'shift-click to remove this item from the list');
        li.appendChild(document.createTextNode(str));
        li.style.padding = '2px 0';
        li.style.margin = '0';
        li.style.fontFamily = props.boxStyle.fontFamily;
        li.addEventListener('mousedown', removeStyleListItem, false);
        document.getElementById('css-checker-style-list').appendChild(li);
    };

    var removeStyleListItem = function(e) {
        if (e.shiftKey) {
            e.target.removeEventListener('mousedown', arguments.callee, false);
            e.target.parentNode.removeChild(e.target);
            e.stopPropagation();
        }
    };

    var makeStyleList = function(e) {
        var elId = e.target.getAttribute('elementId')
        var src = props.struct[elId];

        removeStyleList(document.getElementById('css-checker-styles'));

        var styleDiv = document.createElement('div');
        styleDiv.setAttribute('id', 'css-checker-styles');
        styleDiv.setAttribute('elementId', elId);

        var styleTitle = document.createElement('p');
        var styleTitleText = 'View and edit styles for ' + src.nodeName;
        if (src.hasAttribute('id')) {
            styleTitleText += ', #' + src.getAttribute('id');
        }
        if (src.className != '') {
            styleTitleText += ', .' + src.className;
        }

        styleTitle.appendChild(document.createTextNode(styleTitleText));
        styleTitle.style.margin = '10px 0px';
        styleTitle.style.padding = '0px';
        styleDiv.appendChild(styleTitle);

        var defaultStyles = [];
        defaultStyles.push({name: 'Margins and padding', 
            style1: ['margin', 'padding'], 
            style2: ['-top', '-right', '-bottom', '-left']
        });
        defaultStyles.push({name: 'Dimensions, position and floats', 
            style1: ['position', 'top', 'right', 'bottom', 'left', 'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height', 'float', 'clear'], 
            style2: []
        });
        defaultStyles.push({name: 'Borders',
            title: 'Display all the border styles for the selected element',
            style1: ['border-top', 'border-right', 'border-bottom', 'border-left'],
            style2: ['-width', '-style', '-color']
        });
        defaultStyles.push({name: 'Background',
            style1: ['background'],
            style2: ['-color', '-image', '-repeat', '-position', '-attachment']
        });
        defaultStyles.push({name: 'Fonts and colours', 
            style1: ['font-family', 'line-height', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'color'], 
            style2: []
        });
        defaultStyles.push({name: 'Text', 
            style1: ['text-align', 'letter-spacing', 'line-height', 'text-decoration', 'text-indent', 'text-shadow', 'text-transform', 'word-spacing'], 
            style2: []
        });
        defaultStyles.push({name: 'Visual', 
            style1: ['display', 'visibility', 'clip', 'cursor', 'direction', 'outline-color', 'outline-style', 'outline-width', 'overflow', 'position', 'quotes', 'unicode-bidi', 'vertical-align', 'z-index', 'opacity'], 
            style2: []
        });
        if (src.nodeName.toLowerCase() == 'ul' || src.nodeName.toLowerCase() == 'ol') {
            defaultStyles.push({name: 'List styles',
                style1: ['list-style-type', 'list-style-image', 'list-style-position'],
                style2: []
            });
        }

        styleElement(styleDiv, props.boxStyle,
            'left', e.pageX + 'px',
            'top', e.pageY + 'px',
            'background', '#ddd',
            'padding', '20px',
            'zIndex', 100000
        );

        var inputText = document.createElement('input');
        inputText.setAttribute('type', 'text');
        inputText.style.display = 'block';
        inputText.style.margin = '10px 5px 10px 0';
        inputText.style.border = '1px solid #666';
        inputText.style.cssFloat = 'left';
        inputText.addEventListener('keyup', displayStyles, false);

        styleDiv.appendChild(inputText);

        var defaultStylesSelect = document.createElement('select');
        var firstOption = document.createElement('option');
        firstOption.appendChild(document.createTextNode('View a group of CSS properties'));
        defaultStylesSelect.appendChild(firstOption);

        for (var i = 0; i < defaultStyles.length; ++i) {
            var defaultStyleOption = document.createElement('option');
            defaultStyleOption.appendChild(document.createTextNode(defaultStyles[i].name));
            defaultStyleOption.setAttribute('style1', defaultStyles[i].style1.join('|'));
            defaultStyleOption.setAttribute('style2', defaultStyles[i].style2.join('|'));
            defaultStyleOption.addEventListener('click', displayStyles, false);

            defaultStylesSelect.appendChild(defaultStyleOption);
        }

        var emptyStyleList = document.createElement('option');
        emptyStyleList.appendChild(document.createTextNode('Clear the list of displayed styles'));
        emptyStyleList.addEventListener('click', displayStyles, false);

        defaultStylesSelect.appendChild(emptyStyleList);

        defaultStylesSelect.style.cssFloat = 'left';
        defaultStylesSelect.style.margin = '10px 0 10px 5px';

        styleDiv.appendChild(defaultStylesSelect);

        var displayedStylesList = document.createElement('ul');
        displayedStylesList.setAttribute('id', 'css-checker-style-list');

        styleElement(displayedStylesList, props.listStyle, 
            'listStyleType', 'none',
            'margin', '0px',
            'clear', 'both'
        );

        styleDiv.appendChild(displayedStylesList);

        addDraggableItem(styleDiv);
    };

    var makeElementList = function(x, y) {
        var i = props.struct.length;
        if (i == 0) {
            return false;
        }

        var msgDiv = document.createElement('div');
        var msgList = document.createElement('ol');

        while (i--) {
            var msgItem = document.createElement('li');
            msgItem.style.padding = '2px';
            msgItem.style.margin = '0px';
            msgItem.style.width = 'auto';
            msgItem.style.fontFamily = props.boxStyle.fontFamily;
            msgItem.addEventListener('mousedown', stopEventProp, false);

            var styleLink = document.createElement('span');
            styleLink.appendChild(document.createTextNode(props.struct[i].nodeName));
            styleLink.setAttribute('elementId', i);
            styleLink.setAttribute('title', 'View and Edit style information for this element');

            styleLink.addEventListener('click', makeStyleList, false);

            msgItem.appendChild(styleLink);

            if (props.struct[i].hasAttribute('id')) {
                var s = document.createElement('span');
                s.appendChild(document.createTextNode('#' + props.struct[i].getAttribute('id')));
                s.setAttribute('title', 'id');
                s.style.color = '#33c';
                msgItem.appendChild(document.createTextNode(', '));
                msgItem.appendChild(s);
            }
            if (props.struct[i].className != '') {
                var s = document.createElement('span');
                s.appendChild(document.createTextNode('.' + props.struct[i].className));
                s.setAttribute('title', 'class');
                s.style.color = '#3c3';
                msgItem.appendChild(document.createTextNode(', '));
                msgItem.appendChild(s);
            }

            if (props.displayPresentationalAttrs) {
                var presentationAttrsArr = ['color', 'bgcolor', 'cellpadding', 'cellspacing', 'width', 'height', 'style'];
                var foundPresentationAttrsArr = [];
                for (var j = 0; j < presentationAttrsArr.length; ++j) {
                    if (props.struct[i].hasAttribute(presentationAttrsArr[j])) {
                        foundPresentationAttrsArr.push(presentationAttrsArr[j] + '=' + props.struct[i].getAttribute(presentationAttrsArr[j]));
                    }
                }
                if (foundPresentationAttrsArr.length) {
                    var s = document.createElement('span');
                    s.appendChild(document.createTextNode(foundPresentationAttrsArr.join(' ')));
                    s.setAttribute('title', 'presentational attributes');
                    s.style.color = '#c00';
                    msgItem.appendChild(document.createTextNode(', '));
                    msgItem.appendChild(s);
                }
            }

            msgList.appendChild(msgItem);
        }

        msgDiv.appendChild(msgList);
        msgDiv.setAttribute('id', 'css-checker-elements');

        styleElement(msgDiv, props.boxStyle,
                'left', x + 'px',
                'top', y + 'px',
                'zIndex', 99999
        );
        styleElement(msgList, props.listStyle);

        addDraggableItem(msgDiv);

        return true;
    };

    document.addEventListener('mousedown', function(e) {
        var struct = climbTree(e.target);
        if (typeof struct == 'string') {
            return;
        }

        props.struct = struct;
 
        removeElementList(document.getElementById('css-checker-elements'));
        removeStyleList(document.getElementById('css-checker-styles'));

        if (e.shiftKey) {
            makeElementList(e.pageX, e.pageY);
            e.preventDefault();
        }

    }, false);

})();
