/*
===============================================================================
Chili is the jQuery code highlighter plugin
...............................................................................
LICENSE: http://www.opensource.org/licenses/mit-license.php
WEBSITE: http://noteslog.com/chili/

                                               Copyright 2008 / Andrea Ercolino
===============================================================================
*/


( function($) {

ChiliBook = { //implied global

         version:            "2.1" // 2008-06-29

// options --------------------------------------------------------------------

       , automatic:          true
       , automaticSelector:  "code"

       , lineNumbers:        true

       , codeLanguage:       function( el ) {
               var recipeName = $( el ).attr( "class" );
               return recipeName ? recipeName : '';
       }

       , recipeLoading:      true
       , recipeFolder:       "/wp-content/themes/core5/scripts/chili/" // used like: recipeFolder + recipeName + '.js'

       // IE and FF convert &#160; to "&nbsp;", Safari and Opera do not
       , replaceSpace:       "&#160;"
       , replaceTab:         "&#160;&#160;&#160;&#160;"
       , replaceNewLine:     "&#160;<br/>"

       , selectionStyle:     [ "position:absolute; z-index:3000; overflow:scroll;"
                                                 , "width:16em;"
                                                 , "height:9em;"
                                                 , "border:1px solid gray;"
                                                 , "padding:15px;"
                                                 , "background-color:yellow;"
                                                 ].join( ' ' )

// ------------------------------------------------------------- end of options

       , defaultReplacement: '<span class="$0">$$</span>' // TODO: make this an option again
       , recipes:            {} //repository
       , queue:              {} //registry

       , unique:             function() {
               return (new Date()).valueOf();
       }
};



$.fn.chili = function( options ) {
       var book = $.extend( {}, ChiliBook, options || {} );

       function cook( ingredients, recipe, blockName ) {

               function prepareBlock( recipe, blockName ) {
                       var steps = [];
                       for( var stepName in recipe[ blockName ] ) {
                               steps.push( prepareStep( recipe, blockName, stepName ) );
                       }
                       return steps;
               } // prepareBlock

               function prepareStep( recipe, blockName, stepName ) {
                       var step = recipe[ blockName ][ stepName ];
                       var exp = ( typeof step._match == "string" ) ? step._match : step._match.source;
                       return {
                               recipe: recipe
                               , blockName: blockName
                               , stepName: stepName
                               , exp: "(" + exp + ")"
                               , length: 1                         // add 1 to account for the newly added parentheses
                                       + (exp                          // count number of submatches in here
                                               .replace( /\\./g, "%" )     // disable any escaped character
                                               .replace( /\[.*?\]/g, "%" ) // disable any character class
                                               .match( /\((?!\?)/g )       // match any open parenthesis, not followed by a ?
                                       || []                           // make sure it is an empty array if there are no matches
                                       ).length                        // get the number of matches
                               , replacement: step._replace ? step._replace : book.defaultReplacement
                       };
               } // prepareStep
       
               function knowHow( steps ) {
                       var prevLength = 1;
                       var exps = [];
                       for (var i = 0; i < steps.length; i++) {
                               var exp = steps[ i ].exp;
                               // adjust backreferences
                               exp = exp.replace( /\\\\|\\(\d+)/g, function( m, aNum ) {
                                       return !aNum ? m : "\\" + ( prevLength + 1 + parseInt( aNum, 10 ) );
                               } );
                               exps.push( exp );
                               prevLength += steps[ i ].length;
                       }
                       var prolog = '((?:\\s|\\S)*?)';
                       var epilog = '((?:\\s|\\S)+)';
                       var source = '(?:' + exps.join( "|" ) + ')';
                       source = prolog + source + '|' + epilog;
                       return new RegExp( source, recipe._case ? "g" : "gi" );
               } // knowHow

               function escapeHTML( str ) {
                       return str.replace( /&/g, "&amp;" ).replace( /</g, "&lt;" );
               } // escapeHTML

               function replaceSpaces( str ) {
                       return str.replace( / +/g, function( spaces ) {
                               return spaces.replace( / /g, replaceSpace );
                       } );
               } // replaceSpaces

               function filter( str ) {
                       str = escapeHTML( str );
                       if( replaceSpace ) {
                               str = replaceSpaces( str );
                       }
                       return str;
               } // filter

               function applyRecipe( subject, recipe ) {
                       return cook( subject, recipe );
               } // applyRecipe

               function applyBlock( subject, recipe, blockName ) {
                       return cook( subject, recipe, blockName );
               } // applyBlock

               function applyStep( subject, recipe, blockName, stepName ) {
                       var replaceSpace       = book.replaceSpace;

                       var step = prepareStep( recipe, blockName, stepName );
                       var steps = [step];

                       var perfect = subject.replace( knowHow( steps ), function() {
                               return chef.apply( { steps: steps }, arguments );
                       } );
                       return perfect;
               } // applyStep

               function applyModule( subject, module, context ) {
                       if( ! module ) {
                               return filter( subject );
                       }

                       var sub = module.split( '/' );
                       var recipeName = '';
                       var blockName  = '';
                       var stepName   = '';
                       switch( sub.length ) {
                               case 1:
                                       recipeName = sub[0];
                                       break;
                               case 2:
                                       recipeName = sub[0]; blockName = sub[1];
                                       break;
                               case 3:
                                       recipeName = sub[0]; blockName = sub[1]; stepName = sub[2];
                                       break;
                               default:
                                       return filter( subject );
                       }

                       function getRecipe( recipeName ) {
                               var path = getPath( recipeName );
                               var recipe = book.recipes[ path ];
                               if( ! recipe ) {
                                       throw {msg:"recipe not available"};
                               }
                               return recipe;
                       }

                       try {
                               var recipe;
                               if ( '' == stepName ) {
                                       if ( '' == blockName ) {
                                               if ( '' == recipeName ) {
                                                       //nothing to do
                                               }
                                               else { // ( '' != recipeName )
                                                       recipe = getRecipe( recipeName );
                                                       return applyRecipe( subject, recipe );
                                               }
                                       }
                                       else { // ( '' != blockName )
                                               if( '' == recipeName ) {
                                                       recipe = context.recipe;
                                               }
                                               else {
                                                       recipe = getRecipe( recipeName );
                                               }
                                               if( ! (blockName in recipe) ) {
                                                       return filter( subject );
                                               }
                                               return applyBlock( subject, recipe, blockName );
                                       }
                               }
                               else { // ( '' != stepName )
                                       if( '' == recipeName ) {
                                               recipe = context.recipe;
                                       }
                                       else {
                                               recipe = getRecipe( recipeName );
                                       }
                                       if( '' == blockName ) {
                                               blockName = context.blockName;
                                       }
                                       if( ! (blockName in recipe) ) {
                                               return filter( subject );
                                       }
                                       if( ! (stepName in recipe[blockName]) ) {
                                               return filter( subject );
                                       }
                                       return applyStep( subject, recipe, blockName, stepName );
                               }
                       }
                       catch( e ) {
                               if (e.msg && e.msg == "recipe not available") {
                                       var cue = 'chili_' + book.unique();
                                       if( book.recipeLoading ) {
                                               var path = getPath( recipeName );
                                               if( ! book.queue[ path ] ) {
                                                       /* this is a new recipe to download */
                                                       try {
                                                               book.queue[ path ] = [ {cue: cue, subject: subject, module: module, context: context} ];
                                                               $.getJSON( path, function( recipeLoaded ) {
                                                                       book.recipes[ path ] = recipeLoaded;
                                                                       var q = book.queue[ path ];
                                                                       for( var i = 0, iTop = q.length; i < iTop; i++ ) {
                                                                               var replacement = applyModule( q[ i ].subject, q[ i ].module, q[ i ].context );
                                                                               if( book.replaceTab ) {
                                                                                       replacement = replacement.replace( /\t/g, book.replaceTab );
                                                                               }
                                                                               if( book.replaceNewLine ) {
                                                                                       replacement = replacement.replace( /\n/g, book.replaceNewLine );
                                                                               }
                                                                               $( '#' + q[ i ].cue ).replaceWith( replacement );
                                                                       }
                                                               } );
                                                       }
                                                       catch( recipeNotAvailable ) {
                                                               alert( "the recipe for '" + recipeName + "' was not found in '" + path + "'" );
                                                       }
                                               }
                                               else {
                                                       /* not a new recipe, so just enqueue this element */
                                                       book.queue[ path ].push( {cue: cue, subject: subject, module: module, context: context} );
                                               }
                                               return '<span id="' + cue + '">' + filter( subject ) + '</span>';
                                       }
                                       return filter( subject );
                               }
                               else {
                                       return filter( subject );
                               }
                       }
               } // applyModule


               function addPrefix( prefix, replacement ) {
                       var aux = replacement.replace( /(<span\s+class\s*=\s*(["']))((?:(?!__)\w)+\2\s*>)/ig, "$1" + prefix + "__$3" );
                       return aux;
               } // addPrefix

               function chef() {
                       if (! arguments[ 0 ]) {
                               return '';
                       }
                       var steps = this.steps;
                       var i = 0;  // iterate steps
                       var j = 2;      // iterate chef's arguments
                       var prolog = arguments[ 1 ];
                       var epilog = arguments[ arguments.length - 3 ];
                       if (! epilog) {
                               var step;
                               while( step = steps[ i++ ] ) {
                                       var aux = arguments; // this unmasks chef's arguments inside the next function
                                       if( aux[ j ] ) {
                                               var replacement = '';
                                               if( $.isFunction( step.replacement ) ) {
                                                       var matches = []; //Array.slice.call( aux, j, step.length );
                                                       for (var k = 0, kTop = step.length; k < kTop; k++) {
                                                               matches.push( aux[ j + k ] );
                                                       }
                                                       matches.push( aux[ aux.length - 2 ] );
                                                       matches.push( aux[ aux.length - 1 ] );
                                                       replacement = step.replacement
                                                               .apply( {
                                                                       x: function() {
                                                                               var subject = arguments[0];
                                                                               var module  = arguments[1];
                                                                               var context = {
                                                                                         recipe:    step.recipe
                                                                                       , blockName: step.blockName
                                                                               };
                                                                               return applyModule( subject, module, context );
                                                                       }
                                                               }, matches );
                                               }
                                               else { //we expect step.replacement to be a string
                                                       replacement = step.replacement
                                                               .replace( /(\\\$)|(?:\$\$)|(?:\$(\d+))/g, function( m, escaped, K ) {
                                                                       if( escaped ) {       /* \$ */
                                                                               return "$";
                                                                       }
                                                                       else if( !K ) {       /* $$ */
                                                                               return filter( aux[ j ] );
                                                                       }
                                                                       else if( K == "0" ) { /* $0 */
                                                                               return step.stepName;
                                                                       }
                                                                       else {                /* $K */
                                                                               return filter( aux[ j + parseInt( K, 10 ) ] );
                                                                       }
                                                               } );
                                               }
                                               replacement = addPrefix( step.recipe._name, replacement );
                                               return filter( prolog ) + replacement;
                                       }
                                       else {
                                               j+= step.length;
                                       }
                               }
                       }
                       else {
                               return filter( epilog );
                       }
               } // chef

               if( ! blockName ) {
                       blockName = '_main';
                       checkSpices( recipe );
               }
               if( ! (blockName in recipe) ) {
                       return filter( ingredients );
               }
               var replaceSpace = book.replaceSpace;
               var steps = prepareBlock( recipe, blockName );
               var kh = knowHow( steps );
               var perfect = ingredients.replace( kh, function() {
                       return chef.apply( { steps: steps }, arguments );
               } );
               return perfect;

       } // cook

       function loadStylesheetInline( sourceCode ) {
               if( document.createElement ) {
                       var e = document.createElement( "style" );
                       e.type = "text/css";
                       if( e.styleSheet ) { // IE
                               e.styleSheet.cssText = sourceCode;
                       }  
                       else {
                               var t = document.createTextNode( sourceCode );
                               e.appendChild( t );
                       }
                       document.getElementsByTagName( "head" )[0].appendChild( e );
               }
       } // loadStylesheetInline
                       
       function checkSpices( recipe ) {
               var name = recipe._name;
               if( ! book.queue[ name ] ) {

                       var content = ['/* Chili -- ' + name + ' */'];
                       for (var blockName in recipe) {
                               if( blockName.search( /^_(?!main\b)/ ) < 0 ) {
                                       for (var stepName in recipe[ blockName ]) {
                                               var step = recipe[ blockName ][ stepName ];
                                               if( '_style' in step ) {
                                                       if( step[ '_style' ].constructor == String ) {
                                                               content.push( '.' + name + '__' + stepName + ' { ' + step[ '_style' ] + ' }' );
                                                       }
                                                       else {
                                                               for (var className in step[ '_style' ]) {
                                                                       content.push( '.' + name + '__' + className + ' { ' + step[ '_style' ][ className ] + ' }' );
                                                               }
                                                       }
                                               }
                                       }
                               }
                       }
                       content = content.join('\n');

                       loadStylesheetInline( content );

                       book.queue[ name ] = true;
               }
       } // checkSpices

       function askDish( el ) {
               var recipeName = book.codeLanguage( el );
               if( '' != recipeName ) {
                       var path = getPath( recipeName );
                       if( book.recipeLoading ) {
                               /* dynamic setups come here */
                               if( ! book.queue[ path ] ) {
                                       /* this is a new recipe to download */
                                       try {
                                               book.queue[ path ] = [ el ];
                                               $.getJSON( path, function( recipeLoaded ) {
                                                       book.recipes[ path ] = recipeLoaded;
                                                       var q = book.queue[ path ];
                                                       for( var i = 0, iTop = q.length; i < iTop; i++ ) {
                                                               makeDish( q[ i ], path );
                                                       }
                                               } );
                                       }
                                       catch( recipeNotAvailable ) {
                                               alert( "the recipe for '" + recipeName + "' was not found in '" + path + "'" );
                                       }
                               }
                               else {
                                       /* not a new recipe, so just enqueue this element */
                                       book.queue[ path ].push( el );
                               }
                               /* a recipe could have been already downloaded */
                               makeDish( el, path );
                       }
                       else {
                               /* static setups come here */
                               makeDish( el, path );
                       }
               }
       } // askDish

       function makeDish( el, recipePath ) {
               var recipe = book.recipes[ recipePath ];
               if( ! recipe ) {
                       return;
               }
               var $el = $( el );
               var ingredients = $el.text();
               if( ! ingredients ) {
                       return;
               }

               //fix for msie: \r (13) is used instead of \n (10)
               //fix for opera: \r\n is used instead of \n
               ingredients = ingredients.replace(/\r\n?/g, "\n");

               //reverse fix for safari: msie, mozilla and opera render the initial \n
               if( $el.parent().is('pre') ) {
                       if( ! $.browser.safari ) {
                               ingredients = ingredients.replace(/^\n/g, "");
                       }
               }

               var dish = cook( ingredients, recipe ); // all happens here
       
               if( book.replaceTab ) {
                       dish = dish.replace( /\t/g, book.replaceTab );
               }
               if( book.replaceNewLine ) {
                       dish = dish.replace( /\n/g, book.replaceNewLine );
               }

               el.innerHTML = dish; //much faster than $el.html( dish );
               //tried also the function replaceHtml from http://blog.stevenlevithan.com/archives/faster-than-innerhtml
               //but it was not faster nor without sideffects (it was not possible to count spans into el)


               //opera and safari select PRE text correctly
               if( $.browser.msie || $.browser.mozilla ) {
                       enableSelectionHelper( el );
               }

               if( book.lineNumbers ) {
                       addLineNumbers( el );
               }

       } // makeDish

       function enableSelectionHelper( el ) {
               var element = null;
               $( el )
               .parents()
               .filter( "pre" )
               .bind( "mousedown", function() {
                       element = this;
                       if( $.browser.msie ) {
                               document.selection.empty();
                       }
                       else {
                               window.getSelection().removeAllRanges();
                       }
               } )
               .bind( "mouseup", function( event ) {
                       if( element && (element == this) ) {
                               element = null;
                               var selected = '';
                               if( $.browser.msie ) {
                                       selected = document.selection.createRange().htmlText;
                                       if( '' == selected ) {
                                               return;
                                       }
                                       selected = preserveNewLines( selected );
                                       var container_tag = '<textarea style="STYLE">';
                               }
                               else {
                                       selected = window.getSelection().toString(); //opera doesn't select new lines
                                       if( '' == selected ) {
                                               return;
                                       }
                                       selected = selected
                                               .replace( /\r/g, '' )
                                               .replace( /^# ?/g, '' )
                                               .replace( /\n# ?/g, '\n' )
                                       ;
                                       var container_tag = '<pre style="STYLE">';
                               }
                               var $container = $( container_tag.replace( /\bSTYLE\b/, ChiliBook.selectionStyle ) )
                                       .appendTo( 'body' )
                                       .text( selected )
                                       .attr( 'id', 'chili_selection' )
                                       .click( function() { $(this).remove(); } )
                               ;
                               var top  = event.pageY - Math.round( $container.height() / 2 ) + "px";
                               var left = event.pageX - Math.round( $container.width() / 2 ) + "px";
                               $container.css( { top: top, left: left } );
                               if( $.browser.msie ) {
//                                      window.clipboardData.setData( 'Text', selected ); //I couldn't find anything similar for Mozilla
                                       $container[0].focus();
                                       $container[0].select();
                               }
                               else {
                                       var s = window.getSelection();
                                       s.removeAllRanges();
                                       var r = document.createRange();
                                       r.selectNodeContents( $container[0] );
                                       s.addRange( r );
                               }
                       }
               } )
               ;
       } // enableSelectionHelper

       function getPath( recipeName ) {
               return book.recipeFolder + recipeName + ".js";
       } // getPath

       function getSelectedText() {
               var text = '';
               if( $.browser.msie ) {
                       text = document.selection.createRange().htmlText;
               }
               else {
                       text = window.getSelection().toString();
               }
               return text;
       } // getSelectedText

       function preserveNewLines( html ) {
               do {
                       var newline_flag = ChiliBook.unique();
               }
               while( html.indexOf( newline_flag ) > -1 );
               var text = '';
               if (/<br/i.test(html) || /<li/i.test(html)) {
                       if (/<br/i.test(html)) {
                               html = html.replace( /\<br[^>]*?\>/ig, newline_flag );
                       }
                       else if (/<li/i.test(html)) {
                               html = html.replace( /<ol[^>]*?>|<\/ol>|<li[^>]*?>/ig, '' ).replace( /<\/li>/ig, newline_flag );
                       }
                       var el = $( '<pre>' ).appendTo( 'body' ).hide()[0];
                       el.innerHTML = html;
                       text = $( el ).text().replace( new RegExp( newline_flag, "g" ), '\r\n' );
                       $( el ).remove();
               }
               return text;
       } // preserveNewLines

       function addLineNumbers( el ) {

               function makeListItem1( not_last_line, not_last, last, open ) {
                       var close = open ? '</span>' : '';
                       var aux = '';
                       if( not_last_line ) {
                               aux = '<li>' + open + not_last + close + '</li>';
                       }
                       else if( last ) {
                               aux = '<li>' + open + last + close + '</li>';
                       }
                       return aux;
               } // makeListItem1

               function makeListItem2( not_last_line, not_last, last, prev_li ) {
                       var aux = '';
                       if( prev_li ) {
                               aux = prev_li;
                       }
                       else {
                               aux = makeListItem1( not_last_line, not_last, last, '' )
                       }
                       return aux;
               } // makeListItem2

               var html = $( el ).html();
               var br = /<br>/.test(html) ? '<br>' : '<BR>';
               var empty_line = '<li>' + book.replaceSpace + '</li>';
               var list_items = html
                       //extract newlines at the beginning of a span
                       .replace( /(<span [^>]+>)((?:(?:&nbsp;|\xA0)<br>)+)(.*?)(<\/span>)/ig, '$2$1$3$4' ) // I don't know why <span .*?> does not work here
                       //transform newlines inside of a span
                       .replace( /(.*?)(<span .*?>)(.*?)(?:<\/span>(?:&nbsp;|\xA0)<br>|<\/span>)/ig,       // but here it does
                               function( all, before, open, content ) {
                                       if (/<br>/i.test(content)) {
                                               var pieces = before.split( br );
                                               var lastPiece = pieces.pop();
                                               before = pieces.join( br );
                                               var aux = (before ? before + br : '') //+ replace1( lastPiece + content, open );
                                                       + (lastPiece + content).replace( /((.*?)(?:&nbsp;|\xA0)<br>)|(.*)/ig,
                                                       function( tmp, not_last_line, not_last, last ) {
                                                               var aux2 = makeListItem1( not_last_line, not_last, last, open );
                                                               return aux2;
                                                       }
                                               );
                                               return aux;
                                       }
                                       else {
                                               return all;
                                       }
                               }
                       )
                       //transform newlines outside of a span
                       .replace( /(<li>.*?<\/li>)|((.*?)(?:&nbsp;|\xA0)<br>)|(.+)/ig,
                               function( tmp, prev_li, not_last_line, not_last, last ) {
                                       var aux2 = makeListItem2( not_last_line, not_last, last, prev_li );
                                       return aux2;
                               }
                       )
                       //fix empty lines for Opera
                       .replace( /<li><\/li>/ig, empty_line )
               ;

               el.innerHTML = '<ol>' + list_items + '</ol>';
       } // addLineNumbers

       function revealChars( tmp ) {
               return $
                       .map( tmp.split(''),
                               function(n, i) {
                                       return ' ' + n + ' ' + n.charCodeAt( 0 ) + ' ';
                               } )
                       .join(' ');
       } // revealChars

//-----------------------------------------------------------------------------
// the coloring starts here
       this
       .each( function() {
               var $this = $( this );
               $this.trigger( 'chili.before_coloring' );
               askDish( this );
               $this.trigger( 'chili.after_coloring' );
       } );

       return this;
//-----------------------------------------------------------------------------
};




//main
$( function() {

       if( ChiliBook.automatic ) {
               $( ChiliBook.automaticSelector ).chili();
       }

} );

} ) ( jQuery );
