/*
 * jQuery expanding textarea
 * 
 * https://github.com/ferama/jquery-expanding-textarea
 * 
 * Adapted for jQuery from dojo TextArea widget
 *
 * @author Marco Ferragina
 * @version 1.00
 */

(function($) {

$.fn.expandingTextArea = function() {
    return this.filter('textarea').each(function(){
        var textarea = this;
        var $this = $(this);
        $this.css({
            'overflow-x': 'auto',
            'overflow-y': 'hidden',
            'box-sizing': 'border-box',
            '-moz-box-sizing': 'border-box',
            '-webkit-box-sizing': 'border-box',
            'resize' : 'none'
        });
        var _busyResizing = false;
        var _needsHelpShrinking = $.browser.mozilla || $.browser.webkit;
        var _setTimoutHandle;

        var gcs;

        if ($.browser.webkit) {
            gcs = function(node) {
                var s;
                if(node.nodeType == 1){
                    var dv = node.ownerDocument.defaultView;
                    s = dv.getComputedStyle(node, null);
                    if(!s && node.style){
                        node.style.display = "";
                        s = dv.getComputedStyle(node, null);
                    }
                }
                return s || {};
            };
        } else if ($.browser.msie) {
            gcs = function(node){
                // IE (as of 7) doesn't expose Element like sane browsers
                return node.nodeType == 1 /* ELEMENT_NODE*/ ? node.currentStyle : {};
            };
        } else {
            gcs = function(node){
                return node.nodeType == 1 ?
                    node.ownerDocument.defaultView.getComputedStyle(node, null) : {};
            };
        }

        var px;
        if (!$.browser.msie) {
            px = function(element, value) {
                return parseFloat(value) || 0;
            };
        } else {
            px = function(element, avalue) {
                if(!avalue){ return 0; }
                // on IE7, medium is usually 4 pixels
                if(avalue == "medium"){ return 4; }
                // style values can be floats, client code may
                // want to round this value for integer pixels.
                if(avalue.slice && avalue.slice(-2) == 'px'){ return parseFloat(avalue); }
                with(element){
                    var sLeft = style.left;
                    var rsLeft = runtimeStyle.left;
                    runtimeStyle.left = currentStyle.left;
                    try{
                        // 'avalue' may be incompatible with style.left, which can cause IE to throw
                        // this has been observed for border widths using "thin", "medium", "thick" constants
                        // those particular constants could be trapped by a lookup
                        // but perhaps there are more
                        style.left = avalue;
                        avalue = style.pixelLeft;
                    }catch(e){
                        avalue = 0;
                    }
                    style.left = sLeft;
                    runtimeStyle.left = rsLeft;
                }
                return avalue;
            };
        }

        function _getHeight() {
            var newH = textarea.scrollHeight;
            if ($.browser.msie) {
                newH += textarea.offsetHeight - textarea.clientHeight;
            } else if ($.browser.webkit) {
                newH += getBorderExtents(textarea).h;
            } else if ($.browser.mozilla) {
                newH += textarea.offsetHeight - textarea.clientHeight;
            } else {
                newH += getPadBorderExtents(textarea).h;
            }
            return newH;
        }
        function getPadExtents(n, computedStyle) {
            var
                s = computedStyle||gcs(n),
                l = px(n, s.paddingLeft),
                t = px(n, s.paddingTop);
            return {
                l: l,
                t: t,
                w: l+px(n, s.paddingRight),
                h: t+px(n, s.paddingBottom)
            };
 
        }
        function getBorderExtents(n, computedStyle) {
            var 
                ne = "none",
                s = computedStyle||gcs(n),
                bl = (s.borderLeftStyle != ne ? px(n, s.borderLeftWidth) : 0),
                bt = (s.borderTopStyle != ne ? px(n, s.borderTopWidth) : 0);
            return {
                l: bl,
                t: bt,
                w: bl + (s.borderRightStyle!=ne ? px(n, s.borderRightWidth) : 0),
                h: bt + (s.borderBottomStyle!=ne ? px(n, s.borderBottomWidth) : 0)
            };
        }
        function getPadBorderExtents(n, computedStyle) {
            var 
                s = computedStyle||gcs(n),
                p = getPadExtents(n, s),
                b = getBorderExtents(n, s);
            return {
                l: p.l + b.l,
                t: p.t + b.t,
                w: p.w + b.w,
                h: p.h + b.h
            };
        }

        function _shrink() {
            _setTimoutHandle = null;
            if (_needsHelpShrinking && ! _busyResizing) {
                _busyResizing = true;
                var empty = false;
                if(textarea.value == ''){
                    textarea.value = ' '; // prevent collapse all the way back to 0
                    empty = true;
                }
                var scrollHeight = textarea.scrollHeight;
                if (!scrollHeight) {
                    _estimateHeight();
                } else {
                    var oldPadding = $this.css('padding-bottom');
                    var newPadding = getPadExtents(textarea);
                    newPadding = newPadding.h - newPadding.t;
                    $this.css('padding-bottom', newPadding + 1 +"px");
                    var newH = _getHeight() - 1 + "px";
                    if ($this.css('max-height') != newH) {
                        $this.css('padding-bottom', newPadding + scrollHeight + "px");
                        textarea.scrollTop = 0;
                        $this.css('max-height', _getHeight() - scrollHeight + "px");
                    }
                    $this.css('padding-bottom', oldPadding);
                }
                if(empty){
                    textarea.value = '';
                }
                _busyResizing = false;
            }
        }

        function _estimateHeight() {
            $this.css({
                'max-height': '',
                'height': 'auto'
            });
            textarea.rows = (textarea.value.match(/\n/g) || []).length + 1;
        }

        function _onInput() {
            if (_busyResizing) { return; }
            _busyResizing = true;
            if(textarea.scrollHeight && textarea.offsetHeight && textarea.clientHeight){
                var newH = _getHeight() + "px";
                if ($this.css('height') != newH) {
                    $this.css('height', newH);
                    $this.css('maxHeight', newH);
                }
                if (_needsHelpShrinking) {
                    if (_setTimoutHandle) {
                        clearTimeout(_setTimoutHandle);
                    }
                    _setTimoutHandle = setTimeout(_shrink, 0);
                }
            } else {
                _estimateHeight();
            }
            _busyResizing = false;
        }

        _setTimoutHandle = setTimeout(_onInput, 0);
        $this.unbind('.expandingTextarea')
            .bind('keyup.expandingTextarea', _onInput)
            .bind('keydown.expandingTextarea', _onInput)
            .bind('change.expandingTextarea', _onInput)
            .bind('scroll.expandingTextarea', _onInput)
            .bind('resize.expandingTextarea', _onInput)
            .bind('focus.expandingTextarea', _onInput)
            .bind('blur.expandingTextarea', _onInput)
            .bind('cut.expandingTextarea', _onInput)
            .bind('paste.expandingTextarea', _onInput);
    });
};

})(jQuery);

