// vc_id = "$Id: msve.js 13680 2011-05-05 17:51:06Z jgiven $"

/*-------------------------------------------------------------------------------------------------
Name:            /js/map/msve.js
Purpose:         Functions specific to the Bing map. Markers / Icons div creation. 
Created By:      ?
Date Created:    ?
Change History:
12/03/2010  SDL  Added conditional for Sold icon type in function ListingMSVESetMapCallouts() 
                 var iconType = (fix.statusDescription == 'Sold') ? "sold_" + this.iconStyle : "std_" + this.iconStyle;
12/14/2010  SDL  Nested Sold icon assignment within standard conditional of Open House. 
-------------------------------------------------------------------------------------------------*/

var basepath = APPIMAGES ? APPIMAGES + "/" : "images/";

/** The ListingMapVendorPlugins, these object plugin to the ListingMapRegistry */

var LISTING_MSVE_VIEW_DEFAULT = "WEST";
var LISTING_MSVE_VERTICAL_CONTROL = new GeoRectangle( 0, 0, 136, 28 );
var LISTING_MSVE_HORIZONTAL_CONTROL = new GeoRectangle( 0, 0, 26, 352 );
var LISTING_MSVE_DIRECTIONAL_CONTROL = new GeoRectangle( 15, 15, 72, 72 );
var LISTING_MSVE_OBLIQUE_CONTROL = new GeoRectangle( 136, 0, 202, 28 );
var LISTING_MSVE_DASHBOARD_CONTROL_NA = new GeoRectangle( 0, 0, 20, 26 );
var LISTING_MSVE_OVERVIEW_CONTROL = null;
var LISTING_MSVE_OVERVIEW_CONTROL_WIDTH = 150;
var LISTING_MSVE_OVERVIEW_CONTROL_HEIGHT = 150;

/**
 * ListingMSVE.  
 * This is an implementation of ListingMapIF in terms of GMap.
 * The ListingMapIF extends StateChangeSupport.
 * This is a prototype for all ListingMapImpl.
 * @constructor
 * @param optional client key
 * @return new ListingMSVE instance.
 */
function ListingMSVE( key, oblique )  {
   // attributes
   // StateChangeListener objects.
   this.listeners = new Array( );
   this.name = "map";
   this.state = STATE_CHANGE_STATE_UNINITIALIZED;
   this.status = STATE_CHANGE_STATUS_OK;
   this.release = 0;
   this.count = 0;
   this.key = key;
   this.oblique = oblique ? oblique : 17;

   // StateChangeListener methods
   this.fireStart = ListingMSVEFireStart;
   this.fireStop = ListingMSVEFireStop;
   this.firedEventType = null;
   this.firedEventPosition = new GeoPoint( 0, 0 );
   this.trackEvent = ListingMSVETrackEvent;

   // Map attributes
   this.form = null;
   this.node = null;
   this.icons = null;
   this.server = null;
   this.geocoder = new ListingMSVEGeocoder( this );
   this.getMarkerHTML = ListingMSVEGetMarkerHTML;
   this.getMarkerStyle = ListingMSVEGetMarkerStyle;
   this.iconMap = null;
   this.iconStyle = "drk";
   this.markerLayer = null;
   this.regionComposition = null;

   // ListingMapIF methods
   this.initialize = ListingMSVEInitialize;
   this.finalize = ListingMSVEFinalize;
   this.setMapType = ListingMSVESetMapType;
   this.getMapType = ListingMSVEGetMapType;
   this.setMapTypeDefaults = ListingMSVESetMapTypeDefaults;
   this.setMapCallouts = ListingMSVESetMapCallouts;
   this.setMapRegion = ListingMSVESetMapRegion;
   this.setMapRegionByGeometry = ListingMSVESetMapRegionByGeometry;
   this.setMapRegionByIdentify = ListingMSVESetMapRegionByIdentify;
   this.setMapAction = ListingMSVESetMapAction;
   this.isMapPositionOverControl = ListingMSVEIsMapPositionOverControl;
   this.setExtentView = ListingMSVESetExtentView;
   this.setZoomView = ListingMSVESetZoomView;
   this.find = ListingMSVEFind;
   this.getZoom = ListingMSVEGetZoom;
   this.getZoomMin = ListingMSVEGetZoomMin;
   this.getZoomMax = ListingMSVEGetZoomMax;
   this.getExtent = ListingMSVEGetExtent;
   this.getProjectedExtent = ListingMSVEGetProjectedExtent;
   this.addGeocodeListener = ListingMSVEAddGeocodeListener;
   this.initializeIcons = ListingMSVEInitializeIcons;
   this.initializeDashboardControls = ListingMSVEInitializeDashboardControls;
   this.getMap = ListingMSVEGetMap;

   // handle the continuous zoom
   this.overZoomControl = false;
   this.positionChange = ListingMSVEPositionChange;
   this.continuousZoomID = 0;
   this.continuousZoomHandler = ListingMSVEContinuousZoomHandler;
   this.continuousZoomCallback = ListingMSVEContinuousZoomCallback;
   this.continuousZoomDelay = 500;
   addPositionChangeListener( this );
}
ListingMSVE.prototype = new ListingMapVendor( );

/**
 * Get the marker html
 * @param icon src url
 * @param icon size as a GeoPoint
 * @param icon offset as a GeoPoint
 * @param shadow src url, optional
 * @param shadow size as a GeoPoint, required if shadow exists
 */
function ListingMSVEGetMarkerHTML( iSrc, iSize, iOffset, sSrc, sSize ) {
    var html = "";

    // both the shadow, if it exists, and the icon live in a separate container
    if ( sSrc ) {
      html = html + '<div style="' + this.getMarkerStyle( sSrc, sSize ) + '" />';
    }
    html = html + '<div style="' + this.getMarkerStyle( iSrc, iSize ) + '" />';
    if ( sSrc ) {
	html = '<div style="' + this.getMarkerStyle( null, sSize, iOffset ) + '">' + html + '</div>';
    } else {
	html = '<div style="' + this.getMarkerStyle( null, iSize, iOffset ) + '">' + html + '</div>';
    }
    
    return html;
}

/**
 * Get the marker style, handles png icons
 * @param src url
 * @param size as a GeoPoint
 * @param offset as a GeoPoint.  optional
 * @return string style
 */
function ListingMSVEGetMarkerStyle( src, size, offset ) {
    var style = "";
    var filter = 'DXImageTransform.Microsoft.AlphaImageLoader';

    // process optional offset, relative to enclosing container
    if ( offset ) {
	style = style + "position:absolute;left:" + offset.x + "px;bottom:" + offset.y + "px;";
    } else {
	style = style + "position:absolute;left:0px;bottom:0px;";
    }
    // process size, required with src
    if ( size ) {
	style = style + "width:" + size.x + "px;height:" + size.y + "px;";
    }

    // add image, uses filter for png on ie ( even ie7 ), we can remove this when ie6 usage is n/a
    if ( src ) {
	if ( document.body.filters && document.body.filters.item && 
	     ( src.toLowerCase( ).indexOf( '.png' ) == ( src.length - 4 ) ) ) {
	    style = style + "filter:progid:" + filter + "(src='" + src + "', sizingMethod='scale');";
	} else {
	    style = style + "background-image:url('" + src + "');";
	}
    }

    return style;
}

/**
 * Initialize the icons
 */
function ListingMSVEInitializeIcons( ) {
    this.iconMap = new Array( );
    for ( var index in this.icons ) {
	this.iconMap[index] = new Object( );
	for ( var key in this.icons[index] ) {
	    this.iconMap[index][key] = new VECustomIconSpecification( );
	    this.iconMap[index][key].Image = this.icons[index][key].iSrc;
	    this.iconMap[index][key].CustomHTML = this.getMarkerHTML( this.icons[index][key].iSrc, 
								      this.icons[index][key].iSize, 
								      new GeoPoint( 0, -Math.round( this.icons[index][key].iAnchor.y / 2 ) ),
								      this.icons[index][key].sSrc, 
								      this.icons[index][key].sSize );
	}
    }
}

/**
 * Initializes the dashboard controls.
 * @param hide birdseye if true
 * @param hide 2D/3D if true
 * @param hide collapse if true
 */
function ListingMSVEInitializeDashboardControls( hideOblique, hideMode, hideCollapse ) {

	// BEGIN: Fixes for IE
	var zoomPanel = document.getElementById("MSVE_navAction_leftBackground");
	if( zoomPanel && zoomPanel.style )
	{
		zoomPanel.style.paddingTop = "0px";
		zoomPanel.style.marginTop = "0px";
		zoomPanel.style.height = "136px";
	}
	// END: Fixes for IE
	
	if ( !hideOblique && this.oblique > 12 ) {
	    // turn on oblique only when zoomed way in
	    new ListingMSVEObliqueControl( this, this.oblique );
	}
	if( !hideOblique && !hideMode && !hideCollapse ) return;

	var dashboardWidth = 29.54;
	var controlWidthOffset = 0;

	var hideControl = function( controlID ) {
		var element = document.getElementById( "MSVE_navAction_" + controlID );
		if( element && element.style )
			element.style.display = "none";
	};

	if( hideMode )
	{
		hideControl("modeCell");
		hideControl("separator0");
		dashboardWidth -= 4.0;
		controlWidthOffset -= 48;
	}

	if( hideOblique )
	{
		hideControl("ObliqueMapView");
		// hideControl("separator2");
		dashboardWidth -= 5.0;
		controlWidthOffset -= 60;
	}

	if( hideCollapse )
	{
		hideControl("toggleGlyphWrapper");
		dashboardWidth -= 2.0;
		controlWidthOffset -= 24;
	}

	var dashboard = document.getElementById("MSVE_navAction_container");

	if( dashboard && dashboard.style )
		dashboard.style.cssText = "width:" + dashboardWidth + "em !important";

	LISTING_MSVE_HORIZONTAL_CONTROL.right = LISTING_MSVE_HORIZONTAL_CONTROL.right + controlWidthOffset;

}

/**
 * Get a map object, utilizing earth or projected coords
 * @param an id for the map
 * @param projected, if true use projected coords
 * @return Map object in earth or projected coords
 */
function ListingMSVEGetMap( id, project ) {
    // could pull width, height from the node 
    // instead of form to be independent of form
    var width = this.form.mapWidth.value;
    var height =  this.form.mapHeight.value;
    var screen = new GeoRectangle( 0, 0, height, width );
    var extent = project ? this.getProjectedExtent( ) : this.getExtent( );
    var map = new Map( id, extent, null, screen, this.getZoom( ) );
    map.zoomLevelMax = this.getZoomMax( );
    
    var server = this.server;
    // override toScreenCoordinates for oblique positioning
    map.toScreenCoordinates = function ( extent ) {
	if ( extent.top ) {
	    // must be a GeoRectangle
	    var neCorner = this.toScreenCoordinates( 
	      new GeoPoint( extent.right, extent.top ) );
	    var swCorner = this.toScreenCoordinates( 
	      new GeoPoint( extent.left, extent.bottom ) );
	    return new GeoRectangle( neCorner.y, swCorner.x,
				     swCorner.y, neCorner.x );
	} else {
	    var scrn = server.LatLongToPixel( new VELatLong( extent.y, extent.x ) );
	    return new GeoPoint( scrn.x, scrn.y );
	}
    };
    
    return map;
}

/**
 * method to add a StateChangeListener to the geocoder
 * @param StateChangeListener
 */
function ListingMSVEAddGeocodeListener( listen ) {
    this.geocoder.addListener( listen );
}

/**
 * Track the event
 * @param reason
 * @param event type
 * @param event 
 */
function ListingMSVETrackEvent( reason, eventType, e ) {
    // logMessage( reason + ":" + eventType );
    this.firedEventType = eventType;
    if ( e ) {
	this.firedEventPosition.x = e.screenX;
	this.firedEventPosition.y = e.screenY;
    }
}

/**
 * Fire the start of a move to the listeners
 * @param event type
 * @param event
 */
function ListingMSVEFireStart( eventType, e ) {
    if ( eventType == "onstartpan" ) {
	this.release = this.count + 2;
    }
    this.trackEvent( "START", eventType, e );
    this.setState( STATE_CHANGE_STATE_LOADING );
}

/**
 * Fire the stop of a move to the listeners
 * @param event type
 * @param event
 */
function ListingMSVEFireStop( eventType, e ) {
    if ( eventType == "onendpan" &&
	 e.screenX == this.firedEventPosition.x &&
	 e.screenY == this.firedEventPosition.y ) {
	// no onchangeview will be fired
	this.count++;
    }
    this.trackEvent( "STOP", eventType, e );
    this.setMapTypeDefaults( );
    if ( ++this.count >= this.release ) {
	this.setState( STATE_CHANGE_STATE_COMPLETE );
    }
}

/**
 * Handler for the continuous zoom feature
 * The goal is to delay listeners ( search ) until
 *  zoom is complete.
 * @param string event type
 */
function ListingMSVEContinuousZoomHandler( eventType ) {
    if ( this.overZoomControl ) {
	if ( this.continuousZoomID ) { 
	    clearTimeout( this.continuousZoomID ); 
	}
	this.release = this.count + 2;
	var scs = this;
	this.continuousZoomID = setTimeout( function( ) { scs.continuousZoomCallback.call( scs, eventType ); }, this.continuousZoomDelay );
    }
}

/**
 * Callback for the continuous zoom feature
 * @param string event type
 */
function ListingMSVEContinuousZoomCallback( eventType ) {
    this.continuousZoomID = 0;
    this.fireStop( eventType );
}

/** start of ListingMapIF methods */

/**
 * initialize the ListingMapIF
 * @note called when plugin is activated, or
 *   page is loaded or resized
 */
function ListingMSVEInitialize( ) {
    scopingBar_separator = '';

    // fire an itialization event
    this.fireStart( "init" );
    this.release = this.count + 2;

    // construct the map
    var mapNode = document.getElementById( this.node );
    this.server = new VEMap(this.node);

    // set the client token if provided
    if ( this.key && this.key != "" ) {
	this.server.SetClientToken( this.key );
    }

    var view = retrieveMap( this.form.state1.value );
    if ( view == null ) {
        // todo - bobc: a null view happens when doing an MLS search. 
        // Orig code used the default view, it has been improved to provide the 
        // map based on the first MLS (you can search many) and a zoom into the stree level.
	view = retrieveMap( LISTING_MSVE_VIEW_DEFAULT ); 
        view.center.x = geospatial.mlsCenterLon;
        view.center.y = geospatial.mlsCenterLat;
        view.zoomLevel = 13;
    }

    this.server.LoadMap(new VELatLong( view.center.y, view.center.x ), view.zoomLevel );

    // add obique accuracy
    // this.server.SetShapesAccuracy(VEShapeAccuracy.Pushpin);
    
    // add controls
    this.initializeDashboardControls( false, true, true );

    // initialize the icons
    this.initializeIcons( );

    /**
    // overview map
    var width = mapNode.style.width.substring( 0, mapNode.style.width.indexOf( 'px' ) ) - 0;
    var height = mapNode.style.height.substring( 0, mapNode.style.height.indexOf( 'px' ) ) - 0;
    LISTING_MSVE_OVERVIEW_CONTROL = new GeoRectangle( ( height - LISTING_MSVE_OVERVIEW_CONTROL_HEIGHT ),
						      ( width - LISTING_MSVE_OVERVIEW_CONTROL_WIDTH ),
						      height, width );
    this.server.ShowMiniMap( LISTING_MSVE_OVERVIEW_CONTROL.left,  LISTING_MSVE_OVERVIEW_CONTROL.top, VEMiniMapSize.Small );
    */

    // add listeners to update the map environment
    var scs = this;
    // this.server.AttachEvent( "onstartpan", function( e ) { scs.fireStart.call( scs, "onstartpan", e ); } ); // bootstrap the MSVE events
    // this.server.AttachEvent( "onmouseup", function( e ) { scs.fireStop.call( scs, "onendpan", e ); } ); //bootstrap the MSVE events
    this.server.AttachEvent( "onstartzoom", function( e ) { scs.fireStart.call( scs, "onstartzoom", e ); } ); // bootstrap the MSVE events
    this.server.AttachEvent( "onendzoom", function( e ) { scs.continuousZoomHandler.call( scs, "onendzoom", e ); } ); // bootstrap the MSVE events
    this.server.AttachEvent( "onchangeview", function( e ) { scs.fireStop.call( scs, "onchangeview", e ); } ); // bootstrap the MSVE events
    
    // delay the end of initialization for the map to load
    setTimeout( function( ) { scs.fireStop.call( scs, 'init' ); }, 1000 );
    // this.fireStop( "init" );
//this.server.SetZoomLevel(12);
//this.server.ZoomOut();
}

/**
 * finalize the ListingMapIF
 * @note called when plugin is deactivated, or
 *  page is unloaded
 */
function ListingMSVEFinalize( ) {
    //GUnload( );
    this.server.Clear( );
    this.server.Dispose( );
    document.getElementById( this.node ).innerHTML = '';
}

/**
 * set the map type
 * @param map type
 */
function ListingMSVESetMapType( type ) {
    if ( type == "aerial" ) {
	this.server.SetMapStyle( VEMapStyle.Aerial ); 
    } else if ( type == "hybrid" ) {
	this.server.SetMapStyle( VEMapStyle.Hybrid ); 
    } else if ( type == "oblique" ) {
	this.server.SetMapStyle( VEMapStyle.Birdseye ); 
    } else {
	this.server.SetMapStyle( VEMapStyle.Road ); 
    }
}

/**
 * get the map type
 * @return map type
 */
function ListingMSVEGetMapType(  ) {
    var rcode = "map";	

    if ( this.server.GetMapStyle( )  == VEMapStyle.Aerial ) {
	rcode = "aerial";
    } else if ( this.server.GetMapStyle( )  == VEMapStyle.Hybrid ) {
	rcode = "hybrid";
    } else if ( this.server.GetMapStyle( )  == VEMapStyle.Birdseye ||
		this.server.GetMapStyle( )  == VEMapStyle.Oblique ||
		this.server.GetMapStyle( )  == VEMapStyle.BirdseyeHybrid ) {
	rcode = "oblique";
    }
    return rcode;
}

/**
 * set the defaults that are based on may type.
 * we could dynamically choose these attributes each time requested, based upon
 * map type, but it seems more efficient to set them at each map draw.
 */
function ListingMSVESetMapTypeDefaults( ) {
    if  ( this.getMapType( )  == "map" ) {
	this.iconStyle = "drk";
    } else {
	this.iconStyle = "lt";
    }
}

/**
 * set the map action
 * looks for a center action of pan or extent
 * @param pan or extent
 */
function ListingMSVESetMapAction( mapAction ) {
}

/**
  * Workhorse method for adding callouts to the map.
  * This method looks to the stored LocationFix collection.
  * This method will first clear previously set callouts.
  * @param object representing the fixes
  */
function ListingMSVESetMapCallouts( fixes ) {

    if ( !this.markerLayer ) {
		this.markerLayer = new VEShapeLayer( );
		this.server.AddShapeLayer( this.markerLayer );
    }
    this.markerLayer.DeleteAllShapes( );
    for ( key in fixes ) {
		var fix = fixes[key];
		if (fix.address == 'Address not disclosed'){
			continue;
		}
		
		var iconCollection = this.iconMap[fix.ptyp] ? this.iconMap[fix.ptyp] : this.iconMap[1];
		
		if (fix.attributes.hasOpenHouse( )){
			var iconType = "open_" + this.iconStyle;
		} else {
			if (fix.statusDescription == 'Sold'){
				var iconType = "sold_" + this.iconStyle;
			} else {
				var iconType = "std_" + this.iconStyle;
			}
		}
		
		//var iconType = fix.attributes.hasOpenHouse( ) ? "open_" + this.iconStyle : "std_" + this.iconStyle;
		//var iconType = (fix.statusDescription == 'Sold')  ? "sold_" + this.iconStyle : "std_" + this.iconStyle;
		
		var markerOptions = iconCollection[iconType];
		var marker = new VEShape( VEShapeType.Pushpin, new VELatLong( fix.earth.y, fix.earth.x ) );
		marker.SetCustomIcon( markerOptions );
		this.markerLayer.AddShape( marker );
    }
}

/**
 * Workhorse method for setting a region on the map.
 * This method utilizes a LocationFix.
 * This method will first clear a previously set region.
 * If we need to set more than one region, this method can be altered to accept a collection.
 * @param LocationFix object
 * @param set best map view, boolean, optional
 */
function ListingMSVESetMapRegion( region, bestView ) {
  geospatial.region = region;
  //bobc: orig code; controlling boundary display by choice of which of these two lines is commented.
    //this.setMapRegionByGeometry( region, bestView );//don't show boundary 
    this.setMapRegionByIdentify( region, bestView );  //show boundary
}

/**
 * Workhorse method for setting a region on the map.
 * This method utilizes a LocationFix with defined geometry ( bounds ).
 * This method will first clear a previously set region.
 * If we need to set more than one region, this method can be altered to accept a collection.
 * @param LocationFix object
 * @param set best map view, boolean, optional
 */
function ListingMSVESetMapRegionByGeometry( region, bestView ) {
    if ( !this.regionLayer ) {
	this.regionLayer = new VEShapeLayer( );
	
	geospatial.regionLayer = this.regionLayer;
	//console.log("ListingMSVESetMapRegionByGeometry - geospatial.regionLayer =" + geospatial.regionLayer);
	
	this.server.AddShapeLayer( this.regionLayer );

    }
    
    this.regionLayer.DeleteAllShapes( );
    
    if ( region.bounds ) {
	var shape = Dmp.Core.WktTools.toShape( region.bounds );
	shape.SetDescription( region.id );
	shape.HideIcon( );
	shape.SetLineColor( new VEColor( 38, 70, 146, 1.0 ) );
	shape.SetFillColor( new VEColor( 38, 70, 146, 0.2 ) );
	this.regionLayer.AddShape( shape );

	// set map view based on the region
	// we may want to make this conditional, or turn it off if problematic
	if ( bestView ) {
	    this.server.SetMapView( shape.GetPoints( ) );
	}
    }
}

/**
 * Workhorse method for setting a region on the map.
 * This method utilizes a LocationFix with defined identity and layer.
 * This method will first clear a previously set region.
 * If we need to set more than one region, this method can be altered to accept a collection.
 * @param LocationFix object
 * @param set best map view, boolean, optional
 */
function ListingMSVESetMapRegionByIdentify( region, bestView ) {

    //bobc: converted for use with ParcelStream
    
    if ( this.regionLayer ) {
	if(this.server.getDMPLayerById("highlightLayer")) this.server.removeLayer("highlightLayer");
    }
  
    if ( region.id ) {
	var info = region.id.split( ":" );
	
  	this.regionLayer = new Dmp.Layer.WMSLayer(
		"highlightLayer",  //bobc - naming layers allows easy deletion
		"SS",
    		{ showField: info[1], showValues: info[2], AntiAlias: true, IgnoreHoles: true }
    		);
    		
    	this.regionLayer.addChild(
    		"highlightLayer", 
    		info[0], 
    		"$(MY_FOLDER)/SLD/Identify.sld.xml",
    		{zoomRange: {min: 4, max:19 }}
    		);
	
	this.regionLayer.sldUrl = "$(MY_FOLDER)/SLD/identify.sld.xml";
	this.server.addLayer(this.regionLayer);
    		
	geospatial.regionLayer = this.regionLayer;
	//console.log("ListingMSVESetMapRegionByIdentify - geospatial.regionLayer =" + geospatial.regionLayer);

    }
}

/**
 * are we on top of a control
 * @param positionevent
 * @return true if we are
 */
function ListingMSVEIsMapPositionOverControl( positionevent ) {
    var over = false;

    if ( LISTING_MSVE_HORIZONTAL_CONTROL.isInside( positionevent.screen, true ) || 
	 LISTING_MSVE_VERTICAL_CONTROL.isInside( positionevent.screen, true ) ||
	 LISTING_MSVE_DIRECTIONAL_CONTROL.isInside( positionevent.screen, true ) ||
	 ( LISTING_MSVE_OVERVIEW_CONTROL && 
	   LISTING_MSVE_OVERVIEW_CONTROL.isInside( positionevent.screen, true ) ) ||
	 ( this.getMapType( ) == "oblique" && 
	   LISTING_MSVE_OBLIQUE_CONTROL.isInside( positionevent.screen, true ) ) ) {
	over = true;
    }

    return over;
}

/**
 * position change support method
 * @param positonevent
 */
function ListingMSVEPositionChange( positionevent ) {
    this.overZoomControl =  LISTING_MSVE_VERTICAL_CONTROL.isInside( positionevent.screen, true );
}

/**
 * update the listing msve, by extent.
 * this method accepts a map object to define its extent.
 * @param map object representing the bounds
 * @param silent if true, during initialization
 * @return true if transformed
 */
function ListingMSVESetExtentView( bounds, silent ) {
    var rcode = true;
    this.server.SetViewport( bounds.extent.bottom, bounds.extent.left, bounds.extent.top, bounds.extent.right );
    return rcode;
}

/**
 * update the listing msve, by zoom.
 * this method accepts a map object to define its extent.
 * @param map object representing the bounds
 * @param silent if true
 * @return true if transformed
 */
function ListingMSVESetZoomView( bounds, silent ) {
    var rcode = true;
    this.server.SetCenterAndZoom( new VELatLong( bounds.center.y, bounds.center.x ), bounds.zoomLevel );
    return rcode;
}

/**
 * update the listing msve, by location find.
 * this method accepts a locationfix object to define a location.
 * this method represents a hierarchy finder, with the following order of precedence:
 *  ZipFinder, CityFinder, StateFinder. 
 * @param locationfix object
 * @param silent if true
 * @return true if found
 */
function ListingMSVEFind( location, silent ) {
    return this.geocoder.find( location, silent );
}

/**
 * function to get the resolved zoom
 * @return zoom level, number
 */
function ListingMSVEGetZoom( ) {
    return this.server.GetZoomLevel( );
}

/**
 * function to get the minimum zoom level allowed
 * @return zoom level, number
 */
function ListingMSVEGetZoomMin( ) {
    return 6;
}

/**
 * function to get the maximum zoom level allowed
 * @return zoom level, number
 */
function ListingMSVEGetZoomMax( ) {
    return 19;
}

/**
 * function to get the resolved extent
 * in earth coords
 * @return GeoRectangle
 */
function ListingMSVEGetExtent( ) {
    // GetBirdseyeScene( ).GetBoundingRectangle( ) returns the whole scene
    // which is larger than the bounds from GetMapView( )
    //var bounds = 
    //this.getMapType( ) == "oblique" ? 
    //this.server.GetBirdseyeScene( ).GetBoundingRectangle( ) : this.server.GetMapView( );

    var bounds = this.server.GetMapView( );

    if ( this.getMapType( ) == "oblique" ) {
	var decoder = this.server.m_velayermanager.m_veLatLongDecoder;
	bounds.TopLeftLatLong = decoder.Decode( bounds.TopLeftLatLong );
	bounds.BottomRightLatLong = decoder.Decode( bounds.BottomRightLatLong );

	// we may want to hold this orientation in the GeoRectangle and convert
	// when necessary, like for searching, thus we respect the true region
	// that the map view presents
	var orientation = this.server.GetBirdseyeScene( ).GetOrientation( );
	switch( orientation ) {
	case VEOrientation.South:
	    var temp = bounds.TopLeftLatLong;
	    bounds.TopLeftLatLong = bounds.BottomRightLatLong;
	    bounds.BottomRightLatLong = temp;
	    break;
	case VEOrientation.East:
	    var lon = bounds.TopLeftLatLong.Longitude;
	    bounds.TopLeftLatLong.Longitude = bounds.BottomRightLatLong.Longitude;
	    bounds.BottomRightLatLong.Longitude = lon;
	    break;
	case VEOrientation.West:
	    var lat = bounds.TopLeftLatLong.Latitude;
	    bounds.TopLeftLatLong.Latitude = bounds.BottomRightLatLong.Latitude;
	    bounds.BottomRightLatLong.Latitude = lat;
	    break;
	default:
	    break;
	}
    } 

    var northWest = bounds.TopLeftLatLong;
    var southEast = bounds.BottomRightLatLong;
    var extent = new GeoRectangle( northWest.Latitude, northWest.Longitude,
				   southEast.Latitude, southEast.Longitude );

    return extent;
}

/**
 * function to get the resolved extent
 * in map coords
 * @return GeoRectangle
 */
function ListingMSVEGetProjectedExtent( ) {
    return this.getExtent( );
}

/** end of ListingMapIF methods */

/** start of Geocoder */

/**
 * ListingMSVEGeocoder.  
 * This is an implementation of StateChangeSupport in
 * terms of the geocoder.
 * @constructor
 * @param parent, ListingMapIF object, for server and callback after find
 * @return new ListingMSVEGeocoder
 */
function ListingMSVEGeocoder( parent )  {
   // attributes
   // StateChangeListener objects.
   this.listeners = new Array( );
   this.name = "location";
   this.state = STATE_CHANGE_STATE_UNINITIALIZED;
   this.status = STATE_CHANGE_STATUS_OK;
   this.STATE_CHANGE_STATUS_UNKNOWN_ADDRESS = 602;
   this.STATE_CHANGE_STATUS_UNAVAILABLE_ADDRESS = 603;
   STATE_CHANGE_STATUS_TEXT[this.STATE_CHANGE_STATUS_UNKNOWN_ADDRESS] = "unknown";
   STATE_CHANGE_STATUS_TEXT[this.STATE_CHANGE_STATUS_UNAVAILABLE_ADDRESS] = "unavailable";

   // MSVEGeocoder attributes
   this.map = parent;
   // the current fix that is being found
   this.fix = null;

   // methods
   this.find = ListingMSVEGeocoderFind;
   this.findLocation = ListingMSVEGeocoderFindLocation;
   this.callback = ListingMSVEGeocoderCallback;
}
ListingMSVEGeocoder.prototype = new StateChangeSupport( );

/**
 * update the listing msve, by location find.
 * this method accepts a locationfix object to define a location.
 *  ZipFinder, CityFinder, StateFinder. 
 * @param locationfix object
 * @param silent if true
 * @return true if found
 */
function ListingMSVEGeocoderFind( location, silent ) {
    this.setState(  STATE_CHANGE_STATE_LOADING );
    var scs = this;
    var rcode = false;
    this.fix = location;

    if ( location.scheme && location.scheme == LOCATION_FIX_SCHEME.ZIP ) {
	this.findLocation( location.zip );
    } else if ( location.scheme && location.scheme == LOCATION_FIX_SCHEME.CITY ) {
	this.findLocation( location.city + ", " + location.state );
    } else if ( location.scheme && location.scheme == LOCATION_FIX_SCHEME.STATE ) {
	this.findLocation( location.state );
    } else if ( silent ) {
	// initializing, default view already set to west, so send success
        this.setStatus( STATE_CHANGE_STATUS_OK );
	this.setState(  STATE_CHANGE_STATE_COMPLETE ); 
	rcode = this.map.setZoomView( retrieveMap( LISTING_MSVE_VIEW_DEFAULT ), silent );
	// the above zoom view does not seem to redraw the map and trigger the required events
	// so we will push a completion event, to assure proper initialization and bootstrapping
	this.map.fireStop( "init" );
    } else {
	this.setStatus( this.STATE_CHANGE_STATUS_UNKNOWN_ADDRESS );
        this.setState(  STATE_CHANGE_STATE_COMPLETE );
    }

    return rcode;
}

/**
 * convenience method for calling the geocoders find in terms of a location ( rather than a business )
 * @param location as a string
 */
function ListingMSVEGeocoderFindLocation( location ) {
    var scs = this;
    this.map.server.Find( null, location, VEFindType.Businesses, null, 0, 10, 
			  true, true, true, true, 
			  function( layer, result, place, more, error ) { scs.callback.call( scs, place ); } );
}

/**
 * Callback from the geocoder
 * @param a geocode response, an array of VEPlace class objects or null if no results
 */
function ListingMSVEGeocoderCallback( response ) {
    this.setState(  STATE_CHANGE_STATE_LOADED );

    if ( response ) {
	this.fix.earth = new GeoPoint( response[0].LatLong.Longitude, response[0].LatLong.Latitude );
    }

    this.setStatus( STATE_CHANGE_STATUS_OK );
    this.setState(  STATE_CHANGE_STATE_COMPLETE );  
}

/**
 * Initialize the oblique control
 * @param parent vendor plugin
 * @param zoom level at which to light up the oblique
 */
function ListingMSVEObliqueControl( parent, zoom ) {
    // attributes.
    this.map = parent;
    this.zoom = zoom > 18 ? 18 : zoom;
    this.tooltipDefault = "See this location in bird's eye view";

    // methods
    this.customId = ListingMSVEObliqueControlCustomId;
    this.enableButton = ListingMSVEObliqueControlEnableButton;
    this.disableButton = ListingMSVEObliqueControlDisableButton;
    this.enableTooltip = ListingMSVEObliqueControlEnableTooltip;
    this.stateChange = ListingMSVEObliqueControlStateChange;
    this.map.addListener( this );

    //create custom bird's eye button, and hide the original
    var originalControl = document.getElementById('MSVE_navAction_ObliqueMapView');
    var newControl = originalControl.cloneNode(1);
    originalControl.style.display = "none";    
    this.customId( newControl );
    newControl.style.width = "58.1333px"; 
    document.getElementById("MSVE_navAction_styleGroup").insertBefore(newControl, originalControl); 
    var indicator = document.getElementById("MSVE_obliqueNotification_Custom");
    if ( indicator ) {
	indicator = indicator.removeNode(true);
    } else {
	// create missing elements for >= v6.1, backwards compat with < v6.1
	indicator = document.createElement( 'div' );
	indicator.id = "MSVE_obliqueNotification_Custom";
	indicator.style.display = 'none';
	var notifyText = document.createElement( 'div' );
	notifyText.id = "MSVE_obliqueNotifyText_Custom";
	notifyText.innerHTML = this.tooltipDefault;
	indicator.appendChild( notifyText );
        document.getElementById( "MSVE_obliqueNotification" ).style.visibility = 'hidden';
    }
    document.body.appendChild(indicator);
    indicator.style.top = (findPosY(newControl) + 29) + 'px';
    indicator.style.left = findPosX(newControl) + 'px';

    var oc = this;
    registerEventHandler( document.getElementById("MSVE_navAction_tinyZoomBar_minus"), "click", 
			  function( ) { 
			      if ( oc.map.getMapType() == "oblique" ) {
				  oc.enableTooltip( "Select road or aerial to zoom out" ); } 
			  } );
}

function ListingMSVEObliqueControlCustomId( el ) {
    if ( el.id && ( el.id == "MSVE_navAction_ObliqueMapView" || 
		    el.id == "MSVE_obliqueNotification" ||
		    el.id == "MSVE_obliqueNotifyText" ) ) {
	el.id += "_Custom";
    }
    for ( var i = 0; i < el.childNodes.length; i++ ) {
	this.customId( el.childNodes[i] );
    }
}

/**
 * Enable the bird's eye button
 */
function ListingMSVEObliqueControlEnableButton( ) {
    var control = document.getElementById('MSVE_navAction_ObliqueMapView_Custom');
    //enable custom bird's eye button
    control.className = "MSVE_MapStyle";
    control.title = "Switch to bird's eye";

    var scs = this.map;
    control.onclick = 
        function( ) { if ( scs.getMapType( ) != "oblique" ) { scs.setMapType( "oblique" ); } };
    control.onmouseover = null;

    this.enableTooltip( );
}
      
/**
 * Disable the bird's eye button
 */
function ListingMSVEObliqueControlDisableButton( ) {
    var control = document.getElementById('MSVE_navAction_ObliqueMapView_Custom');
    control.className = "MSVE_MapStyle_disabled";
    control.title = ""; 
    document.getElementById("MSVE_obliqueNotification_Custom").style.display = "none";     
    control.onclick = null;
    var oc = this;
    control.onmouseover = function( ) { if ( oc.map.server.IsBirdseyeAvailable( ) ) { oc.enableTooltip( "Zoom in to enable bird's eye" ); } };
}

/**
 * Enable the tooltip with a message
 * @param string message, optional, otherwise uses default message
 */
function ListingMSVEObliqueControlEnableTooltip( message ) {
    var tooltip = message ? message : this.tooltipDefault;

    // set the tooltip
    document.getElementById("MSVE_obliqueNotifyText_Custom").innerHTML = tooltip;
 
    //show the indicator
    var obliqueButton = document.getElementById("MSVE_navAction_ObliqueMapView_Custom");
    var indicator = document.getElementById("MSVE_obliqueNotification_Custom");
	
    indicator.style.display = "block";
    indicator.style.fontWeight = 400;

    indicator.style.zIndex = '150';
    indicator.style.border = '1px solid #000000';
    indicator.style.backgroundColor = '#E5EDF5';
    indicator.style.width = '157px';
    indicator.style.height = '40px';
    indicator.style.lineHeight = '12pt';
    indicator.style.textAlign = 'left';
    indicator.style.position = 'absolute';
    indicator.style.top = (findPosY(obliqueButton) + 29) + 'px';
    indicator.style.left = (findPosX(obliqueButton) + 10) + 'px';
	indicator.style.backgroundRepeat = 'no-repeat';

    window.setTimeout(function(){ indicator.style.display="none" }, 6000);
}

/**
 * StateChangeListener method
 * @param scs
 */
function ListingMSVEObliqueControlStateChange( scs ) {
    if ( scs.success( ) ) {
	var obliqueButton = document.getElementById("MSVE_navAction_ObliqueMapView_Custom");
	var rotatorContainer = document.getElementById("MSVE_navAction_rotatorContainer");
	
	// Because the word Birds Eye breaks in IE make font slightly smaller
	obliqueButton.style.fontSize = '9.5px';
	
	// Since we are controlling MS's panel widget so much we need to control the height of the 
	// left panel.  we set a fixed height on init() but it most grow in oblique.
	var zoomPanel = document.getElementById("MSVE_navAction_leftBackground");
	if (scs.getMapType() == "oblique") {
		zoomPanel.style.height = '200px';
	} else {
		zoomPanel.style.height = '136px';
	}
	
	// alter the button style based upon map type
        if (scs.getMapType() == "oblique") {
	    //oblique button selected                       
            obliqueButton.style.fontWeight = "bold";
            obliqueButton.style.backgroundImage = 
              "url(http://dev.virtualearth.net/mapcontrol/v6/i/bin/1.0.20070926140324.39/NavAction/nav_select.gif)";
            obliqueButton.style.backgroundRepeat = "no-repeat";
            obliqueButton.style.backgroundPosition = "center bottom";
            document.getElementById("MSVE_obliqueNotification_Custom").style.display = "none";       
        } else {
            //oblique button disabled
            obliqueButton.style.fontWeight = 400;
            obliqueButton.style.backgroundImage = "";
	    if ( scs.getZoom( ) >= this.zoom &&
		 scs.server.IsBirdseyeAvailable( ) ) {
		this.enableButton( );
            } else if ( scs.getZoom( ) == ( this.zoom - 2 ) &&
		 scs.server.IsBirdseyeAvailable( ) ) {
		this.enableTooltip( "Zoom in to enable bird's eye" );
	    } else {
		this.disableButton( );
	    }
        }
    }
    document.getElementById( "MSVE_obliqueNotification" ).style.visibility = 'hidden';
}

