/*
2010-06-23T13:08:02-08:00 - Cross-platform Security error in Firefox when trying to
                    modify rules. It was because the local style sheet was no
                    longer the last sheet. 
                    nsresult: "0x805303e8 (NS_ERROR_DOM_SECURITY_ERR)"
                    Also updated to 4 space tabs.
2010-04-17T16:11:18-08:00 - Fixed note about usage for event.addListener(). Not 
                    a functional change.
2010-03-01T16:08:11-08:00 - Added doc.appendText().
2010-02-27T21:35:59-08:00 - Added callback function to buildPageSizeSelector, which
                    gets called after any size change if it exists.
2010-02-27T09:48:41-08:00 - Fixed buildToC2 support for other than h1-h3 in options.
                    Changed the format use non-bulletted first level item and
                    moved first margin out by 1em (first is 0, was 1em), others 
                    by .5em to compensate for bullets.
2010-02-26T13:15:08-08:00 - Add ability to pass elem ID to event.addListener
2010-02-17T11:58:35-08:00 - Update addLoadEvent to use current standards.
2010-01-31T14:25:04-08:00 - Fix layout on buildPageSizeSelector to go with 
                    new CSS. Should have been done this way oritinally.
                    Fix reload problem displaying selector text twice.
2009-11-30T12:47:30PST - Minor JSlint fixes, mostly == to ===. Add global
                                 comment for JSlint.
2009-08-09T13:26:36PST - Change left margin on ToC2 from 0 to 1em.
2009-05-27T20:26:01PST - Add showToC option to buildToC2.
                                 Add event object with addListenr and removeListener
2009-04-15T15:14:14PST - Add buildPageSizeSelector.
2009-03-23T11:29:13PST - Add V2 of buildToc. Keep support for legacy version.
2009-02-11T19:26:41PST - fix widget.buildToC, problem with innerHTML on anchors,
                                 node.add findFirstText.
2009-02-09T19:56:53PST - add widget.buildToC
2009-01-23T12:59:55PST - add node function deleteNodes and deleteChildren
2008-12-27T15:40:37PST - initial posting

All code in this file runs in the com.YourFriendPaul.util namespace

// deprecated but convenient. Better to use event.addListener( window, 'load', functionname );
addLoadEvent( func )
getElementsByTagNames( elemList, inObj )

node.deleteNodes( node )
node.deleteChildren( node )
node.findFirstText( node )

widget.buildToC( div's-elemId ) -- this requires external CSS
widget.buildToC2( div's-elemId, [ optionsObject ] ) -- CSS generated on execution
widget.buildPageSizeSelector( selectorBoxId [, targetBoxId [, optionsObject ]] )

event.addListener( elem, type, func )
event.removeListener( elem, type, func )

doc.appendText( targetId, text, elemType, attrList )

addNamespace( ... ) -- accepts multiple args
nsUsedLevel( root, ns )
isNamespaceUsed( nsIn )
*/
// for JSlint:
/*global window */
/**********************************************************************/
// create the main namespace for everything
var com = com || {}; // "var" does nothing if com already exists
com.YourFriendPaul = com.YourFriendPaul || {}; // make the object if needed
// Useful for debug messages
if( com.YourFriendPaul.toString() != 'com.YourFriendPaul' ){ 
    com.YourFriendPaul.toString = function(){ return 'com.YourFriendPaul'; };
}

/**********************************************************************/
/**********************************************************************/
/**********************************************************************/
// Useful for debug messages
com.YourFriendPaul.util = com.YourFriendPaul.util || {};
if( com.YourFriendPaul.util.toString() != 'com.YourFriendPaul.util' ){ 
    com.YourFriendPaul.util.toString = function(){ return 'com.YourFriendPaul.util'; };
}


// orig.  concept from QuirksMode http://www.quirksmode.org/dom/getElementsByTagNames.html
// elList is an array of elements to search for, or a comma separate list.
// inObj is the parent object to search, document by default.
(function( $ ){

    $.getElementsByTagNames = function( elList, inObj ) {
    
        // If the MS extension sourceIndex exists, do nothing.
        // If it doesn't, add one.
        // Adding this allows sorting elements into the order that they
        //  appear in the document.
        function forceSourceIndex(){
            var allElems, eCount;
            // return if it's already supported
            if( document.getElementsByTagName("body")[0].sourceIndex ) { return; }
            
            allElems = document.getElementsByTagName("*");
    
            for( eCount=0; eCount<allElems.length; eCount++ ){
            
                allElems[ eCount ].sourceIndex = eCount;
            }
        }
    
        // search entire document if object is not specified
        var obj = inObj || document;
        var tagNames;
    
        if( typeof elList === 'string' ){
            tagNames = elList.split(',');
        }
    
        else{
    
            tagNames = elList;
        }
        
        //alert( 'getElementsByTagNames elList is ' + tagNames );
        
        var resultArray = [], i, j, tags, testNode;
        // Need support for this IE function to do sorting; build our own index if needed
        forceSourceIndex();
        
        // for every tag name in list
        for ( i=0; i<tagNames.length; i++ ) {
            tags = obj.getElementsByTagName( tagNames[i] );
            
            // for every element of that tag type
            for ( j=0; j<tags.length; j++ ) {
                resultArray.push( tags[ j ] );
            }
        }
        
        testNode = resultArray[0];
        // if no results, return
        if (!testNode){ return []; }
        
        // alert if sourceIndex hasn't been built already, a debug thing
        if( !testNode.sourceIndex ){ alert( "couldn't make sourceIndex" ); }
        
        if ( testNode.sourceIndex ) {
            resultArray.sort( function (a,b) {
                    return a.sourceIndex - b.sourceIndex;
            } );
        }
        return resultArray;
    };
    
})( com.YourFriendPaul.util );

/**********************************************************************/
/**********************************************************************/
/**********************************************************************/
//
// The anonymous functions refered to in the return statement
// must have already been executed before they can be 
// added here. That means this needs to go at the end of the file,
// after all the other functions.
//

(function ( ns ){

    /**********************************************************************/
    // Adds functions to window.onload. They will be executed in the order added.
    // use like this:
    //
    // addLoadEvent(nameOfSomeFunctionToRunOnPageLoad); 
    //
    // or like this:
    //
    // addLoadEvent(function() { 
    //      /* more code to run on page load */ 
    //    });
    //
    // Originally from http://simonwillison.net/2004/May/26/addLoadEvent/
    //
/*
function addLoadListener(fn)  
{  

*/
    function addLoadEvent( fn ) {
    
        var oldonload = null;
        
        if ( typeof window.addEventListener !== 'undefined' ){
          
            window.addEventListener('load', fn, false);  
        }  
        else if (typeof document.addEventListener !== 'undefined'){  
        
            document.addEventListener('load', fn, false);  
        } else if (typeof window.attachEvent !== 'undefined'){
          
            window.attachEvent('onload', fn);  
        }
        else if ( typeof window.onload !== 'function' ) {
        
            window.onload = fn; 
        }
        else { 

            oldonload = window.onload;

            window.onload = function() {
                if ( oldonload ) { oldonload(); }
                fn(); 
            };
        }
    }

    ns.addLoadEvent = addLoadEvent;

//    return{
//        addNamespace: com.YourFriendPaul.addNamespace,
//        onLoadAdder: addLoadEvent
//    }

})( com.YourFriendPaul.util );

// ********************************************************
// node functions
// 
// This is complicated because it tries to remove memory leaks in IE
//  by removing any function references before deleting the element.
//  It also deletes all children to make sure they are not left in memory.
// ********************************************************
com.YourFriendPaul.util.node = (function(){


    // deletes all children of a node or element
    function deleteChildren( node ){

        if( null === node ) { return; }
        if( node.hasChildNodes() ){
        
            while( node.lastChild ) { 
                this.deleteNodes( node.lastChild );
            }
        }
    }

    // deletes all children and then the node
    function deleteNodes( node ){
    
        var a, i, l, n;
        if( null === node ) { return; }
        if( node.hasChildNodes() ){
        
            // find the last lastChild
            while( node.lastChild ) { 
                deleteNodes( node.lastChild );
            }
        }

        // remove any functions that are attached
        a = node.attributes;
        if ( a ) {
            l = a.length;
            for ( i = 0; i < l; i += 1 ) {
                n = a[i].name;
                if ( typeof node[ n ] === "function" ) {
                    node.removeAttribute( n );
                    node[ n ] = null;
                }
            }
        }
        node.parentNode.removeChild( node );
    }

    // Finds the first text node attached to node
    // Returns null if not found
    function findFirstText( node ){

        if( null === node || typeof node === "undefined" ) { return null; }
        if( !node.hasChildNodes() ){ return null; }
        
        //alert( node.nodeName + ", " + node.nodeValue + ", has childern: " + node.childNodes.length );
        var iCount=0, tempNode;
        for( iCount=0; iCount< node.childNodes.length; iCount++ ){
            
            //alert( "child" + iCount + " is type " + node.childNodes[ iCount ].nodeType );
            // return the child if it is a text node
            if( node.childNodes[ iCount ].nodeType === 3  ) { 
                return node.childNodes[ iCount ];
            }
            
            if( node.childNodes[ iCount ].hasChildNodes() ){
            
                tempNode = findFirstText( node.childNodes[ iCount ] );
                // return the child if it is a text node
                if( tempNode !== null && tempNode.nodeType === 3  ) { 
                    return tempNode;
                }
            }
        }
        // not found
        return null;
    }

    
    return {
    findFirstText: findFirstText,
    deleteChildren: deleteChildren,
    deleteNodes: deleteNodes
    };

})();

// ********************************************************


// replacement for deprecated target attribute
//
// To modify a link that would have used  target=_blank  before it was deprecated, use 
// this attribute in the anchor tag:  rel="external".
// Run externalLinks() using onload() for the body element.
// Or better yet, use the "addLoadEvent" function to have
// more than one onload function.
// Origianlly from http://www.sitepoint.com/article/1041/3

com.YourFriendPaul.util.linkTargetBlank = function() {

    if ( !document.getElementsByTagName ) { return; }
    var anchors = document.getElementsByTagName( "a" );
    var anch;
    for ( var i=0; i<anchors.length; i++ ) {

        anch = anchors[ i ];
        if ( anch.getAttribute( "href" ) && anch.getAttribute( "rel" ) === "external" ){ 
            anch.target = "_blank";
        }
    }
};

com.YourFriendPaul.util.event = {};

(function( $ ){

    $.addListener = function( elem, eType, funcName ){
    
        var oldE, onName = "on" + eType, elemName = elem;
        
        if( typeof elem === 'string' ){
        
            elem = document.getElementById( elemName );
            if( null === elem ){ 
                //alert( 'addListener: no name for ' + elemName );
                //alert( 'addListener :' + elem.id + ', ' + eType + ', ' + funcName );
                return elem; 
            }
        }
        //alert( 'addListener :' + elem.id + ', ' + eType + ', ' + funcName );
        if( elem.addEventListener ){
            // add event on the 'bubbling' phase
            return elem.addEventListener( eType, funcName, false );
        }
        
        if( elem.attachEvent ){
            
            return elem.attachEvent( onName, funcName );
        } 
        
        if( elem[ onName ] !== null ){
            
            oldE = elem[ onName ];
            elem[ onName ] = function(e){ oldE(e); funcName(e); };
        }
        
        else {
            
            elem[ onName ] = funcName;
        }
        
        return elem[ onName ];
    };

    $.removeListener = function( elem, eType, funcName ){
    
        var onName = "on" + eType;
        
        if ( elem.removeEventListener ){
            return elem.removeEventListener( eType, funcName, false);
        }
        
        if( elem.detachEvent ){
            
            return elem.detachEvent( onName, funcName );
        }
        
        // don't even try with the functions added like onload=...
        return null;
        
    };
    

})( com.YourFriendPaul.util.event );


// ===============================================================================
// ===============================================================================

com.YourFriendPaul.util.widget = {};

if( com.YourFriendPaul.util.widget.toString() != 'com.YourFriendPaul.util.widget' ){ 
    com.YourFriendPaul.util.widget.toString = function(){ return 'com.YourFriendPaul.util.widget'; };
}


(function( $ ){

    //========================= first version ToC code ======================
    // build the ToC and attach it as a child to elemId
    // This first version is deprecated
    $.buildToC = function( elemId ){

        var TOCstate = 'block';
    
        var text = {
            showContents: "show page contents",
            hideContents: "hide page contents",
            space: " ",
            tmpOnClickLoc: "tmpOnClickLoc"
        };
        
        function showhideTOC() {
            TOCstate = (TOCstate === 'none') ? 'block' : 'none';
            var newText = (TOCstate === 'none') ? text.showContents : text.hideContents;
            document.getElementById('contentheader').innerHTML = newText;
            document.getElementById('innertoc').lastChild.style.display = TOCstate;
        }
    

        // build the ToC and append it to z
        function createLinks( z, tocClassNameBase ){
        
            var toBeTOCced, tNode, i, headerId, tmp;
            
            toBeTOCced = com.YourFriendPaul.util.getElementsByTagNames('h1,h2,h3');
            // nothing to show
            if( toBeTOCced.length < 2 ){ return 0; }
        
            for ( i=0; i<toBeTOCced.length; i++ ) {
    
                // create an id for the element if one doesn't exist
                headerId = toBeTOCced[i].id || 'TocLink' + i;
                toBeTOCced[i].id = headerId;
    
                tmp = document.createElement('a');
                tmp.href = '#' + headerId;
    
                // find the text to use in the ToC
                tNode = com.YourFriendPaul.util.node.findFirstText( toBeTOCced[i] );
                
                tmp.innerHTML = ( tNode === null ) ? headerId : tNode.nodeValue;
                tmp.className = 'TocItem';
                z.appendChild(tmp);
                if ( toBeTOCced[i].nodeName === 'H1' ){ tmp.className += text.space + tocClassNameBase + 1; }
                if ( toBeTOCced[i].nodeName === 'H2' ){ tmp.className += text.space + tocClassNameBase + 2; }
                if ( toBeTOCced[i].nodeName === 'H3' ){ tmp.className += text.space + tocClassNameBase + 3; }
                if ( toBeTOCced[i].nodeName === 'H4' ){ tmp.className += text.space + tocClassNameBase + 4; }
                if ( toBeTOCced[i].nodeName === 'H5' ){ tmp.className += text.space + tocClassNameBase + 5; }
                if ( toBeTOCced[i].nodeName === 'H6' ){ tmp.className += text.space + tocClassNameBase + 6; }
            }
            // number of entries made
            return i;
        }
    
        // orig only looked at h2-h6
        function createTOC() {
            var y, x, a, z, entries;
            // create extra do-nothing div to fix display issue in IE6
            y = document.createElement('div');
            x = y.appendChild( document.createElement( "div" ) );
            x.id = 'innertoc';
            a = x.appendChild(document.createElement('span'));
            a.onclick = showhideTOC;
            a.id = 'contentheader';
            a.innerHTML = text.showContents;
            z = x.appendChild(document.createElement('div'));
            z.onclick = showhideTOC;
            entries = createLinks( z, text.tocClassNameBase );
            return entries ? y : null;
        }




        var tocDiv, elem;
        elem = document.getElementById( elemId );
        if( null === elem ) { return; }

        // text is specific to this version, so add it here
        text.tocClassNameBase = "TocIndent";
        
        tocDiv = createTOC();
        if( null !== tocDiv ){
            elem.appendChild( tocDiv );
            showhideTOC();
            // allow object to be deleted now that we're done with it
            tocDiv = null;
        }
    };
    // end of buildToC

    //========================= second version code ======================
    // build the ToC and attach it as a child to elemId
    // first arg is id of div to put the ToC
    // second arg is an options object

    $.buildToC2 = function(){
    
        var TOCstate = 'block';
        
        var text = {
            showContents: "show page contents",
            hideContents: "hide page contents",
            space: " ",
            tocClassNameBase2: "toc2indent",
            tmpOnClickLoc: "tmpOnClickLoc"
        };
        
    
        // can set state with optional parameter, else toggle
        function showhideTOC2( show ) {
            TOCstate = (TOCstate === 'none') ? 'block' : 'none';
            if( show === true || show === false ){
                TOCstate = ( show === true ? 'block' : 'none' ); 
            }
            var newText = (TOCstate === 'none') ? text.showContents : text.hideContents;
            document.getElementById( "tocContentheader" ).innerHTML = newText;
            document.getElementById( "tocContent" ).style.display = TOCstate;
        }
    
        // handle clicks on the li elements in the ToC
        function liClick(){
        
            var attrVal = this.getAttribute( text.tmpOnClickLoc );
            location.replace( attrVal );
            return false;
        }
        
        // A comma separate sting will be separate into an array of those elements
        // A hyphenated string will fill in the items between the hyphen. Be lazy
        //  and assume this could only be a heading element because those are all
        //  that are numbered.
        function parseTagList( inList ){
        
            var minH, maxH, count, tagNum = 0;
            var tags = inList.split( /,\s*/ );

            // either no comma separated or less that 2 types
            if( tags.length < 2 ){
            
                // parse for hyphenated list
                tags = inList.split( /-\s*/ );
                
                // if only one element, give up and just use it, otherwise assume H list
                if( tags.length > 1 ){
                
                    minH = parseInt( tags[ 0 ].charAt( 1 ), 10 );
                    maxH = parseInt( tags[ 1 ].charAt( 1 ), 10 );
                    for( count = minH; count <= maxH; count++ ){
                    
                        tags[ tagNum++ ] = 'h' + count;
                    }
                    //alert( inList + '. tags is ' + tags );
                }
            }
            
            // this will determines the priority of the tags
            //tags = tagList.split( /,\s*/ );
            return tags;
        }

        // build the ToC and append it to container
        // returns number of ToC entries
        function createLinks( container, tocClassNameBase, curSettings ){
        
            var toBeTOCced, tNode, i, headerId, tmp, tmpLi, tags = [], tp, bc;
            
            // this will determines the priority of the tags
            tags = parseTagList( curSettings.tagTypes );
            toBeTOCced = com.YourFriendPaul.util.getElementsByTagNames( tags );

            // nothing to show
            if( toBeTOCced.length < 2 ){ return 0; }
        
            for ( i=0; i<toBeTOCced.length; i++ ) {
    
                // create an id for the element if one doesn't exist
                headerId = toBeTOCced[i].id || 'TocLink' + i;
                toBeTOCced[i].id = headerId;
    
                tmpLi = document.createElement('li');
                container.appendChild( tmpLi );
                tmp = document.createElement('span');
                // don't modify browser history
                // tmp.href = 'javascript:location.replace("#' + headerId + '");';
                // tmp.href = "javascript:void()";
                // save the onclick handler target
                tmpLi.setAttribute( text.tmpOnClickLoc, "#" + headerId );
                tmpLi.onclick = liClick;
    
                // find the text to use in the ToC
                tNode = com.YourFriendPaul.util.node.findFirstText( toBeTOCced[i] );
                
                tmp.innerHTML = ( tNode === null ) ? headerId : tNode.nodeValue;
                tmp.className = 'TocItem';
                tmpLi.appendChild( tmp );
                
                // go through the list and compare each element to the tag list
                for( tp = 0; tp < tags.length; tp++ ){

                    // add class for indent formatting; doesn't have to start with h1
                    if ( toBeTOCced[i].nodeName === tags[ tp ].toUpperCase() ){ 

                        bc = tp+1;
                        tmpLi.className += text.space + tocClassNameBase + bc; 
                    }
                }
            }
            // number of entries made
            return i;
        }
    
        function createToC2( curSettings ){
            var y, x, a, z, entries;
            // create extra do-nothing div to fix display issue in IE6
            y = document.createElement( "div");
            x = y.appendChild( document.createElement( "div" ) );
            x.id = "tocInnertoc";
            a = x.appendChild( document.createElement( "div" ) );
            a.onclick = showhideTOC2;
            a.id = "tocContentheader";
            a.innerHTML = text.showContents;
            z = x.appendChild( document.createElement( "ul"));
            z.id = "tocContent";
            // z.onclick = showhideTOC2;
            entries = createLinks( z, text.tocClassNameBase2, curSettings );
            return entries ? y : null;
        }
    
        
        //  elemId  is the top level elem where this will be inserted
        function applyCSS( elemId, curSettings ){
        
            var lastStyleSheet, rules, sheetNum;
            var settings = curSettings;

            function getFromSettings( name ){
            
                return settings[ name ];
            }
            
            // all of this extra work is to allow overrides
            var objList = {
                "#ToCstyle": { 
                    "float": "right", 
                    "margin": null, 
                    "font-size": null, 
                    "width": null,
                    "border-style": "solid",
                    "border-width": null,
                    "border-color": null,
                    "background-color": null
                },
                
                "#tocContentheader": {
                    cursor: "pointer",
                    padding: ".5em",
                    "text-align": "center"
                }, 
    
                "#tocContent": {
                    //padding: "1em .5em 2em 1em",
                    margin: "0",
                    padding: ".5em .5em 1em 1em",
                    "border-color": null,
                    "border-style": "solid",
                    "border-width": "1px 0 1px 1px"
                }
            };
            
            // puts rules at the beginning of the sheet
            function insertRule( sheet, selector, styles ){
    
                if( sheet.insertRule ){
                    sheet.insertRule( selector + "{" + styles + "}", 0 );
                }
                else {
                    sheet.addRule( selector, styles, 0);
                }
            }
    
            function buildStylesText( obj ){
            
                var text = "";
                for ( var name in obj ) {
                    if ( obj.hasOwnProperty( name )) {
    
                        text += name + ": " + ( obj[ name ] ? obj[ name ] : getFromSettings( name ) ) + "; ";
                    }
                }
                
                return( text );
            }
    
            function insertRuleFromObj( sheet, objName ){
                insertRule( sheet, objName, buildStylesText( objList[ objName ] ) );
            }

            // lastStyleSheet = document.styleSheets[ document.styleSheets.length - 1 ];
            // rules = lastStyleSheet.cssRules ?  lastStyleSheet.cssRules : lastStyleSheet.rules;
            
            sheetNum = document.styleSheets.length - 1;
            
            while ( sheetNum >= 0 ){
            
                try {
                    
                    lastStyleSheet = document.styleSheets[ sheetNum ];
                    // look for an access error
                    rules = lastStyleSheet.cssRules ? lastStyleSheet.cssRules : lastStyleSheet.rules;  
                    
                } catch( e ) {
                
                    if( sheetNum > 0 ){
                    
                        sheetNum--;
                        continue;
                        
                    } else {
                    
                        throw( e );
                    }
                }
                
                // it worked
                break;
            }
            
            // add rules for each possible element in the list
            insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "1", "margin-left: 0em; list-style-type: none" );
            insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "2", "margin-left: 1.5em" );
            insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "3", "margin-left: 2.5em" );
            insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "4", "margin-left: 3.5em" );
            insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "5", "margin-left: 4.5em" );
            insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "6", "margin-left: 5.5em" );
            
            insertRule( lastStyleSheet, "#tocContent li", 
                        "display: list-item; margin-top: .5em; cursor: pointer; text-decoration: underline" );
            insertRule( lastStyleSheet, "#" + elemId, buildStylesText( objList[ "#ToCstyle" ] )  );
            insertRuleFromObj( lastStyleSheet, "#tocContentheader" );
            insertRuleFromObj( lastStyleSheet, "#tocContent" );
        }
        
        
        function overrideDefaults( defaults, options ){
        
            var settings = defaults;
            for ( var oName in options ) {
                if (options.hasOwnProperty( oName )) {
                
                    for ( var sName in settings ) {
                        if ( settings.hasOwnProperty( sName )) {
                        
                            if( sName === oName ){
                                settings[ sName ] = options[ oName ];
                            }
                        }
                    }
                }
            }
            return settings;
        }

        function run( args ){
        
            var elem, elemId, tocDiv, settings, options;
            
            elemId = args[0] || "ToC";
            options = args[1] || {};
            elem = document.getElementById( elemId );

            if( null === elem ) { return; }
    
            settings = overrideDefaults( $.buildToC2.defaults, options );
            
            // if not enough entries, will return null, else the ToC fragment
            tocDiv = createToC2( settings );
            
            if( null !== tocDiv ){
            
                elem.appendChild( tocDiv );
                showhideTOC2( settings.showToC );
                applyCSS( elemId, settings );
                tocDiv = null;
            }
        }
        
        // do this stuff separately to localize variables
        run( arguments );
    };

    $.buildToC2.defaults = {
            
        width: "180px",        // #ToC
        margin: "0 0 1em 0",        // #ToC
        "background-color": "#dddddd",    // #ToC
        "border-color": "#999999",    // #ToC
        "border-width": "5px 0",
        "font-size": "80%",    // #ToC
        tagTypes: "h1-h3",
        showToC: true
    };
})( com.YourFriendPaul.util.widget );

// ===============================================================================
// ===============================================================================
//
//    buildPageSizeSelector
//
// ===============================================================================
// build a page size selector box and attach it as a child to elemId
// first arg is id of div to put the selector box
// second arg is id of div to change the size of
// third arg is an options object

(function( $ ){

    function addListener( eType, elem, funcName ){
    
        com.YourFriendPaul.util.event.addListener( elem, eType, funcName );
    }



    // this is what actually changes the size
    $.sizeClick = function( e ){
    
        //alert( "clicked " + (this === window) ? 'window' : this.id );
        var ei = {
            event: e || window.event,
            target: null,
            keycode: null
        };
        
        ei.keycode = ei.event.keyCode || ei.event.which;
        ei.target = ei.event.target || ei.event.srcElement;
        if( ei.target.nodeType === 3 ){ // defeat Safari bug
                ei.target = ei.target.parentNode;
        }
        //alert( "clicked " + ei.target.innerHTML + ": " + ei.target.clickValue );
        // targetValue is the element to change the style of. It was added
        // as a parameter of the clicked li element
        ei.target.targetValue.style.width =  ei.target.clickValue;
        
        // execute a function now that we're done
        if( ei.target.callback !== null ){
        
            ei.target.callback();
        }
    };
    
    // this is the main public function
    $.buildPageSizeSelector = function( ){

    
        var elemBox, elemTarget, settings, options, elemId, divToChangeId;
        var innerName = "innerWidthChanger";
        
        // build the html and append it to container
        // selections is an array
        // returns number of selection entries
        function createHtml( elemBox, elemTarget, settings ){
        
            var tNode, cNode, iSelNum, tmpLi;
            var selections = settings.selections;
            
            // Create extra do-nothing div to fix display issue in IE6
            // This also gives a way to see if the box already exists
            tNode = document.createElement( "div");
            tNode.id = innerName;
            elemBox.appendChild( tNode );
            cNode = document.createElement( "span" );
            tNode.appendChild( cNode );
            cNode.appendChild( document.createTextNode( "Page width: " ) );
            cNode = document.createElement( "ul" );
            tNode.appendChild( cNode );
            
            // now add the li buttons to the ul
            for ( iSelNum = 0; iSelNum < selections.length; iSelNum++ ) {
    
                tmpLi = document.createElement('li');
                cNode.appendChild( tmpLi );
                tmpLi.appendChild( document.createTextNode( selections[ iSelNum ].label ) );
                tmpLi.clickValue = selections[ iSelNum ].value;
                // the element to change
                tmpLi.targetValue = elemTarget;
                // add the callback, will be null if option wasn't used
                tmpLi.callback = settings.callback;
                // need to add the click event handler here
                // it should work because it's already attached to the document
                addListener( "click", tmpLi, $.sizeClick );
            }
            // number of entries made
            return iSelNum;
        } // createHtml
    
    
        
        
        //  elemId is the top level container elem
        function applyStyles( elemId, curSettings ){
        
            var lastStyleSheet, sheetNum, rules;
            var settings = curSettings;

            // all of this extra work is to allow overrides
            // Each element of objList will be a new style rule.
            var objList = {
                "#WidthChanger": { 
                    "float": "right", 
                    "line-height": "1.25em",
                    "margin": null, 
                    "font-size": null, 
                    //"width": null,
                    "border-style": "solid",
                    "border-width": null,
                    "border-color": null,
                    "background-color": null
                },
                
                // essentially the box title
                "label": {
                    margin: "0 .5em 0 0",
                    padding: "0 .25em 0 0",
                    "float": "left",
                    "line-height": "1.25em",
                    color: "#333333"
                },
                
                "li": {
                    display: "inline",
                    "float": "left",
                    margin: "0 .5em",
                    padding: "0 .25em",
                    "list-style-type": "none", 
                    cursor: "pointer", 
                    "text-decoration": "underline",
                    "line-height": "1.25em",
                    color: "#333333"
                }
            };
            
            function applySettings( settings, objList ){
            
                objList[ "#WidthChanger" ].padding = settings.boxPadding;
                objList[ "#WidthChanger" ].margin = settings.boxMargin;
                objList[ "#WidthChanger" ][ "background-color" ] = settings[ "box-background-color" ];
                objList[ "#WidthChanger" ][ "border-color" ] = settings[ "box-border-color" ];
                objList[ "#WidthChanger" ][ "border-width" ] = settings[ "box-border-width" ];
                objList[ "#WidthChanger" ][ "font-size" ] = settings[ "font-size" ];
                objList.label.color = settings.color;
                objList.li.color = settings.color;
            }
            
            // puts rules at the beginning of the sheet
            function insertRule( sheet, selector, styles ){
    
                if( sheet.insertRule ){
                    sheet.insertRule( selector + "{" + styles + "}", 0 );
                }
                else {
                    sheet.addRule( selector, styles, 0);
                }
            }
    
            function buildStylesText( obj ){
            
                var text = "";
                for ( var name in obj ) {
                    if ( obj.hasOwnProperty( name )) {

                        //text += name + ": " + ( obj[ name ] ? obj[ name ] : settings[ name ] ) + "; ";
                        text += name + ": " + obj[ name ] + "; ";
                    }
                }
                
                return( text );
            }
    
            applySettings( settings, objList );
            //lastStyleSheet = document.styleSheets[ document.styleSheets.length - 1 ];
            //rules = lastStyleSheet.cssRules ?  lastStyleSheet.cssRules : lastStyleSheet.rules;
            
            // security error workaround for FF.
            sheetNum = document.styleSheets.length - 1;
            
            while ( sheetNum >= 0 ){
            
                try {
                    
                    lastStyleSheet = document.styleSheets[ sheetNum ];
                    // look for an access error
                    rules = lastStyleSheet.cssRules ? lastStyleSheet.cssRules : lastStyleSheet.rules;  
                    
                } catch( e ) {
                
                    if( sheetNum > 0 ){
                    
                        sheetNum--;
                        continue;
                        
                    } else {
                    
                        throw( e );
                    }
                }
                
                // it worked
                break;
            }
            
            
            insertRule( lastStyleSheet, "#" + elemId, buildStylesText( objList[ "#WidthChanger" ] ) );
            // need opposite float on innerWidthChanger to work in both IE and FF
            insertRule( lastStyleSheet, "#" + elemId + " #" + innerName, "float: left; line-height: 1.25em" );
            insertRule( lastStyleSheet, "#" + elemId + " #" + innerName + " span", buildStylesText( objList.label ) );
            insertRule( lastStyleSheet, "#" + elemId + " #" + innerName + " ul", 
            "display: inline-table; list-style: none; margin: 0; padding: 0" );
            insertRule( lastStyleSheet, "#" + elemId + " #" + innerName + " li", buildStylesText( objList.li ) );
        }
        // end applyStyles
        
        // copy the value for any properties of options that are also found in defaults
        function overrideValues( defaults, options ){
        
            var settings = defaults;
            var matchFound = false;
            // loop through the options object
            for ( var oName in options ) {
                if ( options.hasOwnProperty( oName )) {
                
                    matchFound = false;
                    // look for a match in defaults object
                    for ( var sName in settings ) {
                        if ( settings.hasOwnProperty( sName )) {
                        
                            if( sName === oName ){
                                settings[ sName ] = options[ oName ];
                                matchFound = true;
                            }
                        }
                        // not a failure, so pretend it worked
                        else { matchFound = true; }
                    }
                    
                    if( matchFound !== true ){
                    
                        // want to have a warning if something changes
                        alert( "Undefined or obsolete option used: " + oName );
                    }
                }
            }
            return settings;
        } // end overrideValues

        elemId = arguments[ 0 ] || "widthChanger";
        divToChangeId = arguments[ 1 ] || "allContentWrapper";
        options = arguments[ 2 ] || {};
        
        elemBox = document.getElementById( elemId );
        if( null === elemBox ) { return; }
        elemTarget = document.getElementById( divToChangeId );
        if( null === elemTarget ) { return; }
        var exists = document.getElementById( innerName );
        // The box is there and the page is being reloaded, don't make two!
        if( null !== exists ) { return; }

        settings = overrideValues( $.buildPageSizeSelector.defaults, options );
        
        var tocDiv = createHtml( elemBox, elemTarget, settings );
        if( null !== tocDiv ){
        
            applyStyles( elemId, settings );
            tocDiv = null;
        }
    };

    $.buildPageSizeSelector.defaults = {
            
        boxPadding: "1em",
        boxMargin: "0 0 1.5em 1.5em",
        "box-background-color": "#dddddd",
        "box-border-color": "#999999",
        "box-border-width": "1px",
        "font-size": "83%",
        color: "#333333",    // font color
        selections: [ { label: "750px", value: "750px" }, { label: "standard", value: "60em" },
        { label: "950px", value: "950px" }, { label: "full width", value: "100%" } ],
        'callback': null
    };
})( com.YourFriendPaul.util.widget );

// ===============================================================================
// ===============================================================================
/*
     appendText()
*/
(function( base ){
    
    base.doc = base.doc || {};
    var $ = base.doc;

    if( $.toString() != base.toString() + '.doc' ){ 
        $.toString = function(){ return base.toString() + '.doc'; }; 
    }

    
    // find index position of {item}
    // in Array {arr} - return -1, if
    // item not found
    // case insensitive
    function findIndex( s, arr ) {
    
        var i, idx;
        var last = arr.length;
        var searchFor = s.toLowerCase();
        
        for ( i = 0; i < last; i++) {
        
            idx = ( searchFor == arr[i].toLowerCase() ) ? i : -1;
            // quit on first "found"
            if( -1 != idx){ break; }
        }
        
        return idx;
    }

    function addAttributes( elem, attribs ){
    
        var count = 0;
        var attr = [];

        if( attribs.length <= 0 ){ return; }
        
        for( count=0; count<attribs.length; count++ ){
        
            attr = attribs[ count ].split( '=' );
            
            if( attr.length === 2 ){
            
                elem.setAttribute( attr[ 0 ], attr[ 1 ] );
            }
        }
    }

    /*
        Append text to a target element, surrounded by an optional elemType tag
        This is only intended as a debugging aid, so limited tags are supported.
        
        targetId - the id of the element where the text will be appended.
        If it is not a string, it is assumed to be an element instead of an id
        text - the text to append, it is appended to a new element if that 
        parameter is included. The text is ignored if the elemType is 'ul'.
        elemType - an optional tag type to wrap the text in.
        attrList - an array of attributes to add to the tag 
                    created by elemType. The format is 'attr=value'. Note 
                    that the values are not quoted.
    */
    $.appendText = function( targetId, text, elemType, attrList ){
    
        var targElem = null;
        var wrapElem = null;
        var validElem = false;
        var validElemList = [ 'p', 'div', 'li', 'ul', 'pre', 'code', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ];
        
        var surElem = elemType || "";
        attrList = attrList || [];
        
        surElem = surElem.toLowerCase();
        
        // assume that targetId is the element if it's not a string, otherwise get it from the string
        if( null != targetId ){
        
            targElem = ( typeof targetId === 'string' ) ? 
                    document.getElementById( targetId ) : targetId;
        }
        
        if( null === targElem ){ 
            throw Error( "Invalid target in " + $.toString() + ".appendText: " + targetId ); 
        }
        
        // make the wrapping element if needed
        if( surElem != '' ){
        
            if( findIndex( surElem, validElemList ) > -1 ){
    
                try {

                    wrapElem = document.createElement( surElem );
                }
                catch( e ) {
                
                    throw Error( "Invalid character in element type in " + $.toString() + ".appendText: " + surElem ); 
                }

                targElem.appendChild( wrapElem );

                if( attrList.length > 0 ){
                
                    addAttributes( wrapElem, attrList )
                }
            }
            
            else {
            
                throw Error( "Invalid element type in " + $.toString() + ".appendText: " + surElem ); 
            }
        }

        // attach to the wrapper if we made one
        if( null !== wrapElem ){
        
            // no text on a ul element
            if( surElem != 'ul' ){
            
                wrapElem.appendChild( document.createTextNode( text ) );
            }
            
            return wrapElem;
        }
        
        // append the text directly to the target
        else{
        
            targElem.appendChild( document.createTextNode( text ) );
            return targElem;
        }
    };
    
})( com.YourFriendPaul.util );


// ===============================================================================
// ===============================================================================
/*
The namespace stuff is really pretty useless, but it stays because
it's still be using it in some older code
*/

// can add one or multiple namespaces to 'com.YourFriendPaul'
com.YourFriendPaul.util.addNamespace = function(){
    var args = arguments;
    var root = null;	// returns null if nothing is done
    var nameParts, iCount, nameStart, iArg;
    
    for( iArg=0; iArg<args.length; iArg++ ){
    
        nameParts = args[ iArg ].split(".");
        root = com.YourFriendPaul;
        // skip over com.YourFriendPaul if it is there
        nameStart = ( nameParts[ 0 ] === "com" && nameParts[ 1 ] === "YourFriendPaul" ) ? 2 : 0;
        for( iCount=nameStart; iCount<nameParts.length; iCount++ ){
            // if root[ nameParts[ iCount ] ] is defined and not null, don't change it, else make it an object
            root[ nameParts[ iCount ] ] = root[ nameParts[ iCount ] ] || {};
            root = root[nameParts[ iCount ]];
        }
    }
    return root;
};

/**********************************************************************/
/**********************************************************************/
/**********************************************************************/
com.YourFriendPaul.util.nsUsedLevel = function( root, ns ){

    var prop;
    
    if( ns  ){
        for( prop in root ){
            if( null !== prop ){
        
                if( prop === ns ){
                    return true;
                }
            }
        }
    }
    
    return  false;
};

// returns true if nsIn exists in com.YourFriendPaul
com.YourFriendPaul.util.isNamespaceUsed = function( nsIn ){

    var root = com.YourFriendPaul;
    var found = false;
    var nameParts, iCount, nameStart;
    var ns = String( nsIn || "" );
    
    nameParts = ns.split(".");
    // skip over com.YourFriendPaul if it is there
    nameStart = ( nameParts[ 0 ] === "com" && nameParts[ 1 ] === "YourFriendPaul" ) ? 2 : 0;
    
    for( iCount=nameStart; iCount<nameParts.length; iCount++ ){
    
        if( nameParts[ iCount ] ){
        
            if( !root.util.nsUsedLevel( root, nameParts[ iCount ] ) ){

                return found;
            }

            root = root[ nameParts[ iCount ] ];
        }
    }

    found = true;
    return found;

};
