//Object Representing a Map Explorer Map
function Map(name, divname, defaultLat, defaultLng, defaultZoom){
	$svGM = jQuery.fn.svGoogleMaps;

	this.settings = {
		typeControl: null,
		control: new google.maps.LargeMapControl3D(),
		scale: new google.maps.ScaleControl(),
		defaultLat: defaultLat,
		defaultLng: defaultLng,
		defaultZoom: defaultZoom,
		defaultPoint: new google.maps.LatLng(defaultLat,defaultLng)
	}
	
	this.bounds = new google.maps.LatLngBounds();
	this.name = name;
	this.gmap = null;
	this.circle = null;

	this.collections = new Object();
	this.defaultCollection = null;

	this.allFilters = new Object();
	this.cancelCentering = false;


	//Create An Alias
	var map = this;
	var maps = $svGM.args.maps;

	this.visibleTips = new Array();

	this.sortCollection = function(collection,sortBy){
		this.collections[collection].sortBy(sortBy);
	}
	
	var defaultStyle = {
		image: imgroot + '/includes/images/shell/google/default_icon:label.png',
		shadow: imgroot + '/includes/images/shell/google/default_shadow.png',
		smimage: imgroot + '/includes/images/shell/google/default_sm_icon:label.png',
		iconSize: new google.maps.Size(29.0, 34.0),
		shadowSize: new google.maps.Size(47.0, 34.0),
		iconAnchor: new google.maps.Point(15.0, 34.0),
		infoWindowAnchor: new google.maps.Point(14.0, 17.0)
	};
	var customStyle = {
		image: imgroot + '/includes/images/shell/google/other_icon:label.png',
		shadow: imgroot + '/includes/images/shell/google/large_shadow.png',
		iconSize: new google.maps.Size(53.0, 39.0),
		shadowSize: new google.maps.Size(73.0, 39.0),
		iconAnchor: new google.maps.Point(34.0, 39.0),
		infoWindowAnchor: new google.maps.Point(26.0, 19.0)
	};
	
	var pushpinStyle = jQuery.extend(false, defaultStyle, {image: imgroot + '/includes/images/shell/google/pushpin_icon:label.png'});
	var otherStyle = jQuery.extend(false, customStyle, {image: imgroot + '/includes/images/shell/google/other_icon:label.png', smimage: imgroot + '/includes/images/shell/google/other_sm_icon:label.png'});
	var hotelStyle = jQuery.extend(false, customStyle, {image: imgroot + '/includes/images/shell/google/hotel_icon:label.png', smimage: imgroot + '/includes/images/shell/google/hotel_sm_icon:label.png'});
	var diningStyle = jQuery.extend(false, customStyle, {image: imgroot + '/includes/images/shell/google/dining_icon:label.png', smimage: imgroot + '/includes/images/shell/google/dining_sm_icon:label.png'});
	var nightlifeStyle = jQuery.extend(false, customStyle, {image: imgroot + '/includes/images/shell/google/nightlife_icon:label.png', smimage: imgroot + '/includes/images/shell/google/nightlifel_sm_icon:label.png'});
	var shoppingStyle = jQuery.extend(false, customStyle, {image: imgroot + '/includes/images/shell/google/shopping_icon:label.png', smimage: imgroot + '/includes/images/shell/google/shopping_sm_icon:label.png'});
	var transStyle = jQuery.extend(false, customStyle, {image: imgroot + '/includes/images/shell/google/trans_icon:label.png', smimage: imgroot + '/includes/images/shell/google/trans_sm_icon:label.png'});
	var localsearchStyle = jQuery.extend(false,customStyle,{image:imgroot+'/includes/images/shell/google/localsearch_icon:label.png',smimage:imgroot+'/includes/images/shell/google/localsearch_sm_icon:label.png'});
	var itineraryStyle = jQuery.extend(false,customStyle,{image:imgroot+'/includes/images/shell/google/itinerary_icon:label.png',smimage:imgroot+'/includes/images/shell/google/itinerary_sm_icon:label.png'});

	this.initCustomIcons = function(){
		this.iconStyles = {
			'default': defaultStyle,
			'pushpin': pushpinStyle,
			'custom': customStyle,
			'other': otherStyle,
			'hotel': hotelStyle,
			'dining': diningStyle,
			'nightlife': nightlifeStyle,
			'shopping': shoppingStyle,
			'trans': transStyle,
			'localsearch': localsearchStyle,
			'itinerary': itineraryStyle
		};
		
		this.catIconStyles = ['other'];
	}

	//Initialize Map
	this.create = function(){
		logit('Called: create');
		this.initCustomIcons();
		map.div = jQuery("#" + divname);
		
		var mapTypes = google.maps.DEFAULT_MAP_TYPES;
		mapTypes.push(google.maps.PHYSICAL_MAP);
		
		for (var i = 0; i < mapTypes.length; i++) {
			//mapTypes[i].getMinimumResolution = function(latlng){ return 8;};
			//mapTypes[i].getMaximumResolution = function(latlng){ return 8;};
		}
		map.gmap = new google.maps.Map2(map.div.get(0), {mapTypes: mapTypes});
		
		var mapControl = new google.maps.HierarchicalMapTypeControl();
		mapControl.clearRelationships();
		mapControl.addRelationship(google.maps.SATELLITE_MAP, google.maps.HYBRID_MAP, 'Labels', true);
		map.gmap.addControl(mapControl);
		map.gmap.enableRotation();

		if(!this.cancelCentering){
			map.gmap.setCenter(map.settings.defaultPoint);
			map.gmap.setZoom(map.settings.defaultZoom);
		}

		if(map.settings.control != null){
			map.control = map.settings.control;
			map.gmap.addControl(map.control);
		}
		if(map.settings.scale != null){
			map.scale = map.settings.scale;
			map.gmap.addControl(map.scale);
		}
		if(map.settings.typeControl != null){
			map.typeControl  = map.settings.typeControl;
			map.gmap.addControl(map.typeControl);
			map.gmap.addMapType(google.maps.PHYSICAL_MAP);
		}
		map.gmap.disableScrollWheelZoom();
		map.gmap.enableDragging();

		//Register Map with main array
		maps[map.name] = map;
		/*
		google.maps.Event.addListener(this.gmap, "zoomend", function() { 
			if(this.allFilters.zoomFilter && !this.allFilters.zoomFilter.cancelled){
				this.allFilters.zoomFilter.run();
			}
		}.bind(this));
		*/
		logit('End: create');
	}
	
	this.removeOverlay = function(overlay){
		if(overlay.closeInfoWindow)
			overlay.closeInfoWindow();
		this.gmap.removeOverlay(overlay);
		if(overlay.tooltip){
			this.gmap.removeOverlay(overlay.tooltip);
		}
	}
	
	this.addCollection = function(oc,isDefault){
		logit('Called: addCollection: ' + oc.name);
		this.collections[oc.name] = oc;
		if(isDefault || this.defaultCollection == null)
			this.defaultCollection = oc;
		oc.map = this;
		logit('End: addCollection: ' + oc.name);
		return oc;
	}

	//Set Current Maps Control
	this.setControl = function(control){
		if(map.settings.control)
			map.gmap.removeControl(map.settings.control);

		if(control)
			map.gmap.addControl(control);
		this.settings.control = control;
	}

	this.setCenter = function(){
		logit('Called: setCenter');
		map.bounds = new google.maps.LatLngBounds();
		var p;
		for (var i in this.collections){
			var c = this.collections[i];
			map.bounds = addBounds(map.bounds,c.calcBounds());
		}
		if(map.bounds.isEmpty()){
			p = map.settings.defaultPoint;
		}
		else{
			p = map.bounds.getCenter();
		}
		map.gmap.setCenter(p);
		logit('End: setCenter');
	}
	
	this.getBounds = function(){
		var bounds = new google.maps.LatLngBounds();
		for (var i in this.collections){
			var c = this.collections[i];
			var cbounds = c.calcBounds();
			bounds = addBounds(bounds,cbounds);
		}
		return bounds;
	}
	
	this.setBoundsCenterAndZoom = function(){
		logit('Called: setBoundsCenterAndZoom');
		map.bounds = this.getBounds();
		if (!map.bounds.isEmpty()) {
			var p = map.bounds.getCenter();
			var z = map.gmap.getBoundsZoomLevel(map.bounds);
		} else {
			logit('Bounds Empty');
			var p = map.settings.defaultPoint;
			var z = map.settings.defaultZoom;
		}
		map.gmap.setCenter(p);
		
		if(this.allFilters.zoomFilter) {
			this.allFilters.zoomFilter.cancelled = true;
		}
		
		/*
		 * Instead of setting a max zoom on the map (which keeps the user from zooming
		 * in further if they want), lets just limit the maximum auto zoom to 14
		 */
		if (z > 14) {
			z = 14;
		}
		logit('Setting zoom to: ' + z);
		map.gmap.setZoom(z);
		
		if(this.allFilters.zoomFilter)
			this.allFilters.zoomFilter.cancelled = false;
		logit('End: setBoundsCenterAndZoom');
	}
	
	this.sendBack = function(){
		if (!this.zindex) {
			this.zindex = 100;
		}
		this.zindex++;
		
		return this.zindex;
	}
	
	this.zoomAndGoTo = function(placemark){
		$svGM.goToPlacemark(placemark.prikey);
	}

	this.createCustomIcon = function (style,label){
		var iconStyle = $svGM.clone(this.iconStyles[style]);
		
		iconStyle.image = iconStyle.image.replace('icon:label',label);
		if (iconStyle.smimage) iconStyle.smimage = iconStyle.smimage.replace('icon:label',label);
		var icon = new google.maps.Icon(iconStyle);
		return icon;
	}
	
	this.registerFilter = function(name,filter){
		this.allFilters[name] = filter;
	}
	
	this.initFilters = function(){
		for(var i in this.allFilters){
			this.allFilters[i].run();
		}
	}
	
	this.getVisiblePlacemarks = function(){
		var arr = new Array();
		for (var i in this.collections){
			var p = this.collections[i].getPage(this.collections[i].currPage);
			for(var j = 0; j < p.length; j++){
				arr.push({
					name: p[j].name,
					collection: this.collections[i].name,
					prikey: p[j].prikey,
					addr1: p[j].addr1,
					addr2: p[j].addr2,
					city: p[j].city,
					state: p[j].state,
					zip: p[j].zip
				});
			}
		}
		return arr;
	}
	//Finds the edges of the icons and sets an adjust zoom level flag if the icon will get cut off
	this.adjustForIcons = function(marker){
		var container = this.gmap.getContainer();
		var containerY = container.scrollHeight;
		var containerX = container.scrollWidth;

		//LatLng of Marker
		var latlng = marker.getLatLng();

		//Convert to Pixel Object
		var divpixels = this.gmap.fromLatLngToContainerPixel(latlng);

		//Placement of icon (in pixels)
		var y = divpixels.y;
		var x = divpixels.x;

		//Width and Height of icon
		var iconWidth = marker.getIcon().iconSize.width;
		var iconHeight = marker.getIcon().iconSize.height;

		//Offset of icon from plotted point
		var anchorWidth = marker.getIcon().iconAnchor.x;
		var anchorHeight = marker.getIcon().iconAnchor.y;

		//Location of top of image (in pixels)
		var iconTop = y - anchorHeight;	
		//Location of bottom of image (in pixels)
		var iconBottom = y + iconHeight - anchorHeight;

		//Location of left edge of icon
		var iconLeft = x - anchorWidth;
		//Location of right edge of icon		
		var iconRight = x + iconWidth + anchorWidth;
		var zoomOut = (iconTop < 0 || iconBottom > containerY || iconLeft < 0 || iconRight > containerX);
		if(zoomOut)
			this.zoomAdjust = -1;
		return zoomOut;
	}
	
}

/*function SVToolbarControl(options) {
	this.toggleView = options.toggleView;
}

// To "subclass" the GControl, we set the prototype object to
// an instance of the google.maps.Control object
SVToolbarControl.prototype = new google.maps.Control();

// Creates a one DIV for each of the buttons and places them in a container
// DIV which is returned as our control element. We add the control to
// to the map container and return the element for the map class to
// position properly.
SVToolbarControl.prototype.initialize = function(map) {
	var tbcontainer = document.createElement("div");
	jQuery(tbcontainer).html("<a href=\"#\" class=\"max\"><img src=\"" + imgroot + "/includes/images/gmaps/expand.png\" alt=\"view large map\" /></a>");
	jQuery(".max", tbcontainer).click(this.toggleView);
	
	map.getContainer().appendChild(tbcontainer);
	return tbcontainer;
}

// By default, the control will appear in the top right corner of the
// map with 7 pixels of padding.
SVToolbarControl.prototype.getDefaultPosition = function() {
	return new google.maps.ControlPosition(google.maps.ANCHOR_TOP_RIGHT, new google.maps.Size(10, 10));
}



function SVNearbyControl(options) {
}

// To "subclass" the google.maps.Control, we set the prototype object to
// an instance of the google.maps.Control object
SVNearbyControl.prototype = new google.maps.Control();

// Creates a one DIV for each of the buttons and places them in a container
// DIV which is returned as our control element. We add the control to
// to the map container and return the element for the map class to
// position properly.
SVNearbyControl.prototype.initialize = function(map) {
	var container = document.createElement("div");
	jQuery(container).html("<select name=\"categories\"><option>find nearby attractions</option></select>");
	jQuery(".max", container).click(this.toggleView);
	
	map.getContainer().appendChild(container);
	return container;
}

// By default, the control will appear in the top left corner of the
// map with 7 pixels of padding.
SVNearbyControl.prototype.getDefaultPosition = function() {
  return new google.maps.ControlPosition(google.maps.ANCHOR_TOP_RIGHT, new google.maps.Size(10, 10));
}



/*function SVZoomControl(map) {
	this.map = map;
}

// To "subclass" the google.maps.Control, we set the prototype object to
// an instance of the google.maps.Control object
SVZoomControl.prototype = new google.maps.Control();

// Creates a one DIV for each of the buttons and places them in a container
// DIV which is returned as our control element. We add the control to
// to the map container and return the element for the map class to
// position properly.
SVZoomControl.prototype.initialize = function(map) {
	html = "<div class=\"zoomContainer\"><a href=\"#\" class=\"zoomIn\"><img src=\"" + imgroot + "/includes/images/gmaps/zoomIn.png\" alt=\"zoom in\" class=\"add5pxBottom\" /></a><br>";
	html += "<a href=\"#\" class=\"zoomOut\"><img src=\"" + imgroot + "/includes/images/gmaps/zoomOut.png\" alt=\"zoom out\" /></a></div>";
	mapcontainer = jQuery(map.getContainer()).append(html);
	jQuery(".zoomIn", mapcontainer).click(function(){map.zoomIn(); return false;});
	jQuery(".zoomOut", mapcontainer).click(function(){map.zoomOut(); return false;});
	return jQuery(".zoomContainer", mapcontainer).get(0);
}

// By default, the control will appear in the top left corner of the
// map with 7 pixels of padding.
SVZoomControl.prototype.getDefaultPosition = function() {
  return new google.maps.ControlPosition(google.maps.ANCHOR_TOP_LEFT, new google.maps.Size(10, 10));
}*/




/*************************************************
	General Utility Functions
**************************************************/

function addBounds(b1,b2){
	if (b1.isEmpty())
		return b2;
		
	if(b2.isEmpty())
		return b1;

	var southWest = b1.getSouthWest();  
	var northEast = b1.getNorthEast();
	var b3 = new google.maps.LatLngBounds();
	b3.extend(southWest);
	b3.extend(northEast);
	var southWest = b2.getSouthWest();  
	var northEast = b2.getNorthEast();
	b3.extend(southWest);
	b3.extend(northEast);
	return b3;
}

function placemarkDistance(p1,p2){
	return distance(parseFloat(p1.latitude),parseFloat(p1.longitude),parseFloat(p2.latitude),parseFloat(p2.longitude));
}

function distance(lat1,lon1,lat2,lon2){
	var R = 6371; // km
	var kpm = 1.609344;  //km per mile
	var dLat = (lat2-lat1).toRad();
	var dLon = (lon2-lon1).toRad(); 
	var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
			Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) * 
			Math.sin(dLon/2) * Math.sin(dLon/2); 
	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
	var d = (R * c) / kpm;
	return d;
}

function parsePlacemarkData(str,p){
	var $svGM = jQuery.fn.svGoogleMaps;
	var fieldlist = ['name','prikey','weburl','addr1','addr2','city','state','zip','phone','latitude','longitude'];
	
	if (p.itinerary == true)
	{
		str = str.replace('placemark:itineraryDetail', '<div class="itin_info_placemark:prikey itineraryAdd"><a href="' + imgroot + '/myTucson/" style="color: #A11E24;">Added to myTucson</a></div>');
		str = str.replace('placemark:itineraryList', '<div class="itin_info_placemark:prikey itineraryAdd"><a href="' + imgroot + '/myTucson/" style="color: #A11E24;">Added to myTucson</a></div>');
		str = str.replace('placemark:itineraryExplorer','<span class="itin_placemark:prikey itineraryAdd"><span id="hideItinAdded_placemark:prikey"><a href="' + imgroot + '/myTucson/" target="_blank" style="color: #A11E24;">Added to myTucson</a></span>');
		str = str.replace('placemark:itinerary','<span class="itin_placemark:prikey itineraryAdd"><div id="hideItinAdded_placemark:prikey"><span class="itineraryAdded bold floatR add10pxRight add10pxTop underline orange small">Added to myTucson</span></div></span>');
		str = str.replace('list:itinerary','<span class="itineraryAdded orange" id="hideItinAdded_placemark:prikey">Added to myTucson</span>');
	} else {
		str = str.replace('placemark:itineraryDetail', '<div class="itin_info_placemark:prikey itineraryAdd"><a href="javascript:ajaxAddItinJQueryDetail(\'' + imgroot + '\', placemark:prikey);" class="blue bold underline">Add to myTucson</a></div>');
		str = str.replace('placemark:itineraryList', '<div class="itin_info_placemark:prikey itineraryAdd"><a href="javascript:ajaxAddItinJQuery(\'' + imgroot + '\', placemark:prikey);" class="blue bold underline">Add to myTucson</a></div>');
		str = str.replace('placemark:itineraryExplorer','<span class="itin_placemark:prikey itineraryAdd"><span id="hideItin_placemark:prikey"><a href="#" onclick="itin_add(placemark:prikey); return false;">Add to myTucson</a></span><span class="gHidden" id="hideItinAdded_placemark:prikey"><a href="' + imgroot + '/itinerary/" target="_blank" style="color: #A11E24;">Added to myTucson</a></span></span>');
		str = str.replace('placemark:itinerary','<span class="itin_placemark:prikey itineraryAdd"><div id="hideItin_placemark:prikey"><a class="white bold floatR add10pxRight arrowBlueSmall underline" href="#" onclick="return gmapsAddItin(placemark:prikey);">Add to myTucson</a></div><div class="gHidden" id="hideItinAdded_placemark:prikey"><span class="itineraryAdded bold floatR add10pxRight add10pxTop underline orange small">Added to myTucson</span></div></span>');
		str = str.replace('list:itinerary','<span id="hideItin_placemark:prikey"><a href="#" onclick="return gmapsAddItin(placemark:prikey);">Add to myTucson</a></span><span class="gHidden itineraryAdded orange" id="hideItinAdded_placemark:prikey">Added to myTucson</span>');
	}
	
	for (var i = 0; i < fieldlist.length; i++){
		var searchstr = 'placemark:' + fieldlist[i];
		var replacewith = '';
		
		if (typeof p[fieldlist[i]] != 'undefined')
			replacewith = String(p[fieldlist[i]]).substr(0,300);
		
		while (str.indexOf(searchstr) > 0 && searchstr != replacewith){
			str = str.replace(searchstr, replacewith);
		}
	}
	

	var searchstr = 'placemark:description';
	if(p.description.length > 0){
		var replacewith = p.description;
	}
	else
		var replacewith = '*No Description Available*';

		while (str.indexOf(searchstr) > 0 && searchstr != replacewith){
			str = str.replace(searchstr,replacewith);
		}


	
	if(p.logofile && p.logofile.length > 0)
		str = str.replace('placemark:logofile', p.logofile);
	else {
		if ($svGM.args.listingNoImage.length > 0)
			str = str.replace('placemark:logofile', $svGM.args.listingNoImage);
		else
			str = str.replace('placeMark:logoDisplay', 'display:none');
	}
	if(p.imagefile && p.imagefile.length > 0)
		str = str.replace('placemark:imagefile', 'thumb_' + p.imagefile);
	else {
		if ($svGM.args.listingNoImage.length > 0)
			str = str.replace('placemark:imagefile', $svGM.args.listingNoImage);
		else
			str = str.replace('placeMark:imagefile', 'display:none');
	}
	
	//Only available when the marker is visible on the map
	if (p.icon) {
		str = str.replace('placemark:iconimage',p.icon.image);
		str = str.replace('placemark:sm_iconimage',p.icon.smimage);
	}
	if (p.distance) {
		str = str.replace('placemark:distance',p.distance.toFixed(2));
	}
	
	if (p.weburl.length > 0)
		str = str.replace('placemark:website_redirect', '<a href=\"' + imgroot + '/includes/redirects/webcount.cfm?listingID=' + String(p.prikey) + '\" target=\"_blank\">Visit Website</a><br>');
	else
		str = str.replace('placemark:website_redirect', '');
		
	var addr = new Address();
	try{
		addr.loadFromObject(p);
		str = str.replace('placemark:address', addr.toHTMLString());
	}
	catch(ex){
		alert(ex);
	}


	return str;
}

function chr(num){
	return String.fromCharCode(num);
}

function asc(str){
	return str.charCodeAt(0);
}

Number.prototype.toRad = function() {  // convert degrees to radians
  return this * Math.PI / 180;
}

//Itinerary Add Function
function gmapsAddItin(listingid)
{
	ajaxAddItinJQuery(imgroot, listingid, '');
	return false;
}

//Itinerary Add Function
function itin_add(listingid)
{
	/*var thisUrl  =  imgroot+'/itinerary/index.cfm';
	var thisData = 'action=ajax_addItin&listingid='+listingid;*/
	var thisUrl  =  imgroot+'/myTucson_Ajax/index.cfm';
	var thisData = 'action=add&listingid='+listingid;
	
	jQuery.ajax({
			url: thisUrl,
			//type: 'POST',
			type: 'GET',
			data: thisData,
			success: function (data, textStatus) {
						jQuery.fn.svGoogleMaps.updatePlacemarkItinerary(listingid);
						jQuery.fn.svGoogleMaps.args.m.gmap.closeInfoWindow();
						
						var placemark = jQuery.fn.svGoogleMaps.findPlacemark(listingid);
						placemark.marker.openInfoWindowHtml(parsePlacemarkData(placemark.infoWindowPlaceholder, placemark));
						
						return true;
					 },
			error: function (request, textStatus, errorThrown) {
						alert('Cound not add to myTucson');
						return false;
				   }
		   }
	);
}

//Itinerary Function
function updatePlacemarkItinerary(prikey){
	var p;
	var $svGM = jQuery.fn.svGoogleMaps;
	
	//for (var i in $svGM.args.placemarks[$svGM.args.mapName]){
		//p = findPlacemark(prikey, i);
		p = $svGM.findPlacemark(prikey);
		if(p != null)
		{
			p.itinerary = !p.itinerary;
			
			if (p.collection) {
				p.infoWindowPlaceholder = p.collection.getInfoHTML();
			}
			
			if(p.itinerary)
			{
				jQuery('#hideItin_' + prikey).hide();
				jQuery('#hideItinAdded_' + prikey).show();
				//For updating the listingDetail link
				if ($("#mtListing").length != 0){
					$("#mtListing").html('<a href="../MyTucson" class="eleven" style="color:#A11E24;">Added to myTucson</a>');
				}
			}
			else
			{
				jQuery('#hideItin_' + prikey).show();
				jQuery('#hideItinAdded_' + prikey).hide();
			}
			//break;
		}
	//}
	if(p.itinerary)
		$svGM.transferFromOverlays(prikey, $svGM.ov.itinerary);
	else
		$svGM.transferToOverlays(prikey, $svGM.ov.itinerary);
	$svGM.goToPlacemark(prikey);
}

var tabdepth = 0;

function logit(str){
	return;
	var outstr = str;
	if (str.indexOf('End') == 0){
		tabdepth -= 1;
	}
	for (var i = 0; i < tabdepth; i++){
		outstr = '--' + outstr;
	}
	if(typeof(console) != "undefined")
		console.info(outstr);
	if(str.indexOf('Called') == 0){
		tabdepth += 1;
	}
}

// jquery adaptation for Function.bind- http://www.quirkey.com/blog/2009/02/25/switched-to-jquery/
jQuery.extend({
  shove: function(fn, object) {
    return function() {
      return fn.apply(object, arguments);
    }
  }
});