/******************************************************************************
 * The MIT License
 *
 * Copyright (c) 2007, Paul Jara <paul@43n79w.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 *****************************************************************************/
/******************************************************************************
 * Version 0.0.1 (Initial Release)
 *
 * The algorithm used to split up the content into columns is naive, yet
 * it still does an acceptable job of yielding practical column layouts that
 * are left-page biased. For instance, in most cases the left-most columns
 * should be the same size or longer than columns to the right, so long as
 * the content is uniform in syntactical nature throughout.
 *
 * Tested on Safari 3, OmniWeb 5.6, FireFox 2, Internet Explorer 6/7.
 * IMPORTANT: This has only been tested on valid XHTML. In fact, I fully expect
 * for it not to work on HTML with tags that are not properly closed, and with
 * non-self-closing tags such as IMG and INPUT.
 *****************************************************************************/
 /******************************************************************************
  * Splits the contents of an element into divs inside a containing div. These
  * divs can then be styled in a stylesheet to appear as newspaper-style
  * columns.
  *
  * Params:
  *
  * options['columnClassName']
  * class name for the column divs that will be created. Defaults to
  * "newspaper-column".
  *
  * options['classToExempt']
  * class name for elements of a particular class that should be IGNORED and
  * left alone (e.g., an introduction paragraph).
  *
  * options['minimumChildrenFor2']
  * if the content has fewer children (e.g., paragraphs) than this, we won't
  * split the content into more than 1 column.
  *
  * options['minimumChildrenFor3']
  * if the content has fewer children (e.g., paragraphs) than this, we won't
  * split the content into more than 2 columns.
  *
  * options['splicePointShortDivisor']
  * divisor used to splice short content (under 4 paragraphs)
  * 
  * options['splicePointLongDivisor']
  * divisor used to splice long content (over 4 paragraphs)
  *
  * options['columns']
  * the number of columns to divide the content into ( valid range: [1,3] )
  *
  * Sample usage:
  * $('#mydiv').makeNewspaperColumns( { 'columnClassName': 'newspaper-column',
  *                                     'classToExempt': 'introduction' } );
  * CSS styles for sample:
  * div.newspaper-column-ctr { clear: both; }
  * div.newspaper-column { float: left; padding: 0 20px; width: 410px; }
  * #newspaper-column-1 { padding: 0 40px 0 0; }
  * #newspaper-column-2 { padding: 0 0 0 40px; }
  *****************************************************************************/
 (function($){
   $.fn.extend({
     makeNewspaperColumns: function(options) {
       options = options || { };

       // If IE 6 would support array notation on strings this would be redundant
       function peekChar(html, startIndex, numCharsToPeekAt) {
         var numChars = numCharsToPeekAt || 1;
         return html.substr(startIndex, numChars);
       }

       // Our splicer
       function spliceHTML(html, spliceTarget) {          
           // We scan the HTML and determine nesting level. We only want to splice on an immediate child (nestLevel = 0)
           var nestLevel = -1;
           var cursor = '';
           var splicePoint = 0;
           var patchIdx = -1;
           var patchTag;

           do {
             cursor = peekChar(html, splicePoint++);

             // We are at an HTML tag
             if (cursor == '<') {

               // Peek ahead, if we see a '/' then decrement the nestLevel, otherwise increment it
               cursor = peekChar(html, splicePoint++);
               if (cursor == '/') {
                 nestLevel--;
               }
               else {
                 nestLevel++;                

                 // We are in a direct child node
                 if (nestLevel == 0) {
                   // If we are past our spliceTarget, we can stop looking, we have out splicePoint now
                   if ((splicePoint > spliceTarget)) {
                     splicePoint = splicePoint - 2; // We are one 0-based index position past where we want to be
                     break;
                   }
                 }
               }
             }
           } while ( splicePoint <= html.length - 1 );

           return [html.substr(0, splicePoint), html.substr(splicePoint)];
       }

       var columnClassName    = options['columnClassName'] || 'newspaper-column';
       var shouldColumnizeAll = (options['classToExempt']) ? false : true;
       var classToExempt      = options['classToExempt'] || null;
       var minimumChildrenFor2= options['minimumChildrenFor2'] || 2;
       var minimumChildrenFor3= options['minimumChildrenFor3'] || 4;
       var suppressWarnings   = options['suppressWarnings'] || false;
       var numColumns         = options['columns'] || 2;

       // The default values for the spliceTarget are arbitrary and were arrived at after experimentation on my content
       var splicePointShortDivisor = options['splicePointShortDivisor'] || 2.2;
       var splicePointLongDivisor  = options['splicePointLongDivisor'] || 1.8;

       // Sanity check, we don't support an infinite number of columns, there's a max of 3.
       if (numColumns > 3) numColumns = 3;

       var column1, column2, column3;
       var html = '';
       var splicePoint = 0;
       var children = $(this).children();
       var numChildren = children.length;

       column1 = $('<div id="' + columnClassName + '-1" class="' + columnClassName + '"></div>');
       column2 = (numColumns > 1) ? $('<div id="' + columnClassName + '-2" class="' + columnClassName + '"></div>') : null;
       column3 = (numColumns > 2) ? $('<div id="' + columnClassName + '-3" class="' + columnClassName + '"></div>') : null;

       children.each(function() {
         if (shouldColumnizeAll || !$(this).hasClass(classToExempt)) {
           // Path for Safari, IE, Opera, and pretty much every other browser
           if (this.outerHTML) {
             html += this.outerHTML;
           }
           // And the path for FireFox... uggh just support outerHTML, you already support innerHTML...
           else {
             var outerHTML = '<' + this.tagName.toLowerCase();
             for (i = 0; i < this.attributes.length; i++) {
               outerHTML += ' ' + this.attributes[i].nodeName;
               outerHTML += '="' + this.attributes[i].nodeValue + '"'
             }
             outerHTML += '>';

             html += outerHTML;
             html += this.innerHTML;
             html += '</' + this.tagName.toLowerCase() + '>';
           }
           $(this).remove();
         }
       });

       // If the html is empty, don't bother processing (the only child element may be the exempt one)
       if (html.length > 0) {

         // Anything over the minimum length gets turned into multiple columns, otherwise we just make one
         if (numColumns > 1 && (numChildren > minimumChildrenFor2)) {

           if (numColumns == 3 && (numChildren > minimumChildrenFor3)) {
             // These values for the spliceTarget are arbitrary and were arrived at after experimentation on my content
             var spliceTarget = ( numChildren < 4 ) ? Math.ceil(html.length / (splicePointShortDivisor + 1)) : Math.ceil(html.length / (splicePointLongDivisor + 1));
             var splices = spliceHTML(html, spliceTarget);
             column1.append(splices[0])

             html = splices[1];
             spliceTarget = ( numChildren < 4 ) ? Math.ceil(html.length / (splicePointShortDivisor)) : Math.ceil(html.length / (splicePointLongDivisor));
             splices = spliceHTML(html, spliceTarget);
             column2.append(splices[0]);
             column3.append(splices[1]);

             $('<div class="' + columnClassName + '-ctr"></div>').append(column1).append(column2).append(column3).appendTo($(this));            
           }
           else {
             // These values for the spliceTarget are arbitrary and were arrived at after experimentation on my content
             var spliceTarget = ( numChildren < 4 ) ? Math.ceil(html.length / splicePointShortDivisor) : Math.ceil(html.length / splicePointLongDivisor);
             var splices = spliceHTML(html, spliceTarget);
             column1.append(splices[0])
             column2.append(splices[1]);

             $('<div class="' + columnClassName + '-ctr"></div>').append(column1).append(column2).appendTo($(this));      
           }
         }
         else {
           column1.append(html);
           $('<div class="' + columnClassName + '-ctr"></div>').append(column1).appendTo($(this));      
         }
       }
     }
   });
 }) (jQuery);
