/**
 * Builds a custom map object which subclasses the Google Map Manager
 * 
 * @param {object} cfg Configuration parameters:
 *  zoom: An object with these members:
 *  	zz: The ID of the zoom zone element (zoom cursor moves in this area)
 *  	zc: The ID of the zoom cursor element
 *  	zp: The ID of the zoom plus element (button for increasing zoom)
 *  	zm: The ID of the zoom minus element (button for decreasing zoom)
 *  pan: An object with these members:
 *  	pn: The ID of the pan north element
 *  	pe: The ID of the pan east element
 *  	pw: The ID of the pan west element
 *  	ps: The ID of the pan south element
 *  maptype: An object with these members:
 *  	tr: The ID of the map type Road element
 *  	ts: The ID of the map type Satellite element
 *  	tb: The ID of the map type Both (road & satellite) element
 *  
 *  @constructor
 */
CustomMap = function (cfg) {
	
	// Pointer to self to create closure reference
	var self = this;

	// Set up custom zoom cursor
	if (cfg.zoom) {
		
		if (cfg.zoom.zz && cfg.zoom.zc && cfg.zoom.zp && cfg.zoom.zm) {
			
			// cache DOM elements as jQuery objects
			this.zoomZone = $('#'+cfg.zoom.zz);
			this.zoomCursor = $('#'+cfg.zoom.zc);
	
			// Do some fancy calculations
			var zoomMax = cfg.zoom.max || 10;
			var zoomMin = cfg.zoom.min || 2;
			var zoomRange = zoomMax - zoomMin;
			var zoomStepSize = (this.zoomZone.height() - this.zoomCursor.height()) / zoomRange;
			var zoomTopLimit = this.zoomCursor.get(0).offsetTop;
			var zoomBottomLimit = zoomTopLimit + (zoomRange * zoomStepSize);
			
			// save properties for use by event handlers
			$.extend(this.zoomZone.get(0),{ 
				maxZoom    : zoomMax,
				minZoom    : zoomMin,
				zoomStep   : zoomStepSize,
				zoomTop    : zoomTopLimit,
				zoomBottom : zoomBottomLimit,
				zooming    : false,
				zoomCursor : this.zoomCursor,
				customMap  : this
			});
			$.extend(this.zoomCursor.get(0),{
				zoomZone : this.zoomZone.get(0)
			});
		
			// Set up event handlers for moving the zoom control
	
			// NOTE THAT e.clientX and e.clientY are measured from edge of window not page (ie: minus px scrolled offscreen)
			// We use e.pageX and e.pageY (provided reliably by jQuery) so the measurements are independent of scroll
			
			this.zoomCursor.mousedown(
				function (e) {
					this.zoomZone.zooming = {start : e.pageY};
					return false;
				}
			);
			
			this.zoomZone.mousemove(
				function (e) {
					if (this.zooming) {
						var newTop = parseInt(this.zoomCursor.css("top")) + (e.pageY - this.zooming.start);
						this.zooming.start = e.pageY;
						newTop = Math.min(Math.max(newTop,this.zoomTop),this.zoomBottom);
						this.zoomCursor.css("top",newTop);
						return false;
					}
				}
			);
		/*
			zoomZone.click(
				function (e) {
					console.log(e);
					if (!zoomZone.zooming) {
						var markerTop = parseInt(zoomCursor.css("top"))+zoomZone.get(0).offsetTop;
						var currentZoom = mapMgr.getZoom();
						var deltaZoomLevel = 0;
						if (e.pageY < markerTop && currentZoom < zoomZone.zoomMax) {
							deltaZoomLevel = 1;
						} else if (e.pageY > (markerTop + zoomCursor.height()) && currentZoom > zoomZone.zoomMin) {
							deltaZoomLevel = -1;
						}
						if (deltaZoomLevel) {
							setZoom(currentZoom + deltaZoomLevel);
							//zoomCursor.css("top",zoomZone.zoomTop+((zoomZone.zoomMax-(currentZoom+deltaZoomLevel))*zoomZone.zoomStep));
						}
					} else {
						zoomZone.zooming = false;
					}
				}
			);
		 */
			$(document).mouseup(function(e){ 
			
				var zoomZone = $('#'+cfg.zoom.zz).get(0);
				var zoomCursor = zoomZone.zoomCursor;
				if (zoomZone.zooming) {
					zoomZone.zooming = false;
					zoomZone.customMap.setZoom(zoomZone.maxZoom - Math.round((parseInt(zoomCursor.css("top")) - zoomZone.zoomTop) / zoomZone.zoomStep));
					return false;
				}
			
			});
	
			// Set up event handlers for Zoom Plus and Zoom Minus
			$('#'+cfg.zoom.zp).click(function (e) { 
				if (e.shiftKey)
					self.resetPosition();
				else {
					var zl = self.getZoom();
					if (zl < self.zoomZone.get(0).maxZoom)
						self.zoomIn();				
				}
			});
			$('#'+cfg.zoom.zm).click(function (e){
				var zl = self.getZoom();
				if (zl > self.zoomZone.get(0).minZoom)
					self.zoomOut();			
			});
	
		}
	
		if (cfg.zoom.cursorpos && typeof(cfg.zoom.cursorpos) === 'object') {
			this.cursorPos = cfg.zoom.cursorpos;
		}
	}

	// Set up event handlers for controls to pan the map
	if (cfg.pan) {
		if (cfg.pan.pn) $('#'+cfg.pan.pn).click(function(e) { self.pan('n'); });
		if (cfg.pan.pe) $('#'+cfg.pan.pe).click(function(e) { self.pan('e'); });
		if (cfg.pan.pw) $('#'+cfg.pan.pw).click(function(e) { self.pan('w'); });
		if (cfg.pan.ps) $('#'+cfg.pan.ps).click(function(e) { self.pan('s'); });
	}

	// Set up controls to switch the map type
	if (cfg.maptype) {
		
		this.customMapType = true;

		// cache map DOM elements as jQuery objects & attach event handlers

		// Changes the map type to a street (road) view
		if (cfg.maptype.tr) {
			this.btnTypeStreet = $('#'+cfg.maptype.tr);
			this.btnTypeStreet.click(function () {
				self.setMapType('street');
				self.btnTypeStreet.addClass('selected');
				self.btnTypeSat.removeClass('selected');
				self.btnTypeBoth.removeClass('selected');
			});
		}
		// Changes the map type to a satellite view
		if (cfg.maptype.ts) {
			this.btnTypeSat = $('#'+cfg.maptype.ts);
			this.btnTypeSat.click(	function () {
				self.setMapType('sat');
				self.btnTypeStreet.removeClass('selected');
				self.btnTypeSat.addClass('selected');
				self.btnTypeBoth.removeClass('selected');
			});
		}
		// Changes the map type to a hybrid street/satellite (both) view
		if (cfg.maptype.tb) {
			this.btnTypeBoth = $('#'+cfg.maptype.tb);
			this.btnTypeBoth.click(function () {
				self.setMapType('hybrid');
				self.btnTypeStreet.removeClass('selected');
				self.btnTypeSat.removeClass('selected');
				self.btnTypeBoth.addClass('selected');
			});		
		}
	} else {
		this.customMapType = false;
	}

	// Call the super class constructor
	CustomMap.superclass.constructor.call(this);

};

// Inherit the GoogleMap class methods and properties
Ext.extend(CustomMap, GoogleMap, {
	
	/**
	 * Moves the map zoom cursor to the appropriate position
	 * based on zoom level
	 * 
	 * @param {number} zl Zoom level
	 */
	updateZoomCursor : function (zl) {
		if (this.cursorPos)
			this.zoomCursor.css('top',(this.cursorPos[zl] || this.cursorPos[0]) + 'px');
	},
	
	/**
	 * Creates a new map in the div passed
	 * and sets the starting point and zoom.
	 * 
	 * @param {string or object} map An ID name or DOM element; the map container
	 * @param {object} start The initial map position and zoom level:
	 *		lat: map center point latitude
	 *		lon: map center point longitude
	 *		zoom: map zoom level (0-17)
	 */
	init : function (map, start) {

		var mapcfg = {
			center: {latitude: start.lat, longitude: start.lon},
			zoomlevel: start.zoom,
			controls: { panZoom: 'none', mapType: !this.customMapType },
			scrollwheelzoom: false,
			continuouszoom: true
		};

		var self = this;
	
		// build the initial map
		this.initMap(map, mapcfg);
	
		if (this.zoomCursor) {
			// Synchronize cursor to starting zoom level
			this.updateZoomCursor(this.getZoom());
			// update zoom cursor after zoom
			this.addZoomCallback(function (ozl,nzl) { 
				self.updateZoomCursor(nzl) 
			});
		}
	},
	
	/**
	 * Wrapper for GoogleMap method; injects the custom maxZoom value
	 * provided to constructor if the caller doesn't provide one.
	 * Also provides a hack to shift the map down to allow markers to
	 * be visible at the extreme top edge of the map.
	 */
	zoomToMarkers : function (mz){
		CustomMap.superclass.zoomToMarkers.call(this, {maxZoom: mz || this.zoomZone.get(0).maxZoom});
		this.map.panBy(new this.api.Size(0,17));
		// so that the center zoom/pan control returns here
		this.map.savePosition();

		
/*		var handle = this.addPanCallback(function (){
			this.map.panBy(new this.api.Size(0,-34));
			this.api.Event.removeListener(handle);
		});*/
		
		// compute marker anchor point
//		var bnds=mapMgr.getBounds();
//		var origin = mapMgr.xlateLatLngToPix(bnds.nLat,bnds.wLng);
//			mapMgr.logMessage('origin = ('+bnds.nLat+','+bnds.wLng+') = ('+origin.x+','+origin.y+')');	
//		var extent = mapMgr.xlateLatLngToPix(bnds.sLat,bnds.eLng);
//			mapMgr.logMessage('extent = ('+bnds.sLat+','+bnds.eLng+') = ('+extent.x+','+extent.y+')');	
//		var pt = mapMgr.xlateLatLngToPix(ll.lat(),ll.lng());
//			mapMgr.logMessage('markerPix = ('+pt.x+','+pt.y+')');
//		var cntr = mapMgr.getCenter();
//			mapMgr.logMessage('center = ('+cntr.lat+','+cntr.lng+')');
//		var iconHeight = 34;
//		var latPerPix = (bnds.nLat-bnds.sLat)/(extent.y-origin.y);
//			mapMgr.logMessage('latPerPix = '+ latPerPix);
//		var offset = iconHeight * latPerPix;
//			mapMgr.logMessage('offset = '+offset);
//			mapMgr.logMessage('new center = ('+(cntr.lat-offset)+','+cntr.lng+')');

	}
});

