/**
 * XMaps Library - A library of extensions for the Google Maps API.
 * Release 1c - 30 August 2005
 * http://xmaps.busmonster.com/
 *
 * Copyright 2005 Chris Smoak (chris.smoak@gmail.com)
 *
 * This software comes with no warranty, whatsoever.
 *
 * You can access the full license for this software at:
 * http://xmaps.busmonster.com/license.html
 *
 **/
function XDebug() {};
XDebug.write = function() {}

// XUserAgent is a copy of the user agent code in maps.10.js

// user agent types
XUserAgent.IE = 1;
XUserAgent.MOZILLA = 2;
XUserAgent.SAFARI = 3;
XUserAgent.OPERA = 4;

// user agent OSes
XUserAgent.WIN = 0;
XUserAgent.NIX = 1;
XUserAgent.MAC = 2;

function XUserAgent(type, version, os) { this.type = type; this.version = version; this.os = os };

XUserAgent.create = function() {
    var userAgent = new XUserAgent(0, 0, null);
    var userAgentName = navigator.userAgent.toLowerCase();
    if (userAgentName.indexOf("opera") != -1) {
        userAgent.type = XUserAgent.OPERA;
        if (userAgentName.indexOf("opera/7") != -1 || userAgentName.indexOf("opera 7") != -1) {
            userAgent.version = 7
        } else if (userAgentName.indexOf("opera/8") != -1 || userAgentName.indexOf("opera 8") != -1) {
            userAgent.version = 8
        }
    } else if (userAgentName.indexOf("msie") != -1 && document.all) {
        userAgent.type = XUserAgent.IE;
        if (userAgentName.indexOf("msie 5")) { userAgent.version = 5 }
    } else if (userAgentName.indexOf("safari") != -1) { userAgent.type = XUserAgent.SAFARI }
    else if (userAgentName.indexOf("mozilla") != -1) { userAgent.type = XUserAgent.MOZILLA }
    if (userAgentName.indexOf("x11;") != -1) { userAgent.os = XUserAgent.NIX }
    else if (userAgentName.indexOf("macintosh") != -1) { userAgent.os = XUserAgent.MAC }

    return userAgent;
};


// XDistance provides convenience functions for converting between various
// measures of distance.

XDistance.M = 0;
XDistance.KM = 1;
XDistance.FT = 2;
XDistance.MI = 3;
XDistance.NM = 4;

//XDistance.latConstantMeters = 180 / XDistance.earthRadiusInMetersWGS84 / Math.PI;
//XDistance.latConstantMiles = 180 / XDistance.earthRadiusInMilesWGS84 / Math.PI;

XDistance.conversionTable = [
    [1, 1 / 1000, 3.2808399, 0.000621371192, 0.000539956803],
    [1000, 1, 3280.8399, 0.621371192, 0.539956803],
    [0.3048, 0.0003048, 1, 1 / 5280, 0.000164578834],
    [1609.344, 1.609344, 5280, 1, 0.868976242],
    [1852, 1.85200, 6076.11549, 1.15077945, 1]
];

function XDistance(value, units) {
    this.value = value;
    this.units = units;
}

XDistance.prototype.convert = function(toUnits) { return XDistance.convert(this.value, this.units, toUnits) }

XDistance.prototype.toMeters = function() { return XDistance.convert(this.value, this.units, XDistance.M) }
XDistance.prototype.toKilometers = function() { return XDistance.convert(this.value, this.units, XDistance.KM) }
XDistance.prototype.toFeet = function() { return XDistance.convert(this.value, this.units, XDistance.FT) }
XDistance.prototype.toMiles = function() { return XDistance.convert(this.value, this.units, XDistance.MI) }
XDistance.prototype.toNauticalMiles = function() { return XDistance.convert(this.value, this.units, XDistance.NM) }

XDistance.convert = function(value, fromUnits, toUnits) {
    return value * XDistance.conversionTable[fromUnits][toUnits];
}

XDistance.between = function(point1, point2) {
    return XMaps.model.getDistanceAndAngle(point1, point2).distance;
}

XDistance.resolveToMeters = function(distanceOrMeters) {
    if (typeof distanceOrMeters == 'number') {
        return distanceOrMeters
    } else {
        return distanceOrMeters.toMeters()
    }
}


XAngle.RAD = 0;
XAngle.DEG = 1;

XAngle.radToDeg = 180 / Math.PI;
XAngle.degToRad = Math.PI / 180;

XAngle.conversionTable = [
    [1, XAngle.radToDeg],
    [XAngle.degToRad, 1]
];

XAngle.normalizeRadians = function(rad) {
    while (rad < 0) { rad += 2 * Math.PI }
    while (rad > 2 * Math.PI) { rad -= 2 * Math.PI }
    return rad
}

XAngle.normalizeDegrees = function(deg) {
    while (deg < 0) { deg += 360 }
    while (deg > 360) { deg -= 360 }
    return deg
}

XAngle.normalizationTable = [
    XAngle.normalizeRadians,
    XAngle.normalizeDegrees
];

function XAngle(value, units) {
    this.value = value;
    this.units = units;
}

XAngle.prototype.convert = function(toUnits) { return XAngle.convert(this.value, this.units, toUnits) }

XAngle.prototype.toRadians = function() { return XAngle.convert(this.value, this.units, XAngle.RAD) }

XAngle.prototype.toDegrees = function() { return XAngle.convert(this.value, this.units, XAngle.DEG) }

XAngle.prototype.normalize = function() {
    this.value = XAngle.normalizationTable[this.units](this.value);
}

XAngle.convert = function(value, fromUnits, toUnits) {
    if (fromUnits == toUnits) {
        return value;
    } else {
        return value * XAngle.conversionTable[fromUnits][toUnits];
    }
}

XAngle.radiansToDegrees = function(radians) {
    var deg = radians * XAngle.radToDeg;
    while (deg < 0) { deg += 360 }
    while (deg >= 360) { deg -= 360 }
    return deg
}

XAngle.degreesToRadians = function(degrees) {
    var rad = degrees * XAngle.degToRad;
    while (rad < 0) { rad += 2 * Math.PI }
    while (rad >= 2 * Math.PI) { rad -= 2 * Math.PI }
    return rad
}

XAngle.resolveToRadians = function(angleOrRadians) {
    if (typeof angleOrRadians == 'number') {
        return XAngle.normalizeRadians(angleOrRadians)
    } else {
        angleOrRadians.normalize();
        return angleOrRadians.toRadians()
    }
}


function XDistanceAndAngle(distance, angle) {
    this.distance = distance;
    this.angle = angle;
}


// XModel is a model of the world we're mapping.
// The meat of this class is from the source code of Navigate for Palm OS,
// written by Rick Chapman.
// http://fermi.jhuapl.edu/navigate/index.html

function XModel(flatteningFactor, semimajorAxis) {
    this.flatteningFactor = flatteningFactor;
    this.semimajorAxisInMeters = semimajorAxis.toMeters();
}

XModel.prototype.getDistanceAndAngle = function(from, to, distance, angle, distanceAndAngle) {
    distance = distance || new XDistance();
    distance.units = XDistance.M;

    angle = angle || new XAngle();
    angle.units = XAngle.RAD;

    distanceAndAngle = distanceAndAngle || new XDistanceAndAngle();
	distanceAndAngle.distance = distance;
	distanceAndAngle.angle = angle;

    var f = 1 / this.flatteningFactor;
    var ra = this.semimajorAxisInMeters;
    var eps = 5e-14;

    var r = 1 - f;
    var tu1 = r * Math.tan(from.y / XAngle.radToDeg);
    var tu2 = r * Math.tan(to.y / XAngle.radToDeg);
    var cu1 = 1 / Math.sqrt(tu1 * tu1 + 1);
    var su1 = cu1 * tu1;
    var cu2 = 1 / Math.sqrt(tu2 * tu2 + 1);
    var s = cu1 * cu2;
    var baz = s * tu2;
    var faz = baz * tu1;
    var x = (to.x - from.x) / XAngle.radToDeg;

    var d, sx, cx, sy, cy, cz, c2a, c, e, y, sa;
    var count = 0;
	do {
		sx = Math.sin(x);
		cx = Math.cos(x);
		tu1 = cu2 * sx;
		tu2 = baz - su1 * cu2 * cx;
		sy = Math.sqrt(tu1 * tu1 + tu2 * tu2);
		cy = s * cx + faz;
		y = Math.atan2(sy, cy);
		sa = s * sx / sy;
		c2a = -sa * sa + 1;
		cz = faz + faz;
		if (c2a > 0) { cz = -cz / c2a + cy }
		e = cz * cz * 2 - 1;
		c = ((-3 * c2a + 4) * f + 4) * c2a * f / 16;
		d = x;
		x = ((e * cy * c + cz) * sy * c + y) * sa;
		x = (1 - c) * x * f + (to.x - from.x) / XAngle.radToDeg;
	} while (Math.abs(d - x) > eps || count++ == 100);
	faz = Math.atan2(tu1, tu2);
	baz = Math.atan2(cu1 * sx, baz * cx - su1 * cu2) + Math.PI;
	x = Math.sqrt((1 / r / r - 1) * c2a + 1) + 1;
	x = (x - 2) / x;
	c = 1 - x;
	c = (x * x / 4 + 1) / c;
	d = (0.375 * x * x - 1) * x;
	x = e * cy;
	s = 1 - e * e;
	s = ((((sy * sy * 4 - 3) * s * cz * d / 6 - x) * d / 4 + cz) * sy * d + y) * c * ra * r;

	distance.value = s;
	angle.value = XAngle.normalizeRadians(faz);

	return distanceAndAngle;
}

XModel.prototype.getPointAtDistanceAndAngle = function(point, distance, angle, outPoint) {
    var meters = XDistance.resolveToMeters(distance);
    var radians = XAngle.resolveToRadians(angle);

    outPoint = outPoint || new GPoint();

	var f = 1 / this.flatteningFactor;
	var ra = this.semimajorAxisInMeters;
	var eps = 1e-13;
	var r = 1 - f;
	var tu = r * Math.tan(point.y * XAngle.degToRad);
	var sf = Math.sin(radians);
	var cf = Math.cos(radians);
	var baz = Math.atan2(tu, cf) * 2;
	var cu = 1 / Math.sqrt(tu * tu + 1);
	var su = tu * cu;
	var sa = cu * sf;
	var c2a = 1 - sa * sa;
	var x = Math.sqrt((1 / r / r - 1) * c2a + 1) + 1;
	var c, d, y, sy, cy, cz, e;

	x = (x - 2) / x;
	c = 1 - x;
	c = (x * x / 4 + 1) / c;
	d = (0.375 * x * x - 1) * x;
	tu = meters / r / ra / c;
	y = tu;
	var count = 0;
	do {
		sy = Math.sin(y);
		cy = Math.cos(y);
		cz = Math.cos(baz + y);
		e = cz * cz * 2 - 1;
		c = y;
		x = e * cy;
		y = e * e - 1;
		y = (((sy * sy * 4 - 3) * y * cz * d / 6 + x) * d / 4 - cz) * sy * d + tu;
	} while(Math.abs(y - c) > eps || count++ == 100);
	baz = cu * cy * cf - su * sy;
	c = r * Math.sqrt(sa * sa + baz * baz);
	d = su * cy + cu * sy * cf;
	var lat2 = Math.atan2(d, c) / XAngle.degToRad;
	c = cu * cy - su * sy * cf;
	x = Math.atan2(sy * sf, c);
	c = ((-3 * c2a + 4) * f + 4) * c2a * f / 16;
	d = ((e * cy * c + cz) * sy * c + y) * sa;
	var lng2 = (point.x * XAngle.degToRad + x - (1 - c) * d * f) / XAngle.degToRad;
	baz = Math.atan2(sa, baz) + Math.PI;

	outPoint.x = lng2;
	outPoint.y = lat2;

	return outPoint
}

XModel.earth = new XModel(298.257223563, new XDistance(6378137, XDistance.M));


// XMaps is the namespace for functions in GMaps that are not exposed but should be along
// with a bunch of other useful code. Some of these functions are copied from maps.10.js.

function XMaps() {};

XMaps.getElementById = function(id) { return document.getElementById(id) }

XMaps.userAgent = XUserAgent.create();

XMaps.toPixelString = function(pixels) { return Math.round(pixels) + "px" }

XMaps.addClassName = function(element, className) {
    if(element.className) {
        element.className += " " + className
    } else {
        element.className = className
    }
}

XMaps.falseFunction = function() { return false }

XMaps.integerCompare = function(a, b) { return (a < b) ? -1 : ((a > b) * 1); }

XMaps.defaultColor = { red: 0, green: 0, blue: 0xff };

XMaps.parseColor = function(color, defaultColor) {
    try {
        if (color.charAt(0) == "#") {
            color = color.substring(1)
        }
        var red = parseInt(color.substring(0, 2), 16);
        var green = parseInt(color.substring(2, 4), 16);
        var blue = parseInt(color.substring(4, 6), 16);
        return { red: red, green: green, blue: blue };
    } catch (e) {
        if (typeof defaultColor == 'object') {
            return defaultColor
        } else {
            return XMaps.parseColor(defaultColor, XMaps.defaultColor)
        }
    }
}


XMaps.imageBaseUrl = "http://www.google.com/mapfiles/";

XMaps.getRotationParameters = function(from, to, autoRotate, autoRotateAngle, outRotationParameters) {
    outRotationParameters = outRotationParameters || {};

    var radians = Math.atan2(to.y - from.y, to.x - from.x);
    if (autoRotate) {
        if (typeof autoRotateAngle == 'undefined') {
            autoRotateAngle = outRotationParameters.autoRotateAngle || new XAngle();
            autoRotateAngle.value = 0;
            autoRotateAngle.units = XAngle.RAD;

            while (radians < -Math.PI / 2) {
                radians += Math.PI;
                autoRotateAngle.value = Math.PI
            }
            while (radians > Math.PI / 2) {
                radians -= Math.PI;
                autoRotateAngle.value = -Math.PI
            }
        } else {
            radians += XAngle.resolveToRadians(autoRotateAngle);
        }
    }
    var sin = Math.sin(radians);
    var cos = Math.cos(radians);

    outRotationParameters.cos = cos;
    outRotationParameters.sin = sin;
    outRotationParameters.angle = outRotationParameters.angle || new XAngle(radians, XAngle.RAD);
    outRotationParameters.autoRotateAngle = autoRotateAngle;
    return outRotationParameters;
};

XMaps.model = XModel.earth;

XMaps.combineStyles = function(overridingStyle, baseStyle, outStyle) {
    outStyle = outStyle || {};

    if (baseStyle) {
        for (key in baseStyle) {
            outStyle[key] = baseStyle[key]
        }
    }

    if (overridingStyle) {
        for (key in overridingStyle) {
            outStyle[key] = overridingStyle[key]
        }
    }

    return outStyle
}

XMaps.setTimeout = function (object, method, milliseconds) {
    var result = window.setTimeout(
            function() { method.apply(object) },
            milliseconds);
    return result
}

XMaps.toFixedString = function (value) {
    if (value.toFixed) {
        return value.toFixed(6).toString()
    } else {
        return value.toString()
    }
}


