/**
 * inspired by DD_roundies 0.0.2a by Drew Diller (drew.diller@gmail.com)
 * URL: http://www.dillerdesign.com/experiment/DD_roundies/
 * Rewritten by Marcus Mennemeier (marcus(at)mennemeier.de)
 */
(function($) {
    $.extend(true, {
        BorderRadius: {
            off: true
        },
        fn: {
            borderRadius: function() { return this; }
        }
    });

    if (!document.namespaces || document['documentMode'] >= 9) return;

    var backgroundCache = {};
    var initDone = false;
    var lastSpans = [];

    $(function() {
        $("body").borderRadius();
    });

    $.fn.borderRadius = function() {
        if ($.BorderRadius.off || !this.length) return this;

        var selectors = getSelectors();
        var current;
        if (this.is("body")) {
            var selector = getSelector(selectors);
            current = $(selector, this.get(0).ownerDocument);
        } else {
            var leafSelector = getLeafSelector(selectors);
            current = this.find(leafSelector).andSelf().filter(":not(input)").filter(getSelector(selectors)); // input doesn't accept child elements
        }
        lastSpans = [];
        current.each(function() {
            applyBorderRadius($(this));
        });
        this.find('span.jgcsBorderRadius').not(lastSpans).remove();
        lastSpans = [];
        return this;

        //// Selector parsing

        function getLeafSelector(selectors) {
            var copy = {};
            $.each(selectors, function(k) {
                var p = k.lastIndexOf(" ");
                copy[p < 0 ? k : k.substring(p + 1)] = true;
            });
            return getSelector(copy);
        }

        function getSelector(selectors) {
            var s = "";
            $.each(selectors, function(k) {
                s += (s ? ", " : "") + k
            });
            return s;
        }

        function getSelectors() {
            var selectors = {};
            for (var i = 0; i < document.styleSheets.length; i++) {
                $.extend(selectors, parseStyleSheet(document.styleSheets(i)));
            }
            return selectors;

            function parseStyleSheet(styleSheet) {
                if (styleSheet.borderRadiusSelectors) {
                    return styleSheet.borderRadiusSelectors;
                }
                var result = {};
                for (var i = 0; i < styleSheet['imports'].length; i++) {
                    $.extend(result, parseStyleSheet(styleSheet['imports'](i)));
                }
                $.each(styleSheet.rules, function() {
                    var s = this.style;
                    if (s.getAttribute('border-top-right-radius')
                            || s.getAttribute('border-bottom-right-radius')
                            || s.getAttribute('border-bottom-left-radius')
                            || s.getAttribute('border-top-left-radius')
                            || s.getAttribute('border-radius')
                            ) {
                        var items = this.selectorText.split(",");
                        for (i = 0; i < items.length; i++) {
                            result[items[i].replace(/(^\\s+|(\\s)\\s+|\\s+$)/g, "$1")] = true;
                        }
                    }
                });
                return styleSheet.borderRadiusSelectors = result;
            }
        }

        // Apply Border Radius
        function applyBorderRadius($$) {
            var old = $$.data('gcsBorderRadius');

            var currentStyle = $$.get(0).currentStyle; // go directly there, otherwise jquery tries to numberfy
            var prop = currentStyle['border-radius'];
            var values;
            if (prop) {
                values = prop.split("/", 1)[0].replace(/(^|( ))\s+|\s+$/g, "$2").split(" ", 4);
                if (values.length == 1) values.push(values[0]);
                if (values.length == 2) values.push(values[0]);
                if (values.length == 3) values.push(values[1]);
            } else {
                values = [0,0,0,0];
            }

            var borderRadius = {
                topLeft: coalesce($$.css('border-top-left-radius'), values[0]),
                topRight: coalesce($$.css('border-top-right-radius'), values[1]),
                bottomRight: coalesce($$.css('border-bottom-right-radius'), values[2]),
                bottomLeft: coalesce($$.css('border-bottom-left-radius'), values[3]),
                hasBorderRadius: function() {
                    return this.topLeft != 0 || this.topRight != 0 || this.bottomRight != 0 || this.bottomLeft != 0;
                },
                equals: equals
            };

            if (!old && !borderRadius.hasBorderRadius()) return;

            if (!initDone) {
                var doc = $("html:first");
                doc.attr('xmlns:vml', "urn:schemas-microsoft-com:vml");
                var head = $("head:first");
                var e = ['shape', 'fill'];
                var s = "";
                for (var i = 0; i < e.length; i++) {
                    if (i > 0) s += ", ";
                    s += "vml\\:" + e[i];
                }
                s += " {behavior: url(#default#VML); display: block; }";
                head.prepend('<style>' + s + '</style>');
            }

            var zIndex = $$.css('z-index');
            if (zIndex == '' || zIndex == 'auto' || zIndex == 0) {
                $$.css('z-index', 0);
            }
            $$.css('padding', '');

            var options = {
                borderRadius: borderRadius,
                borderWidth: {
                    top: parseInt($$.css('border-top-width')) || 0,
                    right: parseInt($$.css('border-right-width')) || 0,
                    bottom: parseInt($$.css('border-bottom-width')) || 0,
                    left: parseInt($$.css('border-left-width')) || 0,
                    hasBorder: function() {
                        return this.top != 0 || this.right != 0 || this.bottom != 0 || this.left != 0;
                    },
                    equals: equals
                },
                dim: {
                    innerWidth: $$.innerWidth(),
                    innerHeight: $$.innerHeight(),
                    outerWidth: $$.outerWidth(),
                    outerHeight: $$.outerHeight(),
                    equals: equals
                },
                equals: equals
            };

            var changed = !old || !options.equals(old);
            options.container = $('> span.jgcsBorderRadius', $$);
            borderColor($$, options, changed);
            backgroundColor($$, options, changed);
            backgroundImage($$, options, changed, currentStyle);
            try {
                var c = options.container;
                delete options.container;
                if (c.length && (options.hasImage || !c.is(':empty'))) {
                    c.css({
                        width: options.dim.outerWidth,
                        height: options.dim.outerHeight
                    });
                    if (!$$.css("z-index") && "absoluterelative".indexOf($$.css("position")) < 0 && c.offsetParent().get(0) != $$.get(0))$$.css({zoom: 1, position: "relative"});
                    lastSpans.push(c.get(0));
                }
            } catch(e) {
                alert(e.description);
            } 
            $$.data('gcsBorderRadius', options);
            //TODO vmlOpacity

            function coalesce(value, newValue) {
                return parseInt(value === undefined ? newValue : value);
            }

            function borderColor($$, options, changed) {
                var old = $$.data('gcsBorderRadius');
                if ($$.css('border-color') == 'transparent') $$.css('border-color', '');
                options.borderColor = options.borderWidth.hasBorder() ? $$.css('border-color') : "transparent";
                if (options.borderColor != "transparent") {
                    if ($.browser.version > 6) {
                        $$.css('border-color', 'transparent');
                    } else {
                        var padding = {
                            overflow: 'visible',
                            border: 'none',
                            paddingTop: (parseInt($$.css('padding-top')) || 0) + options.borderWidth.top,
                            paddingRight: (parseInt($$.css('padding-right')) || 0) + options.borderWidth.right,
                            paddingBottom: (parseInt($$.css('padding-bottom')) || 0) + options.borderWidth.bottom,
                            paddingLeft: (parseInt($$.css('padding-left')) || 0) + options.borderWidth.left
                        };
                        //                    $$.css({borderColor: 'pink', filter: 'chroma(color=pink)'});
                        $$.css(padding);
                    }
                }
                if (!changed && old && options.borderColor == old.borderColor) return;

                if (options.borderColor == "transparent") {
                    options.container.find(".vml-border").remove();
                } else {
                    var container = getContainer($$, options);
                    var border = container.find(".vml-border");
                    if (!border.length) {
                        border = $('<vml:shape class="vml-border" stroked="false" coordorigin="0,0" filled="true">' + '</vml:shape>');
                        changed = true;
                    }
                    if (changed) {
                        border.attr({
                            path: getOuterPath(options.dim, options.borderRadius) + getInnerPath(options.dim, options.borderRadius, options.borderWidth),
                            coordsize: point(options.dim.outerWidth + 1, options.dim.outerHeight + 1),
                            fillcolor: options.borderColor
                        }).css({
                            position: 'absolute',
                            top: 0,
                            left: 0,
                            width: options.dim.outerWidth,
                            height: options.dim.outerHeight
                        });
                    } else {
                        border.attr({
                            fillcolor: options.borderColor
                        });
                    }
                    border.prependTo(container);
                }
            }

            function backgroundColor($$, options, changed) {
                var old = $$.data('gcsBorderRadius');
                if ($$.css('background-color') == 'transparent') $$.css('background-color', '');
                options.color = $$.css('background-color');
                $$.css('background-color', 'transparent');
                if (!changed && old && options.color == old.color) return;

                if (options.color == 'transparent') {
                    options.container.find('.vml-color').remove();
                } else {
                    var container = getContainer($$, options);
                    var c = container.find('.vml-color');
                    if (!c.length) {
                        c = $('<vml:shape class="vml-color" stroked="false" coordorigin="0,0" filled="true">' + '</vml:shape>');
                        changed = true;
                    }
                    if (changed) {
                        c.attr({
                            path: getInnerPath(options.dim, options.borderRadius, options.borderWidth),
                            coordsize: point(options.dim.outerWidth + 1, options.dim.outerHeight + 1),
                            fillcolor: options.color
                        }).css({
                            position: 'absolute',
                            width: options.dim.outerWidth,
                            height: options.dim.outerHeight,
                            left: 0,
                            top: 0
                        });
                    } else {
                        c.attr({fillcolor: options.color});
                    }
                    c.prependTo(container);
                }
            }

            function backgroundImage($$, options, changed, currentStyle) {
                var old = $$.data('gcsBorderRadius');
                if ($$.css('background-image') == 'none') $$.css('background-image', '');
                var background = options.background = {
                    image: cssImage($$.css('background-image')),
                    equals: equals
                };

                if (!background.image) {
                    options.container.find('.vml-image').remove();
                    return;
                }

                options.hasImage = true;

                var src = background.image;
                var dim = backgroundCache[src];
                if (dim) {
                    background.imageWidth = dim.width;
                    background.imageHeight = dim.height;
                    backgroundImage0();
                } else {
                    var img = $('<img/>');
                    img.css({visibility: 'hidden', position: 'absolute'});
                    img.appendTo("body");
                    img.bind('load', function() {
                        backgroundCache[src] = {
                            width: background.imageWidth = img.width(),
                            height: background.imageHeight = img.height(),
                            image: img.get(0)
                        };
                        img.remove();
                        //TODO clipImage
                        backgroundImage0();
                    }).attr('src', src);
                }

                function cssImage(s) {
                    return s == 'none' ? null : s.substring(5, s.length - 2);
                }

                function backgroundImage0() {
                    var maxX = options.dim.innerWidth - background.imageWidth;
                    var maxY = options.dim.innerHeight - background.imageHeight;
                    background.positionX = Math.ceil(parsePosition(currentStyle['backgroundPositionX'], maxX));
                    background.positionY = Math.ceil(parsePosition(currentStyle['backgroundPositionY'], maxY));
                    background.repeat = currentStyle['backgroundRepeat'];
                    if (changed || !old || !background.equals(old.background)) {
                        var container = getContainer($$, options);
                        var image = container.find('.vml-image');
                        if (!image.length) {
                            image = $('<vml:shape class="vml-image" stroked="false" coordorigin="0,0">' +
                                    '<vml:fill color="none" type="tile">' + '</vml:fill>' +
                                    '</vml:shape>');
                            changed = true;
                        }
                        var fill = image.children();
                        if (changed) {
                            image.attr({
                                path: coords(true, options.dim.innerWidth, options.dim.innerHeight, innerRadius(options.borderRadius, options.borderWidth), options.borderWidth.left, options.borderWidth.top),
                                coordsize: point(options.dim.outerWidth + 1, options.dim.outerHeight + 1)
                            }).css({
                                position: 'absolute',
                                width: options.dim.outerWidth,
                                height: options.dim.outerHeight,
                                left: 0,
                                top: 0
                            });
                            fill.attr({
                                position: point(background.positionX / options.dim.outerWidth, background.positionY / options.dim.outerHeight)
                            });
                        }
                        image.css('clip', getCssClip(background, options.dim, options.borderWidth.left, options.borderWidth.top));
                        if (fill.attr("src") != background.image) fill.attr({ src: background.image });

                        var color = container.find('.vml-color');
                        if (color.length) {
                            image.insertAfter(color);
                        } else {
                            image.prependTo(container);
                        }
                        $$.css('background-image', 'none');
                    }

                    function getCssClip(background, dim, left, top) {
                        left += 1;
                        top += 1;
                        var t = 0, r = dim.innerWidth, b = dim.innerHeight, l = 0;
                        if (background.repeat == 'repeat-x' || background.repeat == 'no-repeat') {
                            t = background.positionY;
                            b = Math.min(t + background.imageHeight, b);
                        }
                        if (background.repeat == 'repeat-y' || background.repeat == 'no-repeat') {
                            l = background.positionX;
                            r = Math.min(l + background.imageWidth, r);
                        }
                        return rect(t + top, r + left, b + top, l + left);

                        function rect(t, r, b, l) {
                            return 'rect(' + t + 'px ' + r + 'px ' + b + 'px ' + l + 'px)';
                        }

                    }

                    function parsePosition(position, full) {
                        switch (position) {
                            case 'left':
                            case 'top':
                                return 0;

                            case 'center':
                            case 'middle':
                                return Math.ceil(full / 2);

                            case 'right':
                            case 'bottom':
                                return full;
                        }
                        if (position.indexOf('%') < 0) {
                            return parseInt(position, 10);
                        } else {
                            return Math.ceil(full * parseInt(position, 10) * 0.01);
                        }
                    }
                }
            }

            function getContainer($$, options) {
                if (options.container && options.container.length) return options.container;
                options.container = $('> span.jgcsBorderRadius', $$);
                if (options.container.length) return options.container;
                options.container = $('<span/>').addClass('jgcsBorderRadius').css({
                    position: 'absolute',
                    left: -options.borderWidth.left,
                    top: -options.borderWidth.top,
                    width: options.dim.outerWidth,
                    height: options.dim.outerHeight,
                    padding: 0,
                    border: 0,
                    margin: 0,
                    display: 'inline',
                    'z-index': -1,
                    'float': 'none',
                    overflow: 'visible'
                });
                options.container.prependTo($$);
                return options.container;
            }

            function getInnerPath(dim, borderRadius, borderWidth) {
                return coords(false, dim.innerWidth, dim.innerHeight, innerRadius(borderRadius, borderWidth), borderWidth.left, borderWidth.top);
            }

            function getOuterPath(dim, borderRadius) {
                return coords(true, dim.outerWidth, dim.outerHeight, borderRadius, 0, 0);
            }

            function innerRadius(borderRadius, borderWidth) {
                return {
                    topLeft: borderRadius.topLeft - Math.max(borderWidth.left, borderWidth.top),
                    topRight: borderRadius.topRight - Math.max(borderWidth.top, borderWidth.right),
                    bottomRight: borderRadius.bottomRight - Math.max(borderWidth.right, borderWidth.bottom),
                    bottomLeft: borderRadius.bottomLeft - Math.max(borderWidth.bottom, borderWidth.left)
                };
            }

            function coords(direction, w, h, borderRadius, aL, aT) {
                var minRadius = Math.min(w, h) / 2;
                var topLeft = Math.max(0, Math.min(minRadius, borderRadius.topLeft));
                var topRight = Math.max(0, Math.min(minRadius, borderRadius.topRight));
                var bottomRight = Math.max(0, Math.min(minRadius, borderRadius.bottomRight));
                var bottomLeft = Math.max(0, Math.min(minRadius, borderRadius.bottomLeft));

                var points = [
                    [                          0  , Math.floor(topLeft)],
                    [Math.floor(topLeft),                           0  ],
                    [Math.ceil(w - topRight),                           0  ],
                    [Math.ceil(w), Math.floor(topRight)],
                    [Math.ceil(w), Math.ceil(h - bottomRight)],
                    [Math.ceil(w - bottomRight), Math.ceil(h)],
                    [Math.floor(bottomLeft), Math.ceil(h)],
                    [                          0  , Math.ceil(h - bottomLeft)],
                    [                          0  , Math.floor(topLeft)]
                ];

                if (!direction) points.reverse();

                var cmd = direction
                        ? ['m', 'qy', 'l', 'qx', 'l', 'qy', 'l', 'qx', 'l']
                        : ['m', 'l', 'qy', 'l', 'qx', 'l', 'qy', 'l', 'qx'];
                var s = '';
                var pre = [-1,-1];
                for (var i = 0; i < cmd.length; i++) {
                    var p = points[i];
                    if (p[0] != pre[0] || p[1] != pre[1]) {
                        s += cmd[i] + point(p[0] + aL, p[1] + aT);
                    }
                    pre = p;
                }
                return s;
            }

            function point(x, y) {
                return x + ',' + y;
            }

            function equals(o) {
                if (!o) return false;
                var result = true;
                $.each(this, function(k, v) {
                    return result = v && $.isFunction(v.equals) ? v.equals(o[k]) : v == o[k];
                });
                return result;
            }
        }
    };
})(jQuery);
