
(function($) {
    var $s = $.serpentine;

    $.extend($s, {
        columnize: function(element, options) {
            $.extend(this, options);
            this.element = element;
            this.isInitialized = false;

            if ($.serpentine.hasDimensions(this.element) && !this.isInitialized)
                this._init();

            var instance = this;
            $("#subnav").bind("selectedIndexChanged", function(event, args) {
                if ($.serpentine.hasDimensions(instance.element) && !instance.isInitialized)
                    instance._init();
            });

            $s.trigger(element, 'load');
        }
    });

    $.extend($s.columnize.prototype, {
        _init: function() {
            this.isInitialized = true;

            this.lastWordsOfParagraph = new Array();

            this._setLastWordOfParagraph();

            if (typeof (this.columnGap) == "undefined")
                this.columnGap = 10;
            else
                this.columnGap = parseInt(this.columnGap / 2);

            if (typeof (this.columnsPerPage) == "undefined")
                this.columnsPerPage = 2;

            if (typeof (this.visible) == "undefined")
                this.visible = false;

            if (typeof (this.autoAdjustHeight) == "undefined")
                this.autoAdjustHeight = false;

            //autoAdjustHeight not supporde by IE7
            if ($.browser.msie && (parseInt($.browser.version) == 7))
                this.autoAdjustHeight = false;

            if (typeof (this.autoAdjustLastLine) == "undefined")
                this.autoAdjustLastLine = false;

            var pad = (this.columnsPerPage - 1) * this.columnGap * 2;

            this._minHeight = $.browser.msie ? this.element.clientHeight : window.getComputedStyle(this.element, null).height;
            this._minHeight = parseInt(this._minHeight);

            this._availableWidth = parseInt((this.element.clientWidth - pad) / this.columnsPerPage);

            this._generateColumns(0);
            this._generatePages();

            this._removeLastParagraphMarginBottom();

            if (this.autoAdjustHeight || this.autoAdjustLastLine)
                this._setMaxColumnHeight();

            this._hidePages();

            if (this.visible)
                this.element.style.visibility = "visible";
        },


        _removeLastParagraphMarginBottom: function() {
            var marginBottomParagraph = 0;
            var pages = this.element.children;
            for (i = 0; i < pages.length; i++) {
                var columns = pages[i].children;
                for (j = 0; j < columns.length; j++) {
                    var children = columns[j].children;
                    for (k = 0; k < children.length; k++) {
                        if ((children[k].tagName.toUpperCase() == "P") && marginBottomParagraph == 0)
                            marginBottomParagraph = $.browser.msie ? children[k].currentStyle.marginBottom : window.getComputedStyle(children[k], null).marginBottom;
                        if ((children[k].tagName.toUpperCase() == "P") && children.length == k + 1)
                            children[k].style.marginBottom = "0px";
                    }
                }
            }


            if (this.heightElementSelector && (marginBottomParagraph != 0)) {
                var heightElement = document.getElementById(this.heightElementSelector);
                if (heightElement == null)
                    heightElement = $(this.heightElementSelector).get(0);

                heightElement.style.height = parseInt(heightElement.style.height) - parseInt(marginBottomParagraph) + "px";
            }
            else if (marginBottomParagraph != 0) {
                this.element.style.height = parseInt(this._minHeight) - parseInt(marginBottomParagraph) + "px";
            }
        },

        makeItVisible: function() {
            this.element.style.visibility = "visible";
            this.element.style.overflow = "visible";            
        },

        _hidePages: function() {
            for (i = 1; i < this.element.children.length; i++) {
                this.element.children[i].style.display = "none";
            }
        },

        _setLastWordOfParagraph: function() {
            var paragraphes = this.element.getElementsByTagName("p");
            for (i = 0; i < paragraphes.length; i++) {
                if (paragraphes[i].innerHTML.length - 20 > 0) {
                    var endsWith = paragraphes[i].innerHTML.slice(paragraphes[i].innerHTML.length - 20, paragraphes[i].innerHTML.length)
                    this.lastWordsOfParagraph.push(endsWith);
                }
            }
        },

        _setMaxColumnHeight: function() {
            var pages = this.element.children;
            this.maxColumnHeight = 0;

            for (i = 0; i < pages.length; i++)
                if (this.maxColumnHeight < pages[i].offsetHeight)
                this.maxColumnHeight = pages[i].offsetHeight;
        },

        _adjustLineHeight: function(column, inrement) {
            var paragraphs = column.getElementsByTagName('p');
            for (k = 0; k < paragraphs.length; k++) {
                if (paragraphs[k].style.lineHeight != "") {

                    paragraphs[k].style.lineHeight = parseFloat(paragraphs[k].style.lineHeight) + inrement;
                }
                else
                    paragraphs[k].style.lineHeight = 1;
            }
        },

        adjustLineHeight: function(page, isLastPage) {
            if (page.justified)
                return;

            var cols = page.children;
            for (j = 0; j < cols.length; j++) {
                if (isLastPage && (j == cols.length - 1))
                    continue;

                if (this.autoAdjustHeight && !$.browser.safari)
                    while ((cols[j].offsetHeight < this.maxColumnHeight))
                    this._adjustLineHeight(cols[j], 0.01);

                if (this.autoAdjustLastLine)
                    this._adjustLastLine(cols[j]);
            }

            page.justified = true;
        },

        _adjustLastLine: function(column) {
            var paragraphs = column.getElementsByTagName('p');
            var lastParagraph = paragraphs[paragraphs.length - 1];

            var textAlign = $.browser.msie ? lastParagraph.currentStyle.textAlign : window.getComputedStyle(lastParagraph, null).textAlign;
            var fontSize = $.browser.msie ? lastParagraph.currentStyle.fontSize : window.getComputedStyle(lastParagraph, null).fontSize;

            if (textAlign != "justify")
                return;

            for (k = 0; k < this.lastWordsOfParagraph.length; k++)
                if (lastParagraph.innerHTML.indexOf(this.lastWordsOfParagraph[k], lastParagraph.innerHTML.length - this.lastWordsOfParagraph[k].length) != -1)
                return;

            var height = column.offsetHeight;

            var head = document.getElementsByTagName('head')[0];
            var style = document.createElement('style');
            var rules = document.createTextNode('.text-align-last { text-align-last: justify; }');
            style.type = 'text/css';

            if (style.styleSheet) {//IE
                lastParagraph.className = lastParagraph.className + " text-align-last";
                style.styleSheet.cssText = rules.nodeValue;
                head.appendChild(style);
            }
            else { //Gecko
                lastParagraph.className = lastParagraph.className + " text-align-last-" + fontSize;
                style.appendChild(rules);
                head.appendChild(style);

                rules.replaceWholeText(this._generateGeckoLastLineHack(fontSize));
            }

            column.style.height = height + "px";
        },

        _generateGeckoLastLineHack: function(fontSize) {
            var id = "fontSize" + fontSize;
            var contentElement = document.getElementById(id);

            if (!contentElement) {
                contentElement = document.createElement("span");
                document.getElementsByTagName("body")[0].appendChild(contentElement);
                contentElement.style.fontSize = fontSize;
                contentElement.id = id;
                contentElement.style.position = "fixed";
                contentElement.style.top = "-199px";

                contentElement.innerHTML = " ";
                while (this._availableWidth > contentElement.offsetWidth)
                    contentElement.innerHTML += "_";
            }

            return ".text-align-last-" + fontSize + ":after {  content: ' " + contentElement.innerHTML + "'; line-height: 0; visibility: hidden; }";
        },

        _addWidthAndPaddingToAllColumns: function() {
            var columns = this.element.getElementsByTagName('div');
            for (i = 0; i < columns.length; i++) {
                if (i == 0) {
                    columns[i].style.paddingRight = this.columnGap + "px";
                }
                else if (i == columns.length - 1) {
                    columns[i].style.paddingLeft = this.columnGap + "px";
                }
                else {
                    columns[i].style.paddingRight = this.columnGap + "px";
                    columns[i].style.paddingLeft = this.columnGap + "px";
                }
            }
        },

        _generatePages: function() {
            var columns = this.element.getElementsByTagName('div');

            var i = 0;
            var pages = new Array();
            var divElement = document.createElement('div');

            while (columns.length != 0) {
                if (i == this.columnsPerPage) {
                    i = 0;
                    pages.push(divElement);
                    divElement = document.createElement('div');
                }
                divElement.appendChild(columns.item(0));
                i++
            }
            pages.push(divElement);

            for (i = 0; i < pages.length; i++) {
                var childColumns = pages[i].getElementsByTagName("div");
                var columnGap = this.columnGap + "px";
                for (j = 0; j < childColumns.length; j++) {
                    if (j == 0) {
                        childColumns[j].style.paddingRight = columnGap;
                    }
                    else if (j == childColumns.length - 1) {
                        childColumns[j].style.paddingLeft = columnGap;
                    }
                    else {
                        childColumns[j].style.paddingRight = columnGap;
                        childColumns[j].style.paddingLeft = columnGap;
                    }
                }
                this.element.appendChild(pages[i]);
            }
        },

        _generateColumns: function(index) {
            var i = 0;
            var baseColumn = this.element.getElementsByTagName('div').item(index);
            baseColumn.style.width = this._availableWidth + 'px';

            this.element.insertBefore(baseColumn.cloneNode(false), baseColumn);

            var columns = this.element.getElementsByTagName('div');

            var currentColumn = columns.item(index);

            while (currentColumn.offsetHeight < this._minHeight && baseColumn.hasChildNodes()) {
                if (baseColumn.firstChild.nodeType == 1) {
                    currentColumn.appendChild(baseColumn.firstChild);
                } else {
                    baseColumn.removeChild(baseColumn.firstChild);
                }
            }

            var lastChild = currentColumn.lastChild;
            var nextColumn = columns.item(index + 1);
            switch (lastChild.nodeName.toLowerCase()) {
                case 'p':
                    this._parapgraphWrapper(currentColumn, lastChild, nextColumn, this._minHeight);
                    break;
                case 'ul':
                case 'ol':
                    this._listWrapper(currentColumn, lastChild, nextColumn, this._minHeight);
                    break;
                default:
                    //don't know what to do with this element. Let it stick out.
            }

            this._headingWrapper(currentColumn, nextColumn);

            var lastColumn = columns.item(index + 1);

            if (lastColumn.offsetHeight > this._minHeight)
                this._generateColumns(index + 1);
        },

        _listWrapper: function(sourceColumnIn, sourceListIn, destinationColumnIn, heightIn) {
            var newList = sourceListIn.cloneNode(false);

            destinationColumnIn.insertBefore(newList, destinationColumnIn.firstChild);

            while (currentElement = sourceListIn.lastChild) {
                if (sourceColumnIn.offsetHeight <= heightIn)
                    break;

                if (currentElement.nodeName.toLowerCase() == 'li')
                    newList.insertBefore(currentElement, newList.firstChild);
                else
                    sourceListIn.removeChild(currentElement);
            }

            var numItems = 1;
            var elementList = sourceListIn.childNodes;
            for (var i = 0; i < elementList.length; i++)
                if (elementList[i].nodeName.toLowerCase() == 'li')
                numItems++;

            newList.start = numItems;

            if (sourceListIn.offsetHeight == 0)
                sourceColumnIn.removeChild(sourceListIn);
        },

        _headingWrapper: function(currentColumn, nextColumn) {
            if (/^h[1-6]$/i.test(currentColumn.lastChild.nodeName))
                nextColumn.insertBefore(currentColumn.lastChild, nextColumn.firstChild);
        },

        _parapgraphWrapper: function(sourceColumnIn, sourceParagraphIn, destinationColumnIn, heightIn) {
            this.sourceColumn = sourceColumnIn;
            this.height = heightIn;

            this.processElement = function(source, dest) {
                var lastSourceChild;
                while (lastSourceChild = source.lastChild) {
                    if (lastSourceChild.nodeType == 1) {
                        var newDest = lastSourceChild.cloneNode(false);
                        dest.insertBefore(newDest, dest.firstChild);
                        if (this.processElement(lastSourceChild, newDest))
                            return true;
                    }
                    else if (lastSourceChild.nodeType == 3) {
                        if (this.wrapTextNode(lastSourceChild, dest))
                            return true;
                    }

                    source.removeChild(lastSourceChild);
                }

                return false;
            }

            this.wrapTextNode = function(source, dest) {
                var sourceText = source.nodeValue;

                var sourceTextAray = sourceText.split(/\s/);
                var destTextArray = new Array();

                while (this.sourceColumn.offsetHeight > this.height && sourceTextAray.length > 0) {
                    destTextArray.push(sourceTextAray.pop());
                    source.nodeValue = sourceTextAray.join(' ');
                }

                var newText = (/^\s/.test(sourceText) ? ' ' : '') + (destTextArray.reverse().join(' ')) + (/\s$/.test(sourceText) ? ' ' : '');

                dest.insertBefore(document.createTextNode(newText), dest.firstChild);

                return this.sourceColumn.offsetHeight <= this.height;
            }

            destinationColumnIn.insertBefore(sourceParagraphIn.cloneNode(false), destinationColumnIn.firstChild);

            this.processElement(sourceParagraphIn, destinationColumnIn.firstChild);

            if (sourceParagraphIn.offsetHeight == 0) {
                this.sourceColumn.removeChild(sourceParagraphIn);
            }
        }

    });

    $.fn.sColumnize = function(options) {
        options = $.extend({}, $.fn.sColumnize.defaults, options);

        return this.each(function() {
            options = $.meta ? $.extend({}, options, $(this).data()) : options;

            if (!$(this).data('sColumnize'))
                $(this).data('sColumnize', new $s.columnize(this, options));
        });
    };
})(jQuery);


