//Class which handles record/property view
Ext.namespace('App.remb');

// Contact form class
App.remb.contactForm = function()
{
	this.init();
}
App.remb.contactForm.prototype = {

	// Panel element
	contactEl: null,
    // Constructor
    init : function(){
    	this.formName = 'formContactUs';
    	this.SubmitButtonName = 'submitButton';
    	this.divContactUsErrorEl = Ext.get('divContactUsError');
    	// Set display mode, used by EXT
    	this.divContactUsErrorEl.setVisibilityMode(Ext.Element.DISPLAY);
    	this.messageEl = Ext.get('Message');

    	this.formEl = Ext.get(this.formName);
      	this.AjaxRequest = Ext.Ajax;
      	this.AjaxRequest.url = '/dev/map2/service/Contact.php';
		this.AjaxRequest.on('beforerequest', function () {
			// Clear errors
			this.divContactUsErrorEl.innerHTML = ""; 
			// Disable form
			this.disableForm();
		}, this);

    	// Add listeners
    	Ext.EventManager.addListener(this.SubmitButtonName, 'click', this.submit, this);

    	Ext.QuickTips.init();

		// Change field to default to validation message "under" instead of tooltips
		Ext.form.Field.prototype.msgTarget = 'under';

		var fullName = new Ext.form.TextField({
			allowBlank:false
		});
		fullName.applyTo('FullName');

		var email = new Ext.form.TextField({
			allowBlank:false,
			vtype:'email'
		});
		email.applyTo('Email');
    
        /* IE rendering issue
		var message = new Ext.form.TextArea({
			allowBlank:false
			//grow:true
		});
		message.applyTo('Message');
		*/
    },

    submit: function () {

		var str = Ext.lib.Ajax.serializeForm(this.formName);
       
		this.AjaxRequest.request({
			method:'post',
			params:str,
			success: this.handleResponse,
			failure: this.handleFailure,
			scope : this
    	});
    },

    handleResponse : function(response){
        try{
            var oResponse = Ext.util.JSON.decode(response.responseText);

			this.enableForm();
			if (oResponse.Errors.length > 0) {
				// Show errors box and set errors
			  	this.divContactUsErrorEl.update("<strong>The following errors has been detected:</strong><br />" + 
			  		oResponse.Errors.join("<br />"));
				this.divContactUsErrorEl.show();
			}
			else
			{
				// Hide errors box
				this.divContactUsErrorEl.hide();
				// Clear message text
				this.messageEl.update("");

		      	alert("Your request has been submitted.")
			}

        } catch(e){

			this.enableForm();
        }

    },
    
    handleFailure : function(response){
		alert("Your request failed.")
		this.enableForm();
    },

	// Dispale sending form
	disableForm: function(){ 
		/* Fixed in ext-1.1-rc1 
		* Do not use mask because it gives dorm refreshing problems in IE on tab change
		*/
		//this.formEl.mask("Processing...");
	},  	

	// Enable sending form
	enableForm: function(){ 
		/* 
		* Do not use mask because it gives dorm refreshing problems in IE on tab change
		*/
		//this.formEl.unmask();
	}  	

}

App.remb.recordViewer = function(ds)
{
	this.init(ds);
}
App.remb.recordViewer.prototype = {

	// Panel element
	detailPanel: null,
	imagesPanel: null,
	documentsPanel: null,
	demographicsPanel: null,
	Panel: null,
	
	// Details template
	headerTemplate: null,
	// Rendering based on a record type
	detailsTemplateInv: null,
	detailsTemplateLease: null,
	imagesListTemplate: null,
	imagesTemplate: null,
	filesTemplate: null,
	documentsListTemplate: null,
	demographicsTemplate: null,

	// data store
	ds: null,

    // Constructor
    init : function(ds){
    	this.ds = ds;
		
		// Detail view panels
		this.detailPanel = Ext.get('detailPanel');
		this.imagesPanel = Ext.get('imagesPanel');
		this.documentsPanel = Ext.get('documentsPanel');
		this.demographicsPanel = Ext.get('demographicsPanel');

		this.headerTemplate = new Ext.Template(
		    '<h2>{address} {city}, {state} {zip}</h2>'
		);
		this.headerTemplate.compile();	

		this.detailsTemplateInv = new Ext.Template(
		    '{header}' +
			'<div class="details"><img src="{url}"><div class="details-info">' +
			'<br />' +
			'<b>Property Type:</b> <span>{type}</span><br />' +
			'<b>Area:</b> <span>{area}</span><br />' +
			'<b>Price:</b> <span>${investprice}</span><br />' +
			'<b>Price/SF/Yr:</b> <span>${price}</span><br />' +
			'<b>GLA:</b> <span>{propsize}</span><br />' +
			'<b>Cap Rate:</b> <span>{caprate}</span><br />' +
			'<b>Yr Built:</b> <span>{age}</span><br />' +
			'<b>Net Lease Investment:</b> <span>{netleaseinvestment}</span><br />' +
			'<b>On Ground Lease:</b> <span>{groundlease}</span><br /><BR>' +
			'<b>Description:</b><br> <span>{description}</span><br><BR>' +
			'<b>Property Detail 1:</b> <a href="{url1}">{url1}</a><br />' +
			'<b>Property Flyer:</b> <a href="{url2}">{url2}</a><br />' +
			'</div></div>'
		);
		this.detailsTemplateInv.compile();	


		this.detailsTemplateLease = new Ext.Template(
		    '{header}' +
			'<div class="details"><img src="{url}"><div class="details-info">' +
			'<br />' +
			'<b>Property Type:</b> <span>{type}</span><br />' +
			'<b>Area:</b> <span>{area}</span><br />' +
			'<b>Rate/SF/Yr:</b> <span>${price}</span><br />' +
			'<b>Total Space Avail:</b> <span>{propsize}</span><br />' +
			'<b>Divisible to:</b> <span>{divisible}</span><br />' +
			'<b>Max Contiguous:</b> <span>{maxcontiguous}</span><br />' +
			'<b>Occupancy:</b> <span>{occupancy}</span><br />' +
			'<b>Bldg Size:</b> <span>{bldgsize}</span><br />' +
			'<b>Yr Built:</b> <span>{age}</span><br /><br>' +
			'<b>Description:</b><br><span>{description}</span><br><br>' +
			'<b>Property Detail 1:</b> <a href="{url1}">{url1}</a><br />' +
			'<b>Property Flyer:</b> <a href="{url2}">{url2}</a><br />' +
			'</div></div>'
		);
		this.detailsTemplateLease.compile();	

		this.imagesListTemplate = new Ext.Template(
		    '{header}' +
			'<div class="details">{imageList}' +
			'</div></div>'
		);
		this.imagesListTemplate.compile();	

		this.imagesTemplate = new Ext.Template(
		    '{header}' +
			'<div class="details">' +
			'<h2>{title}</h2>' + 
			'<a href="{url}" target="_blank"><img src="{url}"></a><div class="details-info">' +
			'<br>{description}' + 
			'</div></div>'
		);
		this.imagesTemplate.compile();	


		this.filesTemplate = new Ext.Template(
		    '{header}' +
			'<div class="details"><a href="{url}">{name}</a><div class="details-info">' +
			'</div></div>'
		);
		this.filesTemplate.compile();	

		this.documentsListTemplate = new Ext.Template(
		    '{header}' +
			'<div class="details">{filesList}' +
			'</div></div>'
		);
		this.documentsListTemplate.compile();	

		this.demographicsTemplate = new Ext.Template(
		    '{header}' +
			'<div class="details">{demographics}' +
			'</div></div>'
		);
		this.demographicsTemplate.compile();	
    },

    // Show record details
    showDetails: function(id) {
    	var record = this.ds.getById(id);
    	var url = "";
    	var title = "";
    	var description = "";
    	var urlList = record.get('imagesList');
    	if (urlList.length > 0) {
    		url = urlList[0].ImageUrl;
		}

		var header = this.headerTemplate.applyTemplate(
			{address: record.get('address'),
			city: record.get('city'),
			zip: record.get('zip'),
			state: record.get('state')});

		// Format url
		var url1 = record.get('url1'); 
		if (url1 != "") {
			if (url1.substr(0, 7) != 'http://') {
				url1 = 'http://' + url1;	
			}
		}
		// Format url
		var url2 = record.get('url2'); 
		if (url2 != "") {
			if (url2.substr(0, 7) != 'http://') {
				url2 = 'http://' + url2;	
			}
		}
		// Details
		if (record.get('market') == 1) {
			// Details for investment type
			this.detailsTemplateInv.overwrite(this.detailPanel, {id: id, header: header, url: url, 
				
				propsize: record.get('propsize'),
				stories: record.get('stories'),
				beds: record.get('beds'),
				bath: record.get('bath'),
				age: record.get('age'),
				school: record.get('school'),
				url1: url1,
				url2: url2,
				type: record.get('type'),
				area: record.get('area'),
				price: record.get('price'),
				investprice: record.get('investprice'),
				caprate: record.get('caprate'),
				netleaseinvestment: record.get('netleaseinvestment'),
				groundlease: record.get('groundlease'),
				description: record.get('description')});
		}
		else
		{
			// Details for lease type
			this.detailsTemplateLease.overwrite(this.detailPanel, {id: id, header: header, url: url, 
				propsize: record.get('propsize'),
				stories: record.get('stories'),
				beds: record.get('beds'),
				bath: record.get('bath'),
				age: record.get('age'),
				school: record.get('school'),
				url1: url1,
				url2: url2,
				type: record.get('type'),
				area: record.get('area'),
				price: record.get('price'),
				divisible: record.get('divisible'),
				maxcontiguous: record.get('maxcontiguous'),
				occupancy: record.get('occupancy'),
				bldgsize: record.get('bldgsize'),
				description: record.get('description')});
		}
		// Images
		var imagesListHTML = '';
    	for (var i = 0, len = urlList.length; i < len; i++) {
    		var url = urlList[i].ImageUrl;
    		var title = urlList[i].Title;
    		var description = urlList[i].Description;
			imagesListHTML = imagesListHTML + this.imagesTemplate.applyTemplate({url: url,
				title: title, description: description});
		}
		if (imagesListHTML == '') {
			imagesListHTML = 'No images found.';
		}
		this.imagesListTemplate.overwrite(this.imagesPanel, {header: header, imageList: imagesListHTML});

		// Documents
		var documentsListHTML = '';
    	var filesList = record.get('filesList');
    	for (var i = 0, len = filesList.length; i < len; i++) {
    		var url = filesList[i].url;
    		var name = filesList[i].name;
			documentsListHTML = documentsListHTML + this.filesTemplate.applyTemplate({url: url, name: name});
		}

		if (documentsListHTML == '') {
			documentsListHTML = 'No documents found.';
		}
		this.documentsListTemplate.overwrite(this.documentsPanel, {header: header, filesList: documentsListHTML});

		// demographicsPanel
    	var demographicsValue = record.get('demographics');
		if (demographicsValue == '') {
			demographicsValue = 'No documents found.';
		}
		this.demographicsTemplate.overwrite(this.demographicsPanel, {header: header, demographics: demographicsValue});
    }
}


App.remb.tooltipTemplate = function(ds)
{
	this.init(ds);
}
App.remb.tooltipTemplate.prototype = {

	// data store
	ds: null,
	// Details template
	template: null,

    // Constructor
    init : function(ds){

    	this.ds = ds;
		this.template = new Ext.Template(
			'<div class="details"><div class="tooltipImageContainer"><img src="{url}" class="tooltipImage"></div>' +
			'<div class="details-info">' +
			'<b>Price:</b>' +
			'<span>{price}</span>' +
			'<br/>' +
			'{address} {city}, {state} {zip}' +
			'<br/>' +
			'{propsize} sq ft' +
			'</div></div>'
		);
		this.template.compile();	

    },

    // Show record details
    getHTML: function(id) {

    	var record = this.ds.getById(id);
    	var url = "";
    	var urlList = record.get('imagesList');
    	if (urlList.length > 0) {
    		url = urlList[0].ImageUrl;
		}
        return this.template.apply(
        	{
				address: record.get('address'),
				city: record.get('city'),
				zip: record.get('zip'),
				state: record.get('state'),
				url: url, 
				price: Ext.util.Format.usMoney(record.get('price')),
				propsize: record.get('propsize')
			}
		);
    }
}

var RealEstateMapBuilder = {
	

    init : function(){
        // tab1, built from existing content
        var tab1 = new Ext.TabPanel('tab1');
        tab1.addTab('mapPanel', "Map");
        tab1.addTab('aboutPanel', "Help");
        tab1.activate('mapPanel');

        // tab1, built from existing content
        var tab2 = new Ext.TabPanel('tab2');
        var tab2ListingPanel = tab2.addTab('listingPanel', "Listings");
        tab2ListingPanel.on('activate', function(){
          // Refresh grid (IE 6 workaround)
          if (grid) {
            grid.getView().refresh();
          }
        }, this, true);
        

        tab2.addTab('detailPanel', "Details");
        tab2.addTab('imagesPanel', "Images");
        tab2.addTab('contactPanel', "Contacts");
        tab2.addTab('documentsPanel', "Docs/Files");
        tab2.addTab('demographicsPanel', "Demographics");
        tab2.activate('listingPanel');



        /*
        var ds = new Ext.data.Store({
		        proxy: new Ext.data.MemoryProxy(myData),
		        reader: new Ext.data.ArrayReader({id: 0}, [
                       {name: 'id'},
                       {name: 'company'},
                       {name: 'price', type: 'float'},
                       {name: 'change', type: 'float'},
                       {name: 'pctChange', type: 'float'},
                       {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'},
                       {name: 'lat', type: 'float'},
                       {name: 'lng', type: 'float'}
                  ])
        });
        */

        // get filtering parameters 
        var market = document.formSearch.market.value;

		// create the Data Store
		var ds = new Ext.data.Store({
			// load using script tags for cross domain, if the data in on the same domain as
			// this page, an HttpProxy would be better
			//proxy: new Ext.data.ScriptTagProxy({
			// Use XML HTML Request
			proxy: new Ext.data.HttpProxy({
				//url: 'http://discoveryconsultants.net/dev/map2/service/PropertyList.php'
				//url: 'http://discoveryconsultants.net/dev/map2/service/get-images.php'
				url: '/dev/map2/service/PropertyList.php',
				nocache: true
			}),
	        baseParams: {market:market},

			// create reader that reads the markers
			reader: new Ext.data.JsonReader({
				root: 'markers',
				/* totalProperty: 'totalCount', */
				id: 'id'
			}, [
				// Make sure id is present as a part of record
				{name: 'id', mapping: 'id'},
				{name: 'address', mapping: 'address'},
				{name: 'city', mapping: 'city'},
				{name: 'zip', mapping: 'zip'},
				{name: 'state', mapping: 'state'},
				{name: 'description', mapping: 'description'},
				{name: 'descriptionPlain', mapping: 'descriptionPlain'},
				{name: 'price', type: 'float', mapping: 'Property.Price'},
				{name: 'lat', type: 'float'},
				{name: 'lng', type: 'float'},
				{name: 'imagesList', mapping: 'Property.Images'},
				{name: 'filesList', mapping: 'Property.Files'},
				{name: 'propsize', mapping: 'Property.Square'},
				{name: 'stories', mapping: 'Property.stories'},
				{name: 'beds', mapping: 'Property.beds'},
				{name: 'bath', mapping: 'Property.bath'},
				{name: 'age', mapping: 'Property.age'},
				{name: 'school', mapping: 'Property.school'},
				{name: 'url1', mapping: 'Property.link'},
				{name: 'url2', mapping: 'Property.link2'},
				{name: 'market', mapping: 'Property.market'},
				{name: 'divisible', mapping: 'Property.divisible'},
				{name: 'maxcontiguous', mapping: 'Property.maxcontiguous'},
				{name: 'occupancy', mapping: 'Property.occupancy'},
				{name: 'bldgsize', mapping: 'Property.bldgsize'},
				{name: 'investprice', mapping: 'Property.investprice'},
				{name: 'caprate', mapping: 'Property.caprate'},
				{name: 'netleaseinvestment', mapping: 'Property.netleaseinvestment'},
				{name: 'groundlease', mapping: 'Property.groundlease'},
				{name: 'type', mapping: 'Property.type'},
				{name: 'area', mapping: 'Property.area'},
				{name: 'demographics', mapping: 'Property.demographics'}
			])
		});

		function loadDetails(ds) {
			// Center map
			var mapData = ds.reader.jsonData.map;
			mapViewer.defaultZoom = parseInt(mapData.zoom);
			mapViewer.getMapProxy().setCenter(parseFloat(mapData.lat), parseFloat(mapData.lng), mapViewer.defaultZoom); 

			function processDetails(record)
			{
				// Add locations to a map
				var id = record.get('id');
				var lat = record.get('lat');
				var lng = record.get('lng');
				mapViewer.getMapProxy().addMarker(id, lat, lng);
			}
			// Process data store records
			ds.each(processDetails, this);
		}

		/* Store.load via HttpProxy is async process - the result is not available immediately after the stmt executes.
		* We should use onLoad handler
		*/
		ds.on('load', loadDetails);

		// example of custom renderer function
        function italic(value){
            return '<i>' + value + '</i>';
        }

		// example of custom renderer function
        function change(val){
            if(val > 0){
                return '<span style="color:green;">' + val + '</span>';
            }else if(val < 0){
                return '<span style="color:red;">' + val + '</span>';
            }
            return val;
        }
		// example of custom renderer function
        function pctChange(val){
		    if(val > 0){
		        return '<span style="color:green;">' + val + '%</span>';
		    }else if(val < 0){
		        return '<span style="color:red;">' + val + '%</span>';
		    }
		    return val;
		}

		// pluggable renders
		// Render address and add description
		function renderAddress(value, p, record){
			return String.format('<b>{0}</b><br/>{1}', value, record.data['descriptionPlain']);
		}

		// the DefaultColumnModel expects this blob to define columns. It can be extended to provide
        // custom or reusable ColumnModels
        var colModel = new Ext.grid.ColumnModel([
			{header: "Price", width: 75, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
			{id: "Address", header: "Address", width: 195, sortable: false, dataIndex: 'address'},
			{header: "City", width: 75, sortable: true, dataIndex: 'city'},
			{header: "State", width: 45, sortable: true, dataIndex: 'state'},
			{header: "Zip", width: 45, sortable: true, dataIndex: 'zip'}
		]);


		// RowSelectionModel
		var sm = new Ext.grid.RowSelectionModel({singleSelect:true});

        // create the Grid
        var grid = new Ext.grid.Grid('listingGrid', {
            ds: ds,
            cm: colModel,
	        selModel: sm
            /*autoExpandColumn: 'Address' */
        });


		grid.on('rowclick', 
			function(gridRef, rowIndex, e){ 
				//grid.getView().getRow(ds.indexOf(record));
				var selectedRecord = grid.getSelectionModel().getSelected();
				// selectedRecord === undefined if user clicked on a grid header/footer
				if (selectedRecord != undefined) {
					recordViewer.showDetails(selectedRecord.id);
					// Change to Detail tab if we are on Listing. Otherwise stay on a current tab.
					if (tab2.getActiveTab().id == 'listingPanel') {
				        tab2.activate('detailPanel');
					}
				}
		}, this);

		grid.on('mouseover', 
			function(e, t){
				var row;
				var view = grid.getView();
				if((row = view.findRowIndex(t)) !== false){
					//var r = view.getRowComposite(row);
					
					//row is a row index
					moveIntoMapView(row);
				}
		}, this);

        grid.render();

        /* CHECK 
        grid.getView().focusRow(0);
        //grid.getSelectionModel().selectFirstRow();

        */

        // Record viewer
        var recordViewer = new App.remb.recordViewer(ds);

        // Record viewer
        var tooltipTemplate = new App.remb.tooltipTemplate(ds);

		// Instantiate GeoDeveloper class
		var mapViewer = new App.remb.mapViewer("mapContainer", "mapStatusBar");

		// Instantiate contact form class
		var contactForm = new App.remb.contactForm("contactPanel");

		// Hook to get tooltip text
		mapViewer.getMapProxy().getToolTipText = function (id) {
			return tooltipTemplate.getHTML(id);
		}

		// Hook to handle tooltips onToolTipMouseOut
		mapViewer.getMapProxy().onMarkerMouseOut = function (id) {
		}

		// Hook to handle tooltips onMarkerMouseOver
		mapViewer.getMapProxy().onMarkerMouseOver = function (id) {

		}
		// Hook to handle onMarkerClick event
		mapViewer.getMapProxy().onMarkerClickHook = function (marker) {
			
			//Select a matching record on a grid
        	grid.getSelectionModel().selectRow(marker.id);
        	grid.fireEvent('rowclick');
		}.createDelegate(this);

		// Center record's marker
		moveIntoMapView = function(id) {
			var record = ds.getById(id);
			mapViewer.getMapProxy().setCenter(record.get('lat'), record.get('lng'), mapViewer.getMapProxy().getZoom());
		};

        // Finally load data
        ds.load();
    }

}

Ext.EventManager.onDocumentReady(RealEstateMapBuilder.init, RealEstateMapBuilder, true);


// Run mapping widget in its own namespace
App.remb.mapViewer = function(mapElID, mapStatusBarElID) {

		// Map $ to a document.getElementById method
		function $(element) {
			if (arguments.length > 1) {
				for (var i = 0, elements = [], length = arguments.length; i < length; i++)
				{
					elements.push($(arguments[i]));
				}
				return elements;
			}
			if (typeof element == 'string')
			{
				return document.getElementById(element);
			}
		}	

		////////////////////////////////////////////////////////////////////////////////////////////
		var GApp = {}

		//////////////////////////////////////////////////////////////////////////////////////////////////////
		/*
		* Application map viewer
		*/
		GApp.mapMiniViewer = function (mapEl, opt) {
			this.init(mapEl, opt); 
		}

		GApp.mapMiniViewer.prototype = {
			mapEl: null,
			// Google Map Object
			gMap: null, 
			// Default zoom, used to set initial map center
			defaultZoom: 14,

			// Map configuration options
			DefaultMapType: 'Normal',
			NormalMap: true,
			SatelliteMap: true,
			HybridMap: true,

			// Map Controls
			// Creates a control with buttons to pan in four directions, and zoom in and zoom out.
			SmallMapControl: false,
			// 	Creates a control with buttons to pan in four directions, and zoom in and zoom out, and a zoom slider.
			LargeMapControl: false,
			// Creates a control with buttons to zoom in and zoom out.
			SmallZoomControl: true,
			// Creates a control that displays the map scale.
			ScaleControl: false,

			// Tooltip HTML element
			tooltip: null,

			//Possible values: Overlay, MarkerManager
			markersEngine: 'Overlay',

			// Storage for markers added to a map
			gmarkers: [],

			// Tooltip engines: v1, v2 
			// v2 uses enhanced positioning
			tooltipEngine: 'v1',

			// Active marker ID
			activeMarkerID: null, 

			//navigate to location on marker click
			navigateToLocation: false,

			// Register events
			init: function(mapEl, opt) {
				this.mapEl = document.getElementById(mapEl);
				if (GBrowserIsCompatible()) {

					//First of all, add event to free resources on window unload
					GEvent.addListener(window, 'unload', GUnload);
			
					// load map 
					//this.loadMap();

				}
				else {
					alert("Sorry, the Google Maps API is not compatible with this browser");
				}
			},

			loadMap: function()
			{

				if (this.gMap == null)
				{
					// Map options
					var opt = {}
					opt.mapTypes = []
					if (this.NormalMap) {
						opt.mapTypes.push(G_NORMAL_MAP);
					}
					if (this.SatelliteMap) {
						opt.mapTypes.push(G_SATELLITE_MAP);
					}
					if (this.HybridMap) {
						opt.mapTypes.push(G_HYBRID_MAP);
					}

					this.gMap = new GMap2(this.mapEl, opt); 

					//Enable double click
					this.gMap.enableDoubleClickZoom();

					// Add controls
					if (this.SmallMapControl) {
						this.gMap.addControl(new GSmallMapControl());
					}
					if (this.LargeMapControl) {
						this.gMap.addControl(new GLargeMapControl());
					}
					if (this.SmallZoomControl) {
						this.gMap.addControl(new GSmallZoomControl());
					}
					if (this.ScaleControl) {
						this.gMap.addControl(new GScaleControl());
					}

					if (opt.mapTypes.length > 1) {
						this.gMap.addControl(new GMapTypeControl());
					}
			
				}
			},

			// Set map center
			setCenter: function(lat, lon, zoom)
			{
				var point = new GLatLng(lat, lon);
				this.gMap.setCenter(point, zoom || this.defaultZoom);
			},

			// Add overlay to a map
			addOverlay: function(lat, lon)
			{
				var point = new GLatLng(lat, lon);
				//Check if the map was initialized by setCenter() since it was created.
				if (!this.gMap.isLoaded()) {
					this.gMap.setCenter(point, this.defaultZoom);
				}
				this.gMap.addOverlay(new GMarker(point));
			},

			// return zoom level
			getZoom: function()
			{
				return this.gMap.getZoom();
			},

			//////////////////////////////////////////////////////////////////////////////////////////////////////
			/*
			* Map geocoder (drag and drop support)
			*/

			/* Accuracy Levels
			* 0 	Unknown location. (Since 2.59)
			* 1 	Country level accuracy. (Since 2.59)
			* 2 	Region (state, province, prefecture, etc.) level accuracy. (Since 2.59)
			* 3 	Sub-region (county, municipality, etc.) level accuracy. (Since 2.59)
			* 4 	Town (city, village) level accuracy. (Since 2.59)
			* 5 	Post code (zip code) level accuracy. (Since 2.59)
			* 6 	Street level accuracy. (Since 2.59)
			* 7 	Intersection level accuracy. (Since 2.59)
			* 8 	Address level accuracy. (Since 2.59)
			*/

			accuracy: [
				{
					version: '2.67',
					levels: [
						1, //0
						4, //1
						7, //2
						9, //3
						10, //4
						11, //5
						13, //6
						14, //7
						16 //8
					]
				}
			],

			/**
			* Register map click event
			* @param	none
			*/
			registerMapClick: function() 
			{
				GEvent.bind(this.gMap, "click", this, function(overlay, point) {
					// Add dragable marker
					this.addDragableMarker(point);

					// Run callback method
					this.onMapClick(point.lat(), point.lng());
				});
			},

			/**
			* Add dragable marker to a map
			* @param	none
			*/
			addDragableMarker: function(point) 
			{
				//geodeveloper this.gMap.clearOverlays();
				var marker = new GMarker(point, {draggable: true});
				this.gMap.addOverlay(marker);
			
				GEvent.bind(marker, "dragend", this, function() {
					// Run callback method
					var point = marker.getPoint();
					this.onMapClick(point.lat(), point.lng());
				});
			},

			// Add dragable overlay to a map
			addDragableOverlay: function(lat, lon)
			{
				var point = new GLatLng(lat, lon);
				//Check if the map was initialized by setCenter() since it was created.
				if (!this.gMap.isLoaded()) {
					this.gMap.setCenter(point, this.defaultZoom);
				}
				this.addDragableMarker(point);
			},

			/**
			* Geocode given address and re-center map. Geocode using GClientGeocoder or Yahoo service.
			* @param	{String}	location	Location address
			*/
			geocode: function(address) 
			{
				// Is GClientGeocoder defined
				if (this.gGeocoder == null)
					this.gGeocoder = new GClientGeocoder();
			
				if (this.gGeocoder) 
				{
					/* use more advanced method with results analyses
					this.gGeocoder.getLatLng(
						address,
						function(point) 
						{
							if (!point) 
							{
								alert(address + " not found");
							} else 
							{
								this.gMap.setCenter(point, 14);
							}
						}.bind(this));
					*/

					// Use accuracy for setting proper zoom level
					// getLatLngAsync ???
					this.gGeocoder.getLocations(
						address,

						GEvent.callback(this, function(response) 
						{
							if (!response || response.Status.code != 200) {
								alert("\"" + address + "\" not found. Error code: " + response.Status.code);
							} else {
								place = response.Placemark[0];
								accuracy = place.AddressDetails.Accuracy;
								point = new GLatLng(place.Point.coordinates[1],
													place.Point.coordinates[0]);
								if (this.debug) this.LogWriter.log('"' + address + '" geocoded to accuracy ' + accuracy + ", which is converted to " + this.accuracy[0].levels[accuracy] + ' zoom level', "Info");
								this.gMap.setCenter(point, this.accuracy[0].levels[accuracy]);

								// Add dragable marker 
								//geodeveloper this.addDragableMarker(point);

								//Run callback method
								this.onGeocode(place.Point.coordinates[1], place.Point.coordinates[0], this.accuracy[0].levels[accuracy]);
							}
						}));
				}
			},


			// Return tooltip html element
			getTooltip: function() {
				if (!this.tooltip)
				{
					// set up marker mouseover tooltip div
					this.tooltip = document.createElement("div");
					/* element.setAttribute("class", "somename") works in Firefox and Safari, but not IE. 
					* The gotca is that IE requires element.setAttribute("className", "somename").
					* element.className = "somename"; which works in all browsers.
					* 
					* this.tooltip.setAttribute('className', 'tooltip');
					* this.tooltip.setAttribute('class', 'tooltip');
					*/
					this.tooltip.className =  "tooltip";

					this.gMap.getPane(G_MAP_FLOAT_PANE).appendChild(this.tooltip);
					this.tooltip.style.visibility="hidden";
				}
				return this.tooltip;
			},


			// A function to create the marker and set up the event window
			addMarker: function(id, lat, lng) {
		
				lat = parseFloat(lat);
				lng = parseFloat(lng);
				point = new GLatLng(lat,lng);

				// Standard icon
				var markerOptions = {}; 
				// Set icon
				try 
				{
					markerOptions.icon = this.gIcons[actRecord.iconname]; 
				}
				catch (e)
				{
					markerOptions.icon = new GIcon(G_DEFAULT_ICON); 
					/*
					markerOptions.icon.image = "http://www.homeexploring.com/maps/img/icons/house-green.png";
					markerOptions.icon.shadow = "http://www.google.com/mapfiles/shadow50.png";
					markerOptions.icon.iconSize = new GSize(15, 22);
					markerOptions.icon.shadowSize = new GSize(10, 15);
					*/
				}

				// Instatiate new Gmarker
				var marker = new GMarker(point, markerOptions);

				// Set marker id
				marker.id = id;
		
				GEvent.bind(marker, "click", this, function() {
					this.onMarkerClick(marker);
				});

				this.gmarkers[id] = marker;

		
				var a = null;

				// Add overley to a marker
				//Do we use Marker Manager 
				if (this.markersEngine != 'MarkerManager')
				{
					this.gMap.addOverlay(marker);
				}

				// *********** The new marker "mouseover" and "mouseout" listeners ***********
				GEvent.bind(marker,"mouseover", this, function() {
					this.showTooltip(marker);
					// Change active sidebar row background color
					this.onMarkerMouseOver(id);
				});
		   
				GEvent.bind(marker,"mouseout", this, function() {
					this.tooltip.style.visibility="hidden";
					if (this.activeMarkerID != id)
					{
						this.onMarkerMouseOut(id);
					}

				});

				// Finally return marker
				return marker; 
			},


			// This functions starts when user click on a marker
			onMarkerClick: function(marker)
			{
				this.activeMarkerID = marker.id;

				//re-center map
				if(this.navigateToLocation)
				{
					try
					{
						var latLongToCheck = marker.getPoint();
						// Do not recenter if marker is visible
						if (!this.gMap.getBounds().contains(latLongToCheck))
						{
							// maybe use panTo ?
							this.gMap.setCenter(latLongToCheck);
						}
					}
					catch(e){}
				}

				// Run hook
				this.onMarkerClickHook(marker);
			},

			// ***************** This function displays the tooltip *****************
			// it can be called from an icon mousover or a sideBar mouseover
			showTooltip: function(marker) {
				// marker.id - Active record id
				
				var tooltip = this.getTooltip();
				//Remove previous tooltip
				try
				{
					//tooltip.removeChild(this.tooltipTable); 
				}
				catch(e) {}

				tooltip.innerHTML = this.getToolTipText(marker.id);

				if (this.tooltipEngine == "v2")
				{
					var point = this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(this.gMap.fromDivPixelToLatLng(new GPoint(0,0),true),this.gMap.getZoom());
					var offset = this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(marker.getPoint(),this.gMap.getZoom());
					var anchor = marker.getIcon().iconAnchor;
					var iconW = marker.getIcon().iconSize.width;
					var iconH = marker.getIcon().iconSize.height;

					// Get tooltip width using PrototypeLib
					var oTTDim = Element.getDimensions(tooltip);
					// -- or --
					// use simple DOM methods
					/*
					var oTTDim = {}
					oTTDim.width = tooltip.clientWidth; 
					oTTDim.height = tooltip.clientHeight; 
					*/

					// Map size in pixels
					// Useless var oMapSize = this.gMap.getSize();
					var maxOffset_NE = this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(this.gMap.getBounds().getNorthEast(), this.gMap.getZoom());
					var maxOffset_SW = this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(this.gMap.getBounds().getSouthWest(), this.gMap.getZoom());
					var ttOffset = this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(this.gMap.fromDivPixelToLatLng(new GPoint(oTTDim.width, oTTDim.height),true),this.gMap.getZoom());

					if (this.debug) 
					{
						var logMsg = "oTTDim.height: " + oTTDim.height;
						logMsg +=  " maxOffset_NE.x:  " +  maxOffset_NE.x + " maxOffset_NE.y: " + maxOffset_NE.y;
						logMsg +=  " maxOffset_SW.x:  " +  maxOffset_SW.x + " maxOffset_SW.y: " + maxOffset_SW.y;
						logMsg +=  " offset.y: " + offset.y + " point.y: " + point.y + " anchor.y: " + anchor.y;

						logMsg +=  " COND X: " + (maxOffset_NE.x - offset.x ) + " < " + oTTDim.width;
					}

					if (maxOffset_NE.x - offset.x  <  oTTDim.width)
					{
						// Display left side tooltip
						posX = offset.x - point.x - anchor.x - oTTDim.width;
					}
					else
					{
						// Display right side tooltip
						posX = offset.x - point.x - anchor.x + iconW;
					}

					// Check top position
					if (this.debug) logMsg +=  " COND Y: " + (offset.y - maxOffset_NE.y - oTTDim.height) + "< 0";

					// offset.y - maxOffset_NE.y === actual Y marker's coordinate relative to the NORTH map border 
					if (offset.y - maxOffset_NE.y - oTTDim.height < 0)
					{
						posY = offset.y - point.y - anchor.y /* + iconH */;
					}
					else
					{
						posY = offset.y - point.y - anchor.y /* - iconH */ - oTTDim.height;
					}

					var pos  =  new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(posX, posY)); 
					if (this.debug) this.LogWriter.log(logMsg, "info");

					pos.apply(tooltip);
				}		
				else 
				// Use default engine
				{
					var point=this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(this.gMap.fromDivPixelToLatLng(new GPoint(0,0),true),this.gMap.getZoom());
					var offset=this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(marker.getPoint(),this.gMap.getZoom());
					var anchor=marker.getIcon().iconAnchor;
					var width=marker.getIcon().iconSize.width;
					var height=tooltip.clientHeight;
					var pos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(offset.x - point.x - anchor.x + width, offset.y - point.y -anchor.y -height)); 
					pos.apply(tooltip);
				}

				tooltip.style.visibility = "visible";

			},



			//////////////////////////////////////////////////
			// Geocode hook
			onGeocode: function(lat, lon, accuracy){},
			// Map click hook
			onMapClick: function(lat, lon){},
			// Zoom in hook
			onZoomEnd: function(oldzoom, newzoom){},
			// Hook to get tooltip text
			getToolTipText: function(id){},
			// Marker's on mouse out hook
			onMarkerMouseOut: function(id){},
			// Marker's on mouse over hook
			onMarkerMouseOver: function(id){},
			// Marker's on click hook
			onMarkerClickHook: function(marker){}
		}

		//////////////////////////////////////////////////////////////////////////////////////////////////////
		var App = {}
		App.widget = {}
		/*
		* Map status bar
		*/
		App.widget.StatusBar = function (el, opt) {
			this.init(el, opt); 
		}

		////////////////////////////////////////////////////////////////////////////////////////////
		App.widget.StatusBar.prototype = {
			el: null,
			// Containers for lat/lon values
			lat: null,
			lng: null,
			zoom: null,

			// Constructor
			init: function(el, opt) {
				this.el = $(el);

				/* Use markup layout
				this.lat = document.createElement("span");
				this.el.appendChild(this.lat);
				
				this.lng = document.createElement("span");
				this.el.appendChild(this.lng);
				*/
				
				this.lat = $('mapStatusBarLat__MODULE_ID__');
				this.lng = $('mapStatusBarLng__MODULE_ID__');
				this.zoom = $('mapStatusBarZoom__MODULE_ID__');
			},

			// Make status bar visible
			show: function() {
				this.el.style.display = "block";
			},

			// Display Longitute
			setLat: function(val) {
				this.show();
				this.lat.innerHTML = val;
			},
			// Display Latitude
			setLng: function(val) {
				this.show();
				this.lng.innerHTML = val;
			},

			// Display zoom level
			setZoom: function(val) {
				this.show();
				this.zoom.innerHTML = val;
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////////
		var GGadget = {}

		//GeoDeveloper Google Gadget class
		GGadget.GeoDeveloper = function (mapElID, mapStatusBarElID) {
			this.init(mapElID, mapStatusBarElID); 
		}

		GGadget.GeoDeveloper.prototype = {
			mapEl: null,
			mapStatusBarEl: null,
			addressInputEl: null,

			init: function(mapElID, mapStatusBarElID) {
				// set up our divs
				this.mapEl = $(mapElID);
				this.mapStatusBarEl = $(mapStatusBarElID);

				this.addressInputEl = $('geocodeAddress__MODULE_ID__');
				
				// Attach event to the address input field

				GEvent.bindDom(this.addressInputEl, "keydown", this, function(e) {
					if (!e) var e = window.event;
					if (e.keyCode) var code = e.keyCode;
					else if (e.which) var code = e.which;
					
					//enter key 
					if (code == 13) { 
						// Perform geocoding
						this.geocode();
					}
				});

				// Attach event to the geocode button
				GEvent.bindDom($('geocodeBtn__MODULE_ID__'), "click", this, this.geocode);

				// Load preferences
				if (false) {
					this.prefs = new _IG_Prefs(__MODULE_ID__);

					var mapCenterString = this.prefs.getString("location");
					var mapCenterLatLng = null;
					if ( mapCenterString ) {
						var lat = this.prefs.getString("location.lat");
						var lng = this.prefs.getString("location.long");
						if ( lat != "" && lng != "" ) {
							var mapCenterLatLng = new GLatLng(parseFloat(lat), parseFloat(lng));
						}
					}

					// Check if mapCenterLatLng has been initialized. If not, load it from locale files to get country specific default
					if (mapCenterLatLng == null) {
						mapCenterLatLng = new GLatLng(parseFloat(__MSG_defaultLat__), parseFloat(__MSG_defaultLng__));
					}
				}
				else
				{
					mapCenterLatLng = new GLatLng(40, -100);
				}

				// Instantiate map object
				this.mapViewer = new GApp.mapMiniViewer(this.mapEl.id, {});
				this.mapViewer.LargeMapControl = true;
				// Creates a control with buttons to zoom in and zoom out.
				this.mapViewer.SmallZoomControl = false;

				// Load Google map 
				this.mapViewer.loadMap();

				//this.map.setCenter(lat, lng);
				//Set center after loading data from a server
				//this.mapViewer.gMap.setCenter(mapCenterLatLng, 14);

				// Instantiate Status Bar object
				this.statusBar = new App.widget.StatusBar(mapStatusBarElID);

				//this.gmap.addControl(new GSmallMapControl());

				//Add dragable support
				//geodeveloper this.mapViewer.registerMapClick();

				// Register callback methods
				this.mapViewer.onGeocode = GEvent.callback(this, function(lat, lon, accuracy) {
					/* geodeveloper
					this.statusBar.setLat(lat);
					this.statusBar.setLng(lon);
					this.statusBar.setZoom(this.mapViewer.getZoom());
					*/
				});

				this.mapViewer.onMapClick = this.mapViewer.onGeocode;

				
				/* For some reason simple binding doesn't work. We should use closure function explicitly
				* // Event fired when zoom changes
				* GEvent.bind(this.mapViewer.gMap, "zoomend", this, this.onZoomEnd);
				*/
				// Event fired when zoom changes
				GEvent.bind(this.mapViewer.gMap, "zoomend", this, function(oldzoom, newzoom) {
					//geodeveloper this.statusBar.setZoom(newzoom);
				});

				// Register callback methods which should be defined before loading map
				this.onZoomEnd = function(oldzoom, newzoom) {
					//geodeveloper this.statusBar.setZoom(newzoom);
				};

				// Set initial zoom
				//geodeveloper this.statusBar.setZoom(this.mapViewer.getZoom());

			},

			// Perform geocoding of a text entered into input 
			geocode: function() {
				// Perform geocoding

				//use GClientGeocoder due to key restrictions 
				this.mapViewer.geocode(this.addressInputEl.value);
				
			},
			// Return map proxy object
			getMapProxy: function() {
				return this.mapViewer;
			}
		}

		return new GGadget.GeoDeveloper(mapElID, mapStatusBarElID); 

  }
