/**
 * Provides geolocation utilities for clients that support geolocation.
 *
 * @copyright SiteCrafting, Inc.
 * @author Nick Williams
 * @version 1.0.0
 */
SC.Geolocation = {
	/**
	 * Contains the last known position received.
	 */
	last: undefined,
	heading: undefined,
	lastHeading: undefined,
	IP_INFO_KEY: '5fe9aff8d33beb14f0250189978a33ce8a619ce5c779a6febba3459127837602',
	
	/**
	 * Watches the client's current location, auto-triggeriong locationChanged events at regular intervals.
	 * 
	 * @param Object options the desired options for the watch
	 * @return integer the unique ID for the initiated watch task
	 */
	watch: function(options) {
		// Setup
		var result = -1;
		
		if(typeof options === 'number') {
			options = {maximumAge: options};
		}
		
		options = $.extend({
			enableHighAcurracy: true,
			maximumAge: 30000,
			timeout: undefined
		}, options);
		
		if(navigator.geolocation) {
			result = navigator.geolocation.watchPosition(SC.Geolocation.setPosition, function () {
				SC.Geolocation.jsonFallback(SC.Geolocation.setPosition);
			}, options);
		}
		else {
			SC.Geolocation.jsonFallback(SC.Geolocation.setPosition);
		}
		
		return result;
	},

	setPosition: function (position) {
		if(!SC.Geolocation.last || (SC.Geolocation.last.coords.latitude != position.coords.latitude && SC.Geolocation.last.coords.longitude != position.coords.longitude)) {
			SC.Geolocation.last = position;
			$(document).trigger('locationChanged', position);
		}
	},
	
	/**
	 * Triggers the locationChanged event using the last known location.
	 */
	tickle: function() {
		$(document).trigger('locationChanged', SC.Geolocation.last);
	},
	
	/**
	 * Calculates the distance between two coordinates.
	 *
	 * @param Object pointOne an object with latitude and longitude properties for the first point
	 * @param Object pointTwo an object with latitude and longitude properties for the second point
	 * @param string units the measurement unit to be used (M = mile, K = kilometer, N = nautical mile)
	 * @return float the calculated distance, measured in the specified units
	 */
	distance: function(pointOne, pointTwo, units) {
		// Setup
		var result = 0;
		var radlat1 = Math.PI * pointOne.latitude/180;
		var radlat2 = Math.PI * pointTwo.latitude/180;
		var radlon1 = Math.PI * pointOne.longitude/180;
		var radlon2 = Math.PI * pointTwo.longitude/180;
		var theta = pointOne.longitude - pointTwo.longitude;
		var radtheta = Math.PI * theta/180;
		
		// Calculate Distance
		result = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
		
		result = Math.acos(result);
		result = result * 180/Math.PI;
		result = result * 60 * 1.1515;
		
		switch(units) {
			// Miles
			default:
			case 'M':
				// Do Nothing
				break;
			
			// Kilometers
			case 'K':
				result *= 1.609344;
				break;
			
			// Nautical Miles
			case 'N':
				result *= 0.8684;
				break;
		}
		
		return result;
	},
	
	/**
	 * Calculates the bearing to the second point relative to the first (in degrees).
	 *
	 * @param Object pointOne an object with latitude and longitude properties for the first point
	 * @param Object pointTwo an object with latitude and longitude properties for the second point
	 * @return Number the calculated bearing, in degrees
	 */
	bearing: function(pointOne, pointTwo) {
		// Setup
		var result = 0;
		var lat1 = (pointOne.latitude/1).toRad();
		var lat2 = (pointTwo.latitude/1).toRad();
		var dLon = (pointTwo.longitude - pointOne.longitude).toRad();
		
		// Calculate Bearing
		var y = Math.sin(dLon) * Math.cos(lat2);
		var x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
		
		result = Math.atan2(y, x).toBrng();
		
		return result;
	},
	
	/**
	 * Retrieves the calculated travel distance for two points.
	 *
	 * @param Object pointOne an object with latitude and longitude properties for the first point
	 * @param Object pointTwo an object with latitude and longitude properties for the second point
	 * @param Object callback a callback to be triggered when the direction data is retrieved
	 */
	travelDistance: function(pointOne, pointTwo, callback) {
		var service = new google.maps.DistanceMatrixService();
		var request = {
			origins: [new google.maps.LatLng(pointOne.latitude, pointOne.longitude)],
			destinations: [new google.maps.LatLng(pointTwo.latitude, pointTwo.longitude)],
			travelMode: google.maps.TravelMode.DRIVING,
			unitSystem: google.maps.UnitSystem.IMPERIAL,
			avoidHighways: false,
			avoidTolls: false
		};
		
		service.getDistanceMatrix(request, callback);
	},
	
	/**
	 * Retrieves step-by-step directions for the two specified points.
	 *
	 * @param Object pointOne an object with latitude and longitude properties for the first point
	 * @param Object pointTwo an object with latitude and longitude properties for the second point
	 * @param Object callback a callback to be triggered when the direction data is retrieved
	 */
	directions: function(pointOne, pointTwo, callback) {
		var service = new google.maps.DirectionsService();
		var request = {
			origin: new google.maps.LatLng(pointOne.latitude, pointOne.longitude),
			destination: new google.maps.LatLng(pointTwo.latitude, pointTwo.longitude),
			travelMode: google.maps.TravelMode.DRIVING,
			provideRouteAlternatives: false,
			unitSystem: google.maps.UnitSystem.IMPERIAL
		};
		
		service.route(request, callback);
	},

	jsonFallback: function (callback) {
		$.ajax({
			url: (window.navigator.protocol === 'https' ? 'https' : 'http')+'://api.ipinfodb.com/v3/ip-city/?key='+SC.Geolocation.IP_INFO_KEY+'&format=json',
			dataType: 'jsonp',
			success: function (geo) {
				callback({
					coords: {
						latitude: geo.latitude,
						longitude: geo.longitude,
						accuracy: null
					}
				});
			},
			error: function () {
				callback({
					coords: {
						latitude: 0,
						longitude: 0,
						accuracy: null
					}
				});
			}
		});
	}
};

/**
 * Convert degrees to radians.
 */
Number.prototype.toRad = function() {
  return this * Math.PI / 180;
}

/**
 * Convert radians to degrees (signed).
 */
Number.prototype.toDeg = function() {
	return this * 180 / Math.PI;
}

/**
 * Convert radians to degrees (as bearing: 0 - 360).
 */
Number.prototype.toBrng = function() {
  return (this.toDeg()+360) % 360;
}
