/*
    @note tkeller 2008-02-27: moved initalization code into setup.js

    Offspring.js -- adds the following classes as needed:

        .first-child
        .last-child
        .only-child
        .nth-child-odd
        .nth-child-even
        .nth-child-##

    Configuration:

    Offspring can be configured by defining an "offspringConfiguration"
    object before referencing offspring.js. That object can contain
    one or more of these parameters. (If any parameter is omitted -- which
    is fine -- it gets the default value as described below.)

    offspringConfiguration =
    {
        runningMode: 'full',                        <-- valid values are 'full' and 'light' (default: 'full')
        autoStart: true,                            <-- valid values are true and false (default: true)
        shouldRemoveOldOffspringClassesFirst: false <-- valid values are true and false (default: false)
    }

    * runningMode:
        'full' -- Offspring applies all of its classes (as listed at the very top) [default]
        'light' -- Offspring only applies 'first-child', 'last-child', and 'only-child',
                    omitting 'nth-child-odd', 'nth-child-even', and 'nth-child-##'.
                    (This may allow for faster page-processing in certain scenarios.)

    * autoStart:
        true -- Offspring runs automatically as soon as the DOM is ready [default]
        false -- Offspring must be run manually. This can be done by calling Offspring.start();

    * shouldRemoveOldOffspringClassesFirst:
        true --Offspring first removes any old Offspring classes before applying the new ones.
                (This might be of use if Offspring is to be called on a page that has already
                been processed, such as if a table has been sorted or content has been loaded
                via Ajax.)
        false -- Offspring applies its classes without first removing old Offspring classes that
                might be there. Unless you're doing fancy DOM updates, this is probably the
                better option in most cases. [default]

    ================================================================== */


var offspring = {
    firstChildClass: "first-child",
    lastChildClass:  "last-child",
    oddChildClass:   "nth-child-odd",
    evenChildClass:  "nth-child-even",
    onlyChildClass:  "only-child",
    nthChildClassPrefix:   "nth-child-",

    classNamesArray: [],
    classNameSubstringsArray: [],

    cacheLevel: 0, // current size of the classNames cache

    nthChildren: [],

    regularHashTable: [],
    regularHashTableArray: [],

    lastChildHashTable: [],
    lastChildHashTableArray: [],

    /* Configuration defaults */
    configuration:
    {
        runningMode: 'full', /* Possible values: 'full' / 'light' */
        autoStart: true, /* If Offspring is configured in autoStart mode (which it is by default),
                            it runs as soon as the DOM is ready */
        shouldRemoveOldOffspringClassesFirst: false /* If this is set to 'true', Offspring first
                                                        removes any old Offspring-related classes
                                                        before applying the new ones */
    },

    // Initialize
    init: function() {
        /*
            Offspring's configuration is stored in Offspring.configuration, but
            that con be overridden by users by defining an "offspringConfiguratin"
            object.
        */
        if (typeof offspringConfiguration != "undefined")
        {
            for (var configParameter in offspringConfiguration)
            {
                this.configuration[configParameter] = offspringConfiguration[configParameter];
            }

            // Make sure this option is stored in lowercase
            this.configuration.runningMode = this.configuration.runningMode.toLowerCase();
        }


        /* Set the values for classNamesArray & classNameSubstringArray */

        switch (this.configuration.runningMode)
        {
            case 'full':
                // this represents all possible offspring-related classnames
                this.classNamesArray = [this.firstChildClass, this.lastChildClass, this.oddChildClass, this.evenChildClass, this.onlyChildClass];

                // this represents a list of substrings to match such as for removing classNames
                this.classNameSubstringsArray = [this.nthChildClassPrefix];
                break;

            case 'light':
                // this represents all possible offspring-related classnames
                this.classNamesArray = [this.firstChildClass, this.lastChildClass, this.onlyChildClass];

                // this represents a list of substrings to match such as for removing classNames
                this.classNameSubstringsArray = [];
                break;
        }

        // Define the iterator function on-the-fly depending
        // on the configuration options that were sent in
        this.defineTraverseChildrenFunction();

        // Define the fillCacheTo funtion's iterator on-the-fly
        // depending on the configuration options that were sent in
        this.defineFillCacheToFunction();
        this.fillCacheTo(); // seed the cache with a basic set of values

        this.start();
    },

    // Executed once the page has loaded
    start: function() {
        var startTime = new Date();

        this.traverseChildren(document.getElementsByTagName("body")[0]);

        var endTime = new Date();
        // alert("Offspring Exec time: " + (endTime.getTime() - startTime.getTime()) + "ms");
        // window.status += "Offspring Exec time: " + (endTime.getTime() - startTime.getTime()) + "ms";

    },

    /* Maintenance note for defineTraverseChildrenFunction:

        There are several blocks of code that are marked off as "traverseChildren.A"
        or "traverseChildren.B" -- each of these are identical, respectively. (That is,
        all "traverseChildren.A" blocks are the same and all "traverseChildren.B" are
        the same.)

        So, why not just create a function where the code can be kept in one place?
        While normally a sensible idea, I decided against that approach only so
        that the speed hits associated with the creation of the function stack
        could be averted. At the same time, I didn't want to compromise
        the code's maintainability; so, if any block needs to be updated, they
        can all be kept in sync with some basic copy-n-pasting from one
        block to the next.
    */


    /* This defines the internal iterator function on-the-fly,
        depending on the configuration options */
    defineTraverseChildrenFunction: function() {

        switch (this.configuration.shouldRemoveOldOffspringClassesFirst)
        {
            case true: // shouldRemoveOldOffspringClassesFirst is true

                switch (this.configuration.runningMode)
                {
                    case 'full': // 'full' running mode and shouldRemoveOldOffspringClassesFirst is true
                        this.traverseChildren = function(parent)
                        {
                            /* ============= Begin Code Block "traverseChildren.A" ================ */

                                // If the node has no children, exit
                                if (!parent.childNodes.length) return;


                                /* First, gather up all the element nodes */
                                var childElementNodes = [];

                                var testNode = parent.childNodes[0]; // initialize

                                while (testNode)
                                {
                                    if (testNode.nodeType == 1)
                                    {
                                        childElementNodes.push(testNode);
                                    }
                                    testNode = testNode.nextSibling;
                                }

                                /*
                                    empty this variable to ensure that the JavaScript
                                    interpreter doesn't have to update the variable's
                                    nodelist as DOM changes are made
                                */
                                testNode = null;

                                var childElementNodesLength = childElementNodes.length;

                                // If no element nodes were found, exit
                                if (!childElementNodesLength) return;

                                // Make sure that the CSS-classnames cache has enough entries to cover
                                // the number of child nodes
                                if (childElementNodesLength > this.cacheLevel)
                                {
                                    this.fillCacheTo(childElementNodesLength);
                                }

                                var lastIndex = childElementNodesLength - 1; // index of the last element node

                            /* ============= /End Code Block "traverseChildren.A" ================ */

                            // First, take care of all but the last element
                            for (var i = 0; i < lastIndex; i++)
                            {
                                var currentElement = childElementNodes[i];

                                this.removeMultipleClassNames(currentElement, this.classNamesArray, this.classNameSubstringsArray);

                                // argument syntax: node to act upon, current index, boolean for whether isLast
                                this._addOffspringClassNames(currentElement, i, false);
                                this.traverseChildren(currentElement);
                            }

                            currentElement = null; // prevent memory leaks

                            // Then, take care of the last one
                            var lastElement = childElementNodes[lastIndex];

                            this.removeMultipleClassNames(lastElement, this.classNamesArray, this.classNameSubstringsArray);

                            this._addOffspringClassNames(lastElement, lastIndex, true);
                            this.traverseChildren(lastElement);

                            lastElement = null; // prevent memory leaks

                            /* ============= Begin Code Block "traverseChildren.B" ================ */

                                // prevent memory leaks
                                lastElement = null;
                                parent = null;

                            /* ============= /End Code Block "traverseChildren.B" ================ */

                        }; // end of traverseChildren function definition
                        break;

                    case 'light': // 'light' running mode and shouldRemoveOldOffspringClassesFirst is true
                        this.traverseChildren = function(parent)
                        {
                            /* ============= Begin Code Block "traverseChildren.A" ================ */

                                // If the node has no children, exit
                                if (!parent.childNodes.length) return;


                                /* First, gather up all the element nodes */
                                var childElementNodes = [];

                                var testNode = parent.childNodes[0]; // initialize

                                while (testNode)
                                {
                                    if (testNode.nodeType == 1)
                                    {
                                        childElementNodes.push(testNode);
                                    }
                                    testNode = testNode.nextSibling;
                                }

                                /*
                                    empty this variable to ensure that the JavaScript
                                    interpreter doesn't have to update the variable's
                                    nodelist as DOM changes are made
                                */
                                testNode = null;

                                var childElementNodesLength = childElementNodes.length;

                                // If no element nodes were found, exit
                                if (!childElementNodesLength) return;

                                // Make sure that the CSS-classnames cache has enough entries to cover
                                // the number of child nodes
                                if (childElementNodesLength > this.cacheLevel)
                                {
                                    this.fillCacheTo(childElementNodesLength);
                                }

                                var lastIndex = childElementNodesLength - 1; // index of the last element node

                            /* ============= /End Code Block "traverseChildren.A" ================ */

                            switch (childElementNodesLength)
                            {
                                case 0: return;
                                        break;

                                case 1:
                                    /* Take care of the only element */

                                    var onlyElement = childElementNodes[0];
                                    this.removeMultipleClassNames(onlyElement, this.classNamesArray, this.classNameSubstringsArray);

                                    // argument syntax: node to act upon, current index, boolean for whether isLast
                                    this._addOffspringClassNames( onlyElement, lastIndex, true );

                                    onlyElement = null; // prevent memory leaks

                                    break;

                                default:
                                    /* Take care of the first element */

                                    var firstElement = childElementNodes[0];
                                    this.removeMultipleClassNames(firstElement, this.classNamesArray, this.classNameSubstringsArray);

                                    // argument syntax: node to act upon, current index, boolean for whether isLast
                                    this._addOffspringClassNames( firstElement, 0, false );

                                    firstElement = null; // prevent memory leaks

                                    /* Take care of the last element */

                                    var lastElement = childElementNodes[lastIndex];
                                    this.removeMultipleClassNames(lastElement, this.classNamesArray, this.classNameSubstringsArray);

                                    // argument syntax: node to act upon, current index, boolean for whether isLast
                                    this._addOffspringClassNames( lastElement , lastIndex, true );

                                    lastElement = null; // prevent memory leaks

                                    break;

                            } // end of switch statement for childElementNodesLength

                            // Lastly, loop over all the childern elements
                            for (var i = 0; i < childElementNodesLength; i++)
                            {
                                this.traverseChildren( childElementNodes[i] );
                            }

                            /* ============= Begin Code Block "traverseChildren.B" ================ */

                                // prevent memory leaks
                                lastElement = null;
                                parent = null;

                            /* ============= /End Code Block "traverseChildren.B" ================ */

                        }; // end of traverseChildren function definition

                        break;

                } // end of switch-statement for configuration.runningMode

                break;

            case false: // shouldRemoveOldOffspringClassesFirst is false

                switch (this.configuration.runningMode)
                {
                    case 'full': // 'full' running mode and shouldRemoveOldOffspringClassesFirst is false
                        this.traverseChildren = function(parent)
                        {
                            /* ============= Begin Code Block "traverseChildren.A" ================ */

                                // If the node has no children, exit
                                if (!parent.childNodes.length) return;


                                /* First, gather up all the element nodes */
                                var childElementNodes = [];

                                var testNode = parent.childNodes[0]; // initialize

                                while (testNode)
                                {
                                    if (testNode.nodeType == 1)
                                    {
                                        childElementNodes.push(testNode);
                                    }
                                    testNode = testNode.nextSibling;
                                }

                                /*
                                    empty this variable to ensure that the JavaScript
                                    interpreter doesn't have to update the variable's
                                    nodelist as DOM changes are made
                                */
                                testNode = null;

                                var childElementNodesLength = childElementNodes.length;

                                // If no element nodes were found, exit
                                if (!childElementNodesLength) return;

                                // Make sure that the CSS-classnames cache has enough entries to cover
                                // the number of child nodes
                                if (childElementNodesLength > this.cacheLevel)
                                {
                                    this.fillCacheTo(childElementNodesLength);
                                }

                                var lastIndex = childElementNodesLength - 1; // index of the last element node

                            /* ============= /End Code Block "traverseChildren.A" ================ */

                            // First, take care of all but the last element
                            for (var i = 0; i < lastIndex; i++)
                            {
                                var currentElement = childElementNodes[i];

                                // argument syntax: node to act upon, current index, boolean for whether isLast
                                this._addOffspringClassNames(currentElement, i, false);
                                this.traverseChildren(currentElement);
                            }

                            currentElement = null; // prevent memory leaks

                            /*
                                Then, take care of the last one
                                (this set of code isn't integrated into
                                the for-loop above so as to avoid having
                                an addiitional if-statement inside there)
                            */
                            var lastElement = childElementNodes[lastIndex];

                            this._addOffspringClassNames(lastElement, lastIndex, true);
                            this.traverseChildren(lastElement);
                            lastElement = null; // prevent memory leaks

                            /* ============= Begin Code Block "traverseChildren.B" ================ */

                                // prevent memory leaks
                                lastElement = null;
                                parent = null;

                            /* ============= /End Code Block "traverseChildren.B" ================ */

                        }; // end of traverseChildren function definition
                        break;

                    case 'light': // 'light' running mode and shouldRemoveOldOffspringClassesFirst is false
                        this.traverseChildren = function(parent)
                        {
                            /* ============= Begin Code Block "traverseChildren.A" ================ */

                                // If the node has no children, exit
                                if (!parent.childNodes.length) return;


                                /* First, gather up all the element nodes */
                                var childElementNodes = [];

                                var testNode = parent.childNodes[0]; // initialize

                                while (testNode)
                                {
                                    if (testNode.nodeType == 1)
                                    {
                                        childElementNodes.push(testNode);
                                    }
                                    testNode = testNode.nextSibling;
                                }

                                /*
                                    empty this variable to ensure that the JavaScript
                                    interpreter doesn't have to update the variable's
                                    nodelist as DOM changes are made
                                */
                                testNode = null;

                                var childElementNodesLength = childElementNodes.length;

                                // If no element nodes were found, exit
                                if (!childElementNodesLength) return;

                                // Make sure that the CSS-classnames cache has enough entries to cover
                                // the number of child nodes
                                if (childElementNodesLength > this.cacheLevel)
                                {
                                    this.fillCacheTo(childElementNodesLength);
                                }

                                var lastIndex = childElementNodesLength - 1; // index of the last element node

                            /* ============= /End Code Block "traverseChildren.A" ================ */

                            switch (childElementNodesLength)
                            {
                                case 0: break;

                                case 1:
                                    /* Take care of the only element */

                                    // argument syntax: node to act upon, current index, boolean for whether isLast
                                    this._addOffspringClassNames( childElementNodes[0], lastIndex, true );

                                    // Lastly, loop over all the childern elements
                                    for (var i = 0; i < childElementNodesLength; i++)
                                    {
                                        this.traverseChildren( childElementNodes[i] );
                                    }

                                    break;

                                default:
                                    /* Take care of the first element */

                                    // argument syntax: node to act upon, current index, boolean for whether isLast
                                    this._addOffspringClassNames( childElementNodes[0], 0, false );

                                    /* Take care of the last element */

                                    // argument syntax: node to act upon, current index, boolean for whether isLast
                                    this._addOffspringClassNames( childElementNodes[lastIndex] , lastIndex, true );

                                    // Lastly, loop over all the childern elements
                                    for (var i = 0; i < childElementNodesLength; i++)
                                    {
                                        this.traverseChildren( childElementNodes[i] );
                                    }

                                    break;
                            }

                            /* ============= Begin Code Block "traverseChildren.B" ================ */

                                // prevent memory leaks
                                lastElement = null;
                                parent = null;

                            /* ============= /End Code Block "traverseChildren.B" ================ */

                        }; // end of traverseChildren function definition

                        break;
                } // end of switch-statement for configuration.runningMode

                break;

        } // end of switch-statement for configuration.shouldRemoveOldOffspringClassesFirst

    }, // end of defineTraverseChildrenFunction

    // Recursive

    /*
        If "shouldRemoveOldOffspringClassesFirst" is deined and set to true
        (it's optional), traverseChildren will remove old Offspring-related
        classes before applying new ones to a node. This could be useful
        for reapplying classes if the DOM is rejiggered.
    */

    traverseChildren: function(parent) {

        /* ============= Begin Code Block "traverseChildren.A" ================ */

            // If the node has no children, exit
            if (!parent.childNodes.length) return;


            /* First, gather up all the element nodes */
            var childElementNodes = [];

            var testNode = parent.childNodes[0]; // initialize

            while (testNode)
            {
                if (testNode.nodeType == 1)
                {
                    childElementNodes.push(testNode);
                }
                testNode = testNode.nextSibling;
            }

            /*
                empty this variable to ensure that the JavaScript
                interpreter doesn't have to update the variable's
                nodelist as DOM changes are made
            */
            testNode = null;

            var childElementNodesLength = childElementNodes.length;

            // If no element nodes were found, exit
            if (!childElementNodesLength) return;

            // Make sure that the CSS-classnames cache has enough entries to cover
            // the number of child nodes
            if (childElementNodesLength > this.cacheLevel)
            {
                this.fillCacheTo(childElementNodesLength);
            }

            var lastIndex = childElementNodesLength - 1; // index of the last element node

        /* ============= /End Code Block "traverseChildren.A" ================ */


        /* ==== Add the classes ====== */

        this._childrenIterator(childElementNodes, childElementNodesLength, lastIndex);


        /* ============= Begin Code Block "traverseChildren.B" ================ */

            // prevent memory leaks
            lastElement = null;
            parent = null;

        /* ============= /End Code Block "traverseChildren.B" ================ */

    },

    /*
        This function adds the Offspring classnames to a given element,
        given its position among it siblings (with zero being "first")
        and whether it's the last element in its set.
    */
    _addOffspringClassNames: function(element, index, isLastElement) {

        index++; // normalize since the arrays are indexed with a "1" starting point

        // Steps if the element has no existing classnames...

        if ((!element.className) || (!element.className.length))
        {
            switch (isLastElement)
            {
                case false: // it isn't the last element
                        element.className = this.regularHashTable[index];
                        return;
                        break;

                case true: // it is the last element
                        element.className = this.lastChildHashTable[index];
                        return;
                        break;

            } // end of isLastElement switch-statement

        } // end of if-statement for checking whether the element has no existing className

        // At this point, the incoming element already has className(s)

        switch (isLastElement)
        {
            case false: // it isn't the last element
                    var applicableClassNames = this.regularHashTableArray[index];
                    break;

            case true: // it is the last element
                    var applicableClassNames = this.lastChildHashTableArray[index];
                    break;

        } // end of isLastElement switch-statement

        var originalClassNames = element.className.split(' ');

        var classNamesToAdd = originalClassNames; // initialize

        for (var i = 0, applicableClassNamesLength = applicableClassNames.length; i < applicableClassNamesLength; i++)
        {
            var alreadyThere = false; // boolean for whether a given class name is already assigned to the element

            var testApplicableClassName = applicableClassNames[i];

            for (var j = 0, originalClassNamesLength = originalClassNames.length; j < originalClassNamesLength; j++)
            {
                if (originalClassNames[j] == testApplicableClassName)
                {
                    alreadyThere = true;
                    break;
                } // end of if-statement for checking if the element already has a given className

            } // end of the originalClassNames for-loop

            if (!alreadyThere)
            {
                classNamesToAdd.push(testApplicableClassName);
            }

        } // end of applicableClassNames for-loop


        // Then, after checking over the element's existing classNames, add the new version
        element.className = classNamesToAdd.join(' ');
        element = null; // prevent memory leaks

        return;

    }, // end of _addOffspringClassNames()

    /* Maintenance note for defineFillCacheToFunction:

        [Aside: This is basically conveys the same idea as the comment above
        defineTraverseChildrenFunction. So, if you're read that one, you
        probably already have the basic idea of what's going on here.]

        There are several blocks of code that are marked off as "fillCacheTo.A"
        or "fillCacheTo.B" -- each of these are identical, respectively. (That is,
        all "fillCacheTo.A" blocks are the same and all "fillCacheTo.B" are
        the same.)

        So, why not just create a function where the code can be kept in one place?
        While normally a sensible idea, I decided against that approach only so
        that the speed hits associated with the creation of the function stack
        could be averted. At the same time, I didn't want to compromise
        the code's maintainability; so, if any block needs to be updated, they
        can all be kept in sync with some basic copy-n-pasting from one
        block to the next.
    */


    /* This defines the internal loop function for fillCacheTo,
        depending on how the configuration options are set */
    defineFillCacheToFunction: function() {

        switch (this.configuration.runningMode)
        {
            case 'full': // 'full' running mode
                this.fillCacheTo = function(fillAmount)
                {
                    /* ============= Begin Code Block "fillCacheTo.A" ================ */

                        var fillAmount = fillAmount || 15; // default value

                        if (!this.cacheLevel) this.cacheLevel = 0; // set this to a default value if needed

                        // If the cache level is already full enough, exit
                        if (this.cacheLevel >= fillAmount) return;

                        var startingPoint = this.cacheLevel++;

                    /* ============= /End Code Block "fillCacheTo.A" ================ */

                    var isOdd = !((startingPoint % 2) == 0); // initialize

                    // cache these object name resolutions
                    var firstChildClass = this.firstChildClass;
                    var lastChildClass = this.lastChildClass;
                    var oddChildClass = this.oddChildClass;
                    var evenChildClass = this.evenChildClass;
                    var onlyChildClass = this.onlyChildClass;
                    var nthChildClassPrefix = this.nthChildClassPrefix;

                    for (var i = startingPoint; i <= fillAmount; i++)
                    {
                        this.nthChildren[i] = [nthChildClassPrefix, i].join('');

                        var nthChildrenI = this.nthChildren[i]; // cache this look-up

                        switch (i)
                        {
                            case 1:
                                    this.regularHashTableArray[i] = [firstChildClass, oddChildClass, nthChildrenI];
                                    this.lastChildHashTableArray[i] = [firstChildClass, oddChildClass, onlyChildClass, nthChildrenI, lastChildClass];
                                    break;

                            default:
                                    switch (isOdd)
                                    {
                                        case true: // "odd" is true
                                                this.regularHashTableArray[i] = [oddChildClass, nthChildrenI];
                                                this.lastChildHashTableArray[i] = [oddChildClass, nthChildrenI, lastChildClass];
                                                break;

                                        case false: // "odd" is false
                                                this.regularHashTableArray[i] = [evenChildClass, nthChildrenI];
                                                this.lastChildHashTableArray[i] = [evenChildClass, nthChildrenI, lastChildClass];
                                                break;

                                    } // end of isOdd switch-statement


                        } // end of switch-statement for i

                        // Now make the joined versions for a given "i"

                        this.regularHashTable[i] = this.regularHashTableArray[i].join(' ');
                        this.lastChildHashTable[i] = this.lastChildHashTableArray[i].join(' ');

                        isOdd = !isOdd; // flip the isOdd flag

                    } // end of filling for-loop

                    /* ============= Begin Code Block "fillCacheTo.B" ================ */

                        // If it got this far, the cacheLevel must made it to the fill amount, so update that
                        this.cacheLevel = fillAmount;

                    /* ============= /End Code Block "fillCacheTo.B" ================ */

                }; // end of fillCacheTo function definition
                break;

            case 'light': // 'light' running mode
                this.fillCacheTo = function(fillAmount)
                {
                    /* ============= Begin Code Block "fillCacheTo.A" ================ */

                        var fillAmount = fillAmount || 15; // default value

                        if (!this.cacheLevel) this.cacheLevel = 0; // set this to a default value if needed

                        // If the cache level is already full enough, exit
                        if (this.cacheLevel >= fillAmount) return;

                        var startingPoint = this.cacheLevel++;

                    /* ============= /End Code Block "fillCacheTo.A" ================ */

                    // cache these object name resolutions
                    var firstChildClass = this.firstChildClass;
                    var lastChildClass = this.lastChildClass;

                    var onlyChildClass = this.onlyChildClass;

                    for (var i = startingPoint; i <= fillAmount; i++)
                    {

                        switch (i)
                        {
                            case 1:
                                    this.regularHashTableArray[i] = [firstChildClass];
                                    this.lastChildHashTableArray[i] = [firstChildClass, onlyChildClass, lastChildClass];
                                    break;

                            default:

                                    this.regularHashTableArray[i] = [];
                                    this.lastChildHashTableArray[i] = [lastChildClass];

                        } // end of switch-statement for i

                        // Now make the joined versions for a given "i"

                        this.regularHashTable[i] = this.regularHashTableArray[i].join(' ');
                        this.lastChildHashTable[i] = this.lastChildHashTableArray[i].join(' ');

                    } // end of filling for-loop

                    /* ============= Begin Code Block "fillCacheTo.B" ================ */

                        // If it got this far, the cacheLevel must made it to the fill amount, so update that
                        this.cacheLevel = fillAmount;

                    /* ============= /End Code Block "fillCacheTo.B" ================ */

                }; // end of fillCacheTo function definition
                break;

        } // end of switch statement for this.configuration.runningMode

    }, // end of defineFillCacheToFunction

    // This fills the className caches to the specified amount
    fillCacheTo: function(fillAmount) {

        /* ============= Begin Code Block "fillCacheTo.A" ================ */

            var fillAmount = fillAmount || 15; // default value

            if (!this.cacheLevel) this.cacheLevel = 0; // set this to a default value if needed

            // If the cache level is already full enough, exit
            if (this.cacheLevel >= fillAmount) return;

            var startingPoint = this.cacheLevel++;

        /* ============= /End Code Block "fillCacheTo.A" ================ */

        this._fillCacheToIterator(startingPoint, fillAmount);

        /* ============= Begin Code Block "fillCacheTo.B" ================ */

            // If it got this far, the cacheLevel must made it to the fill amount, so update that
            this.cacheLevel = fillAmount;

        /* ============= /End Code Block "fillCacheTo.B" ================ */

    }, // end of fillCacheTo()

    /* Returns true if testString is found in the array,
        or returns false otherwise */
    _checkIfStringFoundInArray: function(testString, testArray) {

        // Loop through all testArray[] and if/when there's a match, return true
        for (var i = 0, len=testArray.length; i < len; i++)
        {
            if (testString == testArray[i]) return true;
        }

        // If it got this far, it must not have found the string in the array
        return false;

    }, // end of _checkIfStringFoundInArray

    /* Returns true if the beginning of testString matches one of the substrings
        in the array. Otherwise, it returns false.

        For example, given the array ['plum', 'orange', 'pine'] and
        the testString 'pineapples', the function would return true. However,
        given the testString 'range', it would return false (since none of
        the strings in the array start with 'range')
    */
    _checkIfStringMatchInSubstringArray: function(testString, testArray) {

        // Loop through all testArray[] and if/when there's a match, return true
        for (var i = 0, len=testArray.length; i < len; i++)
        {
            var currentArrayItem = testArray[i];

            /* string.substr() accepts two parameters:
                - The starting point of the substring
                - The length of the substring
            */
            var testSubstring = testString.substr(0, currentArrayItem.length);

            if (testSubstring == currentArrayItem) return true;
        }

        // If it got this far, it must not have found the string in the array
        return false;

    }, // end of _checkIfStringMatchInSubstringArray

    /*
        This removes multiple classnames from an element. It does this by
        checking each of an element's class names against
        classNameStrings[] for an exact match and, if a given class name
        didn't match there, it's then checked to see if it matches
        as a substring against classNAmeSubstrings[].

        Of note, when comparing substrings, this intentionally only compares
        the beginning of the strings for a match. So, for example, "ora" would
        match as a substring of "orange", but "range" would not match as a substring
        of "orange". It was done this way because that was the only type of substring-
        comparison that was needed in this case, and a more thorough substring
        comparison would needlesslly use processor time.
    */
    removeMultipleClassNames: function(element, classNameStrings, classNameSubstrings) {

        if (!element) return;
        var newClassName = '';
        var classNamesArray = element.className.split(' ');

        for (var i = 0, len = classNamesArray.length; i < len; i++)
        {
            var currentClassName = classNamesArray[i];

            var isStringInClassNameStrings = this._checkIfStringFoundInArray(currentClassName, classNameStrings);

            if (isStringInClassNameStrings) continue;

            var isStringMatchingClassNameSubstrings = this._checkIfStringMatchInSubstringArray(currentClassName, classNameSubstrings);

            if (isStringMatchingClassNameSubstrings) continue;

            // If it got this far, it must not have matched any of the potential classNameStrings
            // or classNameRegexes, so add the current iteration to the neClassName

            if (i > 0) newClassName = newClassName + ' ';
            newClassName = newClassName + currentClassName;

        }
        element.className = newClassName;

    } // end of removeMultipleClassNames
}
