(function(moduleName) {

//----------
// UTILITIES
//----------

    $m = function() {
        var elements = [];
        for (var i = 0; i < arguments.length; i++) {
            var element = arguments[i];
            if (typeof(element) == 'string') {
                element = document.getElementById(element);
            }
            if (arguments.length == 1) {
                return element;
            }
            elements.push(element);
        }
        return elements;
    }
    
//-------------
// MAPSTRACTION
//-------------

	mxn.Mapstraction = function(element, api, key, debug) {
        this.api = api; // could detect this from imported scripts?
        this.maps = {};
        this.currentElement = $m(element);
        this.eventListeners = [];
        this.markers = [];
        this.layers = [];
        this.polylines = [];
        this.images = [];
        this.loaded = {};
        this.onload = {};

        // Mapstraction.writeInclude(api, "nothing");

        // optional debug support
        if (debug === true) {
            this.debug = true;
        }
        else {
            this.debug = false;
        }

        // This is so that it is easy to tell which revision of this file
        // has been copied into other projects.
        this.svn_revision_string = 'mxnRevision: 208 mxn';
        this.addControlsArgs = {};

        // if (this.currentElement) {
        this.addAPI($m(element), api, key);
    // }
	};
	
	mxn.Mapstraction.prototype.checkLoad = function(funcDetails){
	    if(this.loaded[this.api] === false) {
		    var scope = this;
		    this.onload[this.api].push( function() { funcDetails.callee.apply(scope, funcDetails); } );
		    return true;
	    }
	    return false;
    }

	mxn.Mapstraction.prototype.addAPI = function(element, api, key) {
   		this.loaded[api] = false;
        this.onload[api] = [];

        return mxn.invoke(this.api, moduleName, 'addAPI', this, arguments);
	};
	
    mxn.Mapstraction.prototype.moveendHandler = function(me) {
        this.callEventListeners('moveend', {});
    };
    
    mxn.Mapstraction.prototype.callEventListeners = function(sEventType, oEventArgs) {
        oEventArgs.source = this;
        for(var i = 0; i < this.eventListeners.length; i++) {
            var evLi = this.eventListeners[i];
            if(evLi.event_type == sEventType) {
                // only two cases for this, click and move
                if(evLi.back_compat_mode) {
                    if(evLi.event_type == 'click') {
                        evLi.callback_function(oEventArgs.location);
                    }
                    else {
                        evLi.callback_function();
                    }
                }
                else {
                    var scope = evLi.callback_object || this;
                    evLi.callback_function.call(scope, oEventArgs);
                }
            }
        }
    };

	mxn.Mapstraction.prototype.setCenterAndZoom = function(point, zoom) {
	    if (this.checkLoad(arguments)) return;
        return mxn.invoke(this.api, moduleName, 'setCenterAndZoom', this, arguments);
    };

    mxn.Mapstraction.prototype.getBounds = function() {
	    if (this.checkLoad(arguments)) return;
        return mxn.invoke(this.api, moduleName, 'getBounds', this, arguments);
    };

	mxn.Mapstraction.prototype.addMarker = function(marker, old) {
       	if (this.checkLoad(arguments)) return;
        
        marker.mapstraction = this;
        marker.api = this.api;
        marker.location.api = this.api;
        marker.map = this.maps[this.api];
   
        return mxn.invoke(this.api, moduleName, 'addMarker', this, arguments);
    };
    
    mxn.Mapstraction.prototype.addOverlay = function(url, autoCenterAndZoom) {
	    if (this.checkLoad(arguments)) return;
        if(autoCenterAndZoom == null) autoCenterAndZoom = false;
    };

//------------
// BOUNDINGBOX
//------------

    mxn.BoundingBox = function(swlat, swlon, nelat, nelon) {
        //FIXME throw error if box bigger than world
        //alert('new bbox ' + swlat + ',' +  swlon + ',' +  nelat + ',' + nelon);
        this.sw = new mxn.LatLonPoint(swlat, swlon);
        this.ne = new mxn.LatLonPoint(nelat, nelon);
    };

    /**
     * getSouthWest returns a LatLonPoint of the south-west point of the bounding box
     * @returns the south-west point of the bounding box
     * @type LatLonPoint
     */
    mxn.BoundingBox.prototype.getSouthWest = function() {
        return this.sw;
    };

    /**
     * getNorthEast returns a LatLonPoint of the north-east point of the bounding box
     * @returns the north-east point of the bounding box
     * @type LatLonPoint
     */
    mxn.BoundingBox.prototype.getNorthEast = function() {
        return this.ne;
    };

    /**
     * isEmpty finds if this bounding box has zero area
     * @returns whether the north-east and south-west points of the bounding box are the same point
     * @type boolean
     */
    mxn.BoundingBox.prototype.isEmpty = function() {
        return this.ne == this.sw; // is this right? FIXME
    };

    /**
     * contains finds whether a given point is within a bounding box
     * @param {LatLonPoint} point the point to test with
     * @returns whether point is within this bounding box
     * @type boolean
     */
    mxn.BoundingBox.prototype.contains = function(point){
        return point.lat >= this.sw.lat && point.lat <= this.ne.lat && point.lon >= this.sw.lon && point.lon <= this.ne.lon;
    };

    /**
     * toSpan returns a LatLonPoint with the lat and lon as the height and width of the bounding box
     * @returns a LatLonPoint containing the height and width of this bounding box
     * @type LatLonPoint
     */
    mxn.BoundingBox.prototype.toSpan = function() {
        return new mxn.LatLonPoint( Math.abs(this.sw.lat - this.ne.lat), Math.abs(this.sw.lon - this.ne.lon) );
    };

    /**
     * extend extends the bounding box to include the new point
     */
    mxn.BoundingBox.prototype.extend = function(point) {
        if(this.sw.lat > point.lat) {
            this.sw.lat = point.lat;
        }
        if(this.sw.lon > point.lon) {
            this.sw.lon = point.lon;
        }
        if(this.ne.lat < point.lat) {
            this.ne.lat = point.lat;
        }
        if(this.ne.lon < point.lon) {
            this.ne.lon = point.lon;
        }
        return;
    };
    
//------------
// LATLONPOINT
//------------

	mxn.LatLonPoint = function(lat, lon) {
        // TODO error if undefined?
        //  if (lat == undefined) alert('undefined lat');
        //  if (lon == undefined) alert('undefined lon');
        this.lat = lat;
        this.lon = lon;
        this.lng = lon; // lets be lon/lng agnostic
    };

//-------
// MARKER
//-------

    mxn.Marker = function(point) {
        this.location = point;
        this.onmap = false;
        this.proprietary_marker = false;
        this.attributes = [];
        this.pinID = "mspin-"+new Date().getTime()+'-'+(Math.floor(Math.random()*Math.pow(2,16)));
    };
    
    mxn.Marker.prototype.setChild = function(some_proprietary_marker) {
        this.proprietary_marker = some_proprietary_marker;
        this.onmap = true;
    };
        	
    // Register
	mxn.registerModule(moduleName);

})('mapstraction');