(function($){
	// creates initial location finder
	$.fn.locationFinder = function(options)
	{
		// extend the default options with options from argument
		$.locationFinder.options = $.extend(true, $.locationFinder.options, (typeof options == 'undefined' ? {} : options));

		// create reference to location finder container
		$.locationFinder.container = this;

		// create map
		$.locationFinder.createMap();

		// create list
		$.locationFinder.createList();

		// handle form submission
		$.locationFinder.createForm();

		// handle form submission
		$.locationFinder.submitForm();

		return this;
	};

	// creates new locationFinder object
	$.locationFinder = new Object();

	// default options for locationFinder
	$.locationFinder.options = {
		form : {
			id: 'location-search',
			address: 'address',
			radius: 'radius'
		},
		map : {
			id: 'location-map',
			options: {
				zoom: 1,
				lat: 37.0625,
				long: -95.677068,
				mapTypeId: google.maps.MapTypeId.ROADMAP,
				mapTypeControl: false,
				navigationControl: true,
				scaleControl: false
			}
		},
		marker: {
			icon: 'http://maps.google.com/mapfiles/ms/micons/red-dot.png',
			shadow: new google.maps.MarkerImage('http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', null, null, new google.maps.Point(16, 32))
		},
		infoWindow: {
			height: ''
		},
		results: {
			id: 'location-results',
			start: '<ul>',
			end: '</ul>',
			dataType: 'json',
			preprocess: function(data){return data;},
			complete: function(){}
		},
		result: {
			start: '<li>',
			end: '</li>',
			empty: '<h3>No Results</h3>'
		}
	};

	// handle form submission
	$.locationFinder.submitForm = function()
	{
		// set form reference
		var form = $.locationFinder.form;

		// get address
		var address = $('[name=' + $.locationFinder.options.form.address + ']', form);

		$(form).submit(function(e){
			e.preventDefault();
		
			$.locationFinder.submitQuery();
		}).find('input, select').change(function(){
			$.locationFinder.submitQuery();
		});

		if ($(address, form).val() != '')
		{
			$.locationFinder.submitQuery();
		}
	}

	// submit form data and list results
	$.locationFinder.submitQuery = function(center)
	{
		$.locationFinder.active = true;

		// set form reference
		var form = $.locationFinder.form;

		// get form information
		var action = $(form).attr('action');
		var data = $(form).serializeArray();
		var method = $(form).attr('method');

		// set default method type
		if (typeof method == 'undefined')
		{
			method = 'post';
		}

		// create object from data array
		var dataObj = $.serializeArrayToObject(data);

		// get radius value
		var radius = parseInt(dataObj[$.locationFinder.options.form.radius]);

		// check if radius is not a number
		if (isNaN(radius))
		{
			// set default radius of 10
			radius = 10;
		}

		// convert miles to meters
		radius =  radius * 1609.344;

		// check if center location was not provided
		if (typeof center == 'undefined')
		{
			// geocode address
			$.locationFinder.geocoder.geocode({'address': dataObj[$.locationFinder.options.form.address]}, function(results, status){
				if (status == google.maps.GeocoderStatus.OK)
				{
					// set map center
					$.locationFinder.setMapCenter(results[0].geometry.location, radius);

					// get locations
					$.locationFinder.getLocations(action, data, method, results[0].geometry.location);
				}
				else
				{
					// remove all locations and set no results message
					$.locationFinder.removeLocations();
					$.locationFinder.noResults();
				}
			});
		}
		else
		{
			// get locations
			$.locationFinder.getLocations(action, data, method, center);
		}
	}

	// set map center 
	$.locationFinder.setMapCenter = function(center, radius)
	{
		// create circle with center and radius
		var circle = new google.maps.Circle({center: center, radius: radius});

		// fit map to bounds of the circle
		$.locationFinder.map.fitBounds(circle.getBounds());
	}

	// instantiate Google Map
	$.locationFinder.createMap = function()
	{
		// create map if doesn't exist
		if ($('[id=' + $.locationFinder.options.map.id + ']').length <= 0)
		{
			// create map
			var locationMap = $('<div />').attr('id', $.locationFinder.options.map.id);

			// add map
			$.locationFinder.container.append(locationMap);
		}

		// set reference for map
		$.locationFinder.mapRef = $('[id=' + $.locationFinder.options.map.id + ']');

		// set map senter based on longitude and latitude
		var center = new google.maps.LatLng($.locationFinder.options.map.options.lat, $.locationFinder.options.map.options.long);

		// add center value to gmap options
		$.extend($.locationFinder.options.map.options, { center: center });

		// create map
		$.locationFinder.map = new google.maps.Map($.locationFinder.mapRef.get(0), $.locationFinder.options.map.options);

		// create geocoder
		$.locationFinder.geocoder = new google.maps.Geocoder();

		// create map markers
		$.locationFinder.markers = new Array();

		// create infoWindow
		$.locationFinder.infoWindow = new google.maps.InfoWindow();

		// set initial state of infoWindow to closed
		$.locationFinder.infoWindow.opened = null;

		// add close event listener to infoWindow
		google.maps.event.addListener($.locationFinder.infoWindow, 'closeclick', function(e){
			// removes focus from locations
			$.locationFinder.blurLocations();
		});

		// add close event listener to infoWindow
		google.maps.event.addListener($.locationFinder.map, 'dragend', function(e){
			// get map center
			center = $.locationFinder.map.getCenter();

			// submit query with map center
			$.locationFinder.submitQuery(center);
		});
	}

	// handle click event for result list
	$.fn.locationFinderListClick = function()
	{
		this.click(function(e){
			// get the clicked list item
			var li = $(e.target).closest($.locationFinder.resultElement);

			// get index of list item in list
			var index = li.parent().children().index(li);

			// check that a valid index is returned
			if (index >= 0)
			{
				// set focus to location
				$.locationFinder.focusLocation(index);
			}
		});

		return this;
	}

	// instantiate location List
	$.locationFinder.createList = function()
	{
		// create map
		$.locationFinder.resultElement = $($.locationFinder.options.result.start + $.locationFinder.options.result.end).get(0).tagName;

		// create map if doesn't exist
		if ($('[id=' + $.locationFinder.options.results.id + ']').length <= 0)
		{
			// create location list
			var locationList = $($.locationFinder.options.results.start + $.locationFinder.options.results.end).attr('id', $.locationFinder.options.results.id).locationFinderListClick();
			
			// add location list
			$.locationFinder.container.append(locationList);
		}

		// set reference for location list
		$.locationFinder.list = $('[id=' + $.locationFinder.options.results.id + ']').locationFinderListClick();
	}

	// instantiate location List
	$.locationFinder.createForm = function()
	{
		// create form if doesn't exist
		if ($('form[id=' + $.locationFinder.options.form.id + ']').length <= 0)
		{
			var inputAddress = $('<input />').attr('type', 'text').attr('name', $.locationFinder.options.form.address);
			var inputRadius = $('<input />').attr('type', 'text').attr('name', $.locationFinder.options.form.radius);
			var inputSubmit = $('<button />').attr('type', 'submit').text('Go');
			var form = $('<form />').attr('id', $.locationFinder.options.form.id).attr('method', 'post').attr('action', '')
				.html('<p>' + inputAddress + ' ' + inputRadius + ' ' + inputSubmit);

			// add form
			$.locationFinder.container.before(form);
		}

		// set reference for form
		$.locationFinder.form = $('form[id=' + $.locationFinder.options.form.id + ']');
	}

	// retreives locations from database
	$.locationFinder.getLocations = function(action, data, method, center)
	{
		$.locationFinder.active = true;
		
		// add latitude to form data
		data.push({
			name: 'lat',
			value: center.lat()
		});

		// add longitude to form data
		data.push({
			name: 'lng',
			value: center.lng()
		});

		// add paramater to data indicating that request is made via AJAX
		data.push({
			name: 'ajax',
			value: true
		});

		// create an AJAX request
		$.ajax({
			url: action,
			type: method,
			dataType: $.locationFinder.options.results.dataType,
			data: $.param(data),
			success: function(data){
				// check if expected data type is xml and xml2json plugin exists
				if ($.locationFinder.options.results.dataType == 'xml' && $.xml2json())
				{
					// create variable containing all locations from returned XML
					var locations = $('location', data);

					// create new data object with empty locations object
					data = new Object();
					data.locations = new Array();

					// iterate through each location
					locations.each(function(){
						// add location after converting to json
						data.locations.push($.xml2json(this));
					});
				}

				// process data
				data = $.locationFinder.options.results.preprocess(data);

				// remove all previous locations
				$.locationFinder.removeLocations();

				// close infoWindow
				$.locationFinder.infoWindow.close();

				// remove focus from locations
				$.locationFinder.blurLocations();

				// check if results are returned
				if (data.locations.length > 0)
				{
					// iterate through each location
					$.each(data.locations, function(i, location){
						// add location
						$.locationFinder.addLocation(location);
					});
				}
				else
				{
					$.locationFinder.noResults();
				}

				$.locationFinder.options.results.complete();

				$.locationFinder.active = false;
			}
		});
	}

	// adds no results item to result list
	$.locationFinder.noResults = function()
	{
		// create no results item
		var noResults = $($.locationFinder.options.result.start + $.locationFinder.options.result.end).addClass('no-results').html($.locationFinder.options.result.empty);

		// add item to search result list
		$.locationFinder.list.append(noResults);
	}

	// adds marker to map
	$.locationFinder.addLocation = function(location)
	{
		// create latLng object from location coordinates
		var latLng = new google.maps.LatLng(location.lat, location.lng);

		// add marker
		$.locationFinder.addMarker(latLng, location.content.marker);

		// add item to result list
		$.locationFinder.addResult(location.content.result);
	}

	// adds marker to map
	$.locationFinder.removeLocations = function(latLng, content)
	{
		// iterate through each marker
		$.each($.locationFinder.markers, function(i){
			// remove marker from map
			$.locationFinder.markers[i].setMap(null);
		});

		// empty markers reference array
		$.locationFinder.markers = new Array();

		// empty result list
		$.locationFinder.list.empty();

		// check if jScrollPane plugin is being used
		if ($.locationFinder.jScrollPane())
		{
			$.locationFinder.list[0].scrollTo(0);
		}
	}

	// adds marker to map
	$.locationFinder.addMarker = function(latLng, content)
	{
		// create basic marker options
		var options = {
			position: latLng,
			map: $.locationFinder.map
		}

		// add marker icon image if set
		if (typeof $.locationFinder.options.marker.icon != 'undefined')
		{
			$.extend(options, {
				icon: $.locationFinder.options.marker.icon
			});
		}

		// add marker shadow image if set
		if (typeof $.locationFinder.options.marker.shadow != 'undefined')
		{
			$.extend(options, {
				shadow: $.locationFinder.options.marker.shadow
			});
		}

		// create marker
		marker = new google.maps.Marker(options);

		// set height
		var height = parseInt($.locationFinder.options.infoWindow.height);

		// check if height is a number
		if (isNaN(height))
		{
			height = 'auto';
		}
		else
		{
			height = height + 'px';
		}

		// set marker content
		marker.content = '<div class="marker-content" style="height:' + height + '">' + content + '</div>';

		// add marker and popup to global arrays
		var markerId = $.locationFinder.markers.push(marker) - 1;

		// add click event listener to marker
		google.maps.event.addListener(marker, 'click', function(e){
			// set focus to location
			$.locationFinder.focusLocation(markerId, true);
		});
	}

	// adds item to result list
	$.locationFinder.addResult = function(content)
	{
		// add item to search result list
		$.locationFinder.list.append($.locationFinder.options.result.start + content + $.locationFinder.options.result.end);
	}

	// adds focus to location
	$.locationFinder.focusLocation = function(markerId, markerClicked)
	{
		// set default value for markerClicked
		var markerClicked = (typeof markerClicked == 'undefined') ? false : markerClicked;

		// check if the opened infoWindow is for the selected marker
		if ($.locationFinder.infoWindow.opened != markerId)
		{
			// check that marker content is not equal to selected marker's content
			if ($.locationFinder.infoWindow.getContent() != $.locationFinder.markers[markerId].content)
			{
				// set content and open infoWindow for marker
				$.locationFinder.infoWindow.setContent($.locationFinder.markers[markerId].content);
			}

			// open infoWindow for selected marker
			$.locationFinder.infoWindow.open($.locationFinder.map, $.locationFinder.markers[markerId]);

			// set a reference to the opened marker
			$.locationFinder.infoWindow.opened = markerId;
		}

		// remove all "focused" classes from result list items and add "focus" class to selected item
		$($.locationFinder.resultElement, $.locationFinder.list).removeClass('focused').filter(':eq(' + markerId + ')').addClass('focused');

		// check if the marker was clicked
		if (markerClicked)
		{
			// check if scrollTo plugin is loaded
			if ($.fn.scrollTo)
			{
				// check if jScrollPane plugin is being used
				if ($.locationFinder.jScrollPane())
				{
					// get the offset of result list and result item
					var listOffset = parseInt($.locationFinder.list.offset().top);
					var itemOffset = $($.locationFinder.resultElement + ':eq(' + markerId + ')', $.locationFinder.list).offset().top;

					// scroll to result item location
					$.locationFinder.list[0].scrollTo(itemOffset - listOffset);
				}
				else
				{
					// scroll to result item
					$($.locationFinder.list).scrollTo($.locationFinder.resultElement + ':eq(' + markerId + ')');	
				}
			}
		}
	}

	// checks if jScrollPane is being used for location list
	$.locationFinder.jScrollPane = function()
	{
		return $.locationFinder.list.parent('.jScrollPaneContainer').find('> .jScrollPaneTrack').length > 0;
	}

	// removes focus from locations
	$.locationFinder.blurLocations = function()
	{
		// remove reference for opened marker
		$.locationFinder.infoWindow.opened = null;

		// remove "focused" class from all items in the result list
		$($.locationFinder.resultElement, $.locationFinder.list).removeClass('focused');
	}

	// converts a serialized array to an object
	$.serializeArrayToObject = function(array)
	{
		// create new object
		var obj = new Object();

		// iterate through each item in the array
		$.each(array, function(i){
			// create new vars for name and value
			var name = array[i].name;
			var value = array[i].value;

			// add item to object
			obj[name] = value;
		});

		return obj;
	}
})(jQuery);
