

/**
 * @author David Irving
 * @version 1.0
 * @copyright 2010 BondView
 **/

Ext.namespace('BondMap', 'BondMap.Tooltip');

BondMap = Ext.extend(Ext.util.Observable, {

	/* Private */

	_bondData: [ ],

	_config: {
		clickFields: ['bonds_security_description', 'bonds_cusip'],
		closeable: true,
		cusips: [ ],
		destroyOnClose: true,
		draggable: false,
		height: 400,
		hoverFields: ['bonds_security_description', 'bonds_cusip'],
		modal: true,
		showSidebar: true,
		title: 'Bond Location Map',
 		width: 700,
 		position: null
	},

	_gmap: null,

	_icon: G_DEFAULT_ICON,

	_mask: null,

	_templateListing: new Ext.Template(['<div class="bondmap-listing">{data}</div>']).compile(),

	_templateLocation: new Ext.Template([
		'<table>',
			'{location}',
			'<tr>',
				'<td valign="top">State:</td>',
				'<td>{state}</td>',
			'</tr>',
		'</table>'
	]).compile(),

	_templateRecord: new Ext.Template([
		'<div class="header">{label}</div>',
		'<div class="value">{value}</div>'
	]).compile(),

	_templateRow: new Ext.Template([
		'<tr>',
			'<td class="label">{label}:</td>',
			'<td class="value">{value}</td>',
		'</tr>'
	]).compile(),

	_visible: false,

	_window: null,

	/* Private */
	_addBondsToMap: function() {
		var listPanel = Ext.ComponentMgr.get('bondPanel');
		for (var i=0;i<this._bondData.length;i++) {
			// Generate panel ID
			this._bondData[i].panelId = Ext.id();

			// Add marker
			if (this._bondData[i].point) {
			        this._bondData[i].marker = new GMarker(this._bondData[i].point, { icon: this._icon });
				this._gmap.addOverlay(this._bondData[i].marker);

		                GEvent.bind(this._bondData[i].marker, 'mouseover', this, this._onMouseOverMarker);
		                GEvent.bind(this._bondData[i].marker, 'mouseout', this, this._onMouseOutMarker);
		                GEvent.bind(this._bondData[i].marker, 'click', this, this._onMarkerClick);

				var tooltip = '';
				for (var j=0;j<this._config.hoverFields.length;j++) {
					if (this._bondData[i].data[this._config.hoverFields[j]]) {
						var data = this._bondData[i].data[this._config.hoverFields[j]];
						tooltip = tooltip + this._templateRow.apply({
							label: data.label,
							value: data.value
						});
					}
				}
				
				this._bondData[i].marker.tooltip = new BondMap.Tooltip(this._bondData[i].marker, Ext.DomHelper.createDom({
					tag: 'div',
					cls: 'bondmap-tooltip',
					html: '<table>' + tooltip + '</table>'
				}));

				this._gmap.addOverlay(this._bondData[i].marker.tooltip);

			}

			// Build listing
			var data = '';
			for (var id in this._bondData[i].data) {
				if (this.hasOwnProperty(id)) {
					continue;
				}
				data = data + this._templateRecord.apply(this._bondData[i].data[id]);
			}

			var subLoc = '';
			if (this._bondData[i].loc_field_lbl && this._bondData[i].loc_field_lbl != 'State') {
				subLoc = this._templateRow.apply({
					label: this._bondData[i].loc_field_lbl,
					value: this._bondData[i].loc_field_val
				});
			}

			data = data + this._templateRecord.apply({
				label: 'Location',
				value: this._templateLocation.apply({
					location: subLoc,
					state: (this._bondData[i].state) ? this._bondData[i].state : 'Not Available'
				})
			});

			listPanel.add({
				id: this._bondData[i].panelId,
				title: this._bondData[i].data.bonds_security_description.value,
				html: this._templateListing.apply({data: data}),
				layout: 'fit',
				autoHeight: true,
				bmData: this._bondData[i],
				listeners: {
					expand: function(panel) {
						if (panel.bmData.point) {
							this._gmap.panTo(panel.bmData.point);
						}
					},
					scope: this
				}
			});
		}

		listPanel.doLayout();
	},

	/**
 	 * Constructor for the BondMap object. You may pass a configuration object
	 * which can contain any of the following properties:
	 *	array		clickFields	An array of fields to display when the user clicks
	 * 					a stick pin on the map. See the BondMap controller for
	 *					an available list of fields.
	 *
	 *	boolean		closeable	True to allow the user to close the panel
	 *
	 * 	array		cusips		The array of cusips to display on the map
	 *
	 *	string		dataUrl		The URL to retrieve bond data from
	 *
	 *	boolean		destroyOnClose	True to destroy the window when closed or false
	 * 					to keep the window in memory (useful to prevent
	 *					polling the server each time the window is displayed
	 *					with the same bonds)
	 *
	 *	boolean		draggable	True to allow the user to drag the window around the screen
	 *
	 *	int		height		The height of the window
	 *
	 *	array		hoverFields	An array of fields to display when the user hovers
	 *					over a stick pin on the map. See the BondMap controller
	 *					for an available list of fields.
	 *
	 * 	boolean		modal		Whether or not to make this window a modal
	 *
	 *	boolean		showSidebar	True to show the sidebar of bond information
	 *
	 *	string		title		The title of the map window
	 *
	 *	int		width		The width of the window (including the sidebar)
	 *
	 * @returns BondMap
	 **/
        constructor: function(config) {
		if (!typeof(config) == 'object') {
			throw 'You must pass an object to the constructor';
		}

		Ext.apply(this._config, config);

		if (this._config.width < 200) {
			throw 'Your width must be at least 200px';
		}
	},

	/* Private */
	_getBondByGLatLong: function(gLatLon) {
		for (var i=0;i<this._bondData.length;i++) {
			if (this._bondData[i].marker && gLatLon == this._bondData[i].marker.getLatLng()) {
				return this._bondData[i];
			}
		}
		return false;
	},

	/**
	 * Hide the window
	 * @return void
	 **/
	hide: function() {
		this._visible = false;
		this._window.hide();
	},

	/**
	 * Returns the visibility of the window
	 * @return boolean
	 **/
	isVisible: function() {
		return this._visible;
	},

	/* Private */
	_onAfterRender: function(win) {
		this._mask = new Ext.LoadMask(win.body, {
			msg: 'Loading your bond information...'
		});
		this._mask.show();

		Ext.Ajax.request({
			url: this._config.dataUrl,
			success: this._onDataReady,
			failure: this._onDataFailure,
			params: { 
				cusips: Ext.encode(this._config.cusips),
				hoverFields: Ext.encode(this._config.hoverFields),
				clickFields: Ext.encode(this._config.clickFields)
			},
			scope: this
		});
	},

	/* Private */
	_onDataFailure: function() {
		this._window.destroy();
		Ext.Msg.show({
			title: 'Data Error',
			msg: 'There was an error retrieving information from the server. Please try again later.',
			buttons: Ext.Msg.OK,
			icon: Ext.MessageBox.ERROR,
			maxWidth: 320
		});
	},

	/* Private */
	_onDataReady: function(response, options) {
		try {
			this._bondData = Ext.decode(response.responseText);
		} catch (err) {
			this._onDataFailure();
			return;
		}

		this._gmap = new GMap2(Ext.ComponentMgr.get('mapPanel').body.dom);
		GEvent.bind(this._gmap, 'load', this, this._onMapReady);

		var latlng = [ ];
		for (var i=0;i<this._bondData.length;i++) {
			if (this._bondData[i].lat && this._bondData[i].lon) {
				latlng[latlng.length] = this._bondData[i].point = new GLatLng(this._bondData[i].lat, this._bondData[i].lon);
			}
		}

		var bounds = new GLatLngBounds();
		for (var i=0;i<latlng.length;i++) {
			bounds.extend(latlng[i]);
		}

		var zoom = this._gmap.getBoundsZoomLevel(bounds);
		zoom = (zoom > 10) ? 10 : zoom;

		this._gmap.setCenter(bounds.getCenter( ), zoom);
		this._gmap.addControl(new GLargeMapControl());
	},

	/* Private */
	_onMapReady: function() {
		this._addBondsToMap();
		this._mask.hide();
		this._mask.destroy();
	},

	/* Private */
	_onMarkerClick: function(mLatLon) {
		var bond = this._getBondByGLatLong(mLatLon);
		Ext.ComponentMgr.get(bond.panelId).expand();
	},

	/* Private */
	_onMouseOutMarker: function(mLatLon) {

		if (this._config.hoverFields.length == 0) {
			return;
		}

		var bond = this._getBondByGLatLong(mLatLon);
		bond.marker.tooltip.hide();
	},

	/* Private */
	_onMouseOverMarker: function(mLatLon) {

		if (this._config.hoverFields.length == 0) {
			return;
		}

		var bond = this._getBondByGLatLong(mLatLon);
		bond.marker.tooltip.show();

	},

	/* Private */
 	_onBeforeShow: function() {
 		this._setPosition();
 	},
 
 	/* Private */
	_onClose: function() {
		this._visible = false;
		if (this._config.destroyOnClose) {
			this._window.destroy();
		}
	},

	/* Private */
	_onSideBarStateChange: function(p) {

	},

	/* Private */
	_onWindowResize: function() {
		if (this._visible) {
 			this._setPosition();
 		}
 	},
 
 	/* Private */
 	_setPosition: function() {
 		if (!this._config.position || !(this._config.position instanceof Array) || this._config.position.length < 2) {			this._window.center();
			return;
		}
 
 		var maxX = Ext.lib.Dom.getViewWidth() - this._config.width;
 		var maxY = Ext.lib.Dom.getViewHeight() - this._config.height;
 
 		var cX = parseFloat(this._config.position[0]);
 		if (cX < 0) { cX = 0; }
 		if (cX > 1) { cX = 1; }
 
 		var cY = parseFloat(this._config.position[1]);
 		if (cY < 0) { cY = 0; }
 		if (cY > 1) { cY = 1; }
 
 		var xA = (this._config.position.length >= 3) ? parseFloat(this._config.position[2]) : 0;
 		var yA = (this._config.position.length >= 4) ? parseFloat(this._config.position[3]) : 0;
 
 		var x = (maxX * cX) + xA;
 		var y = (maxY * cY) + yA;
 
 		this._window.setPosition(x, y);
	},

	/**
	 * Set the visibility of the sidebar
	 * @param boolean showState True to show the panel, false to hide it
	 **/
	setSidebarVisible: function(showState) {
		var p = Ext.ComponentMgr.get('bondPanel');
		if (p.isVisible() == showState) {
			return;
		}

		p.on((showState) ? 'show' : 'hide', function(p) {
			p.ownerCt.doLayout();
			this._gmap.checkResize();
		}, this, {single: true});

		p.setVisible(showState);
	},

	/**
	 * Displays the window (if it isn't already)
	 * @return void
	 **/
	show: function() {

		if (this._visible) {
			return;
		}

		if (!this._window) {
			this._window = new Ext.Window({
				closeAction: 'hide',
				title: this._config.title,
				width: this._config.width,
				height: this._config.height,
				modal: this._config.modal,
				resizable: false,
				closeable: this._config.closeable,
				draggable: this._config.draggable,
				layout: 'border',
				items: [{
					xtype: 'panel',
					region: 'center',
					id: 'mapPanel'
				}, {
					region: 'east',
					layout: 'accordion',
					id: 'bondPanel',
					collapsible: false,
					width: 200,
					fill: false,
					autoScroll: true,
					hidden: (!this._config.showSidebar) ? true : false,
					listeners: {
						hide: this._onSideBarStateChange,
						show: this._onSideBarStateChange,
						scope: this
					}
				}]
			});

			Ext.EventManager.onWindowResize(this._onWindowResize, this);
			this._window.on('afterrender', this._onAfterRender, this);
			this._window.on('close', this._onClose, this);
			this._window.on('hide', this._onClose, this);
 			this._window.on('beforeshow', this._onBeforeShow, this);
		}

		this._visible = true;

		this._window.show();
	}

});

/**
 * Based on the public domain Google Tooltip code found at
 * http://onemarco.com
 **/
BondMap.Tooltip = Ext.extend(GOverlay, {

	_map: null,

	_div: null,

	_shadowUrl: '/js/bondmap/resources/images/bondmap/tooltip_shadow.png',

	_marker: null,

	_content: null,

	constructor: function(marker, content) {
		this._marker = marker;
		this._content = content;
	},

	initialize: function(map) {
		this._div = document.createElement("div");
		var innerContainer = this._div.cloneNode(false);
		this._div.appendChild(innerContainer);
		this._div.style.position = 'absolute';
		this._div.style.visibility = 'hidden';
		
		this.shadowQuadrants = [{},{},{},{}]
		this.shadowQuadrants[0].div = document.createElement('div');
		this.shadowQuadrants[0].div.style.position = 'absolute';	
		this.shadowQuadrants[0].div.style.overflow = 'hidden';
		this.shadowQuadrants[0].img = this.createPngElement(this._shadowUrl);
		this.shadowQuadrants[0].img.style.position = 'absolute';
		this.shadowQuadrants[0].div.appendChild(this.shadowQuadrants[0].img);
		this.shadowQuadrants[1].div = this.shadowQuadrants[0].div.cloneNode(false);
		this.shadowQuadrants[1].img = this.shadowQuadrants[0].img.cloneNode(true);
		this.shadowQuadrants[1].div.appendChild(this.shadowQuadrants[1].img);
		this.shadowQuadrants[2].div = this.shadowQuadrants[0].div.cloneNode(false);
		this.shadowQuadrants[2].img = this.shadowQuadrants[0].img.cloneNode(true);
		this.shadowQuadrants[2].div.appendChild(this.shadowQuadrants[2].img);
		this.shadowQuadrants[3].div = this.shadowQuadrants[0].div.cloneNode(false);
		this.shadowQuadrants[3].img = this.shadowQuadrants[0].img.cloneNode(true);
		this.shadowQuadrants[3].div.appendChild(this.shadowQuadrants[3].img);
		
		this.shadowQuadrants[0].div.style.right = '0px';	
		this.shadowQuadrants[0].div.style.top = '0px';
		this.shadowQuadrants[0].img.style.top = '0px';
		this.shadowQuadrants[0].img.style.right = '0px';
		this.shadowQuadrants[1].div.style.left = '0px';
		this.shadowQuadrants[1].div.style.top = '0px';
		this.shadowQuadrants[1].img.style.top = '0px';
		this.shadowQuadrants[2].div.style.left = '0px';
		this.shadowQuadrants[2].div.style.bottom = '0px';
		this.shadowQuadrants[2].img.style.bottom = '0px';
		this.shadowQuadrants[2].img.style.left = '0px';
		this.shadowQuadrants[3].div.style.right = '0px';
		this.shadowQuadrants[3].div.style.bottom = '0px';
		this.shadowQuadrants[3].img.style.bottom = '0px';	
		
		this.shadow = this._div.cloneNode(false);
		this.shadow.appendChild(this.shadowQuadrants[0].div);
		this.shadow.appendChild(this.shadowQuadrants[1].div);
		this.shadow.appendChild(this.shadowQuadrants[2].div);
		this.shadow.appendChild(this.shadowQuadrants[3].div);
		
		innerContainer.className = 'tooltip';
		
		var child = typeof this._content == 'string' ? 
			document.createTextNode(this._content) :
			this._content;
		innerContainer.appendChild(child);
		map.getPane(G_MAP_FLOAT_PANE).appendChild(this._div);
		map.getPane(G_MAP_MARKER_SHADOW_PANE).appendChild(this.shadow);
		this._map = map;
	},

	remove: function() {
		this._div.parentNode.removeChild(this._div);
	},

	copy: function() {
		var content = typeof this._content == 'string' ? this._content : this._content.cloneNode(true);
		return new Tooltip(this._marker,content, 5);
	},

	redraw: function(force) {
		if (!force) return;
		
		//draw tooltip
		var markerPos = this._map.fromLatLngToDivPixel(this._marker.getPoint());
		var iconAnchor = this._marker.getIcon().iconAnchor;
		var xPos = Math.round(markerPos.x - this._div.clientWidth / 2);
		var yPos = markerPos.y - iconAnchor.y - this._div.clientHeight - 5;

		this._div.style.top = yPos + 'px';
		this._div.style.left = xPos + 'px';

		//draw shadow
		//calculate shadow location
		shadowAnchor = new GPoint(
			markerPos.x + Math.round((this._marker.getIcon().iconSize.height + 5) / 2) ,
			markerPos.y - Math.round((this._marker.getIcon().iconSize.height + 5) / 2) + 4);
		
		//calculate shadow dimenstions
		var shadowSize = new GSize(this._div.clientWidth + Math.round(this._div.clientHeight / 2) + 8,
			Math.round(this._div.clientHeight / 2) + 10);
		if(shadowSize.width % 2 == 1) shadowSize.width--;
		if(shadowSize.height % 2 == 1) shadowSize.height--;
		
		//apply shodaw location and dimensions
		this.shadow.style.left = (shadowAnchor.x - (shadowSize.width - shadowSize.height - 10 )/ 2) + 'px';
		this.shadow.style.top = (shadowAnchor.y - shadowSize.height) + 'px';
		this.shadow.style.width = (shadowSize.width) + 'px';
		this.shadow.style.height =  shadowSize.height + 'px';	
		
		//get quadrant dimensions
		var qHeight = shadowSize.height / 2;
		var qOddWidth = shadowSize.height > shadowSize.width ?
			shadowSize.height / 2:
			(shadowSize.width) / 2;
		var qEvenWidth = shadowSize.width - qOddWidth;
		
		//apply quadrant dimensions, calculate and apply Q2 and Q4 image offsets
		this.shadowQuadrants[0].div.style.width = qOddWidth + 'px';
		this.shadowQuadrants[0].div.style.height = qHeight + 'px';
		
		this.shadowQuadrants[1].div.style.width = qEvenWidth + 'px';
		this.shadowQuadrants[1].div.style.height = qHeight + 'px';
		this.shadowQuadrants[1].img.style.left = -(160 - shadowSize.height) + 'px';
		
		this.shadowQuadrants[2].div.style.width = qOddWidth + 'px';
		this.shadowQuadrants[2].div.style.height = qHeight + 'px';
		
		this.shadowQuadrants[3].div.style.width = qEvenWidth + 'px';
		this.shadowQuadrants[3].div.style.height = qHeight + 'px';
		this.shadowQuadrants[3].img.style.right = -(160 - shadowSize.height) +'px';	
	},

	show: function() {
		this._div.style.visibility = 'visible';
		this.shadow.style.visibility = 'visible';
	},

	hide: function() {
		this._div.style.visibility = 'hidden';
		this.shadow.style.visibility = 'hidden';
	},

	createPngElement: function(src) {
		var img = document.createElement('img');
		img.setAttribute('src',src);	
		if(Ext.isIE6){
			img.style.visibility = 'hidden';
			var div = document.createElement('div');
			div.appendChild(img);
			div.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + src + '\',sizingMethod=\'crop\')';
			return div;
		}
		return img;
	}

});

