// version 1.4.0
// http://welcome.totheinter.net/columnizer-jquery-plugin/
// created by: Adam Wulf adam.wulf@gmail.com

(function($){

 $.fn.columnize = function(options) {


    var defaults = {
        // default width of columnx
        width: 400,
        // optional # of columns instead of width
        columns : false,
        // true to build columns once regardless of window resize
        // false to rebuild when content box changes bounds
        buildOnce : false,
        // an object with options if the text should overflow
        // it's container if it can't fit within a specified height
        overflow : false,
        // this function is called after content is columnized
        doneFunc : function(){},
        // if the content should be columnized into a
        // container node other than it's own node
        target : false,
        // re-columnizing when images reload might make things
        // run slow. so flip this to true if it's causing delays
        ignoreImageLoading : true,
        // should columns float left or right
        float : "left",
        // ensure the last column is never the tallest column
        lastNeverTallest : false
    };
    var options = $.extend(defaults, options);

    return this.each(function() {
        var $inBox = options.target ? $(options.target) : $(this);
        var maxHeight = $(this).height();
        var $cache = $('<div></div>'); // this is where we'll put the real content
        var lastWidth = 0;
        var columnizing = false;
        $cache.append($(this).children().clone(true));

        // images loading after dom load
        // can screw up the column heights,
        // so recolumnize after images load
        if(!options.ignoreImageLoading && !options.target){
            if(!$inBox.data("imageLoaded")){
                $inBox.data("imageLoaded", true);
                if($(this).find("img").length > 0){
                    // only bother if there are
                    // actually images...
                    var func = function($inBox,$cache){ return function(){
                        if(!$inBox.data("firstImageLoaded")){
                            $inBox.data("firstImageLoaded", "true");
                            $inBox.empty().append($cache.children().clone(true));
                            $inBox.columnize(options);
                        }
                    }}($(this), $cache);
                    $(this).find("img").one("load", func);
                    $(this).find("img").one("abort", func);
                    return;
                }
            }
        }

        $inBox.empty();

        columnizeIt();

        if(!options.buildOnce){
            $(window).resize(function() {
                if(!options.buildOnce && $.browser.msie){
                    if($inBox.data("timeout")){
                        clearTimeout($inBox.data("timeout"));
                    }
                    $inBox.data("timeout", setTimeout(columnizeIt, 200));
                }else if(!options.buildOnce){
                    columnizeIt();
                }else{
                    // don't rebuild
                }
            });
        }

        /**
         * return a node that has a height
         * less than or equal to height
         *
         * @param putInHere, a dom element
         * @$pullOutHere, a jQuery element
         */
        function columnize($putInHere, $pullOutHere, $parentColumn, height){
            while($parentColumn.height() < height &&
                  $pullOutHere[0].childNodes.length){
                $putInHere.append($pullOutHere[0].childNodes[0]);
            }
            if($putInHere[0].childNodes.length == 0) return;

            // now we're too tall, undo the last one
            var kids = $putInHere[0].childNodes;
            var lastKid = kids[kids.length-1];
            $putInHere[0].removeChild(lastKid);
            var $item = $(lastKid);


            if($item[0].nodeType == 3){
                // it's a text node, split it up
                var oText = $item[0].nodeValue;
                var counter2 = options.width / 18;
                if(options.accuracy)
                counter2 = options.accuracy;
                var columnText;
                var latestTextNode = null;
                while($parentColumn.height() < height && oText.length){
                    if (oText.indexOf(' ', counter2) != '-1') {
                        columnText = oText.substring(0, oText.indexOf(' ', counter2));
                    } else {
                        columnText = oText;
                    }
                    latestTextNode = document.createTextNode(columnText);
                    $putInHere.append(latestTextNode);

                    if(oText.length > counter2){
                        oText = oText.substring(oText.indexOf(' ', counter2));
                    }else{
                        oText = "";
                    }
                }
                if($parentColumn.height() >= height && latestTextNode != null){
                    // too tall :(
                    $putInHere[0].removeChild(latestTextNode);
                    oText = latestTextNode.nodeValue + oText;
                }
                if(oText.length){
                    $item[0].nodeValue = oText;
                }else{
                    return false; // we ate the whole text node, move on to the next node
                }
            }

            if($pullOutHere.children().length){
                $pullOutHere.prepend($item);
            }else{
                $pullOutHere.append($item);
            }

            return $item[0].nodeType == 3;
        }

        function split($putInHere, $pullOutHere, $parentColumn, height){
            if($pullOutHere.children().length){
                $cloneMe = $pullOutHere.children(":first");
                $clone = $cloneMe.clone(true);
                if($clone.attr("nodeType") == 1 && !$clone.hasClass("dontend")){
                    $putInHere.append($clone);
                    if($clone.is("img") && $parentColumn.height() < height + 20){
                        $cloneMe.remove();
                    }else if(!$cloneMe.hasClass("dontsplit") && $parentColumn.height() < height + 20){
                        $cloneMe.remove();
                    }else if($clone.is("img") || $cloneMe.hasClass("dontsplit")){
                        $clone.remove();
                    }else{
                        $clone.empty();
                        if(!columnize($clone, $cloneMe, $parentColumn, height)){
                            if($cloneMe.children().length){
                                split($clone, $cloneMe, $parentColumn, height);
                            }
                        }
                        if($clone.get(0).childNodes.length == 0){
                            // it was split, but nothing is in it :(
                            $clone.remove();
                        }
                    }
                }
            }
        }


        function singleColumnizeIt() {
            if ($inBox.data("columnized") && $inBox.children().length == 1) {
                return;
            }
            $inBox.data("columnized", true);
            $inBox.data("columnizing", true);

            $inBox.empty();
            $inBox.append($("<div class='first last column' style='width:98%; padding: 3px; float: " + options.float + ";'></div>")); //"
            $col = $inBox.children().eq($inBox.children().length-1);
            $destroyable = $cache.clone(true);
            if(options.overflow){
                targetHeight = options.overflow.height;
                columnize($col, $destroyable, $col, targetHeight);
                // make sure that the last item in the column isn't a "dontend"
                if(!$destroyable.children().find(":first-child").hasClass("dontend")){
                    split($col, $destroyable, $col, targetHeight);
                }

                while(checkDontEndColumn($col.children(":last").length && $col.children(":last").get(0))){
                    var $lastKid = $col.children(":last");
                    $lastKid.remove();
                    $destroyable.prepend($lastKid);
                }

                var html = "";
                var div = document.createElement('DIV');
                while($destroyable[0].childNodes.length > 0){
                    var kid = $destroyable[0].childNodes[0];
                    for(var i=0;i<kid.attributes.length;i++){
                        if(kid.attributes[i].nodeName.indexOf("jQuery") == 0){
                            kid.removeAttribute(kid.attributes[i].nodeName);
                        }
                    }
                    div.innerHTML = "";
                    div.appendChild($destroyable[0].childNodes[0]);
                    html += div.innerHTML;
                }
                var overflow = $(options.overflow.id)[0];
                overflow.innerHTML = html;

            }else{
                $col.append($destroyable);
            }
            $inBox.data("columnizing", false);

            if(options.overflow){
                options.overflow.doneFunc();
            }

        }

        function checkDontEndColumn(dom){
            if(dom.nodeType != 1) return false;
            if($(dom).hasClass("dontend")) return true;
            if(dom.childNodes.length == 0) return false;
            return checkDontEndColumn(dom.childNodes[dom.childNodes.length-1]);
        }

        function columnizeIt() {
            if(lastWidth == $inBox.width()) return;
            lastWidth = $inBox.width();

            var numCols = Math.round($inBox.width() / options.width);
            if(options.columns) numCols = options.columns;
//          if ($inBox.data("columnized") && numCols == $inBox.children().length) {
//              return;
//          }
            if(numCols <= 1){
                return singleColumnizeIt();
            }
            if($inBox.data("columnizing")) return;
            $inBox.data("columnized", true);
            $inBox.data("columnizing", true);

            $inBox.empty();
            $inBox.append($("<div style='width:" + (Math.round(100 / numCols) - 2)+ "%; padding: 3px; float: " + options.float + ";'></div>")); //"
            $col = $inBox.children(":last");
            $col.append($cache.clone());
            maxHeight = $col.height();
            $inBox.empty();

            var targetHeight = maxHeight / numCols;
            var firstTime = true;
            var maxLoops = 3;
            var scrollHorizontally = false;
            if(options.overflow){
                maxLoops = 1;
                targetHeight = options.overflow.height;
            }else if(options.height && options.width){
                maxLoops = 1;
                targetHeight = options.height;
                scrollHorizontally = true;
            }

            for(var loopCount=0;loopCount<maxLoops;loopCount++){
                $inBox.empty();
                var $destroyable;
                try{
                    $destroyable = $cache.clone(true);
                }catch(e){
                    // jquery in ie6 can't clone with true
                    $destroyable = $cache.clone();
                }
                $destroyable.css("visibility", "hidden");
                // create the columns
                for (var i = 0; i < numCols; i++) {
                    /* create column */
                    var className = (i == 0) ? "first column" : "column";
                    var className = (i == numCols - 1) ? ("last " + className) : className;
                    $inBox.append($("<div class='" + className + "' style='width:" + (Math.round(100 / numCols) - 2)+ "%; float: " + options.float + ";'></div>")); //"
                }

                // fill all but the last column (unless overflowing)
                var i = 0;
                while(i < numCols - (options.overflow ? 0 : 1) || scrollHorizontally && $destroyable.children().length){
                    if($inBox.children().length <= i){
                        // we ran out of columns, make another
                        $inBox.append($("<div class='" + className + "' style='width:" + (Math.round(100 / numCols) - 2)+ "%; float: " + options.float + ";'></div>")); //"
                    }
                    var $col = $inBox.children().eq(i);
                    columnize($col, $destroyable, $col, targetHeight);
                    // make sure that the last item in the column isn't a "dontend"
                    if(!$destroyable.children().find(":first-child").hasClass("dontend")){
                        split($col, $destroyable, $col, targetHeight);
                    }else{
//                      alert("not splitting a dontend");
                    }

                    while(checkDontEndColumn($col.children(":last").length && $col.children(":last").get(0))){
                        var $lastKid = $col.children(":last");
                        $lastKid.remove();
                        $destroyable.prepend($lastKid);
                    }
                    i++;
                }
                if(options.overflow && !scrollHorizontally){
                    var IE6 = false /*@cc_on || @_jscript_version < 5.7 @*/;
                    var IE7 = (document.all) && (navigator.appVersion.indexOf("MSIE 7.") != -1);
                    if(IE6 || IE7){
                        var html = "";
                        var div = document.createElement('DIV');
                        while($destroyable[0].childNodes.length > 0){
                            var kid = $destroyable[0].childNodes[0];
                            for(var i=0;i<kid.attributes.length;i++){
                                if(kid.attributes[i].nodeName.indexOf("jQuery") == 0){
                                    kid.removeAttribute(kid.attributes[i].nodeName);
                                }
                            }
                            div.innerHTML = "";
                            div.appendChild($destroyable[0].childNodes[0]);
                            html += div.innerHTML;
                        }
                        var overflow = $(options.overflow.id)[0];
                        overflow.innerHTML = html;
                    }else{
                        $(options.overflow.id).empty().append($destroyable.children().clone(true));
                    }
                }else if(!scrollHorizontally){
                    // the last column in the series
                    $col = $inBox.children().eq($inBox.children().length-1);
                    while($destroyable.children().length) $col.append($destroyable.children(":first"));
                    var afterH = $col.height();
                    var diff = afterH - targetHeight;
                    var totalH = 0;
                    var min = 10000000;
                    var max = 0;
                    var lastIsMax = false;
                    $inBox.children().each(function($inBox){ return function($item){
                        var h = $inBox.children().eq($item).height();
                        lastIsMax = false;
                        totalH += h;
                        if(h > max) {
                            max = h;
                            lastIsMax = true;
                        }
                        if(h < min) min = h;
                    }}($inBox));

                    var avgH = totalH / numCols;
                    if(options.lastNeverTallest && lastIsMax){
                        // the last column is the tallest
                        // so allow columns to be taller
                        // and retry
                        targetHeight = targetHeight + 30;
                        if(loopCount == maxLoops-1) maxLoops++;
                    }else if(max - min > 30){
                        // too much variation, try again
                        targetHeight = avgH + 30;
                    }else if(Math.abs(avgH-targetHeight) > 20){
                        // too much variation, try again
                        targetHeight = avgH;
                    }else {
                        // solid, we're done
                        loopCount = maxLoops;
                    }
                }else{
                    // it's scrolling horizontally, fix the width/classes of the columns
                    $inBox.children().each(function(i){
                        $col = $inBox.children().eq(i);
                        $col.width(options.width + "px");
                        if(i==0){
                            $col.addClass("first");
                        }else if(i==$inBox.children().length-1){
                            $col.addClass("last");
                        }else{
                            $col.removeClass("first");
                            $col.removeClass("last");
                        }
                    });
                    $inBox.width($inBox.children().length * options.width + "px");
                }
                $inBox.append($("<br style='clear:both;'>"));
            }
            $inBox.find('.column').find(':first.removeiffirst').remove();
            $inBox.find('.column').find(':last.removeiflast').remove();
            $inBox.data("columnizing", false);

            if(options.overflow){
                options.overflow.doneFunc();
            }
            options.doneFunc();
        }
    });
 };
})(jQuery);


