/*global jQuery window console alert*/
(function ($, window) {
	"use strict";


	$.log = function (message) {
		if (window.console && window.console.debug) {
			console.debug(arguments);
		} else if (window.console && window.console.log) {
			console.log(arguments);
		} else {
			alert(message);
		}
	};

	$.fn.bindEvent = function () {
		var ev = arguments[0].event,
			callback = arguments[0].callback,
			l = callback.length,
			callbackMethod,
			i,
			bindMethod = function (e) {
				var arg = [],
					x;

				try {
					for (x = 1; x < arguments.length; x += 1) {
						arg.push(arguments[x]);
					}

					e.data.n[e.data.f].apply(e.data.n, arg);
				} catch (error) {
					$.log(error.message);
				}
			};


		for (i = 0; i < l; i += 1) {
			callbackMethod = callback[i];
			$(this).bind(ev, callbackMethod, bindMethod);
		}
	};

	$.objectWalk = function (obj, func, args) {
		var i,
			ar = args ? args : [];

		if (typeof func !== "function") {
			throw new Error("file: custom.js, Error: " + func + " is not a function");
		}
		func.apply(obj, ar);

		for (i in obj) {
			if (obj[i] && typeof obj[i] === "object" && (!obj[i].length && obj[i].length !== 0)) {
				$.objectWalk(obj[i], func, ar);
			}
		}
	};

	$.fn.normalize = function () {
		var that = this,
            i, // number
            l = that.length,
            removeEmptyTextNodes = function (elem, elem2) {

            	if (typeof elem !== "object" && elem2) {
            		elem = elem2;
            	} else if (typeof elem !== "object") {
            		return;
            	}

            	var childNodes = elem.childNodes,
                    i = 0,
                    l = childNodes.length,
                    text = elem.innerText ? elem.innerText : elem.textContent;

            	if (l) {
            		for (i = (l - 1); i > -1; i -= 1) {
            			if (childNodes[i].nodeType === 3 && !childNodes[i].childNodes.length && !$.trim(text)) {
            				childNodes[i].parentNode.removeChild(childNodes[i]);
            			}
            		}
            	}
            };

		for (i = 0; i < l; i += 1) {
			removeEmptyTextNodes(that[i]);

			$(that[i]).find("*").each(removeEmptyTextNodes);
		}

		return that;
	};

	/**
	* Creates and returns a HTMLElement.
	* 
	* Has shaky or no support for <object> & <embed> (depending on browser).
	* 
	* ex: newElementObject = {tagName : "a", href : "/", innerHTML : "A link", className : "someClass"}.
	* 
	* Specials: 
	*     append: {tagName: "ul", append: [{tagName: "li"}, {tagName: "li"}]}, append takes an Array of Objects or a single Object
	*     repeat: {tagName: "p", repeat: 10} adds 10 cloned <p>
	* 
	* 
	* @param {Object|String|empty} newElementObject|tagName (if !newElementObject $.create returns an empty <div>)
	* @returns HTMLElement
	*/
	$.create = function (newElementObject) {

		// custom create HTML
		var neo = newElementObject,
			neoLength = neo ? (neo.length ? neo.length : 0) : 0,
	        tagName = (neo && neo.tagName) ? neo.tagName : "div",
	        newElement = document.createElement(tagName),
	        ne = newElement,
	        p, // properties
	        i = 0,
			clone = null,
			clones = neo ? (neo.repeat ? true : false) : false,
			df = document.createDocumentFragment(),
			dynProp = null, //dynamic property
			breakLoop = false,
            loadFunctions = [],
            data;

		if (!newElementObject) {
			return ne;
		}

		if (typeof newElementObject === "string") {
			return document.createElement(newElementObject);
		}

		if (neo.type) {
			ne.setAttribute("type", neo.type);
		}

		if (neo.src) {
			ne.setAttribute("src", neo.src);
		}

		if (neo) {

			if ($.isArray(neo)) {

				df = document.createDocumentFragment();

				for (i = 0; i < neoLength; i += 1) {
					try {
						df.appendChild($.create(neo[i]));
					} catch (e) {
						alert(e + "\n\ntagName = " + tagName + "\n\n" + p + ":\n" + neo[p] + "\n\ncreate loop (isArray)");
						break;
					}
				}

				newElement = df;
			} else {

				for (p in neo) {

					if (neo.hasOwnProperty(p) && (/(string|function|object|number)/).test(typeof neo[p])) {

						if ((/^((un)?load|(dbl)?click|change|resize|scroll|select|submit|focus(in|out)?|blur|mouse(enter|leave|over|out|move|down|up)|key(press|down|up))$/).test(p) && typeof neo[p] === "function") {
							$(ne).bind(p, neo[p]);

							if (p === "load") {
								loadFunctions.push(neo[p]);
							}

						} else if (typeof neo[p] === "function") {
							dynProp = neo[p]();
							if (dynProp) {
								if (typeof ne.style[p] === "string") {
									ne.style[p] = dynProp;
								} else {
									ne[p] = dynProp;
								}
							}
						} else if (p === "data") {
							for (data in neo[p]) {
								if (neo[p].hasOwnProperty(data) && (/^(object|function|number|string|boolean)$/).test(typeof neo[p][data])) {
									$.data(ne, data, neo[p][data]);
								}
							}

						} else if (!(/(tagName|append(Child(ren)?)?|type|repeat|src)/).test(p)) {

							if (typeof ne.style[p] === "string" && p !== "opacity") {
								try {
									ne.style[p] = neo[p];
								} catch (er) {
									console.log(er + ", " + neo + ", " + p + ", " + neo[p]);
								}
							} else if (typeof ne.style[p] === "string" && p === "opacity") {
								$(ne).css("opacity", neo[p]);
							} else {
								try {
									/**
									* create does not add a class attribute if there is no value.
									*/
									if (!((/^(className)$/).test(p) && !neo[p])) {
										ne[p] = neo[p];
									}

								} catch (f) {
									alert(f + "\n\ntagName = " + tagName + "\n\n" + p + ":\n" + neo[p] + "\n\nattribute");
									breakLoop = true;
								}
							}
						} else if ((/^attr$/i).test(p)) {

							$.log(neo[p], p);

						} else if ((/^(append)(Child(ren)?)?$/).test(p)) {
							if (typeof neo[p] === "object" && neo[p].length) {
								for (i = 0; i < neo[p].length; i += 1) {
									try {
										ne.appendChild($.create(neo[p][i]));
									} catch (g) {
										alert(g + "\n\ntagName = " + tagName + "\n\n" + p + ":\n" + neo[p] + "\n\ncreate loop");
										breakLoop = true;
									}
								}
							} else if (typeof neo[p] === "object") {
								try {
									ne.appendChild($.create(neo[p]));
								} catch (h) {
									alert(h + "\n\ntagName = " + tagName + "\n\n" + p + ":\n" + neo[p] + "\n\ncreate");
									breakLoop = true;
								}
							}
						}

						if (breakLoop) {
							break;
						}
					}
				}
			}
		}

		if (clones) {
			df = document.createDocumentFragment();

			for (i = 0; i < neo.repeat; i += 1) {
				clone = newElement.cloneNode(true);
				df.appendChild(clone);
			}

			newElement = df;
		}

		if (loadFunctions.length) {
			setTimeout(function () {

				for (i = 0; i < loadFunctions.length; i += 1) {
					loadFunctions[i].apply(newElement);
				}

			}, 10);
		}

		return newElement;
	};

	/**
	* $.runWhenExists will run a maximum of 50 times before trying to run the callback.
	* If that does not work it does nothing.
	*
	* @param {Function} exists (have to return a falsy or truthy value)
	* @param {Function} callback (will run when "exists" returns a truthy value)
	*/
	$.runWhenExists = function (exists, callback, customTime, customWait) {

		var iv = 0,
            ivCount = 0,
            time = (customTime && typeof customTime === "number") ? customTime : 100,
            wait = (customWait && typeof customWait === "number") ? customWait : 100,
            allCount = 0,
            maxCount = 50,
            runFunc = function () {
            	ivCount += 1;
            	allCount += 1;

            	if (allCount >= maxCount) {
            		clearInterval(iv);
            		try {
            			callback();
            		} catch (error) {
            			//throw new Error(error);
            		}
            	}

            	if (exists()) {
            		// wait 100 or [customWait] ms before running the callback, just in case...
            		clearInterval(iv);
            		setTimeout(callback, wait);
            	}

            	if (ivCount === 5) {
            		clearInterval(iv);

            		time = (time * 2);
            		ivCount = 0;

            		iv = setInterval(runFunc, time);
            	}
            };

		iv = setInterval(runFunc, time);

	};
	$.runWhenTrue = $.runWhenExists;

	$.runAll = function () {
		var that = this,
			prop,
			obj;

		$.objectWalk(that, function () {
			if (this[arguments[0]]) {
				if (typeof this[arguments[0]] === "function") {
					this[arguments[0]]();
				} else if (typeof this[arguments[0]] === "object" && (!this[arguments[0]].length && this[arguments[0]].length !== 0)) {
					// this generates an Error in JSLInt:
					// "Problem at line 41 character 26: Bad for in variable 'k'."

					// I have no idea what it should be called to be a 'good one'.
					// The 'k' variable is apparently ok in '$.objectWalk'...
					obj = this[arguments[0]];
					for (prop in obj) {
						if (typeof obj[prop] === "function") {
							obj[prop]();
						}
					}
				}
			}
		}, [arguments[0]]);
	};

	$.runInit = function (obj) {
		$.runAll.apply(obj, ["init"]);
	};


} ($, window));
