/**
 * Filename    : element.js
 * Author      : Robert Cerny
 * Created     : 24.01.2004
 * Last Change : 22.06.2005
 *
 * Basic functions for element manipulation. Holds also some
 * functions for other objects, which are related to element, but do
 * not have enough weight yet to put them in a seperate file.
 *
 * Depends on  : trans.js, misc.js
 *
 * Copyright 2004 Robert Cerny
 */

/**
 * Checks wether object is a Node. Returns true if o is a Node,
 * otherwise false.
 *
 * o - the object to check.
 */
function isNode(o) {
    if (o != null
        && o.nodeType)
        return true;

    return false;
}

/**
 * Checks wether object is a Element. Returns true if o is a Element,
 * otherwise false.
 *
 * o - the object to check.
 */
function isElement(o) {
    if (isNode(o)
        && o.nodeType == 1)
        return true;

    return false;
}

/**
 * Checks wether object is a TextNode. Returns true if o is a
 * TextNode, otherwise false.
 *
 * o - the object to check.
 */
function isTextNode(o) {
    if (isNode(o)
        && o.nodeType == 3)
        return true;

    return false;
}

/**
 * Checks wether object is a Div. Returns true if o is a Div,
 * otherwise false.
 *
 * o - the object to check.
 */
function isDiv(o) {
    if (isElement(o)
        && o.tagName == "DIV")
        return true;

    return false;
}

/**
 * Checks wether object is a span. Returns true if o is a span,
 * otherwise false.
 *
 * o - the object to check.
 */
function isSpan(o) {
    if (isElement(o)
        && o.tagName == "SPAN")
        return true;

    return false;
}

/**
 * Checks wether object is a Paragraph. Returns true if o is a
 * Paragraph, otherwise false.
 *
 * o - the object to check.
 */
function isParagraph(o) {
    if (isElement(o)
        && o.tagName == "P")
        return true;

    return false;
}

/**
 * Returns true if o is html headline element.
 *
 * o - the object to check
 */
function isHeadline1(o) {
    if (isElement(o)
        && o.tagName == "H1")
        return true;

    return false;
}


/**
 * Checks wether object is a Link. Returns true if o is a Link,
 * otherwise false.
 *
 * o - the object to check.
 */
function isLink(o) {
    if (isElement(o)
        && o.tagName == "A")
        return true;

    return false;
}

/**
 * Checks wether object is a input field. Returns true if o is a input
 * field, otherwise false.
 *
 * o - the object to check.
 */
function isInputField(o) {
    if (isElement(o) && o.value != null)
        return true;

    return false;
}

/**
 * Adds a String pClass to the end of the class Attribute of element.
 *
 * element - the element to add the class to
 * _class - string containing the name of the class
 */
function addClass(element, _class, append) {
    if (!isElement(element) || _class == null)
        return;
    if (append == null)
        append = true;

    var classA = element.getAttribute(trans("class"));
    if (classA == null || classA.length == 0) {
        element.setAttribute(trans("class"), _class);
    } else if (classA.indexOf(_class) < 0) {
        if (append)
            newClass = classA + " " + _class;
        else
            newClass = _class + " " + classA;
        element.setAttribute(trans("class"), newClass);
    }
}

/**
 * Returns class of element.
 *
 * Has underline in name because otherwise a redeclaration error
 * occurs.  getClass seems to be defined already. Maybe similar to
 * Java.
 *
 * element - element to return class of.
 */
function _getClass(element) {
    if (!isElement(element))
        return;

    var classA = element.getAttribute(trans("class"));
    return classA;
}

/**
 * Replace a String classOld by a String classNew
 * in the class Attribute of element.
 *
 * element - the element to replace the class in
 * classOld - the name of the class to replace
 * classNew - the name of the replacing class
 * force - add classNew, if classOld is not occuring
 */
function replaceClass(element, classOld, classNew, force) {
    if (!isElement(element))
        return;

    var classA = element.getAttribute(trans("class"));

    if (!classA || classA.length == 0) {
        element.setAttribute(trans("class"), classNew);
    } else {
        // this line makes troubles in batik
        element.setAttribute(trans("class"), classA.replace(classOld, classNew));
        classA = element.getAttribute("class");
        if (force && classA.indexOf(classNew) < 0) {
            addClass(element, classNew);
        }
    }
}

/**
 * Deletes the String _class from the class attribute of element.
 *
 * element - the element to delete the class from
 * _class - name of the class to delete
 */
function deleteClass(element, _class) {
    var regexp = "/(\\s|^)" + _class + "(\\s|$)/g";
    var classString = _getClass(element);
    if (classString == null || classString.length == 0)
        return;

    classString = classString.replace(new RegExp(eval(regexp)), " ");
    // no whitespace in the beginning
    classString = classString.replace(/^\s+/, "");
    // no whitespace in the end
    classString = classString.replace(/\s+$/, "");
    // not more than one whitespace in the middle
    classString = classString.replace(/\s{2,}/g, " ");
    element.setAttribute(trans("class"), classString);
}

/**
 * Sets the id of an element independent of browser.
 *
 * element - the element to set the style for
 * id - the id for the element
 */
function setId(element, _id) {
    if (!isElement(element))
        return;

    if (is_ie)
        element.id = _id;
    else
        element.setAttribute("id", _id);

}

/**
 * Sets the css-style of an element independent of browser.
 *
 * element - the element to set the style for
 * style - the style for the element
 */
function setStyle(element, _style) {
    if (!isElement(element))
        return;

    if (is_ie)
        element.style.cssText = _style;
    else
        element.setAttribute("style", _style);

}

/**
 * Sets the css-style of an element independent of browser.
 *
 * element - the element to set the style for
 */
function getStyle(element) {
    if (is_ie)
        return element.style.cssText;
    else
        return element.getAttribute("style");
}

/**
 * Adds a css expression to element.
 * e.g.style = "top:29px;"
 *
 * element - element to add the style to
 * style - style to add to the element
 * append - if true, style is appended, otherwise it is put in front
 */
function addStyle(element, style, append) {
    var currentStyle = getStyle(element);
    if (append)
        currentStyle = currentStyle + " " + style;
    else
        currentStyle = style + " " + currentStyle;

    setStyle(element, currentStyle);

    return;
}

/**
 * Deletes a style from an element.
 *
 * WARNING: Styles that are specified in the HTML Document are
 * sometimes modified (uppercased and ";" removed).
 */
function deleteStyle(element, style) {
    var currentStyle = getStyle(element).toLowerCase();

    if (currentStyle.indexOf(style) >= 0)
        setStyle(element, currentStyle.replace(style, ""));
}

/**
 * Removes all style from an element.
 *
 * element - the element to remove all style from
 */
function removeAllStyle(element) {
    if (!isElement(element))
        return;

    if (is_ie)
        element.style.cssText = "";
    else
        element.removeAttribute("style");
}


/**
 * Hides an Element.
 *
 * element - the element to hide
 */
function hide(element) {
    if (!isElement(element))
        return;

    addClass(element, "hidden");
}

/**
 * Shows an Element.
 *
 * element - the element to show
 */
function show(element) {
    if (!isElement(element))
        return;

    deleteClass(element, "hidden");

}

/**
 * Moves element element before the element anchor.
 *
 * element - element to move before anchor
 * anchor - element to move element element before
 */
function moveBefore(element, anchor) {
    if (!isElement(element) || !isElement(anchor))
        return;

    if (element.parentNode != anchor.parentNode)
        return;

    var temp = anchor.parentNode.removeChild(element);
    anchor.parentNode.insertBefore(temp, anchor);
}

/**
 * Returns the aboslute X Coordinate of the element.
 *
 * element - element to get X Coordinate of
 */
function getAbsoluteX(element) {
    var x = element.offsetLeft;
    var parent = element.offsetParent;
    while (parent != null) {
        x += parent.offsetLeft;
        parent = parent.offsetParent;
    }
    return x;
}

/**
 * Returns the aboslute Y Coordinate of the element.
 *
 * element - element to get Y Coordinate of
 */
function getAbsoluteY(element) {
    var y = element.offsetTop;
    var parent = element.offsetParent;
    while (parent != null) {
        y += parent.offsetTop;
        parent = parent.offsetParent;
    }
    return y;
}

/**
 * Returns the nth (next or previous) sibling that matches filter.
 *
 * _node - node to return next sibling of
 * filter - the filter the element should match
 * _forward - if true, the search is performed forward in the tree
 * nth - number of siblings to fetch
 */
function getNthSibling(_node, filter, next, nth) {
    if (!isNode(_node))
        return;

    if (nth == null || nth < 1)
        nth = 1;

    if (next == true)
        direction = "nextSibling";
    else
        direction = "previousSibling";

    var sibling = _node;
    var matches = 0;
    while (sibling != null && matches < nth) {
        sibling = sibling[direction];
        if (filter(sibling) == true) {
            matches++;
        }
    }
    return sibling;
}

/**
 * Returns the closest sibling that matches filter.
 *
 * _node - the node to return the closest sibling for
 * filter - the filter that must match
 * next - direction; true - forward, false - backward.
 */
function getClosestSibling(_node, filter, next) {
    return getNthSibling(_node, filter, next, 1);
}

/**
 * Returns the first child of an element that passes through filter.
 *
 * _element - element to return first child of
 * filter - the filter that must match
 */
function getFirstChild(_element, filter) {
    if (!isElement(_element))
        return;

    var first = _element.firstChild;
    if (filter(first))
        return first;
    else
        return getClosestSibling(first, filter, true);
}

/**
 * Copy an element to a destination.
 *
 * element - element to copy
 * destinaton - destnination element
 * anchor - if not null element is inserted before anchor
 */
function copy(element, destination, anchor) {
    if (!isNode(anchor))
        anchor = null;
    var clone = element.cloneNode(true);
    destination.insertBefore(clone, anchor);
    return clone;
}

/**
 * Counts the childnodes in an element, that pass through filter
 * and returns the number.
 *
 * element - the element to count
 * filter - the filter for the elements to be counted
 */
function countNodes(element, filter) {
    if (!isElement(element))
        return;

    if (filter == null)
        filter = isNode;

    var count = 0;
    var children = element.childNodes;
    for (var i = 0; i < children.length; i++) {
        if (filter(children[i]))
            count++;
    }

    return count;
}

/**
 * Returns true, if child is a grandchild of grandparent.
 * False, otherwise.
 * child - child to check
 * grandparent - possible grandparent
 */
function isGrandchildOf(child, grandparent) {
    if (!isElement(child) || !isElement(grandparent))
        return false;

    var climber = child;
    while (climber != null && climber != grandparent)
        climber = climber.parentNode;

    if (climber == grandparent)
        return true;

    return false;
}

/**
 * Sets the text of an element.
 *
 * element - element to set the text of
 * text - the text to set in element
 */
function setText(element, text) {
    if (!isElement(element) || text == null)
        return;

    var textNode = getFirstChild(element, isTextNode);
    if (textNode == null) {
        textNode = document.createTextNode(text);
        element.appendChild(textNode);
    } else {
        textNode.data = text;
    }
}

/**
 * Returns the first text node of an element. Or "", if there is none.
 *
 * element - the element to return the text node of.
 */
function getText(element) {
    if (!isElement(element))
        return;
    var textNode = getFirstChild(element, isTextNode);
    if (textNode == null || textNode.data == null)
        return "";
    else
        return textNode.data;
}

/**
 * Delete all children of an element.
 *
 * element - the element to remove the children from
 * filter - the function to filter the children
 */
function deleteChildren(element, filter) {
    if (element == null || filter == null)
        return;
    var children = element.childNodes;
    for (var i = children.length; i >= 0; i--) {
        if (filter(children[i]))
            element.removeChild(children[i]);
    }
}

/**
 * Applies a function to all nodes in a tree.
 * Returns the concatenation of all results.
 *
 * node - Root of the tree
 * _function - the function to apply to nodes
 * filter - Filter to which nodes the function should be applied
 * fromLeavesToRoot - if true, the function is applied first to the children
 *                             and then to the parent
 *                  - if false, otherwise
 */
function apply(node, _function, filter, fromLeavesToRoot) {
    if (!isNode(node))
        return 0;

    var result = null;

    if (!fromLeavesToRoot && filter(node))
        result += _function(node);

    var children = node.childNodes;
    for(var i = 0; i < children.length; i++) {
        var nextChild = children.item(i);
        result += apply(nextChild, _function, filter, fromLeavesToRoot);
    }

    if (fromLeavesToRoot && filter(node))
        result += _function(node);

    return result;
}

/**
 * Createsa element in the document and returns it.  Has one advantage
 * over document.createElement: text can be in the element.  Name must
 * start with underline because of obfuscation.
 *
 * tagName - the tagName of the Element to create
 * text - the text of the Element to create
 * attributes - unlimited number of Strings of the form "attributeName=attributeValue"
 */
function _createElement(tagName,text) {
    var element = document.createElement(tagName);
    setText(element, text);

    for (var i = 2; i < arguments.length; ++i) {
        var next = arguments[i];
        var position = next.indexOf("=");
        var attributeName = "";
        var attributeValue = "";
        if (position > 0) {
            attributeName = next.substring(0, position);
            attributeValue = next.substring(position + 1, next.length);
        }
        if (attributeName == "id") {
            setId(element, attributeValue);
        } else if (attributeName == "style") {
            setStyle(element, attributeValue);
        } else {
            element.setAttribute(trans(attributeName), attributeValue);
        }
    }
    return element;
}

/**
 * Returns the html code of the Element element.
 *
 * element - the Element to return the html code of
 * elementNameFilter - function to filter out elments by name
 * attributeNameFilter - function to filter out attributes by name
 * attributeValueFilter - function to filter out attributes by value
 * indentLevel - the level of indentation to use for element,
 *               if minus no indentation done
 */
function getHtmlCode(element,
                     elementNameFilter,
                     attributeNameFilter,
                     attributeValueFilter,
                     indentLevel) {
    if (!elementNameFilter(element.tagName.toLowerCase()))
        return "";

    var indentString = "";
    for (var i = 0; i < indentLevel; i++) {
        indentString += "  ";
    }

    var code = "\n" + indentString  + "<" + element.tagName.toLowerCase();
    var _attributes = element.attributes;
    for (var i = 0; i < _attributes.length; i++) {
        var attributeName = _attributes[i].name.toLowerCase();
        var attributeValue = _attributes[i].value;

        if (attributeNameFilter(attributeName) && attributeValueFilter(attributeValue)) {
            code += ' ' + attributeName + '="' + attributeValue + '"';
        }
    }

    var children = element.childNodes;

    if (children.length == 0)
        code += "></" + element.tagName.toLowerCase() + ">";
    else
        code += ">";

    var hasElementChildren = false;
    for (var i = 0; i < children.length; i++) {
        if (children[i].nodeType == 1) {
            code += getHtmlCode(children[i],
                                elementNameFilter,
                                attributeNameFilter,
                                attributeValueFilter,
                                (indentLevel >= 0 ? indentLevel + 1 : -1));
            hasElementChildren = true;
        }
        if (children[i].nodeType == 3) {
            if (!isBlankString(children[i].data)) {
                code += children[i].data;
            }
        }
    }

    var leadString = "";
    if (hasElementChildren) {
        leadString = "\n" + indentString;
    }

    if (children.length > 0)
        code += leadString + "</" + element.tagName.toLowerCase() + ">";

    return code;
}

/**
 * Returns true, if  argument is null. False, otherwise.
 *
 * value - attribute value to check
 */
function isNotNull(value) {
    if (value != null && value != "" && value != "null")
        return true;
    return false;
}

function isNull(value) {
    return !isNotNull(value);
}

/**
 * Returns true, if  string is non empty. False, otherwise.
 *
 * value - attribute value to check
 */
function isEmptyString(value) {
    if (isNull(value) || trim(value).length == 0)
        return true;
    return false;
}

/**
 * Gets first ancestor which matches filter.
 *
 * node - node to get ancestor from
 * filter - filter to apply
 */
function getAncestor(node, filter) {
    if (!isNode(node))
        return null;
    if (filter == null)
        filter = isNode;
    if (node.parentNode == null)
        return null;
    if (filter(node.parentNode) == true)
        return node.parentNode;
    else
        return getAncestor(node.parentNode, filter);
}

/**
 * Replace node by byNode.
 *
 * node - the node to replace
 * byNode - the node by which to replace
 */
function _replace(node, byNode) {
    if (isNode(node) && isNode(byNode)) {
        node.parentNode.insertBefore(byNode, node);
        node.parentNode.removeChild(node);
    }
}


/**
 * Shows an element and it's ancestors.
 *
 * element - the element to show
 */
function showAncestors(element) {
    if (!isElement(element))
        return;

    showAncestors(element.parentNode);
    deleteClass(element, "hidden");
}

/**
 * Returns true if element is hidden. False, otherwise.
 *
 * element - element to check
 */
function isHidden(element) {
    if (!isElement(element))
        return;

    var _class = element.getAttribute(trans("class"));

    if (_class != null && _class.length > 0 && _class.indexOf("hidden") >= 0)
        return true;

    return false;
}

/**
 * Applies _function to element with _id.
 *
 * id - id of the element to aplly function to
 * _function - function to apply to element with id
 */
function _do(id, _function) {
    var element = document.getElementById(id);

    if (isElement(element)) {
        _function(element);
    }
}

/**
 * Toggles Visibility of element.
 *
 * element - the element to toggle visibility of
 */
function showHide(element) {
    if (isElement(element)) {
        if (isHidden(element)) {
            show(element);
        } else {
            hide(element);
        }
    }
}

/**
 * Moves element element before the element anchor.
 *
 * element - element to move before anchor
 * anchor - element to move element element before
 */
function moveBefore(element, anchor) {
    if (!isElement(element) || !isElement(anchor))
        return;

    if (element.parentNode != anchor.parentNode)
        return;

    var temp = anchor.parentNode.removeChild(element);
    anchor.parentNode.insertBefore(temp, anchor);
}
