/**
 * STRING
 */


/**
 * Returns the given string, with whitespace characters stripped from left side
 *
 * @param      string sValue
 * @return     string
 */
var ltrim = function (sValue)
{
    return String(sValue).replace(/^[\s]+/, '');
}


/**
 * Returns the given string, with whitespace characters stripped from right
 * side
 *
 * @param      string sValue
 * @return     string
 */
var rtrim = function (sValue)
{
    return String(sValue).replace(/[\s]+$/, '');
}


/**
 * Returns the given string, with whitespace characters stripped from both
 * sides
 *
 * @param      string sValue
 * @return     string
 */
var trim = function (sValue)
{
    return String(sValue).replace(/^[\s]+/, '').replace(/[\s]+$/, '');
}


/**
 * ARRAY
 */


/**
 * Find highest value
 *
 * This function returns the numerically highest value in that array.
 *
 * @param      array aInput
 * @return     int
 */
var arrayMax = function (aInput)
{
    // set the first input as maximum value
    var iMax = aInput[0];

    // from the second input to the last one, get the maximum value
    for (var i = 1;
         i < aInput.length;
         i++)
    {
        iMax = Math.max(aInput[i],
                        iMax);
    };

    // return that maximum value
    return iMax;
}


/**
 * Searches the array for a given value and returns the corresponding key if
 * successful
 *
 * Searches haystack for needle and returns the key if it is found in the
 * array, FALSE otherwise.
 *
 * @param      mixed xNeedle
 * @param      array aHaystack
 * @param      bool bStrict
 * @return     bool
 */
var arraySearch = function (xNeedle,
                            aHaystack,
                            bStrict)
{
    // get input array length
    var iLength = aHaystack.length;

    // strict comparison
    if (Boolean(bStrict))
    {
        // look in the input array
        for (var i = 0;
             i < iLength;
             i++)
        {
            // needle not strictly the same, continue
            if (xNeedle !== aHaystack[i])
            {
                continue;
            };

            // value found !
            return i;
        };
    }
    // normal comparison (auto-cast)
    else
    {
        // look in the input array
        for (var i = 0;
             i < iLength;
             i++)
        {
            // needle not the same, continue
            if (xNeedle != aHaystack[i])
            {
                continue;
            };

            // value found !
            return i;
        };
    };

    // value not found
    return false;
}


/**
 * Checks if a value exists in an array
 *
 * Searches haystack for needle and returns TRUE  if it is found in the array,
 * FALSE otherwise.
 *
 * @param      mixed xNeedle
 * @param      array aHaystack
 * @param      bool bStrict
 * @return     bool
 */
var inArray = function (xNeedle,
                        aHaystack,
                        bStrict)
{
    // get input array length
    var iLength = aHaystack.length;

    // strict comparison
    if (Boolean(bStrict))
    {
        // look in the input array
        for (var i = 0;
             i < iLength;
             i++)
        {
            // needle not strictly the same, continue
            if (xNeedle !== aHaystack[i])
            {
                continue;
            };

            // value found !
            return true;
        };
    }
    // normal comparison (auto-cast)
    else
    {
        // look in the input array
        for (var i = 0;
             i < iLength;
             i++)
        {
            // needle not the same, continue
            if (xNeedle != aHaystack[i])
            {
                continue;
            };

            // value found !
            return true;
        };
    };

    // value not found
    return false;
}


/**
 * NATIVE OBJECTS OVERLOAD
 */


/**
 * Returns the scrolling offset of the page
 *
 * This method returns (an array with) the left and top scrolling offset of the
 * page.
 *
 * @link       http://www.quirksmode.org/viewport/compatibility.html
 * @return     array
 */
document.getScroll = function ()
{
    // all except Explorer
    if (self.pageXOffset ||
        self.pageYOffset)
    {
        return [self.pageXOffset,
                self.pageYOffset];
    };

    // Explorer 6 Strict
    var oDE = document.documentElement;
    if (oDE &&
        (oDE.scrollLeft ||
         oDE.scrollTop))
    {
        return [oDE.scrollLeft,
                oDE.scrollTop];
    };

    // all other Explorers
    var oB = document.body;
    if (oB)
    {
        return [oB.scrollLeft,
                oB.scrollTop];
    };
}


/**
 * Returns size of the page
 *
 * This method returns (an array with) the total width and height of the page,
 * inluding offscreen parts.
 *
 * @link       http://www.quirksmode.org/viewport/compatibility.html
 * @return     array
 */
document.getSize = function ()
{
    // all except Explorer
    if (self.innerWidth ||
        self.innerHeight)
    {
        return [self.innerWidth,
                self.innerHeight];
    };

    // Explorer 6 Strict Mode
    var oDE = document.documentElement;
    if (oDE &&
        (oDE.clientWidth ||
         oDE.clientHeight))

    {
        return [document.documentElement.clientWidth,
                document.documentElement.clientHeight];
    };

    // other Explorers
    var oB = document.body;
    if (oB)
    {
        return [oB.clientWidth,
                oB.clientHeight];
    };
}


/**
 * CUSTOM OBJECTS
 */


/**
 * Root object for useful DHTML functions
 */
var dhtml = new function ()
{

    /**
     * Cross browser method for DOM event registration
     *
     * This method allows cross-browser registration of events into DOM nodes.
     * Please pay attention that the "this" keyword isn't fully working when
     * the event is processed in a specific browser (I let you guess which
     * one).
     *
     * @link       http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
     * @access     public
     * @param      object oNode
     * @param      string sType
     * @param      function fHandler
     * @return     void
     */
    this.addEvent = function (oNode,
                              sType,
                              fHandler)
    {
        // W3C event registration model
        if (oNode.addEventListener)
        {
            oNode.addEventListener(sType,
                                   fHandler,
                                   false);
            return;
        };

        // µ$ event registration model
        if (oNode.attachEvent)
        {
            oNode['e'+sType+fHandler] = fHandler;
            oNode[sType+fHandler] = function ()
            {
                oNode['e'+sType+fHandler](window.event);
            };
            oNode.attachEvent('on'+sType,
                              oNode[sType+fHandler]);
            return;
        };
    }


    /**
     * Returns the position of this HTML node (DOM) in the page
     *
     * This method returns the node position from the upper left corner of the
     * document. If this node is nested in an absolutely positionned one, the
     * position will refer from the upper left corner of this parent node.
     *
     * @access     public
     * @param      object oNode
     * @return     array
     */
    this.findPos = function (oNode)
    {
        var iLeft = iTop = 0;
        if (oNode.offsetParent)
        {
            iLeft = oNode.offsetLeft
            iTop = oNode.offsetTop
            while (oNode = oNode.offsetParent)
            {
                iLeft += oNode.offsetLeft
                iTop += oNode.offsetTop
            };
        };
        return [iLeft,
                iTop];
    }


    /**
     * Returns whether the node has the given class name
     *
     * This method returns true if this HTML node (DOM) already has the given
     * class name in its attribute.
     *
     * @access     public
     * @param      object oNode
     * @param      string sClassName
     * @return     bool
     */
    this.hasClassName = function (oNode,
                                  sClassName)
    {
        // get the class names array
        var aClassNames = oNode.className.split(' ');

        // look for the class name in that array
        return inArray(sClassName,
                       aClassNames);
    }


    /**
     * Cross browser method for DOM event unregistration
     *
     * This method allows cross-browser unregistration of events from DOM
     * nodes.
     *
     * @link       http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
     * @access     public
     * @param      object oNode
     * @param      string sType
     * @param      function fCallback
     * @return     void
     */
    this.removeEvent = function (oNode,
                                 sType,
                                 fHandler)
    {
        // W3C event unregistration model
        if (oNode.removeEventListener)
        {
            oNode.removeEventListener(sType,
                                    fHandler,
                                    false);
            return;
        };

        // µ$ event unregistration model
        if (oNode.detachEvent)
        {
            oNode.detachEvent('on'+sType,
                            oNode[sType+fHandler]);
            oNode[sType+fHandler] = null;
            oNode['e'+sType+fHandler] = null;
            return;
        };
    }


    /**
     * Set the specified class name to the given HTML node
     *
     * This method applies the CSS class name to the given HTML node, without
     * class names duplication.
     *
     * @access     public
     * @param      object oNode
     * @param      string sClassName
     * @return     void
     */
    this.setClassName = function (oNode,
                                  sClassName)
    {
        // get the class names array
        var aClassNames = oNode.className.split(' ');

        // class name already exists
        if (inArray(sClassName,
                    aClassNames))
        {
            return;
        };

        // add class name
        aClassNames.push(sClassName);
        oNode.className = aClassNames.join(' ');
    }


    /**
     * If the node has the given class name, removes it
     *
     * This methods remove the given class name from this HTML node (DOM).
     *
     * @access     public
     * @param      object oNode
     * @param      string sClassName
     * @return     void
     */
    this.unsetClassName = function (oNode,
                                    sClassName)
    {
        // get the class names array
        var aClassNames = oNode.className.split(' ');

        // look for the class name in that array, and retreive the key
        var iKey = arraySearch(sClassName,
                               aClassNames);

        // no class name found
        if (false === iKey)
        {
            return;
        };

        // remove the class name
        aClassNames.splice(iKey--,
                           1);
        oNode.className = aClassNames.join(' ');
    }
}


