var Cathmhaol = window.Cathmhaol || {};

/**
* Map/GPS/distance methods and constants
*
* @author	Robert King (hrobertking@cathmhaol.com)
*
* @example	var fMile = Cathmhaol.Gps.miles(Cathmhaol.Gps.distance(new Cathmhaol.Gps.Location(new Cathmhaol.Gps.Latitude(33, "S", 29, 20.7204), new Cathmhaol.Gps.Longitude(-111, 56, 42.9684)), new Cathmhaol.Gps.Location(new Cathmhaol.Gps.Latitude(33, 31, 52.5936), new Cathmhaol.Gps.Longitude(112, "W", 0, 16.7544))));
*/
Cathmhaol.Gps = {
	/**
	* @property	Radius of the earth at the equator
	* @type	{float}
	*/
	EARTH_RADIUS: 6366.707,

	/**
	* @method	Converts miles to kilometers
	* @returns	{float}
	* @argument	{float} mi
	*/
	kilometers: function(mi) {
		// We attempt to cast the argument as a float. If that doesn't work, we return null; otherwise
		// we divide the provided argument by the MI -> KM constant and return the result.

		mi = parseFloat(mi);
		if (isNaN(mi)) {
			return;
		} else {
			return (mi / 0.621371192);
		}
	},

	/**
	* @method	Converts kilometers into miles
	* @returns	{float}
	* @argument	{float} km
	*/
	miles: function(km) {
		// We attempt to cast the argument as a float. If that doesn't work, we return null; otherwise
		// we divide the provided argument by the KM -> MI constant and return the result.

		km = parseFloat(km);
		if (isNaN(km)) {
			return;
		} else {
			return (km / 1.609344);
		}
	},

	/**
	* @method	Determines the distance between a starting location and an ending location in kilometers
	* @returns	{float}
	* @argument	{Cathmhaol.Gps.Location} start
	* @argument	{Cathmhaol.Gps.Location} stop
	*/
	distance: function(start, stop) {
		// Using the radius of the earth set in this library, we calculate the distance between points on the surface of the
		// earth using the great sphere equation.

		var R_SQR_DBL = Math.pow(this.EARTH_RADIUS, 2) * 2;
		return Math.acos((R_SQR_DBL - (Math.pow(start.x - stop.x, 2) + Math.pow(start.y - stop.y, 2) + Math.pow(start.z - stop.z, 2))) / R_SQR_DBL) * this.EARTH_RADIUS;
	},
};

/**
* @method	Creates a point object.
* @returns	{object}
* @argument	{float} degrees
* @argument	{string} direction
* @argument	{float} minutes
* @argument	{float} seconds
*/
Cathmhaol.Gps.Latitude = function(degrees, direction, minutes, seconds) {
	/**
	* @method	Point description
	* @returns	{string}
	*/
	this.toString = function() {
		var d = this.degrees || 0, o = this.direction || "N", m = this.minutes || 0, s = this.seconds || 0;
		return (d) + (o.toUpperCase()) + " " + ((m < 10 ? "0" : "") + m + "'") + " " + ((s < 10 ? "0" : "") + (Math.floor(s * 100)/100) + '"');
	};

	/**
	* @property	Decimal representation of the latitude
	* @type	{float}
	*/
	this.decimal = null;

	/**
	* @property	Decimal representation of the degrees
	* @type	{float}
	*/
	this.degrees = null;

	/**
	* @property	Cardinal direction. N(orth) or S(outh)
	* @type	{string}
	*/
	this.direction = null;

	/**
	* @property	Decimal representation of the minutes
	* @type	{float}
	*/
	this.minutes = null;

	/**
	* @property	Decimal representation of the radial latitude
	* @type	{float}
	*/
	this.radial = null;

	/**
	* @property	Decimal representation of the seconds
	* @type	{float}
	*/
	this.seconds = null;

	// First we figure out what type of constructor we have and adjust the named arguments accordingly
	if (typeof(direction) == "string" && isNaN(direction)) {
		seconds = seconds || 0;
		minutes = minutes || 0;
		degrees = degrees || 0;
		direction = (degrees < 0 || direction.substr(0, 1).toLowerCase() == "n") ? "N" : "S";
	} else {
		seconds = minutes || 0;
		minutes = direction || 0;
		degrees = degrees || 0;
		direction = (degrees < 0) ? "N" : "S";
	}
	this.degrees = Math.abs(degrees) <= 180 ? Math.abs(degrees) : 0;
	this.minutes = Math.abs(minutes) <= 60 ? Math.abs(minutes) : 0;
	this.seconds = Math.abs(seconds) <= 60 ? Math.abs(seconds) : 0;
	this.decimal = (Math.round((Math.round(this.degrees * 1000000) / 1) + (Math.round(this.minutes * 1000000) / 60) + (Math.round(this.seconds * 1000000) / 3600)) / 1000000) * (this.direction == "N" ? -1 : 1);
	this.radial = Math.PI / 2 + (Math.PI * this.decimal / 180);
};

/**
* @method	Creates a location object based on the two point objects provided.
* @returns	{object}
* @argument	{Cathmhaol.Gps.Latitude} latitude
* @argument	{Cathmhaol.Gps.Longitude} longitude
*/
Cathmhaol.Gps.Location = function(latitude, longitude) {
	/**
	* @property	X Coordinate
	* @type	{float}
	*/
	this.x = (Cathmhaol.Gps.EARTH_RADIUS * Math.cos(longitude.radial) * Math.sin(latitude.radial));

	/**
	* @property	Y Coordinate
	* @type	{float}
	*/
	this.y = (Cathmhaol.Gps.EARTH_RADIUS * Math.sin(longitude.radial) * Math.sin(latitude.radial));

	/**
	* @property	Z Coordinate
	* @type	{float}
	*/
	this.z = (Cathmhaol.Gps.EARTH_RADIUS * Math.cos(latitude.radial));
};

/**
* @method	Creates a point object.
* @returns	{object}
* @argument	{float} degrees
* @argument	{string} direction
* @argument	{float} minutes
* @argument	{float} seconds
*/
Cathmhaol.Gps.Longitude = function() {
	/**
	* @method	Point description
	* @returns	{string}
	*/
	this.toString = function() {
		var d = this.degrees || 0, o = this.direction || "W", m = this.minutes || 0, s = this.seconds || 0;
		return (d) + (o.toUpperCase()) + " " + ((m < 10 ? "0" : "") + m + "'") + " " + ((s < 10 ? "0" : "") + (Math.floor(s * 100)/100) + '"');
	};

	/**
	* @property	Decimal representation of the latitude
	* @type	{float}
	*/
	this.decimal = null;

	/**
	* @property	Decimal representation of the degrees
	* @type	{float}
	*/
	this.degrees = null;

	/**
	* @property	Cardinal direction. N(orth) or S(outh)
	* @type	{string}
	*/
	this.direction = null;

	/**
	* @property	Decimal representation of the minutes
	* @type	{float}
	*/
	this.minutes = null;

	/**
	* @property	Decimal representation of the radial latitude
	* @type	{float}
	*/
	this.radial = null;

	/**
	* @property	Decimal representation of the seconds
	* @type	{float}
	*/
	this.seconds = null;

	// First we figure out what type of constructor we have and adjust the named arguments accordingly
	if (typeof(direction) == "string" && isNaN(direction)) {
		seconds = seconds || 0;
		minutes = minutes || 0;
		degrees = degrees || 0;
		direction = (degrees < 0 || direction.substr(0, 1).toLowerCase() == "w") ? "W" : "E";
	} else {
		seconds = minutes || 0;
		minutes = direction || 0;
		degrees = degrees || 0;
		direction = (degrees < 0) ? "W" : "E";
	}
	this.degrees = Math.abs(degrees) <= 180 ? Math.abs(degrees) : 0;
	this.minutes = Math.abs(minutes) <= 60 ? Math.abs(minutes) : 0;
	this.seconds = Math.abs(seconds) <= 60 ? Math.abs(seconds) : 0;
	this.decimal = (Math.round((Math.round(this.degrees * 1000000) / 1) + (Math.round(this.minutes * 1000000) / 60) + (Math.round(this.seconds * 1000000) / 3600)) / 1000000) * (this.direction == "W" ? -1 : 1);
	this.radial = Math.PI / 2 + (Math.PI * this.decimal / 180);
};

