mxn.register('openlayers', {	

    Mapstraction: {

        init: function(element, api){
            var me = this;
            this.maps[api] = new OpenLayers.Map(
                element.id,
                {
                    maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34),
                    maxResolution:156543,
                    numZoomLevels:18,
                    units:'meters',
                    projection: "EPSG:41001"
                }
            );

            this.layers['osmmapnik'] = new OpenLayers.Layer.TMS(
                'OSM Mapnik',
                [
                "http://a.tile.openstreetmap.org/",
                "http://b.tile.openstreetmap.org/",
                "http://c.tile.openstreetmap.org/"
                ],
                {
                    type:'png',
                    getURL: function (bounds) {
                        var res = this.map.getResolution();
                        var x = Math.round ((bounds.left - this.maxExtent.left) / (res * this.tileSize.w));
                        var y = Math.round ((this.maxExtent.top - bounds.top) / (res * this.tileSize.h));
                        var z = this.map.getZoom();
                        var limit = Math.pow(2, z);
                        if (y < 0 || y >= limit) {
                            return null;
                        } else {
                            x = ((x % limit) + limit) % limit;
                            var path = z + "/" + x + "/" + y + "." + this.type;
                            var url = this.url;
                            if (url instanceof Array) {
                                url = this.selectUrl(path, url);
                            }
                            return url + path;
                        }
                    },
                    displayOutsideMaxExtent: true
                }
            );

            this.layers['osm'] = new OpenLayers.Layer.TMS(
                'OSM',
                [
                "http://a.tah.openstreetmap.org/Tiles/tile.php/",
                "http://b.tah.openstreetmap.org/Tiles/tile.php/",
                "http://c.tah.openstreetmap.org/Tiles/tile.php/"
                ],
                {
                    type:'png',
                    getURL: function (bounds) {
                        var res = this.map.getResolution();
                        var x = Math.round ((bounds.left - this.maxExtent.left) / (res * this.tileSize.w));
                        var y = Math.round ((this.maxExtent.top - bounds.top) / (res * this.tileSize.h));
                        var z = this.map.getZoom();
                        var limit = Math.pow(2, z);
                        if (y < 0 || y >= limit) {
                            return null;
                        } else {
                            x = ((x % limit) + limit) % limit;
                            var path = z + "/" + x + "/" + y + "." + this.type;
                            var url = this.url;
                            if (url instanceof Array) {
                                url = this.selectUrl(path, url);
                            }
                            return url + path;
                        }
                    },
                    displayOutsideMaxExtent: true
                }
            );

            this.maps[api].addLayer(this.layers['osmmapnik']);
            this.maps[api].addLayer(this.layers['osm']);
        },

        applyOptions: function(){
            // var map = this.maps[this.api];
            // var myOptions = [];
            // if (this.options.enableDragging) {
            //     myOptions.draggable = true;
            // } 
            // if (this.options.enableScrollWheelZoom){
            //     myOptions.scrollwheel = true;
            // } 
            // map.setOptions(myOptions);
        },

        resizeTo: function(width, height){	
            this.currentElement.style.width = width;
            this.currentElement.style.height = height;
            this.maps[this.api].updateSize();
        },

        addControls: function( args ) {
            var map = this.maps[this.api];	
            // FIXME: OpenLayers has a bug removing all the controls says crschmidt
            for (var i = map.controls.length; i>1; i--) {
                map.controls[i-1].deactivate();
                map.removeControl(map.controls[i-1]);
            }
            if ( args.zoom == 'large' )      {
                map.addControl(new OpenLayers.Control.PanZoomBar());
            }
            else if ( args.zoom == 'small' ) {
                map.addControl(new OpenLayers.Control.ZoomPanel());
                if ( args.pan) {
                    map.addControl(new OpenLayers.Control.PanPanel()); 
                }
            }
            else {
                if ( args.pan){
                    map.addControl(new OpenLayers.Control.PanPanel()); 
                }
            }
            if ( args.overview ) {
                map.addControl(new OpenLayers.Control.OverviewMap());
            }
            if ( args.map_type ) {
                map.addControl(new OpenLayers.Control.LayerSwitcher());
            }
        },

        addSmallControls: function() {
            var map = this.maps[this.api];
            this.addControlsArgs.pan = false;
            this.addControlsArgs.scale = false;                        
            this.addControlsArgs.zoom = 'small';
            map.addControl(new OpenLayers.Control.ZoomBox());
            map.addControl(new OpenLayers.Control.LayerSwitcher({
                'ascending':false
            }));            
        },

        addLargeControls: function() {
            var map = this.maps[this.api];
            map.addControl(new OpenLayers.Control.PanZoomBar());
            this.addControlsArgs.pan = true;
            this.addControlsArgs.zoom = 'large';
        },

        addMapTypeControls: function() {
            var map = this.maps[this.api];
            map.addControl( new OpenLayers.Control.LayerSwitcher({
                'ascending':false
            }) );
            this.addControlsArgs.map_type = true;
        },

        setCenterAndZoom: function(point, zoom) { 
            var map = this.maps[this.api];
            var pt = point.toProprietary(this.api);
            map.setCenter(point.toProprietary(this.api), zoom);
        },

        addMarker: function(marker, old) {
            var map = this.maps[this.api];
            var pin = marker.toProprietary(this.api);
            if (!this.layers['markers']) {
                this.layers['markers'] = new OpenLayers.Layer.Markers('markers');
                map.addLayer(this.layers['markers']);
            }
            this.layers['markers'].addMarker(pin);

            return pin;
        },

        removeMarker: function(marker) {
            var map = this.maps[this.api];
            var pin = marker.toProprietary(this.api);
            this.layers['markers'].removeMarker(pin);
            pin.destroy();

        },

        removeAllMarkers: function() {
            var map = this.maps[this.api];

            // TODO: Add provider code
        },

        declutterMarkers: function(opts) {
            var map = this.maps[this.api];

            // TODO: Add provider code
        },

        addPolyline: function(polyline, old) {
            var map = this.maps[this.api];
            var pl = polyline.toProprietary(this.api);

            if (!this.layers['polylines']) {
                this.layers['polylines'] = new OpenLayers.Layer.Vector('polylines');
                map.addLayer(this.layers['polylines']);
            }
            polyline.setChild(pl);
            this.layers['polylines'].addFeatures([pl]);
            return pl;
        },

        removePolyline: function(polyline) {
            var map = this.maps[this.api];
            var pl = polyline.toProprietary(this.api);
            this.layers['polylines'].removeFeatures([pl]);
        },
        removeAllPolylines: function() {
            var olpolylines = [];
            for(var i = 0, length = this.polylines.length; i < length; i++){
                olpolylines.push(this.polylines[i].toProprietary(this.api));
            }
            if (this.layers['polylines']) this.layers['polylines'].removeFeatures(olpolylines);            
        },

        getCenter: function() {
            var map = this.maps[this.api];
            pt = map.getCenter();
            return new mxn.LatLonPoint(pt.lat, pt.lon);
        },

        setCenter: function(point, options) {
            var map = this.maps[this.api];
            var pt = point.toProprietary(this.api);
            map.setCenter(pt);
            
        },

        setZoom: function(zoom) {
            var map = this.maps[this.api];
            map.zoomTo(zoom);
        },

        getZoom: function() {
            var map = this.maps[this.api];
            return map.zoom;
        },

        getZoomLevelForBoundingBox: function( bbox ) {
            var map = this.maps[this.api];
            // throw 'Not implemented';
            return zoom;
        },

        setMapType: function(type) {
            var map = this.maps[this.api];
            throw 'Not implemented (setMapType)';

            // switch(type) {
            //     case mxn.Mapstraction.ROAD:
            //     map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
            //     break;
            //     case mxn.Mapstraction.SATELLITE:
            //     map.setMapTypeId(google.maps.MapTypeId.SATELLITE);
            //     break;
            //     case mxn.Mapstraction.HYBRID:
            //     map.setMapTypeId(google.maps.MapTypeId.HYBRID);
            //     break;
            //     default:
            //     map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
            // }	 
        },

        getMapType: function() {
            var map = this.maps[this.api];
            // TODO: implement actual layer support
            return mxn.Mapstraction.ROAD;

            // var type = map.getMapTypeId();
            // switch(type) {
            //     case google.maps.MapTypeId.ROADMAP:
            //     return mxn.Mapstraction.ROAD;
            //     case google.maps.MapTypeId.SATELLITE:
            //     return mxn.Mapstraction.SATELLITE;
            //     case google.maps.MapTypeId.HYBRID:
            //     return mxn.Mapstraction.HYBRID;
            //     //case google.maps.MapTypeId.TERRAIN:
            //     //        return something;
            //     default:
            //     return null;
            // }
        },

        getBounds: function () {
            var map = this.maps[this.api];
            var olbox = map.calculateBounds();
            return new mxn.BoundingBox(olbox.bottom, olbox.left, olbox.top, olbox.right);            
        },

        setBounds: function(bounds){
            var map = this.maps[this.api];
            var sw = bounds.getSouthWest();
            var ne = bounds.getNorthEast();

            if(sw.lon > ne.lon) {
                sw.lon -= 360;
            }

            var obounds = new OpenLayers.Bounds();
            
            obounds.extend(new mxn.LatLonPoint(sw.lat,sw.lon).toProprietary(this.api));
            obounds.extend(new mxn.LatLonPoint(ne.lat,ne.lon).toProprietary(this.api));
            map.zoomToExtent(obounds);
        },

        addImageOverlay: function(id, src, opacity, west, south, east, north, oContext) {
            var map = this.maps[this.api];

            // TODO: Add provider code
        },

        setImagePosition: function(id, oContext) {
            var map = this.maps[this.api];
            var topLeftPoint; var bottomRightPoint;

            // TODO: Add provider code

            //oContext.pixels.top = ...;
            //oContext.pixels.left = ...;
            //oContext.pixels.bottom = ...;
            //oContext.pixels.right = ...;
        },

        addOverlay: function(url, autoCenterAndZoom) {
            var map = this.maps[this.api];

            // TODO: Add provider code

        },

        addTileLayer: function(tile_url, opacity, copyright_text, min_zoom, max_zoom) {
            var map = this.maps[this.api];

            // TODO: Add provider code
        },

        toggleTileLayer: function(tile_url) {
            var map = this.maps[this.api];

            // TODO: Add provider code
        },

        getPixelRatio: function() {
            var map = this.maps[this.api];

            // TODO: Add provider code	
        },

        mousePosition: function(element) {
            var map = this.maps[this.api];

            // TODO: Add provider code	
        }
    },

    LatLonPoint: {

        toProprietary: function() {
            var ollon = this.lon * 20037508.34 / 180;
            var ollat = Math.log(Math.tan((90 + this.lat) * Math.PI / 360)) / (Math.PI / 180);
            ollat = ollat * 20037508.34 / 180;
            return new OpenLayers.LonLat(ollon, ollat);            
        },

        fromProprietary: function(olPoint) {
            var lon = (olPoint.lon / 20037508.34) * 180;
            var lat = (olPoint.lat / 20037508.34) * 180;
            lat = 180/Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2);
            this.lon = lon;
            this.lat = lat;
        }

    },

    Marker: {

        toProprietary: function() {
            var size, anchor, icon;
            if(this.iconSize) {
                size = new OpenLayers.Size(this.iconSize[0], this.iconSize[1]);
            }
            else {
                size = new OpenLayers.Size(21,25);
            }

            if(this.iconAnchor) {
                anchor = new OpenLayers.Pixel(this.iconAnchor[0], this.iconAnchor[1]);
            }
            else {
                // FIXME: hard-coding the anchor point
                anchor = new OpenLayers.Pixel(-(size.w/2), -size.h);
            }

            if(this.iconUrl) {
                icon = new OpenLayers.Icon(this.iconUrl, size, anchor);
            }
            else {
                icon = new OpenLayers.Icon('http://openlayers.org/dev/img/marker-gold.png', size, anchor);
            }
            var marker = new OpenLayers.Marker(this.location.toProprietary("openlayers"), icon);

            if(this.infoBubble) {
                var popup = new OpenLayers.Popup(null,
                    this.location.toProprietary("openlayers"),
                    new OpenLayers.Size(100,100),
                    this.infoBubble,
                    true);
                    popup.autoSize = true;
                    var theMap = this.map;
                    if(this.hover) {
                        marker.events.register("mouseover", marker, function(event) {
                            theMap.addPopup(popup);
                            popup.show();
                        });
                        marker.events.register("mouseout", marker, function(event) {
                            popup.hide();
                            theMap.removePopup(popup);
                        });
                    }
                    else {
                        var shown = false;
                        marker.events.register("mousedown", marker, function(event) {
                            if (shown) {
                                popup.hide();
                                theMap.removePopup(popup);
                                shown = false;
                            } else {
                                theMap.addPopup(popup);
                                popup.show();
                                shown = true;
                            }
                        });
                    }
                }

                if(this.hoverIconUrl) {
                    // TODO
                }

                if(this.infoDiv){
                    // TODO
                }
                return marker;
            },

        openBubble: function() {		
            // TODO: Add provider code
        },

        hide: function() {
            this.proprietary_marker.setOptions({visible:false});
        },

        show: function() {
            this.proprietary_marker.setOptions({visible:true});
        },

        update: function() {
            // TODO: Add provider code
        }

    },

    Polyline: {

        toProprietary: function() {
            var olpolyline;
            var olpoints = [];
            var ring;
            var style = {
                strokeColor: this.color || "#000000",
                strokeOpacity: this.opacity || 1,
                strokeWidth: this.width || 1,
                fillColor: this.fillColor || "#000000",
                fillOpacity: this.getAttribute('fillOpacity') || 0.2
            };

            //TODO Handle closed attribute

            for (var i = 0, length = this.points.length ; i< length; i++){
                olpoint = this.points[i].toProprietary("openlayers");
                olpoints.push(new OpenLayers.Geometry.Point(olpoint.lon, olpoint.lat));
            }

            if (this.closed) {
                // a closed polygon
                ring = new OpenLayers.Geometry.LinearRing(olpoints);
            } else {
                // a line
                ring = new OpenLayers.Geometry.LineString(olpoints);
            }

            olpolyline = new OpenLayers.Feature.Vector(ring, null, style);

            return olpolyline;
        },

        show: function() {
            throw 'Not implemented';
        },

        hide: function() {
            throw 'Not implemented';
        }

    }

});