var Cathmhaol = window.Cathmhaol || {};

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

	/**
	* @method
	* @description	Converts miles to kilometers
	* @returns {float}
	* @param {float} mi
	*/
	kilometers: function(mi) {
		mi = parseFloat(mi);
		if (isNaN(mi)) {
			return;
		} else {
			return (mi / 0.621371192);
		}
	},

	/**
	* @method
	* @description	Converts kilometers into miles
	* @returns {float}
	* @param {float} km
	*/
	miles: function(km) {
		km = parseFloat(km);
		if (isNaN(km)) {
			return;
		} else {
			return (km / 1.609344);
		}
	},

	/**
	* @method
	* @description	 Determines the distance between a starting location and an ending location in kilometers
	* @returns float
	* @param Cathmhaol.Cps.Location start
	* @param Cathmhaol.Cps.Location stop
	*/
	distance: function(start, stop) {
		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;
	}
};

/**
* @description	Creates a latitude object, returning true if created successfully, false if not.
* @returns {boolean}
* @param {float} deg	Degrees
* @param {string} dir	Cardinal direction. Valid values are n (north) or s (south) [optional]
* @param {float} min	Minutes
* @param {float} sec	Seconds
*/
Cathmhaol.Gps.Latitude = function() {
	switch (arguments.length) {
		case 3: this._initialize(arguments[0], arguments[1], arguments[2]); break;
		case 4: this._initialize2(arguments[0], arguments[1], arguments[2], arguments[3]); break;
		default: return false;
	}
};
Cathmhaol.Gps.Latitude.prototype = {
	/**
	* @property
	* @description	Decimal representation of the latitude
	* @type {float}
	*/
	decimal: null,

	/**
	* @property
	* @description	Decimal representation of the degrees
	* @type {float}
	*/
	degrees: null,

	/**
	* @property
	* @description	Cardinal direction. N(orth) or S(outh)
	* @type {string}
	*/
	direction: null,

	/**
	* @property
	* @description	Decimal representation of the minutes
	* @type {float}
	*/
	minutes: null,

	/**
	* @property
	* @description	Decimal representation of the radial latitude
	* @type {float}
	*/
	radial: null,

	/**
	* @property
	* @description	Decimal representation of the seconds
	* @type {float}
	*/
	seconds: null,

	/**
	* @private
	* @methods
	* @description	Initializes the sign of the degrees, the decimal longitude, and the radial longitude
	* @returns {void}
	*/
	_initCalc: function() {
		this.decimal = Math.round((Math.round(this.degrees * 1000000) / 1) + (Math.round(this.minutes * 1000000) / 60) + (Math.round(this.seconds * 1000000) / 3600)) / 1000000;
		if (this.direction == "N") { this.decimal = this.decimal * -1; }
		this.radial = Math.PI / 2 + (Math.PI * this.decimal / 180);
	},

	/**
	* @private
	* @method
	* @description	Initializes the longitude object using signed shorthand for direction. Returns false if initialization is incomplete.
	* @returns {boolean}
	* @param {float} degrees
	* @param {float} minutes
	* @param {float} seconds
	*/
	_initialize: function(degrees, minutes, seconds) {
		try {
			if (Math.abs(degrees) <= 180) { this.degrees = Math.abs(degrees); }
			if (degrees < 0) { this.direction = "N" } else { this.direction = "S"; }
			if (Math.abs(minutes) <= 60) { this.minutes = Math.abs(minutes); }
			if (Math.abs(seconds) <= 60) { this.seconds = Math.abs(seconds); }
			this._initCalc();
		} catch(e) {
			return false;
		}
	},

	/**
	* @private
	* @method
	* @description	Initializes the longitude object using all four pieces of information. Returns false if initialization is incomplete.
	* @returns {boolean}
	* @param {float} degrees
	* @param {string} direction	Cardinal direction. Valid values are N(orth) or S(outh)
	* @param {float} minutes
	* @param {float} seconds
	*/
	_initialize2: function(degrees, direction, minutes, seconds) {
		try {
			if (Math.abs(degrees) <= 90) { this.degrees = Math.abs(degrees); }
			if (degrees < 0 || direction.substr(0, 1).toLowerCase() == "n") { this.direction = "N" } else { this.direction = "S"; }
			if (Math.abs(minutes) <= 60) { this.minutes = Math.abs(minutes); }
			if (Math.abs(seconds) <= 60) { this.seconds = Math.abs(seconds); }
			this._initCalc();
		} catch(e) {
			return false;
		}
	}
};

/**
* @description	Creates a location object, returning true if created successfully, false if not.
* @returns {boolean}
* @param {Cathmhaol.Gps.Latitude} lat
* @param {Cathmhaol.Gps.Longitude} lon
*/
Cathmhaol.Gps.Location = function(lat, lon) {
	return this._initialize(lat, lon);
};
Cathmhaol.Gps.Location.prototype = {
	/**
	* @property
	* @description	X Coordinate
	* @type {float}
	*/
	x: null,

	/**
	* @property
	* @description	Y Coordinate
	* @type {float}
	*/
	y: null,

	/**
	* @property
	* @description	Z Coordinate
	* @type {float}
	*/
	z: null,

	/**
	* @private
	* @method	_initialize
	* @description	Initializes the location using the latitude and longitude objects provided. Returns true if initialized correctly, false if not
	* @returns {boolean}
	* @param {Cathmhaol.Gps.Latitude} lat
	* @param {Cathmhaol.Gps.Longitude} long
	*/
	_initialize: function(lat, lon) {
		try {
			var coslat = Math.cos(lat.radial);
			var coslon = Math.cos(lon.radial);
			var sinlat = Math.sin(lat.radial);
			var sinlon = Math.sin(lon.radial);

			this.x = (Cathmhaol.Gps.EARTH_RADIUS * coslon * sinlat);
			this.y = (Cathmhaol.Gps.EARTH_RADIUS * sinlon * sinlat);
			this.z = (Cathmhaol.Gps.EARTH_RADIUS * coslat);

			return (!isNaN(this.x) && !isNaN(this.y) && !isNaN(this.z));
		} catch (e) {
			return false;
		}
	}
};

/**
* @description	Creates a longitude object, returning true if created successfully, false if not.
* @returns {boolean}
* @param {float} deg	Degrees
* @param {string} dir	Cardinal direction. Valid values are e (east) or w (west) [optional]
* @param {float} min	Minutes
* @param {float} sec	Seconds
*/
Cathmhaol.Gps.Longitude = function() {
	switch (arguments.length) {
		case 3: this._initialize(arguments[0], arguments[1], arguments[2]); break;
		case 4: this._initialize2(arguments[0], arguments[1], arguments[2], arguments[3]); break;
		default: return false;
	}
};
Cathmhaol.Gps.Longitude.prototype = {
	/**
	* @property
	* @description	Decimal representation of the longitude
	* @type {float}
	*/
	decimal: null,

	/**
	* @property
	* @description	Decimal representation of the degrees
	* @type {float}
	*/
	degrees: null,

	/**
	* @property
	* @description	Cardinal direction. E(ast) or W(est)
	* @type {string}
	*/
	direction: null,

	/**
	* @property
	* @description	Decimal representation of the minutes
	* @type {float}
	*/
	minutes: null,

	/**
	* @property
	* @description	Decimal representation of the radial longitude
	* @type {float}
	*/
	radial: null,

	/**
	* @property
	* @description	Decimal representation of the seconds
	* @type {float}
	*/
	seconds: null,

	/**
	* @private
	* @methods
	* @description	Initializes the sign of the degrees, the decimal longitude, and the radial longitude
	* @returns {void}
	*/
	_initCalc: function() {
		this.decimal = Math.round((Math.round(this.degrees * 1000000) / 1) + (Math.round(this.minutes * 1000000) / 60) + (Math.round(this.seconds * 1000000) / 3600)) / 1000000;
		if (this.direction == "W") { this.decimal = this.decimal * -1; }
		this.radial = Math.PI * 2 + (Math.PI * this.decimal / 180);
	},

	/**
	* @private
	* @method
	* @description	Initializes the longitude object using signed shorthand for direction. Returns false if initialization is incomplete.
	* @returns {boolean}
	* @param {float} degrees
	* @param {float} minutes
	* @param {float} seconds
	*/
	_initialize: function(degrees, minutes, seconds) {
		try {
			if (Math.abs(degrees) <= 180) { this.degrees = Math.abs(degrees); }
			if (degrees < 0) { this.direction = "W" } else { this.direction = "E"; }
			if (Math.abs(minutes) <= 60) { this.minutes = Math.abs(minutes); }
			if (Math.abs(seconds) <= 60) { this.seconds = Math.abs(seconds); }
			this._initCalc();
		} catch(e) {
			return false;
		}
	},

	/**
	* @private
	* @method
	* @description	Initializes the longitude object using all four pieces of information. Returns false if initialization is incomplete.
	* @returns {boolean}
	* @param {float} degrees
	* @param {string} direction	Cardinal direction. Valid values are E(ast) or W(est)
	* @param {float} minutes
	* @param {float} seconds
	*/
	_initialize2: function(degrees, direction, minutes, seconds) {
		try {
			if (Math.abs(degrees) <= 180) { this.degrees = Math.abs(degrees); }
			if (degrees < 0 || direction.substr(0, 1).toLowerCase() == "w") { this.direction = "W" } else { this.direction = "E"; }
			if (Math.abs(minutes) <= 60) { this.minutes = Math.abs(minutes); }
			if (Math.abs(seconds) <= 60) { this.seconds = Math.abs(seconds); }
			this._initCalc();
		} catch(e) {
			return false;
		}
	}
};
