/**
 * xhrForm
 * submits form values via XHR
 * responses can trigger a "normal" submit, a redirect or
 * will contain objects with name of elements, new values and
 * possible error messages
 * can be attached to any element within form
 * 
 * @version 0.1.7 2008-12-15
 * @author Gregor Kofler, info@gregorkofler.at
 * 
 * @todo wrong position of xhrImg upon second submit in Chrome, Safari - scrollOffset not considered
 */
/*global window, document, vxJS*/

if(!vxJS || !vxJS.xhr) { throw new Error("widget.xhrSubmit: vxJS core or vxJS.xhr missing."); }

/**
 * @param {Object} form DOM form element
 * @param {Object} xhr request object containing command and URI
 */
vxJS.widget.xhrForm = function(form, xhrReq) {

	if (!form.nodeName || form.nodeName != "FORM") {
		throw new Error("widget.xhrForm: no form element specified!");
	}

	var	xhr, prevErr = [], msgBoxes = [], that = {},
		xhrImg = function() {
			var i;
			i = "img".setProp("src", "js/xhr_activity.gif").create();
			i.style.position = "absolute";
			vxJS.dom.setElementPosition(i,  { x: -100, y: -100 } );
			document.body.appendChild(i);
			return i;
		}(), xhrImgSize;

	var setValues = function(v) {
		var i, j, k, e = form.elements;

		for(i = v.length; i--;) {
			for(j = e.length; j--; ) {
				if ((e[j].name != v[i].name)) {
					continue;
				}

				switch(e[j].type) {
					case 'textarea':
					case 'text':
					case "hidden":
						e[j].value = v[i].value;
						break;

					case 'select-multiple':
						if(typeof v[i].value != "object" || !v[i].value.length) { break; }
						for(k = e[j].options.length; k--;) {
							 e[j].options[k].selected = v[i].value.inArray(e[j].options[k].value);
						}
						break;

					case 'select-one':
						e[j].selectedIndex = isNaN(+v[i].value) ? null : +v[i].value;
						break;

					case 'radio':
					case 'checkbox':
						e[j].checked = v[i].value ? true : false;
				}
			}
		}
	};

	var setErrors = function(err) {
		var e = prevErr, i, j, n, fe = form.elements;

		for (j = fe.length; j--;) {
			fe[j].className = fe[j].className.replace(/\s*error$/, "");
		}

		for(i = e.length ; i--;) {
			if(!document.getElementById("error_"+e[i].name)) {
				continue;
			}
			n = document.getElementById("error_"+e[i].name);
			n.className = n.className.replace(/\s*error$/, "");
			if(e[i].text) {
				n.removeChild(n.lastChild);
			}
		}

		prevErr = [];

		for(i = err.length; i--;) {
			for (j = fe.length; j--;) {
				if(fe[j].name === err[i].name) {
					fe[j].className += !fe[j].className ? "error" : " error";
					break;
				}
			}
			
			if(!document.getElementById("error_"+err[i].name)) {
				continue;
			}

			n = document.getElementById("error_"+err[i].name);
			n.className += !n.className ? "error" : " error";
			if(err[i].text) {
				n.appendChild(document.createTextNode(err[i].text));
			}

			prevErr.push({
				name: err[i].name,
				text: err[i].text ? true : false
			});
		}
	};

	var getValues = function(fe, submit) {
		var	 i, v, j, o, vals = {};

		for (i = fe.length; i--;) {
			v = null;
			if (fe[i].type && !fe[i].disabled) {
				switch (fe[i].type) {
					case "radio":
					case "checkbox":
						if (fe[i].checked) {
							v = fe[i].value;
						}
						break;
						
					case "textarea":
					case "text":
					case "hidden":
						v = fe[i].value;
						break;
						
					case "select-multiple":
						o = fe[i].options;
						v = [];
						for (j = o.length; j--;) {
							if (o[j].selected) {
								v.push(o[j].value);
							}
						}
						break;
						
					case "select-one":
						v = fe[i].options[fe[i].selectedIndex].value;
						break;
						
					case "submit":
					case "image":
					case "button":
						if (submit && fe[i] === submit) {
							v = fe[i].value;
						}
				}
				if (v !== null) {
					vals[fe[i].name] = v;
				}
			}
		}
		return vals;
	};
	
	var clearMsgBoxes = function() {
		var i;

		for(i = msgBoxes.length; i--;) {
			vxJS.dom.deleteChildNodes(msgBoxes[i].container);
		}
	};
	
	var findMsgBox = function(id) {
		var i;

		for(i = msgBoxes.length; i--;) {
			if (id === msgBoxes[i].id) {
				return msgBoxes[i].container;
			}
		}
	};

	/**
	 * handle response
	 * @param {Object} resp response object
	 * 
	 * can handle commands (redirect, submit),
	 * set values of elements and corresponding errors
	 * fill message boxes, invoke previously added callback methods 
	 */
	var handleXhrResponse = function(resp) {
		var i, n, v = [], e = [], m, c, cb;

		if(resp.command) {
			switch(resp.command) {
				case "redirect":
					if(resp.location) {
						window.location.href = resp.location;
					}
					break;
				case "submit":
					form.submit();
					break;
			}
		}

		if(resp.elements) {
			for(i = resp.elements.length; i--;) {
				n = resp.elements[i].name;
				if(resp.elements[i].value) { v.push({name: n, value: resp.elements[i].value }); }
				if(resp.elements[i].error) { e.push({name: n, text: resp.elements[i].errorText || null}); }  
			}
			setValues(v);
			setErrors(e);
		}
		
		clearMsgBoxes();

		if((m = resp.msgBoxes)) {
			for (i = m.length; i--;) {
				if(m[i].id && (c = findMsgBox(m[i].id))) {
					c.appendChild(vxJS.dom.parse(m[i].elements));
				}
			}
		}
		
		if((cb = resp.callbacks)) {
			if(!cb.length) {
				cb = [cb];
			}
			for(i = 0; i < cb.length; i++) {
				if(typeof that[cb[i].f] === "function") {
					that[cb[i].f].apply(that, cb[i].param || []);
				}
			}
		}
	};

	var handleClick = function(e) {
		var p, s;

		if(!xhrImgSize && xhrImg.complete) {
			xhrImgSize = vxJS.dom.getElementSize(xhrImg);
		}
		if(xhrImgSize) {
			p = vxJS.dom.getElementOffset(this);
			s = vxJS.dom.getElementSize(this);
			p.x += s.x+4;
			p.y += (s.y-xhrImgSize.y)/2;
			vxJS.dom.setElementPosition(xhrImg, p);
		}

		if(xhr) {
			xhr.use(null, null, { elements: getValues(form.elements, this) }, { node: xhrImgSize ? xhrImg : null });
		}
		else {
			xhr = vxJS.xhr(
				xhrReq,
				{ completed: handleXhrResponse },
				{ elements: getValues(form.elements, this) },
				{ node: xhrImgSize ? xhrImg : null }
			);
		}
		vxJS.event.preventDefault(e);
	};

	that.addSubmit = function(elem) {
		var elemForm;

		if(elem.nodeName === "INPUT" && (!(elemForm = elem.form || vxJS.dom.getParentElement(elem, "form")) || elemForm !== form)) {
			throw new Error("widget.xhrForm: form element not found!");
		}

		vxJS.event.addListener(elem, "click", handleClick);
	};
	
	that.addMessageBox = function(elem, id) {
		if(!elem) { return; }
		msgBoxes.push({
			container: elem,
			id: id || "MsgBox" + msgBoxes.length
		});
	};

	that.forceRequest = function() {
		if(xhr) {
			xhr.abort();
		}

		xhr = vxJS.xhr(
			xhrReq,
			{ completed: handleXhrResponse },
			{ elements: getValues(form.elements, this), forcedRequest: true }
		);
	};

	return that;
};