(function () {
	if ('FormHandler' in window) {
		return;
	}
	window.FormHandler = {};


	var CLASS_NAME = 'form-handler';

	var STATE_CONNECTING = 0;
	var STATE_OPEN       = 1;
	var STATE_CLOSING    = 2;
	var STATE_CLOSE      = 3;

	var webSocketURL = undefined;
	var webSocket    = null;


	function openWebSocket(callback) {
		if (webSocket) {
			if (webSocket.readyState == STATE_OPEN) {
				callback();
				return;
			}
			else if (webSocket.readyState == STATE_CONNECTING) {
				socket.onopen = callback;
				return;
			}
		}

		webSocket = null;

		if ('WebSocket' in window) {
			var parts = webSocketURL.match('^(.+:\/\/)([^\/]+)(\/.*)$');
			var url   = parts[1] + Punycode.toASCII(parts[2]) + parts[3];

			webSocket = new WebSocket(url);
		}

		if (webSocket) {
			webSocket.onopen = callback;
			webSocket.onmessage = (function () {
				return function (event) {
					if (!(event.data == 'ping')) {
						logMessage('Received: ' + event.data);

						var message;

						try {
							message = $.evalJSON(event.data);
						}
						catch (error) {
							logMessage('Error: ' + error);
						}

						if (message.result && message.result.element) {
							var form;

							try {
								form = $('#' + message.result.element);
							}
							catch (error) {}

							if (form) {
								var callback = form.data(message.error ? 'onerror' : 'onsuccess');

								if (callback) {
									callback.call(form[0], message);
								}
							}
						}
						if (message.error) {
							logMessage('Error: ' + message.error.message);
						}
					}
					else {
						webSocket.send('pong');
					}
				}
			})();
			webSocket.onerror = (function () {
				return function (event) {
					webSocket = null;
				}
			})();
			webSocket.onclose = (function () {
				return function (event) {
					webSocket = null;
				}
			})();
		}
	}

	function handleFormSubmit(event) {
		var form   = $(this);
		var values = {};

		$.each(form.serializeArray(), function(index, value) {
			if (value.name in values) {
				if ($.isArray(values[value.name])) {
					values[value.name].push(value.value);
				}
				else {
					values[value.name] = [values[value.name], value.value];
				}
			}
			else {
				values[value.name] = value.value;
			}
		});

		var message = {
			params: {
				id:      form.data('id'),
				element: form.attr('id'),
				values:  values
			}
		};

		openWebSocket(function () {
			webSocket.send($.toJSON(message));
			logMessage('Sent: ' + $.toJSON(message));
		});

		return false;
	}

	function initForm (form, callbacks) {
		form = $(form);
		if (form.hasClass(CLASS_NAME)) {
			return false;
		}

		form.addClass(CLASS_NAME);
		if (!form.attr('id')) {
			form.attr('id', CLASS_NAME + '-' + Math.random().toString().substr(2));
		}
		if (form.attr('class')) {
			var prefix = CLASS_NAME + '-id-';
			var value  = ($.grep(form.attr('class').split(/\s+/), function (value) {
				return value.indexOf(prefix) == 0;
			}))[0];

			if (value) {
				form.data('id', value.substr(prefix.length));
			}
			else {
				form.data('id', form.attr('id'));
			}
		}
		if ('success' in callbacks) {
			form.data('onsuccess', callbacks.success);
		}
		if ('error' in callbacks) {
			form.data('onerror', callbacks.error);
		}
		form.submit(handleFormSubmit);

		return true;
	}

	function logMessage(message) {
		if (window.FORM_HANDLER_DEBUG) {
			try {
				console.log(message);
			}
			catch (error) {}
		}
	}

	window.FormHandler.initForm = initForm;


	// Load additional libraries.
	var cache = $.ajaxSettings.cache;

	$.ajaxSetup({cache: true});
	$.getScript('/_s/js/punycode.js');
	if ('MozWebSocket' in window && window.MozWebSocket) {
		// Gecko.
		window.WebSocket = MozWebSocket;
	}
	else if (!('WebSocket' in window)) {
		// Browsers without WS support.
		$.getScript('/_s/js/swfobject.js');
		$.getScript('/_s/js/websocket.js');
	}


	// Set up websockets.
	$(document).ready(function () {
		if ('WEB_SOCKET_URL' in window) {
			webSocketURL = window.WEB_SOCKET_URL;
		}
		$('form').each(function () { initForm(this); });
	})
	.unload(function () {
		if (webSocket.readyState != STATE_CLOSING || webSocket.readyState != STATE_CLOSE) {
			webSocket.close();
		}
	});
})();

