// ARRAY FUNTIONS

/**
 * Test is some array member is equal (===) to tested value.
 *
 * @param arr array to test
 * @param value value to test
 * @type Boolean
 */
function inArray(arr, value) {
	for (var i = 0; i < arr.length; i++) {
		if (arr[i] === value) {
			return true;
		}
	}
	return false;
}

// STRING FUNCTIONS


/**
 * Get relative URL.
 *
 * @param url URL string
 * @return relative part of the URL
 * @type String
 */ 
function getRelativeUrl(url, baseUrl) {
    if (baseUrl === undefined) {
        var i = url.lastIndexOf("/");
        if (i != -1) {
            return url.substring(i + 1);
        } else {
            return url;
        }
    } else {
        if (url.substring(0, baseUrl.length) === baseUrl) {
            return url.substring(baseUrl.length);
        } else {
            return undefined;
        }
    }
}

/**
 * Fix file scheme URLs generated by IE.
 *
 * @param url file scheme URL to fix
 */
/* 
function fixUrl(url) {
  var _url;
  if (pathFileUrl.test(url)) {
    _url = url.replace(pathFileUrl, "file:///$1/$2");
  } else {
    _url = url;
  }
  return _url.replace("\\", "/");

}
var pathFileUrl = new RegExp("^file://([a-zA-Z]:)[\\\\/](.+)$");
*/

/**
 * Get string value for array where list is in prose.
 *
 * @param array array to get the string value for
 * @type String
 * @return array string value in prose
 */
function toProseString(array) {
	switch (array.length) {
	case 0:
		return "";
	case 1:
		return array[0];
	case 2:
		return array[0] + " and " + array[1];
	default:
		var t = "";
		for (var i = 0; i < array.length - 1; i++) {
			t += array[i] + ", ";
		}
		return t + "and " + array[array.length - 1];
	}
}


// EVENT FUNCTIONS

/**
 * Stop event.
 *
 * @param event event to stop
 */
function stopEvent(event) {
    if (event.stopPropagation) {
        event.stopPropagation();
    } else {
        event.cancelBubble = true;
    }
    if (event.preventDefault) {
        event.preventDefault();
    } else {
        event.returnValue = false;
    }
}

/**
 * Return target node of an event.
 *
 * @return target node of the event
 * @type Node
 */
function getTargetNode(event) {
	if (event.target) {
		return event.target;
	} else if (event.srcElement) {
		return event.srcElement;	
	} else {
		return null;	
	}
}

/**
 * Attach event listener to a DOM node.
 *
 * @param node DOM node to attach lister to
 * @param type Type of event to catch
 * @param func Function to act as the handler 
 */
function attachEventListener(node, type, func) {
    if (node.addEventListener) {
        node.addEventListener(type, func, false);
    } else if (node.attachEvent) {
        node.attachEvent("on" + type, function() { func.call(this, event); } );
    }
}


// CSS CLASS FUNCTIONS


/**
 * Switch class of an element.
 *
 * @param n Element node to change the class of
 * @param f Class to change from
 * @param t Class to change to
 * @param arr Class array (optional)
 */
function switchClass(n, f, t, arr) {
    if (arr === undefined) {
        arr = getClassArray(n.className);
    }
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] == f) {
            arr[i] = t;
        } else if (arr[i] == t) {
            arr[i] = f;
        }
    }
    n.className = arr.join(" "); 
}

/**
 * Test if element is of class.
 *
 * @param n Element node to test
 * @param c Class to test for
 * @param arr Class array (optional)
 * @return boolean value if element is of the tested class
 */
function isClass(n, c, arr) {
    if (arr === undefined) {
        arr = getClassArray(n.className);
    }
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] == c) {
            return true;
        }
    }
    return false;
}

/**
 * Add class to an element.
 *
 * @param n Element node to add the class to
 * @param c Class to add
 * @param arr Class array (optional)
 */
function addClass(n, c, arr) {
    if (arr === undefined) {
        arr = getClassArray(n.className);
    }
    if (!isClass(n, c, arr)) {
        n.className = n.className + " " + c;
    }
}

/**
 * Remove class
 *
 * @param n Element node to remove the class from
 * @param c Class to remove
 * @param arr Class array (optional)
 */
function removeClass(n, c, arr) {
    if (arr === undefined) {
        arr = getClassArray(n.className);
    }
    if (isClass(n, c, arr)) {
        var ret = [];
        for (var i = 0; i < arr.length; i++) {
            if (arr[i] != c) {
                ret.push(arr[i]);
            }
        }
        n.className = ret.join(" ");
    }
}

/**
 * Get class names as an array
 *
 * @param c class name
 * @return array of class members
 * @type Array
 */
function getClassArray(c) {
    return String(c).split(" ");
}


// COLLECTION CLASSES


/**
 * Multi value Map.
 *
 * Usage:
 *
 * >> var m = new Map();
 * >> m.put("a", "first");
 * >> m.put("a", "second");
 * >> m.size();
 * 1
 * >> m.get("a");
 * ["first", "second"]
 *
 * @constructor
 * @param {Boolean} multi Boolean to define whether map will store dublicate values into an array
 */
function Map(multi) {
    this.keys = [];
    this.values = [];
    this.multi = (multi === undefined ? false : multi); 
}
Map.prototype.toString = function() {
    var val = "[Object Map";
    if (this.keys.length !== 0) {
        val += ": {";
        for (var i = 0; i < this.keys.length; i++) {
        	if (i !== 0) {
        		val += "; ";
        	}
        	val += this.keys[i] + ":";
	        for (var j = 0; j < this.values[i].length; j++) {
	        	if (j !== 0) {
	        		val += ",";
	        	}
	        	val += " " + this.values[i][j].toString();
	        }
	       
        }
        val += "}";
    }
    val += "]";
    return val;
};
/**
 * @param {String} key
 * @param {Object} value
 */
Map.prototype.put = function(key, value) {
    var index = this._find(key);
    var a;
    if (index != -1 ) {
        a = this.values[index];   
    } else {
        index = this.keys.length;
        this.keys[index] = key;
        a = [];
    }
    if (this.multi) {
        a[a.length] = value;
    } else {
        a[0] = value;
    }
    this.values[index] = a;
};
/**
 * @param {String} key
 * @type Object
 */
Map.prototype.get = function(key) {
    var index = this._find(key);
    if (index != -1) {
        if (this.multi) {
            return this.values[index];
        } else {
            return this.values[index][0];
        }   
    } else {
        return null;
    }
};
/**
 * Iterator for the value set.
 *
 * @return Iterator for the values
 * @type ArrayIterator
 */
Map.prototype.iterator = function(key) {
    var index = this._find(key);
    var val;
    if (index != -1) {
        val = this.values[index];
    } else {
        val = [];
    }
    return new ArrayIterator(val);
};
/**
 * @type Number
 */
Map.prototype.size = function() {
	return this.keys.length;
};
/**
 * @param {String} key
 * @type Number
 */
Map.prototype._find = function(key) {
    var index = -1;
    for (var i = 0; i < this.keys.length; i++) {
        if (this.keys[i] == key) {
            index = i;
            break;
        }
    }
    return index;
};

/**
 * Map.
 *
 * Usage:
 *
 * >> var m = new SingleValueMap();
 * >> m.put("a", "first");
 * >> m.put("a", "second");
 * >> m.size();
 * 1
 * >> m.get("a");
 * "second"
 *
 * @constructor
 */
function SingleValueMap() {
    this.keys = [];
    this.values = [];
    this.multi = false; 
}
SingleValueMap.prototype = new Map(false);
SingleValueMap.prototype.toString = function() {
    return "[Object SingleValueMap]";
};

/**
 * Set.
 *
 * @constructor
 */
function Set() {
	this.keys = [];
}
Set.prototype.toString = function() {
	var val = "[object Set";
	if (this.keys.length > 0) {
		val += ":";
		for (var i = 0; i < this.keys.length; i++) {
			if (i !== 0) {
				val += ",";
			}
			val += " " + this.keys[i];
		}
	}
	val += "]";
	return val;
};
Set.prototype.add = function(value) {
	var i = this.m_find(value);
	if (i == -1) {
		this.keys[this.keys.length] = value;
	}
};
Set.prototype.iterator = function() {
	return new ArrayIterator(this.keys);
};
Set.prototype.size = Map.prototype.size;
Set.prototype.m_find = Map.prototype._find;

/**
 * Array Iterator
 *
 * Usage:
 *
 * >> var arr = new Array("a", "b", "c");
 * >> for (var itr = new ArrayIterator(arr); itr.hasNext();) {
 * >>    alert(itr.next());
 * >> }
 * "a"
 * "b"
 * "c"
 *
 * @constructor
 * @param {Array} arrayLike array to iterate through
 */
function ArrayIterator(arrayLike) {
    this.arrayLike = arrayLike;
    this.i = 0;
    NoSuchElementException = function() {};
    NoSuchElementException.prototype = new Error();
    NoSuchElementException.prototype.name = 'NoSuchElementException';
    NoSuchElementException.prototype.message = 'Iteration has no more elements';
}
/**
 * String value of the object.
 *
 * @type String
 */
ArrayIterator.prototype.toString = function() {
  return "[Object ArrayIterator]";
};
/**
 * @type Object
 */
ArrayIterator.prototype.next = function() {
    if (!this.hasNext()) {
        throw new NoSuchElementException();
    }
    if (this.arrayLike.item) {
    	return this.arrayLike.item(this.i++);
    } else {
    	return this.arrayLike[this.i++];
    }
};
/*/
 * @type Boolean
 */
ArrayIterator.prototype.hasNext = function() {
    return this.i < this.arrayLike.length;
};


// DOM FUNCTIONS


/**
 * Get next sibling by element name
 *
 * @param n current element
 * @param t element name of the desired sibling
 * @return next sibling element with a given name or null if not found
 * @type Element
 */
function getNextSiblingByTagName(n, t) {
    if (n != null) {
        var tag = t.toLowerCase();
    	var i = n;
    	while (true) {
    		if (i.nodeType == 1 && i.nodeName.toLowerCase() == tag) {
    		    return i;
    		}
    		if (i.nextSibling != null) {
    			i = i.nextSibling;
    		} else {
    			return null;
    		}
    	}
	}
	return null;
}

/**
 * Get child elements by element name.
 *
 * @param node parent element
 * @param name element name of the desired child elements
 * @return array of child elements
 * @type Array
 */
function getChildElementsByTagName(node, name) {
	var buf = [];
	var tag = undefined;
    if (name !== undefined) {
        tag = name.toLowerCase();
    }
	for (var n = node.firstChild; n != null; n = n.nextSibling) {
		if (n.nodeType == 1 && (name === undefined || n.nodeName.toLowerCase() == tag)) {
			buf[buf.length] = n;
		}
	}
	return buf;
}

/**
 * Get first child element by element name.
 *
 * @param node parent element
 * @param name element name of the desired child elements
 * @return DOM Element, null if none found
 * @type Object
 */
function getFirstChildElementByTagName(node, name) {
	var tag = undefined;
    if (name !== undefined) {
        tag = name.toLowerCase();
    }
	for (var n = node.firstChild; n != null; n = n.nextSibling) {
		if (n.nodeType == 1 && (name === undefined || n.nodeName.toLowerCase() == tag)) {
		  return n;
		}
	}
	return null;
}


/**
 * Wrap child nodes of an element to wrapper element.
 *
 * @param parent element whose child nodes to wrapper
 * @param wrapper wrapper element 
 */
function wrapChildElements(parent, wrapper) {
	for (var c = parent.firstChild, n = null; c != null; c = parent.firstChild) {
		n = parent.removeChild(c);
		wrapper.appendChild(n);
	}
	parent.appendChild(wrapper);
}

/**
 * Get elements by class name.
 *
 * @param node DOM node whose descendants select
 * @param cls class name
 * @param elem element name (optional)
 * @return array of DOM nodes
 * @type Array
 */
function getElementsByClassName(node, cls, elem) {
	if (elem === undefined) {
		elem = "*";
	}
	var res = [];
	for (var el = document.getElementsByTagName(elem), i = 0; i < el.length; i++) {
	  	if (isClass(el[i], cls)) {
	    	res[res.length] = el[i];
		}
	}
	return res;
}

/**
 * Get first element by class name.
 *
 * @param node DOM node whose descendants select
 * @param cls class name
 * @param elem element name (optional)
 * @return DOM Element, null if none found
 * @type Object
 */
function getFirstElementByClassName(node, cls, elem) {
  	if (elem === undefined) {
  		elem = "*";
  	}
  	for (var el = document.getElementsByTagName(elem), i = 0; i < el.length; i++) {
  	  	if (isClass(el[i], cls)) {
  	  	    return el[i];
        }
  	}
  	return null;
}

// FORM FUNCTIONS

function formSubmitHandler(event) {
    var f = getTargetNode(event);
    if (!validateForm(f)) {
        stopEvent(event);
    }
}

/**
 * Validate form
 *
 * @param form form to validate
 * @type Boolean
 * @return boolean value whether validation was successful
 */
function validateForm(form) {
    var isValid = true;
    var labels;
    var r = [];
    var missing;
    var col = form.elements;
    elements: for (var i = 0; i < col.length; i++) {
        if (isClass(col[i], "required") && !col[i].disabled) {
            var f = col[col[i].name];
            var valid = false;
            if (col[i].type === "checkbox" || col[i].type === "radio") {            
                if (col[i].value != f[0].value) { // test only the first
                    continue;
                }
                for (var j = 0; j < f.length; j++) {
                    if (f[j].checked && f[j].className.indexOf("nonrequired") == -1) {
                        continue elements;
                    }
                }
            } else if (col[i].type === "select-one") {
                if (!isClass(col[i][col[i].selectedIndex], "nonvalue")) {
                    continue;
                }
            } else {
                if (f.value !== "") {
                    continue;
                }
            }
            
            if (labels === undefined) {
            	labels = form.getElementsByTagName("label");
            }
            var label;
            for (j = 0; j < labels.length; j++) {
                if (labels[j].htmlFor == col[i].name) {
                	label = labels[j];
                }
            }
            if (!valid) {
            	isValid = false;
                if (missing === undefined) {
                	missing = [];
                }
                missing[missing.length] = label.firstChild.data;
                addClass(label, "form_invalid");
            } else {
            	removeClass(label, "form_invalid");
            }
        }
    }
 	  if (!isValid) {
	      window.alert("Required field" + (missing.length < 2 ? "" : "s")	+
	            " " + toProseString(missing) +
	            " " + (missing.length < 2 ? "has" : "have") + " no value");
    }
    return isValid;
}