jQuery.fn.populate = function (obj, options) {


	// ------------------------------------------------------------------------------------------
	// JSON conversion function

	// convert
	function parseJSON(obj, path) {
		// prepare
		path = path || '';

		// iteration (objects / arrays)
		if (obj == undefined) {
			// do nothing
		}
		else if (obj.constructor == Object) {
			for (var prop in obj) {
				var name = path + (path == '' ? prop : '[' + prop + ']');
				parseJSON(obj[prop], name);
			}
		}

		else if (obj.constructor == Array) {
			for (var i = 0; i < obj.length; i++) {
				var index = options.useIndices ? i : '';
				index = options.phpNaming ? '[' + index + ']' : index;
				var name = path + index;
				parseJSON(obj[i], name);
			}
		}

		// assignment (values)
		else {
			// if the element name hasn't yet been defined, create it as a single value
			if (arr[path] == undefined) {
				arr[path] = obj;
			}

			// if the element name HAS been defined, but it's a single value, convert to an array and add the new value
			else if (arr[path].constructor != Array) {
				arr[path] = [arr[path], obj];
			}

			// if the element name HAS been defined, and is already an array, push the single value on the end of the stack
			else {
				arr[path].push(obj);
			}
		}

	};


	// ------------------------------------------------------------------------------------------
	// population functions

	function debug(str) {
		if (window.console && console.log) {
			console.log(str);
		}
	}

	function getElementName(name) {
		if (!options.phpNaming) {
			name = name.replace(/\[\]$/, '');
		}
		return name;
	}

	function populateElement(parentElement, name, value) {
		var selector = options.identifier == 'id' ? '#' + name : '[' + options.identifier + '="' + name + '"]';
		var element = jQuery(selector, parentElement);
		value = value.toString();
		value = value == 'null' ? '' : value;
		element.html(value);
	}

	function populateFormElement(form, name, value) {

		// check that the named element exists in the form
		var name = getElementName(name); // handle non-php naming
		var element = form[name];

		// if the form element doesn't exist, check if there is a tag with that id
		if (element == undefined) {
			// look for the element
			element = jQuery('#' + name, form);
			if (element) {
				element.html(value);
				return true;
			}

			// nope, so exit
			if (options.debug) {
				debug('No such element as ' + name);
			}
			return false;
		}

		// debug options
		if (options.debug) {
			_populate.elements.push(element);
		}

		// now, place any single elements in an array.
		// this is so that the next bit of code (a loop) can treat them the
		// same as any array-elements passed, ie radiobutton or checkox arrays,
		// and the code will just work

		elements = element.type == undefined && element.length ? element : [element];


		// populate the element correctly

		for (var e = 0; e < elements.length; e++) {

			// grab the element
			var element = elements[e];

			// skip undefined elements or function objects (IE only)
			if (!element || typeof element == 'undefined' || typeof element == 'function') {
				continue;
			}

			// anything else, process
			switch (element.type || element.tagName) {

				case 'radio':
					// use the single value to check the radio button
					element.checked = (element.value != '' && value.toString() == element.value);

				case 'checkbox':
					// depends on the value.
					// if it's an array, perform a sub loop
					// if it's a value, just do the check

					var values = value.constructor == Array ? value : [value];
					for (var j = 0; j < values.length; j++) {
						element.checked |= element.value == values[j];
					}

					//element.checked = (element.value != '' && value.toString().toLowerCase() == element.value.toLowerCase());
					break;

				case 'select-multiple':
					var values = value.constructor == Array ? value : [value];
					for (var i = 0; i < element.options.length; i++) {
						for (var j = 0; j < values.length; j++) {
							element.options[i].selected |= element.options[i].value == values[j];
						}
					}
					break;

				case 'select':
				case 'select-one':
					element.value = value.toString() || value;
					break;

				case 'text':
				case 'button':
				case 'textarea':
				case 'submit':
				default:
					value = value == null ? '' : value;
					element.value = value;

			}

		}

	}


	// ------------------------------------------------------------------------------------------
	// options & setup

	// exit if no data object supplied
	if (obj === undefined) {
		return this;
	}
	;

	// options
	var options = jQuery.extend
	(
		{
			phpNaming: true,
			phpIndices: false,
			resetForm: true,
			identifier: 'id',
			debug: false
		},
		options
	);

	if (options.phpIndices) {
		options.phpNaming = true;
	}

	// ------------------------------------------------------------------------------------------
	// convert hierarchical JSON to flat array

	var arr = [];
	parseJSON(obj);

	if (options.debug) {
		_populate =
		{
			arr: arr,
			obj: obj,
			elements: []
		}
	}

	// ------------------------------------------------------------------------------------------
	// main process function

	this.each
	(
		function () {

			// variables
			var tagName = this.tagName.toLowerCase();
			var method = tagName == 'form' ? populateFormElement : populateElement;

			// reset form?
			if (tagName == 'form' && options.resetForm) {
				this.reset();
			}

			// update elements
			for (var i in arr) {
				method(this, i, arr[i]);
			}
		}
	);

	return this;
};