// these functions are executed without error on all browsers that support JavaScript 1.2
// if a function is not supported, no action is executed, and/or 'false' is returned
//
// design considerations: 
// 1. dynamic function rewriting: the first time a function is executed, the most efficient function for the browser is located;
//    and this function replaces the function being called
// 2. there is no need to call an initializer for this library
// 3. this library contains the lowest level functionality
// 4. compatibility reporting: it is possible to let the library report it when a feature is not supported by the browser

// determine browser characteristics ********************************************

function isDefined(v)
{
    return ((typeof v) != "undefined");
}

function getIEVersion()
{
    var ua = navigator.userAgent;
    var i = ua.indexOf("MSIE ");
    if (i == -1) return 0;
    else return parseFloat(ua.substring(i+5, ua.indexOf(";", i)));
}

function isCSS(){ return (document.body && document.body.style) ? true : false }
function isW3C(){ return (isCSS() && document.getElementById) ? true : false }
function isOpera(){ return (navigator.userAgent.indexOf("Opera") != -1) ? true : false }
function isIE4(){ return (isCSS() && document.all) ? true : false }
function isIE55(){ return (isIE4() && (getIEVersion() > 5.5) && !isOpera()) ? true : false }
function isNN4(){ return (document.layers) ? true : false }
function isGecko(){ return (!isNN4() && (navigator.userAgent.indexOf("Gecko") != -1)) ? true : false }
function isIE6CSS(){ return (document.compatMode && (document.compatMode.indexOf("CSS1") >= 0)) ? true : false }

var gfLogDebug = 0;
var gfLogWarn = 1;
var gfLogNone = 2;
var gfLogLevel = gfLogNone;

//  ****************************************************************************************

function gfSupportsFunctionRewrite()
{
    gfSupportsFunctionRewrite = function(){ return true; }
    return false;
}
function gfAssert(assertion, message)
{
    if (gfLogLevel > gfLogWarn) return;
    if (!assertion) alert(message);
}
function gfPerformBasicChecks()
{
    var s = "Basic Checks:\n\n";
    
    s += "CSS: " + isCSS();
    s += " | W3C: " + isW3C();
    s += " | Opera: " + isOpera();
    s += " | IE4: " + isIE4();
    s += " | IE55: " + isIE55();
    s += " | NN4: " + isNN4();
    s += " | Gecko: " + isGecko();
    s += " | IE6CSS: " + isIE6CSS();
    s += "\nfunction rewrite: " + (gfSupportsFunctionRewrite() || gfSupportsFunctionRewrite());
    
    alert(s);
}

// this function reports unsupported functions
var gfUnsupportedFunctions = new Object();
function gfUnsupported(functionName)
{
    if (gfLogLevel > gfLogDebug) return;
    
    // only mention once that a function is unsupported
    if (!gfUnsupportedFunctions[functionName])
    {
        gfUnsupportedFunctions[functionName] = true;
        alert("unsupported function: " + functionName);

        // is this the first time this function is called?
        if (!gfUnsupportedFunctions["gfUnsupported"])
        {
            gfUnsupportedFunctions["gfUnsupported"] = true;
            gfPerformBasicChecks();
        }
    }
}

// returns the element that has id
// supported: ie4+, ff1+, nn6+, (at least) opera6+
function gfGet(id)
{
    if (document.getElementById) gfGet = function(id){ return document.getElementById(id); }
    else if (isIE4()) gfGet = function(id){ return document.all[id]; }
    else return gfUnsupported("gfGet");

    return gfGet(id);
}

// cookies ****************************************************************************************

// sets a local variable (cookie) to be retrieved in a later browser session
// if expiryDate is undefined, the cookie expires never (in 200 years)
// 'value' is escaped, 'name' isn't
// supported: ie4+, ff1+, nn6+, (at least) opera6+
function gfSetCookie(name, value, expiryDate)
{
    gfAssert(name, "gfSetCookie: name");
    gfAssert(value, "gfSetCookie: value");
    if (!expiryDate)
    {
        expiryDate = new Date();
        expiryDate.setUTCFullYear(expiryDate.getUTCFullYear() + 200);
    }
    document.cookie = name+"="+escape(value)+";expires="+expiryDate.toGMTString();
}
// returns the cookie's value, or 'false' if none could be found
// supported: ie4+, ff1+, nn6+, (at least) opera6+
function gfGetCookie(name)
{
    var c = document.cookie;
    var pos = c.indexOf(name);
    if (pos == -1) return false;
    var start = pos+name.length+1;
    var end = c.indexOf(";", start);
    if (end == -1) end = c.length;
    return unescape(c.substring(start, end));
}

// inline position ****************************************************************************************

// returns the inline element's (border's) left position
function gfGetInlineLeft(element)
{
    if (isDefined(element.offsetLeft)) gfGetInlineLeft = 
        function(element)
        { 
            var left = element.offsetLeft;
            var e = element.offsetParent;
            while (e)
            {
                left += e.offsetLeft;
                e = e.offsetParent;
            }
            return left;
        }
    else return gfUnsupported("gfGetInlineLeft");
    
    return gfGetInlineLeft(element);
}

// returns the inline element's (border's) top position
function gfGetInlineTop(element)
{
    if (isDefined(element.offsetTop)) gfGetInlineTop = 
        function(element)
        { 
            var top = element.offsetTop;
            var e = element.offsetParent;
            while (e)
            {
                top += e.offsetTop;
                e = e.offsetParent;
            }
            return top;
        }
    else return gfUnsupported("gfGetInlineTop");
    
    return gfGetInlineTop(element);
}

// box position, width and height ****************************************************************************************

// positions element by setting it's border's left and top
function gfSetBorderPos(element, left, top)
{
    if (element.style) gfSetBorderPos = 
        function(element, left, top)
        {
            element.style.left = left;
            element.style.top = top;
        }
    else return gfUnsupported("gfSetPos");
    
    return gfSetBorderPos(element, left, top);
}

// returns the positioned element's (border's) left position
function gfGetPositionedLeft(element)
{
    if (document.defaultView) gfGetPositionedLeft = 
        function(element)
        {
            return parseInt(document.defaultView.getComputedStyle(element, "").getPropertyValue("left"));
        }
    else if (element.currentStyle) gfGetPositionedLeft = 
        function(element)
        {
            return parseInt(element.currentStyle.left);
        }
    else if (element.style) gfGetPositionedLeft = 
        function(element)
        {
            return parseInt(element.style.left);
        }
    else return gfUnsupported("gfGetPositionedLeft");
    
    return gfGetPositionedLeft(element);
}

// returns the positioned element's (border's) top position
function gfGetPositionedTop(element)
{
    if (document.defaultView) gfGetPositionedTop = 
        function(element)
        {
            return parseInt(document.defaultView.getComputedStyle(element, "").getPropertyValue("top"));
        }
    else if (element.currentStyle) gfGetPositionedTop = 
        function(element)
        {
            return parseInt(element.currentStyle.top);
        }
    else if (element.style) gfGetPositionedTop = 
        function(element)
        {
            return parseInt(element.style.top);
        }
    else return gfUnsupported("gfGetPositionedTop");
    
    return gfGetPositionedTop(element);
}

// returns the width of the element's box including padding, border, and margin
// supported: ie4+, nn6+, ff1+, (at least) opera6+
// reverts to gfGetBorderSpaceWidth for unsupported browsers
function gfGetBoxWidth(element)
{
    if (isGecko()) gfGetBoxWidth = 
        function(element){ return element.offsetWidth; }
    else if (isIE4() || isOpera()) gfGetBoxWidth = 
        function(element)
        { 
            var width = element.offsetWidth; 
            if (element.style.marginLeft) width += parseInt(element.style.marginLeft) + parseInt(element.style.marginRight); 
            return width;
        }
    else return gfGetBorderSpaceWidth(element);
    
    return gfGetBoxWidth(element);
}
// returns the width of the element including padding and border
// supported: ie4+
function gfGetBorderSpaceWidth(element)
{
    if (isIE4() || isOpera()) gfGetBorderSpaceWidth = 
        function(element){ return element.offsetWidth; }
    else if (isGecko()) gfGetBorderSpaceWidth = 
        function(element){ return element.offsetWidth - parseInt(element.style.marginLeft) - parseInt(element.style.marginRight); }
    else
    {
        // just trying
        if (element.offsetWidth) return element.offsetWidth;
        else if (getComputedStyle)
        {
               var cs = getComputedStyle(element, "");
            return parseInt(cs.getPropertyValue("width"));
        }
        // i have really no clue
        return gfUnsupported("gfGetBorderSpaceWidth");
    }
    
    return gfGetBorderSpaceWidth(element);
}
// returns the width of the element including padding
// supported: ie4+, nn6+, ff1+, (at least) opera6+
// reverts to gfGetBorderSpaceWidth for unsupported browsers
function gfGetPaddingSpaceWidth(element)
{
    if (isGecko() || isIE4() || isOpera()) gfGetPaddingSpaceWidth = 
        function(element)
        {
            return (gfGetBorderSpaceWidth(element) - parseInt(element.style.borderLeftWidth) - parseInt(element.style.borderRightWidth));
        }
    else return gfGetBorderSpaceWidth(element);
    
    return gfGetPaddingSpaceWidth(element);
}
// returns the width of the element's content ex(!)cluding padding, border, and margin
// supported: ie4+, nn6+, ff1+, (at least) opera6+
// reverts to gfGetBorderSpaceWidth for unsupported browsers
function gfGetElementWidth(element)
{
    if (isGecko() || isIE4() || isOpera()) gfGetElementWidth = 
        function(element)
        {
            return (gfGetBorderSpaceWidth(element) 
                - parseInt(element.style.borderLeftWidth) - parseInt(element.style.borderRightWidth)
                - parseInt(element.style.paddingLeft) - parseInt(element.style.paddingRight));
        }
    else return gfGetBorderSpaceWidth(element);
    
    return gfGetElementWidth(element);
}

// returns the height of the element's box including padding, border, and margin
// supported: ie4+, nn6+, ff1+, (at least) opera6+
// reverts to gfGetBorderSpaceHeight for unsupported browsers
function gfGetBoxHeight(element)
{
    if (isGecko()) gfGetBoxHeight = 
        function(element){ return element.offsetHeight; }
    else if (isIE4() || isOpera()) gfGetBoxHeight = 
        function(element)
        { 
            var width = element.offsetHeight; 
            if (element.style.marginTop) width += parseInt(element.style.marginTop) + parseInt(element.style.marginBottom); 
            return width;
        }
    else return gfGetBorderSpaceHeight(element);
    
    return gfGetBoxHeight(element);
}
// returns the height of the element including padding and border
// supported: ie4+
function gfGetBorderSpaceHeight(element)
{
    if (isIE4() || isOpera()) gfGetBorderSpaceHeight = 
        function(element){ return element.offsetHeight; }
    else if (isGecko()) gfGetBorderSpaceHeight = 
        function(element){ return element.offsetHeight - parseInt(element.style.marginLeft) - parseInt(element.style.marginRight); }
    else
    {
        // just trying
        if (element.offsetHeight) return element.offsetHeight;
        else if (getComputedStyle)
        {
               var cs = getComputedStyle(element, "");
            return parseInt(cs.getPropertyValue("height"));
        }
        // i have really no clue
        return gfUnsupported("gfGetBorderSpaceHeight");
    }
    
    return gfGetBorderSpaceHeight(element);
}
// returns the height of the element including padding
// supported: ie4+, nn6+, ff1+, (at least) opera6+
// reverts to gfGetBorderSpaceHeight for unsupported browsers
function gfGetPaddingSpaceHeight(element)
{
    if (isGecko() || isIE4() || isOpera()) gfGetPaddingSpaceHeight = 
        function(element)
        {
            return (gfGetBorderSpaceHeight(element) - parseInt(element.style.borderLeftHeight) - parseInt(element.style.borderRightHeight));
        }
    else return gfGetBorderSpaceHeight(element);
    
    return gfGetPaddingSpaceHeight(element);
}
// returns the height of the element's content ex(!)cluding padding, border, and margin
// supported: ie4+, nn6+, ff1+, (at least) opera6+
// reverts to gfGetBorderSpaceHeight for unsupported browsers
function gfGetElementHeight(element)
{
    if (isGecko() || isIE4() || isOpera()) gfGetElementHeight = 
        function(element)
        {
            return (gfGetBorderSpaceHeight(element) 
                - parseInt(element.style.borderLeftHeight) - parseInt(element.style.borderRightHeight)
                - parseInt(element.style.paddingLeft) - parseInt(element.style.paddingRight));
        }
    else return gfGetBorderSpaceHeight(element);
    
    return gfGetElementHeight(element);
}

// element characteristics ****************************************************************************************

// sets the visibility attribute to 'set'
// set: boolean
function gfSetVisible(element, set)
{
    if (element.style && isDefined(element.style.visibility)) gfSetVisible = 
        function(element, set){ element.style.visibility = (set ? 'visible' : 'hidden'); }
    else
        return gfUnsupported("gfSetVisible");

    gfSetVisible(element, set);
}
function gfIsVisible(element)
{
    if (element.style && isDefined(element.style.visibility)) gfIsVisible = 
        function(element){ return (element.style.visibility == 'visible'); }
    else
        return gfUnsupported("gfIsVisible");

    return gfIsVisible(element);
}
// Opacity: in [0..1]
function gfSetOpacity(element, opacity)
{
    if (isGecko()) gfSetOpacity = 
        function(element, opacity)
        { 
            if ((opacity > 0) != gfIsVisible(element)) gfSetVisible(element, (opacity > 0));
            if (opacity > 0.99) opacity = 0.99;
            element.style.MozOpacity = opacity;
        }
    else if (isIE4()) gfSetOpacity = 
        function(element, opacity)
        { 
            if ((opacity > 0) != gfIsVisible(element)) gfSetVisible(element, (opacity > 0));
            element.style.filter = "alpha(opacity="+opacity*100+")";
        }
    else return gfUnsupported("gfSetOpacity");
    
    gfSetOpacity(element, opacity);
}
function gfSetColor(element, color)
{
    if (element.style) gfSetColor = 
        function(element, color){ element.style.color = color; }
    else
        return gfUnsupported("gfSetColor");

    gfSetColor(element, color);
}

// node functions ****************************************************************************************

// clone node from other document
// see also: http://www.quirksmode.org/bugreports/archives/2004/11/Cloning_nodes_from_an_iframe.html
function gfImportNode(node, recurse)
{
    if (document.importNode)
    {
        gfImportNode = function(node, recurse){ return document.importNode(node, recurse); }
    }
    else if (isW3C() || isIE4())
    {
        gfImportNode = function(oNode, bImportChildren)
        {
            var oNew;

            if(oNode.nodeType == 1){
                oNew = document.createElement(oNode.nodeName);
                for(var i = 0; i < oNode.attributes.length; i++){
                    oNew.setAttribute(oNode.attributes[i].name, oNode.attributes[i].value);
                }
                oNew.style.cssText = oNode.style.cssText;
            } else if(oNode.nodeType == 3){
                oNew = document.createTextNode(oNode.nodeValue);
            }
    
            if(bImportChildren && oNode.hasChildNodes()){
                for(var oChild = oNode.firstChild; oChild; oChild = oChild.nextSibling){
                    oNew.appendChild(gfImportNode(oChild, true));
                }
            }
            return oNew;
        }
    }
    else
        return gfUnsupported("gfImportNode");
    
    return gfImportNode(node, recurse);
}
// returns the document from an iframe
function gfGetIFrameDocument(iframe)
{
    gfAssert(iframe, "gfGetIFrameDocument: iframe undefined");
    if (iframe.contentDocument) return iframe.contentDocument;
    else if (iframe.contentWindow) return iframe.contentWindow.document;
    else return gfUnsupported("gfGetIFrameDocument");
}
function gfCreateElement(type, attributes)
{
    var element = document.createElement(type);
    if (attributes) gfSetAttributes(element, attributes);
    return element;
}
function gfGetFirstChildElement(element, tagName)
{
    var elements = element.getElementsByTagName(tagName);
    if (elements.length == 0) return 0;
    if (elements[0].parentNode != element) return 0;
    else return elements[0];
}
function gfGetFirstChildElementText(element, tagName)
{
    var element = gfGetFirstChildElement(element, tagName);
    if (!element) return "";
    else return element.firstChild.nodeValue;
}
function gfGetChildElements(element, tagName)
{
    if (!tagName) return element.childNodes;
    else return element.getElementsByTagName(tagName);
}
function gfGetFirstSiblingElement(element, tagName)
{
    var parent = element.parentNode;
    return gfGetFirstChildElement(parent, tagName);
}
function gfGetFirstSiblingElementText(element, tagName)
{
    var element = gfGetFirstSiblingElement(element, tagName);
    if (!element) return "";
    else return element.firstChild.nodeValue;
}
// ie6, nn6
function gfGetAttribute(element, name)
{
    return element.getAttributeNode(name);
}
// ie6, nn6
function gfGetAttributeText(element, name)
{
    return element.getAttributeNode(name).nodeValue;
}
function gfAddChild(parent, element)
{
    parent.appendChild(element);
}
function gfAddChildText(parent, text)
{
    parent.appendChild(document.createTextNode(text));
}
function gfCreateAndAdd(parent, element, attributes)
{
    var element = gfCreateElement(element);
    gfAddChild(parent, element);
    if (attributes) gfSetAttributes(element, attributes);
	
	return element;
}
function gfRemoveChildren(parent)
{
	var childNodes = parent.childNodes;
	for (i=0; i<childNodes.length; i++) parent.removeChild(childNodes[i]);
}

// events ****************************************************************************************

function gfGetMouseX(evt)
{
	if (evt && isDefined(evt.screenX) ) gfGetMouseX = function(evt){ return evt.screenX; } // Opera
	else if (evt && isDefined(evt.offsetX)) gfGetMouseX = function(evt){ return evt.offsetX + document.body.scrollLeft; } // ?
	else if (evt && isDefined(evt.pageX)) gfGetMouseX = function(evt){ return evt.pageX; } // mozilla
	else if (window.event) gfGetMouseX = function(evt){ return window.event.clientX; } // ie
	else return gfUnsupported("gfGetMouseX");
	
	return gfGetMouseX(evt);
}

function gfGetMouseY(evt)
{
	if (evt && isDefined(evt.screenY)) gfGetMouseY = function(evt){ return evt.screenY; }
	else if (evt && isDefined(evt.offsetY)) gfGetMouseY = function(evt){ return evt.offsetY + document.body.scrollTop; }
	else if (evt && isDefined(evt.pageY)) gfGetMouseY = function(evt){ return evt.pageY; }
	else if (window.event) gfGetMouseY = function(evt){ return window.event.clientY; }
	else return gfUnsupported("gfGetMouseY");
	
	return gfGetMouseY(evt);
}

