var MapKit = Class.create({
    initialize: function(element, options) {
        // we'll define self in the beginning of every class method so
        // we can use "this" in JQuery and Prototype blocks and not get confused
        var self = this;

        self.mapElement = element || "map";
        self.movedWindow = false;
        self.currentMarker = null;
        self.showDebug = true;
        self.markers = $H({});
        self.managers = $H({});
        self.managerMarkers = $H({});
        self.feeds = $H({});
        self.feedRefreshFlags = $H({});
        self.refreshPeopleFlag = true;
        self.refreshSessionFlag = true;
    
        self.socialBrowsingFlag = false;
        self.myMarker = null;
        self.myLatLng = null;
        self.personIcon = 1;
    
        self.adsManager = null;

        self.mapOptions = MapKit.mergeOptions({}, self.mapOptions, options);
        MapKit.set(this);
    },
  
    initMapCenter: function(centerLat, centerLng, centerZoomLevel) {
        var self = this;
        if(self.mapOptions.mapCenter != undefined)
        {
            return;
        }
    
        // initialize map center
        if(centerLat != "" && centerLng != "" && centerZoomLevel != "")
        {
            self.mapOptions.mapCenter = [parseFloat(centerLat), parseFloat(centerLng)];
            self.mapOptions.mapZoom = parseInt(centerZoomLevel);
        }
    },

    initViewCenter: function(centerLat, centerLng, centerZoomLevel) {
        var self = this;
        if(self.mapOptions.mapCenter != undefined)
        {
            return;
        }

        // initialize view center
        if(centerLat != "" && centerLng != "" && centerZoomLevel != "")
        {
            self.mapOptions.mapCenter = [parseFloat(centerLat), parseFloat(centerLng)];
            self.mapOptions.mapZoom = parseInt(centerZoomLevel);
        }
    },
  
    enableSocialBrowsing: function(lat, lng) {
        var self = this;
        self.socialBrowsingFlag = true;
        if(lat != "" && lng != "")
        {
            self.myLatLng = [parseFloat(lat), parseFloat(lng)];
        }
    },
  
    setup: function() {
        var self = this;
    
        if(self.myLatLng)
        {
            self.mapOptions.mapCenter = self.myLatLng;
        }
    
        if(self.mapOptions.mapCenter == undefined || self.mapOptions.mapZoom == undefined)
        {
            self.mapOptions.mapCenter = [self.mapOptions.default_lat, self.mapOptions.default_lng];
            self.mapOptions.mapZoom = self.mapOptions.default_zoom;
        }
    
        // init map
        self.initGoogleMap();
    
        // configure adsense
        if(self.mapOptions.enable_ads && self.mapOptions.publisher_id != undefined && self.mapOptions.publisher_id != "")
        {
            self.adsManager = new GAdsManager(self.gmap, self.mapOptions.publisher_id,
            {
                max_ads_on_map: self.mapOptions.max_ads_on_map,
                minZoomLevel: self.mapOptions.min_zoom_level_for_ads
            }
            );
            self.adsManager.enable();
        }
    
        // configure session tracker
        if(self.mapOptions.refresh_session_interval > 0)
        {
            GEvent.addListener(self.gmap, "moveend", function() {
                self.refreshSessionFlag = true;
            });
            new PeriodicalExecuter(function() {
                self.refreshSession();
            }, self.mapOptions.refresh_session_interval);
        }
        else
        {
            GEvent.addListener(self.gmap, "moveend", function() {
                self.refreshSessionFlag = true;
                self.refreshSession();
            });
        }
    
        // configure feed tracker
        if(self.mapOptions.refresh_feeds_interval > 0)
        {
            new PeriodicalExecuter(function() {
                self.refreshFeeds();
            }, self.mapOptions.refresh_feeds_interval);
        }
    
        // configure social browsing
        if(self.socialBrowsingFlag)
        {
            if(self.mapOptions.refresh_people_interval > 0)
            {
                GEvent.addListener(self.gmap, "moveend", function() {
                    self.refreshPeopleFlag = true;
                });
        
                new PeriodicalExecuter(function() {
                    self.refreshPeople();
                }, self.mapOptions.refresh_people_interval);
            }
            else
            {
                GEvent.addListener(self.gmap, "moveend", function() {
                    self.refreshPeopleFlag = true;
                    self.refreshPeople();
                });
            }
            self.createManager("people")
            self.refreshPeople();
      
            // default my latlng
            if(self.myLatLng == null)
            {
                mapCenter = self.gmap.getCenter();
                self.myLatLng = [mapCenter.lat(), mapCenter.lng()];
            }
      
            // set default title for my marker
            var myMarkerTitle = $j("#my_marker_content .marker").attr("title");
            if(myMarkerTitle == "" || myMarkerTitle == undefined)
            {
                myMarkerTitle = "My Marker";
            }
      
            // set up my marker
            self.myMarker = self.buildMarker("my_marker", {
                draggable: true,
                latLng: self.myLatLng,
                title: myMarkerTitle,
                bubbleStyle: self.mapOptions.my_bubble_style,
                getHtml: function() {
                    return "<div id='my_marker_bubble'>"+$j("#my_marker_content").html()+"</div>";
                }
            });
      
            // when starting to drag, close my marker bubble
            GEvent.addListener(self.myMarker, "dragstart", function() {
                self.gmap.closeExtInfoWindow();
            });
      
            // when ending drag, open up my marker bubble
            GEvent.addListener(self.myMarker, "dragend", function() {
                newCenter = self.myMarker.getPoint();
                self.myLatLng = [newCenter.lat(), newCenter.lng()];
                self.myMarker.openBubble();
                self.refreshSessionFlag = true;
                self.refreshSession();
            });

      
      
            self.gmap.addOverlay(self.myMarker);
        }
    
        // create the default marker manager
        self.createManager("default");
        self.bindUnload();

        return self.gmap;
    },
  
    // Add a Dynamic Loading Marker Feed
    addFeed: function(feedLink){
        var self = this;
    
        self.feeds.set(feedLink.id, feedLink.href);
        var manager = self.createManager(feedLink.id)
        self.refreshFeed(feedLink.id);
    
        if(self.mapOptions.refresh_feeds_interval > 0)
        {
            GEvent.addListener(self.gmap, "moveend", function() {
                self.feedRefreshFlags.set(feedLink.id, true);
            });
        }
        else
        {
            GEvent.addListener(self.gmap, "moveend", function() {
                self.refreshFeed(feedLink.id);
            });
        }
    },
  
    initGoogleMap: function() {
        var self = this;

        // Initialise the map with the passed settings
        self.gmap = new GMap2(document.getElementById(self.mapElement));
        self.gmap.setCenter(new GLatLng(self.mapOptions.mapCenter[0], self.mapOptions.mapCenter[1]),
            self.mapOptions.mapZoom);

        // set enable type position
        var typePosition = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(75, 5));

        // Attach a controller to the map view
        // Will attach a large or small.  If any other value passed (i.e. "none") it is ignored
        switch(self.mapOptions.map_control_style)
        {
            case "default-small":
                self.gmap.addControl(new GSmallMapControl());
                self.gmap.addControl(new GMapTypeControl(), typePosition);
                if(self.mapOptions.map_enable_scale_control) {
                    self.gmap.addControl(new GScaleControl());
                }
                break;
            case "default-large":
                self.gmap.addControl(new GLargeMapControl());
                self.gmap.addControl(new GMapTypeControl(), typePosition);
                if(self.mapOptions.map_enable_scale_control) {
                    self.gmap.addControl(new GScaleControl());
                }
                break;
            default:
                self.gmap.addControl(new MapKitZoomControl({
                    color: self.mapOptions.map_control_color
                }));
                self.gmap.addControl(new MapKitPanControl({
                    color: self.mapOptions.map_control_color
                }));
                self.gmap.addControl(new MapKitMapTypeControl({
                    color: self.mapOptions.map_control_color
                }));
        }
    
        // set map type
        var maptype = G_NORMAL_MAP;
        switch(self.mapOptions.mapType) {
            case "sat": // Satallite Imagery
                maptype = G_SATELLITE_MAP;
                break;
            case "hybrid":  //Hybrid Map
                maptype = G_HYBRID_MAP;
                break;
        }
        self.gmap.setMapType(maptype);
    
        // add map overview window if enabled
        if(self.mapOptions.map_enable_overview)
        {
            self.gmap.addControl(new GOverviewMapControl());
        }
    
        // add double click zooming capability if enabled
        if(self.mapOptions.map_enable_double_click_zoom)
        {
            self.gmap.enableDoubleClickZoom();
        }
    
        // add smooth zoom functionality if enabled
        if(self.mapOptions.map_enable_smooth_zoom)
        {
            self.gmap.enableContinuousZoom();
        }
    
        // add google search bar if enabled
        if(self.mapOptions.map_enable_google_bar)
        {
            self.gmap.enableGoogleBar();
        }
    
        if(self.mapOptions.map_enable_scroll_zoom)
        {
            self.gmap.enableScrollWheelZoom();
        }
    },
  
    refreshFeeds: function() {
        var self = this;
        self.feedRefreshFlags.each(function(pair) {
            if(self.feedRefreshFlags.get(pair.key))
            {
                self.refreshFeed(pair.key);
            }
        });
    },
  
    // bind livequery for looking at _markers element
    addContainer: function(container) {
        var self = this;
    
        // create manager for container
        var container_id = container.id;
        var manager = self.createManager(container_id);
    
        // find all the marker elements
        $j("#"+container_id+" .marker").livequery(
            // when a new marker element is added to dom
            // this function is run automatically
            function() {
                $marker = $j(this);
        
                self.log("\nAdding Marker from: "+container_id);
                self.log(this);
        
                // add basic content
                markerOptions = {
                    latLng: MapKit.parseLatLng($marker.attr("rel")),
                    html: $marker.find(".bubbleHTML").html(),
                    icon: self.buildIcon($marker.attr("icon")),
                    managerName: container_id,
                    title: ""
                };
        
                if($marker.find(".title").size() > 0)
                {
                    markerOptions.title = $marker.find(".title").html();
                }
        
                if($marker.attr("min"))
                {
                    markerOptions.minZoom = parseInt($marker.attr("min"));
                }

                if($marker.attr("max"))
                {
                    markerOptions.maxZoom = parseInt($marker.attr("max"));
                }
        
                if($marker.attr("expand"))
                {
                    markerOptions.expand = ($marker.attr("expand") == "true");
                }
        
                if($marker.attr("bubbleStyle"))
                {
                    markerOptions.bubbleStyle = $marker.attr("bubbleStyle");
                }
        
                self.addMarker($marker.attr("id"), markerOptions);
            },

            // when a marker element is deleted from the dom
            // this function is run automatically
            function() {
                $marker = $j(this);
        
                self.log("\nRemoving Marker from: "+container_id);
                self.log(this);
        
                self.removeMarker(self.markers.get($marker.attr("id")), container_id);
                manager.refresh();
            }
            );
    
        if($j(container).attr("autozoom") == "true")
        {
            //autozoom after 1 sec
            setTimeout(function(){
                self.zoomFit();
            }, 1000);
        }
    },
  
    addMarker: function(id, options)
    {
        var self = this;
    
        var options = MapKit.mergeOptions({}, self.markerDefaults, options);
        var marker = self.buildMarker(id, options);
    
        if(options.managerName != undefined)
        {
            var manager = self.managers.get(options.managerName);
            manager.addMarker(marker, options.minZoom, options.maxZoom);
            self.managerMarkers.get(options.managerName).push(marker);
        }
        else
        {
            self.gmap.addOverlay(marker);
        }
    
        self.markers.set(id, marker);
    
        return marker;
    },
  
    // create a marker object from options
    buildMarker: function(id, options) {
        var self = this;
    
        var options = MapKit.mergeOptions({}, self.markerDefaults, options);
    
        var marker = new GMarker(new GLatLng(options.latLng[0], options.latLng[1]), options);
        marker.id = id;
        marker.expand = options.expand;
        marker.title = options.title;
        marker.bubbleStyle = options.bubbleStyle;
        marker.ajaxUrl = options.ajaxUrl;

        // set current marker when opening / closing info window
        GEvent.addListener(marker, "infowindowopen", function(e) {
            self.currentMarker = e;
        });
        GEvent.addListener(marker, "infowindowclose", function(e) {
            self.currentMarker = undefined;
        });
    
        // If it has HTML to pass in, add an event listner for a click
        if(options.html)
        {
            marker.openBubble = function() {
                self.openBubble(marker, marker.bubbleStyle, options.html);
            };
        }
        else if(options.getHtml)
        {
            marker.openBubble = function(){
                var currentHtml = options.getHtml();
                self.openBubble(marker, marker.bubbleStyle, currentHtml);
            };
        }
        else
        {
            marker.openBubble = function() {
        
            }
        }
        GEvent.addListener(marker, options.htmlOpenEvent, marker.openBubble);

        return marker;
    },
  
    openBubble: function(marker, bubbleStyle, html) {
        var self = this;
    
        var bubbleHtml = "<div class='infoWindowView'><h3 class='title'>"+marker.title+"</h3><div class='content'>"+html+"</div></div>";
    
        if(bubbleStyle == null || bubbleStyle == "")
        {
            bubbleStyle = self.mapOptions.default_bubble_style;
        }
    
        marker.openExtInfoWindow(self.gmap, bubbleStyle, bubbleHtml, {
            beakOffset: 0,
            ajaxUrl: marker.ajaxUrl,
            maxContent: marker.expand ? "map/expand_content/"+marker.id : null,
            maxWidth: 500,
            maxHeight: 250,
            maximizeEnabled: marker.expand
        });
    },
  
    buildIcon: function(iconId){
        var self = this;
        try
        {
            iconElement = $("icon_"+iconId);
            if(iconElement == undefined)
            {
                self.log("Unable to find icon element for: icon_"+iconId)
                return;
            }
    
            iconImage = iconElement.down("img.marker-icon");
            shadowImage = iconElement.down("img.marker-shadow");

            if(iconImage != undefined)
            {
                var icon = new GIcon();
                icon.image = iconImage.src;
                var imgWidth = parseInt($j(iconImage).attr("w"));
                var imgHeight = parseInt($j(iconImage).attr("h"));
        
                icon.iconAnchor = new GPoint(imgWidth/2, imgHeight/2);
                icon.infoWindowAnchor = new GPoint(imgWidth/2, 0)
                icon.iconSize = new GSize(imgWidth, imgHeight);
                if(shadowImage != undefined)
                {
                    icon.shadow = shadowImage.src;
                    var shadowWidth = parseInt($j(shadowImage).attr("w"));
                    var shadowHeight = parseInt($j(shadowImage).attr("h"));
          
                    icon.shadowSize = new GSize(shadowWidth, shadowHeight);
                }
                return icon;
            }
            else
            {
                return new GIcon(G_DEFAULT_ICON);
            }
        }
        catch(ex) {
            console.error(ex);
        }
    },
  
    zoomFit: function(zoomLevel) {
        var self = this;
    
        var bounds = new GLatLngBounds();
    
        self.markers.each(function(pair) {
            bounds.extend(pair.value.getPoint())
        });
    
        if(self.myLatLng == undefined)
        {
            self.gmap.setCenter(bounds.getCenter(), self.gmap.getBoundsZoomLevel(bounds));
        }
    },
  
    // mark people around you
    refreshPeople: function()
    {
        var self = this;
        if(self.refreshPeopleFlag)
        {
            self.log("Refreshing Map People...");

            var bounds = self.gmap.getBounds();
            var northEast = bounds.getNorthEast();
            var southWest = bounds.getSouthWest();

            new Ajax.Request('/map_sessions', {
                method: "get",
                parameters: {
                    max_lat: northEast.lat(),
                    max_lng: northEast.lng(),
                    min_lat: southWest.lat(),
                    min_lng: southWest.lng()
                },
                onSuccess: function(transport){
                    try
                    {
                        var jsonMarkers = transport.responseText.evalJSON();
                        var mapMarkers = $A([]);

                        // build mapMarkers from JSON Response
                        jsonMarkers.each(function(jsonMapSession) {
                            var tempMarker=self.buildMarker("marker_"+jsonMapSession.id, {
                                latLng: [jsonMapSession.marker_lat, jsonMapSession.marker_lng],
                                ajaxUrl: "/map_session/?id="+jsonMapSession.id,
                                title: jsonMapSession.name,
                                html: "Loading...",
                                bubbleStyle: jsonMapSession.bubble_style,
                                icon: self.buildIcon(self.personIcon)
                            });
              
                            mapMarkers.push(tempMarker);
                        });
           
                        // Replace Markers on Map Manager
                        self.log("People Markers: "+mapMarkers.size());
                        self.replaceManagerMarkers("people", mapMarkers, 0, 17);
                    }
                    catch(ex) {
                        console.error(ex);
                    }
                }
            });
            self.refreshPeopleFlag = false;
        }
    },
  
    // set map session data, and look for new messages
    refreshSession: function() {
        var self = this;
        if(self.refreshSessionFlag)
        {
            var sessionParams = {
                "map_session[lat]": self.gmap.getCenter().lat(),
                "map_session[lng]": self.gmap.getCenter().lng(),
                "map_session[zoom_level]": self.gmap.getZoom(),
                "_method": "put"
            }
      
            if(self.myLatLng)
            {
                sessionParams["map_session[marker_lat]"] = self.myLatLng[0];
                sessionParams["map_session[marker_lng]"] = self.myLatLng[1];
            }
    
            new Ajax.Request('/map_session', {
                method: "post",
                parameters: sessionParams,
                onSuccess: function(transport){
                    var jsonMessages = transport.responseText.evalJSON();
                    jsonMessages.each(function(jsonMessage) {
                        mapkit_viewmessage_open(jsonMessage.id);
                    });
                }
            });
            self.refreshSessionFlag = false;
        }
    },
  
    // create a new marker manager for the map
    createManager: function(name, options) {
        var self = this;
    
        var options = MapKit.mergeOptions({}, self.managerDefaults, options);
    
        var manager = new MarkerManager(self.gmap, options);
        self.managers.set(name, manager);
        self.managerMarkers.set(name, $A([]));
    
        return manager;
    },
  
    // refresh the feed markers for a specified feed
    refreshFeed: function(name) {
        var self = this;
    
        var manager = self.managers.get(name);
        var managerName = name;

        var bounds = self.gmap.getBounds();
        var northEast = bounds.getNorthEast();
        var southWest = bounds.getSouthWest();
    
        // look up matching markers
        var feedUrl = self.feeds.get(name);
        new Ajax.Request(feedUrl, {
            method: "get",
            parameters: {
                "bounds[max_lat]": northEast.lat(),
                "bounds[max_lng]": northEast.lng(),
                "bounds[min_lat]": southWest.lat(),
                "bounds[min_lng]": southWest.lng(),
                "current_zoom": self.gmap.getZoom()
            },
            onSuccess: function(transport){
                var jsonMarkers = transport.responseText.evalJSON();
                var mapMarkers = $A([]);
                var minZoom = 0;
                var maxZoom = 17;
        
                // build mapMarkers from JSON Response
                jsonMarkers.each(function(jsonMarker) {
                    var markerOptions = {
                        latLng: [jsonMarker.lat, jsonMarker.lng],
                        html: jsonMarker.bubble_html,
                        icon: self.buildIcon(jsonMarker.icon_id),
                        title: jsonMarker.title,
                        bubbleStyle: jsonMarker.bubble_style
                    };
          
                    if(jsonMarker.expanded_html != null && jsonMarker.expanded_html != "")
                    {
                        markerOptions.maxContent = jsonMarker.expanded_html;
                        markerOptions.expand = true;
                    }
          
                    // create marker
                    var marker = self.buildMarker("marker_"+jsonMarker.id, markerOptions);
                    if(self.mapOptions.ajax_bubble_content)
                    {
                        marker.ajaxUrl = "map/expand_content/"+marker.id;
                    }
                    mapMarkers.push(marker);
                    
                    minZoom = jsonMarker.min_zoom;
                    maxZoom = jsonMarker.max_zoom;
                });

                //Start updating nearby locations content
                var mapIndex;
                var location_list = document.getElementById("nearby_locations_table");
                if(location_list != null) {
                  while(location_list.rows.length>0){
                      location_list.deleteRow(0);
                  }
                  var tBody = location_list.getElementsByTagName('tbody')[0];

                  var newTR;
                  for(mapIndex=0;mapIndex<mapMarkers.size();mapIndex++){
                    // max of 10 nearby locations displayed
                    if(mapIndex >= 10)
                      break;
                    
                    if(mapIndex%2 == 0){
                         newTR= document.createElement('tr');
                         newTR.className="row";
                    }
                    var newTD = document.createElement('td');
                    newTD.className="cell link";
                    var newA = document.createElement('a');
                    var ahrefID=mapMarkers[mapIndex].id;
                    ahrefID=ahrefID.substring(ahrefID.indexOf('_')+1);

                    newA.href="/locations/"+ahrefID;
                    newA.innerHTML = mapMarkers[mapIndex].getTitle();

                    newTD.appendChild (newA);
                    newTR.appendChild (newTD);
                    if(mapIndex%2 == 0){
                        tBody.appendChild (newTR);
                    }
                  }
                }
                // Replace Markers on Map Manager
                self.log("New Markers: "+mapMarkers.size());
                self.replaceManagerMarkers(managerName, mapMarkers, minZoom, maxZoom);
            }
            
        });
    
        self.feedRefreshFlags.set(name, false);
    },

    
    // replace the visible markers for a particular marker manager
    replaceManagerMarkers: function(managerName, newMarkers, minZoom, maxZoom)
    {
        var self = this;
    
        var manager = self.managers.get(managerName);
        var currentMarkers = self.managerMarkers.get(managerName);
        // self.log_array("Current Markers", currentMarkers);
    
        // find all the markers that are in the new but not in the current
        var markersToAdd = newMarkers.reject(function(m) {
            return currentMarkers.map(function(cm) {
                return cm.id;
            }).include(m.id);
        });
        // self.log_array("Adding Markers", markersToAdd);

        // find all the markers that are in the old but not in the new
        var markersToDelete = currentMarkers.reject(function(m) {
            return newMarkers.map(function(cm) {
                return cm.id;
            }).include(m.id);
        });
        // self.log_array("Removing Markers", markersToDelete);
        
        // make the additions + subtractions
        // set marker hash
        markersToAdd.each(function(m) {
            manager.addMarker(m, minZoom, maxZoom);
            self.markers.set(m.id, m);
            currentMarkers.push(m);
        });
    
        // remove other markers
        markersToDelete.each(function(markerToDelete) {
            self.removeMarker(markerToDelete, managerName);
        });
    },
  
    // remove a marker from the map
    removeMarker: function(marker, managerName)
    {
        try{
            var self = this;
    
            // dont remove an opened marker
            if(self.currentMarker != undefined && marker.id == self.currentMarker.id)
            {
                return false;
            }
    
            if(managerName != undefined)
            {
                var manager = self.managers.get(managerName);
                manager.removeMarker(marker);
                self.managerMarkers.set(managerName, self.managerMarkers.get(managerName).without(marker));
            }
            else
            {
                self.gmap.removeOverlay(marker);
            }
    
            self.markers.unset(marker.id);
            return true;
        }
        catch(ex) {
            console.error(ex);
        }
    },
  
    bindUnload: function() {
        document.body.onunload = function() {
            if (GBrowserIsCompatible()) {
                GUnload();
            }
        }
    },
  
    log: function(msg) {
        if(this.showDebug)
        {
            console.log(msg);
        }
    },
  
    log_array: function(msg, array) {
        this.log("\n"+message+": "+array.size());
        this.log(array.toArray());
    },
  
    // default map options
    mapOptions: {
        // map_control_style can be "default-large", "default-small", or "mapkit"
        map_control_style: "mapkit",
    
        // corresponds to the map_controls folder used in the image paths
        map_control_color: "black",
    
        // default map type can be "sat", "hybrid", or "normal"
        mapType: "normal",
    
        // bubble style can be "bubble_default", "bubble_blue", "bubble_green", "bubble_red"
        default_bubble_style: "bubble_default",
        my_bubble_style: "bubble_red",
        people_bubble_style: "bubble_blue",
    
        // standard map options
        map_enable_scroll_zoom: true,
        map_enable_smooth_zoom: true,
        map_enable_google_bar: true,
        map_enable_double_click_zoom: true,
        map_enable_type_position: true,
        map_enable_overview: true,
        map_enable_scale_control: false,

        // set to 0 for realtime
        refresh_people_interval: 0, // 60
        refresh_session_interval: 0, // 5
        refresh_feeds_interval: 0, // 0.5
    
        // map adsense
        enable_ads: false,
        publisher_id: "",
        max_ads_on_map: 5,
        min_zoom_level_for_ads: 6
    },

    // default options for creating markers
    markerDefaults: {
        // Point lat & lng
        latLng: [],
        // Point HTML for infoWindow
        html: null,
        // Event to open infoWindow (click, dblclick, mouseover, etc)
        htmlOpenEvent: "click",
        // Point is draggable?
        draggable: false,
        // These two are only required if adding to the marker manager
        minZoom: 0,
        maxZoom: 17,
        // Optional Icon to pass in
        icon: null,
        // For maximizing infoWindows
        maxContent: null
    },

    // default options for creating marker managers
    managerDefaults: {
        // Border Padding in pixels
        borderPadding: 100,
        // Max zoom level
        maxZoom: 17,
        // Track markers
        trackMarkers: false
    }
});

// Wrap MapKit in an singleton
// MapKit.get returns the instance
Object.extend(MapKit, {
    set: function(mapkit) {
        MapKit.instance = mapkit;
    },
    get: function(){
        if(MapKit.instance == null)
        {
            MapKit.instance = new MapKit();
        }
        return MapKit.instance;
    },
    mergeOptions: function(){
        // copy reference to target object
        var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
        // Handle a deep copy situation
        if ( target.constructor == Boolean ) {
            deep = target;
            target = arguments[1] || {};
            // skip the boolean and the target
            i = 2;
        }
        // Handle case when target is a string or something (possible in deep copy)
        if ( typeof target != "object" && typeof target != "function" )
        {
            target = {};
        }
        // extend jQuery itself if only one argument is passed
        if ( length == i ) {
            target = this;
            --i;
        }
        for ( ; i < length; i++ )
        {
            // Only deal with non-null/undefined values
            if ( (options = arguments[ i ]) != null )
            {
                // Extend the base object
                for ( var name in options ) {
                    var src = target[ name ], copy = options[ name ];
                    // Prevent never-ending loop
                    if ( target === copy )
                    {
                        continue;
                    }
                    // Recurse if we're merging object values
                    if ( deep && copy && typeof copy == "object" && !copy.nodeType )
                        target[ name ] = jQuery.extend( deep,
                            // Never move original objects, clone them
                            src || ( copy.length != null ? [ ] : { } )
                            , copy );

                    // Don't bring in undefined values
                    else if ( copy !== undefined )
                    {
                        target[ name ] = copy;
                    }
                }
            }
        }

        // Return the modified object
        return target;
    },
    // Parse a Lat Lng String separated by 'x'
    // eg "-34.53434x45.3483497394"
    // returns an array of floats
    parseLatLng: function(str) {
        parts = [];
        if(str)
        {
            parts = str.split("x");
            parts[0] = parseFloat(parts[0]);
            parts[1] = parseFloat(parts[1]);
        }
        return parts;
    }
});
