// vc_id = "$Id: listingMapRegistry.js 13457 2011-01-24 19:45:18Z slloyd $"
/*-------------------------------------------------------------------------------------------------
Name:            /js/map/listingMapRegistry.js
Purpose:         Contains icon initialization for house, condo, manufactured, multi-family, land, and rental. 
                 This object accepts vendor plugins.  
Created By:      ?
Date Created:    ?
Change History:
12/03/2010  SDL  Added Sold entry for House icon and Condo icon. 
12/09/2010  SDL  Added standard and Sold icons for Land, Manufactured and Multi-family 
12/14/2010  SDL  Commented out icons for Condo, Land, Manufactured and Multi-family. Swapped out pin for map_house icons. 
12/20/2010  SDL  Set all iOffsets to 0,0 
01/06/2011  SDL  Set all iOfsetts back to 10,40 
01/06/2011  SDL  Set all iOfsetts to 10,60 to vertically center the rollover hotspot.
-------------------------------------------------------------------------------------------------*/

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


/**
 * Constructor for a ListingMapRegistry object.
 * This object utilizes a vendor plugin architecture.
 * There must be at least one vendor implementation available.
 * @param form object that the listing map will act upon
 * @param string representing the id of the map node
 * @param string representing the id of the mask node
 */
function ListingMapRegistry( form, node, mask ) {
    this.form = form;
    this.node = node;
    this.mask = mask;
    this.origin = null;
   
    // which product are we using, this will be in the form
    this.product = null;

    // the vendor plugins
    this.vendors = new Object( );

    // StateChangeListener objects.
    this.listeners = new Array( );    
    this.geocodeListeners = new Array( );

    // the mask, to generate the missing mousedown events that GMap captures
    addPositionChangeListener( new MapEventGenerator( this ) );

    // icon array with ptyp index  
    // object holds standard and open icons, in dark and light
    // light icons are for dark backgrounds, like aerial
    this.icons = new Array( );
    
	this.icons[1] = new Object( );
	
    this.icons[1].std_drk = 
	new ListingMapIcon( basepath + "map/map_house.png", new GeoPoint( 44, 50 ), new GeoPoint( 10, 60 ),
			    basepath + "map/map_house_shadow.png", new GeoPoint( 44, 50 ) );
    this.icons[1].std_lt = new ListingMapIcon( this.icons[1].std_drk );
    this.icons[1].std_lt.iSrc = basepath + "map/map_house.png";
	
	this.icons[1].sold_drk = 
	new ListingMapIcon( basepath + "map/map_house_sold.png", new GeoPoint( 44, 50 ), new GeoPoint( 10, 60 ),
			    basepath + "map/map_house_shadow.png", new GeoPoint( 44, 50 ) );
    this.icons[1].sold_lt = new ListingMapIcon( this.icons[1].sold_drk );
    this.icons[1].sold_lt.iSrc = basepath + "map/map_house_sold.png";
    
	this.icons[1].open_drk =
	new ListingMapIcon( basepath + "map/map_house_open.png", new GeoPoint( 51, 50 ), new GeoPoint( 10, 60 ),
			    basepath + "map/map_house_open_shadow.png", new GeoPoint( 51, 50 ) );
    this.icons[1].open_lt = new ListingMapIcon( this.icons[1].open_drk );
    this.icons[1].open_lt.iSrc = basepath + "map/map_house_open.png"
    
	/*****************************
	this.icons[2] = new Object( );
	
    this.icons[2].std_drk = 
	new ListingMapIcon( basepath + "map/pin_condo.png", new GeoPoint( 22, 34 ), new GeoPoint( 10, 33  ),
			    basepath + "map/shadow-condo.png", new GeoPoint( 41, 36 ) );
    this.icons[2].std_lt = new ListingMapIcon( this.icons[2].std_drk );
    this.icons[2].std_lt.iSrc = basepath + "map/pin_condo_inverted.png";
	
	this.icons[2].sold_drk = 
	new ListingMapIcon( basepath + "map/pin_sold_condo.png", new GeoPoint( 22, 34 ), new GeoPoint( 10, 33  ),
			    basepath + "map/shadow-condo.png", new GeoPoint( 41, 36 ) );
    this.icons[2].sold_lt = new ListingMapIcon( this.icons[2].sold_drk );
    this.icons[2].sold_lt.iSrc = basepath + "map/pin_sold_condo_inverted.png";
    
	this.icons[2].open_drk =
	new ListingMapIcon( basepath + "map/pin-open-condo2.png", new GeoPoint( 41, 49 ), new GeoPoint( 11, 48  ),
			    basepath + "map/shadow-open-condo.png", new GeoPoint( 41, 49 ) );
    this.icons[2].open_lt = new ListingMapIcon( this.icons[2].open_drk );
    this.icons[2].open_lt.iSrc = basepath + "map/pin-open-condo-reverse2.png";
	
	this.icons[3] = new Object( );
	
    this.icons[3].std_drk = 
	new ListingMapIcon( basepath + "map/pin_land.png", new GeoPoint( 22, 34 ), new GeoPoint( 10, 33  ),
			    basepath + "map/shadow.png", new GeoPoint( 41, 34 ) );
    this.icons[3].std_lt = new ListingMapIcon( this.icons[3].std_drk );
    this.icons[3].std_lt.iSrc = basepath + "map/pin_land_inverted.png";
	
	this.icons[3].sold_drk = 
	new ListingMapIcon( basepath + "map/pin_sold_land.png", new GeoPoint( 22, 34 ), new GeoPoint( 10, 33  ),
			    basepath + "map/shadow.png", new GeoPoint( 41, 34 ) );
    this.icons[3].sold_lt = new ListingMapIcon( this.icons[3].sold_drk );
    this.icons[3].sold_lt.iSrc = basepath + "map/pin_sold_land_inverted.png";
    
	// I didn't make an open house land icon, so it is the default house one.
	this.icons[3].open_drk =
	new ListingMapIcon( basepath + "map/pin-open2.png", new GeoPoint( 41, 49 ), new GeoPoint( 11, 48  ),
			    basepath + "map/shadow-open.png", new GeoPoint( 41, 49 ) );
    this.icons[3].open_lt = new ListingMapIcon( this.icons[3].open_drk );
    this.icons[3].open_lt.iSrc = basepath + "map/pin-open-reversed2.png"
	
	this.icons[4] = new Object( );
	
    this.icons[4].std_drk = 
	new ListingMapIcon( basepath + "map/pin_manufactured2.png", new GeoPoint( 22, 34 ), new GeoPoint( 10, 33  ),
			    basepath + "map/shadow.png", new GeoPoint( 41, 34 ) );
    this.icons[4].std_lt = new ListingMapIcon( this.icons[4].std_drk );
    this.icons[4].std_lt.iSrc = basepath + "map/pin_manufactured2_inverted.png";
	
	this.icons[4].sold_drk = 
	new ListingMapIcon( basepath + "map/pin_sold_manufactured2.png", new GeoPoint( 22, 34 ), new GeoPoint( 10, 33  ),
			    basepath + "map/shadow.png", new GeoPoint( 41, 34 ) );
    this.icons[4].sold_lt = new ListingMapIcon( this.icons[4].sold_drk );
    this.icons[4].sold_lt.iSrc = basepath + "map/pin_sold_manufactured2_inverted.png";
    
	// I didn't make an open house manufactured icon, so it is the default house one.
	this.icons[3].open_drk =
	new ListingMapIcon( basepath + "map/pin-open2.png", new GeoPoint( 41, 49 ), new GeoPoint( 11, 48  ),
			    basepath + "map/shadow-open.png", new GeoPoint( 41, 49 ) );
    this.icons[3].open_lt = new ListingMapIcon( this.icons[3].open_drk );
    this.icons[3].open_lt.iSrc = basepath + "map/pin-open-reversed2.png"

	this.icons[7] = new Object( );
	
    this.icons[7].std_drk = 
	new ListingMapIcon( basepath + "map/pin_multiFamily.png", new GeoPoint( 22, 34 ), new GeoPoint( 10, 33  ),
			    basepath + "map/shadow.png", new GeoPoint( 41, 34 ) );
    this.icons[7].std_lt = new ListingMapIcon( this.icons[7].std_drk );
    this.icons[7].std_lt.iSrc = basepath + "map/pin_multiFamily_inverted.png";
	
	this.icons[7].sold_drk = 
	new ListingMapIcon( basepath + "map/pin_sold_multiFamily.png", new GeoPoint( 22, 34 ), new GeoPoint( 10, 33  ),
			    basepath + "map/shadow.png", new GeoPoint( 41, 34 ) );
    this.icons[7].sold_lt = new ListingMapIcon( this.icons[7].sold_drk );
    this.icons[7].sold_lt.iSrc = basepath + "map/pin_sold_multiFamily_inverted.png";
    
	this.icons[7].open_drk =
	new ListingMapIcon( basepath + "map/pin-open-multiFamily.png", new GeoPoint( 41, 49 ), new GeoPoint( 11, 48  ),
			    basepath + "map/shadow-open.png", new GeoPoint( 41, 49 ) );
    this.icons[7].open_lt = new ListingMapIcon( this.icons[7].open_drk );
    this.icons[7].open_lt.iSrc = basepath + "map/pin-open-multiFamily-inverted.png"
	
	
	this.icons[6] = new Object( );
	
    this.icons[6].std_drk = 
	new ListingMapIcon( basepath + "map/pin_Rental.png", new GeoPoint( 22, 34 ), new GeoPoint( 10, 33  ),
			    basepath + "map/shadow.png", new GeoPoint( 41, 34 ) );
    this.icons[6].std_lt = new ListingMapIcon( this.icons[6].std_drk );
    this.icons[6].std_lt.iSrc = basepath + "map/pin_Rental.png";
	******************************/
	
	
    // methods
    this.registerVendor = ListingMapRegistryRegisterVendor;
    this.getVendor = ListingMapRegistryGetVendor;
    this.stateChange = ListingMapRegistryStateChange;
    this.setMapType = ListingMapRegistrySetMapType;
    this.setMapProduct = ListingMapRegistrySetMapProduct;
    this.setMapCallouts = ListingMapRegistrySetMapCallouts;
    this.setMapRegion = ListingMapRegistrySetMapRegion;
    this.setMapAction = ListingMapRegistrySetMapAction;
    this.updateMap = ListingMapRegistryUpdateMap;
    this.setExtentView = ListingMapRegistrySetExtentView;
    this.setZoomView = ListingMapRegistrySetZoomView;
    this.setFixView = ListingMapRegistrySetFixView;
    this.findLocation = ListingMapRegistryFindLocation;
    this.addGeocodeListener = ListingMapRegistryAddGeocodeListener;
}
ListingMapRegistry.prototype = new StateChangeSupport( );

/**
 * method for map vendor registration
 * @param id for this vendor
 * @param ListingMapVendor object
 */
function ListingMapRegistryRegisterVendor( id, vendor ) {
    // add the vendor
    this.vendors[ id ] = vendor;
    this.product = id;

    // add the receiver as a listener on the map vendor plugin
    vendor.addListener( this );
    vendor.addGeocodeListener( this );
    vendor.form = this.form;
    vendor.node = this.node;
    vendor.icons = this.icons;
}

/**
 * method to retrieve the current vendor implementation
 * @return ListingMapVendor plugin
 */
function ListingMapRegistryGetVendor( ) {
    return this.vendors[ this.product ];
}

/**
 * Listing map callback function
 * The vendor plugin will be calling back the receiver.
 * In this case, the scs container is the vendor plugin.
 * We then bubble the scs out to the receiver listeners.
 * @param StateChangeSupport container
 */
function ListingMapRegistryStateChange( scs ) {    
    if ( scs.success( ) && scs.name == "map" ) {
	var form = this.form;
	form.mapConnectionType.value = this.getVendor( ).getMapType( );
	initializeListingMap( this.getVendor( ).getMap( LISTING_MAP, true ),
			      this.getVendor( ).getMap( LISTING_EARTH ) );
        initializePositionChangeSupport( this.getVendor( ).getMap( POSITION_MAP, true ),
					 this.getVendor( ).getMap( POSITION_EARTH ) );
				
        registerListingMap( );
	
        form.mapExtentLeft.value = retrieveMap( LISTING_MAP ).extent.left;
        form.mapExtentRight.value = retrieveMap( LISTING_MAP ).extent.right;
        form.mapExtentTop.value = retrieveMap( LISTING_MAP ).extent.top;
        form.mapExtentBottom.value = retrieveMap( LISTING_MAP ).extent.bottom;
        form.mapExtentNorth.value = retrieveMap( LISTING_EARTH ).extent.top;
        form.mapExtentSouth.value = retrieveMap( LISTING_EARTH ).extent.bottom;
        form.mapExtentWest.value = retrieveMap( LISTING_EARTH ).extent.left;
        form.mapExtentEast.value = retrieveMap( LISTING_EARTH ).extent.right;
        form.extentNorth.value = retrieveMap( LISTING_EARTH ).extent.top;
        form.extentSouth.value = retrieveMap( LISTING_EARTH ).extent.bottom;
        form.extentWest.value = retrieveMap( LISTING_EARTH ).extent.left;
        form.extentEast.value = retrieveMap( LISTING_EARTH ).extent.right;
	
	if (  form.mapExtentTopDefault.value == "" ||
	      form.mapExtentBottomDefault.value == "" ||
	      form.mapExtentRightDefault.value == "" ||
	      form.mapExtentLeftDefault.value == "" ) {       
	    form.mapExtentTopDefault.value = form.mapExtentTop.value;
	    form.mapExtentBottomDefault.value = form.mapExtentBottom.value;
	    form.mapExtentRightDefault.value = form.mapExtentRight.value;
	    form.mapExtentLeftDefault.value = form.mapExtentLeft.value;
        }
    }
    
    // bubble the scs ( vendor plugin ) out to the receiver listeners
    if ( scs.name == "map" ) {
	var size = this.listeners.length;
	for ( var i = 0; i < size; i++ ) {
	    this.listeners[ i ].stateChange( scs );
	}
    } else if ( scs.name == "location" ) {
	var size = this.geocodeListeners.length;
	for ( var i = 0; i < size; i++ ) {
	    this.geocodeListeners[ i ].stateChange( scs );
	}
    }
}

/**
 * set the map type
 * @param optional type, if sent, the form field will also be set,
 *  otherwise the value is pulled from the form field
 */
function ListingMapRegistrySetMapType( type ) {
    if ( type ) {
	this.form.mapConnectionType.value = type;
    } else {
	type =  this.form.mapConnectionType.value;
    }

    this.getVendor( ).setMapType( type );
}

/**
 * set the map product
 * @param optional product, if sent, the form field will also be set,
 *  otherwise the value is pulled from the form field
 */
function ListingMapRegistrySetMapProduct( product ) {
    if ( this.product != product ) {
	this.updateMap( "final" );
	this.form.mapConnectionProduct = product;
	this.product = product;
	this.updateMap( "init" );
    }
}

/**
 * set the map action
 * looks for a center action of pan or extent
 * @param pan or extent
 */
function ListingMapRegistrySetMapAction( mapAction ) {
    var mapAction = this.form.centerAction.value;
    var mapMask = document.getElementById( this.mask );
    if ( mapAction == "extent" ) {
	mapMask.style.display = 'block';
    } else {
        mapMask.style.display = 'none';
    }
    this.getVendor( ).setMapAction( 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 poi
  */
function ListingMapRegistrySetMapCallouts( poi ) {
    this.getVendor( ).setMapCallouts( poi );
}

/**
 * Workhorse method for setting a region on the map.
 * This method utilizes a LocationFixe 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 ListingMapRegistrySetMapRegion( region, bestView ) {
    this.getVendor( ).setMapRegion( region, bestView );
}

/**
 * function to update the listing map.
 * this method holds the map view business logic.
 * @param action being taken
 */
function ListingMapRegistryUpdateMap( action ) {
    var vendor = this.getVendor( );

     if ( action == "init" ) {
         // initialize the mapMask, to generate mousedown events
	 var mapNode = document.getElementById( this.node );
	 var mapMask = document.getElementById( this.mask );
	 mapMask.style.left = findPosX( mapNode ) + 'px';
	 mapMask.style.top =  findPosY( mapNode ) + 'px';
	 mapMask.style.width = mapNode.style.width;
	 mapMask.style.height = mapNode.style.height;
	 //mapMask.style.width = width + 'px';
	 //mapMask.style.height = height + 'px';
	 this.form.mapWidth.value = mapNode.style.width.substring( 0, mapNode.style.width.indexOf( 'px' ) );
	 this.form.mapHeight.value = mapNode.style.height.substring( 0, mapNode.style.height.indexOf( 'px' ) );

	 // save this for position change help.
	 this.origin = new GeoPoint( findPosX( mapNode ), findPosY( mapNode ) );

	 // init the plugin
	 vendor.initialize( );
	 
	 // set the map type
	 this.setMapType( );

	 // set the map action
	 this.setMapAction( this.form.centerAction.value );

	 // was there a region previously defined, priority; extent, zoom, find
	 var rcode = this.setExtentView( true );
	 if ( !rcode ) { rcode = this.setZoomView( true ) };
	 if ( !rcode ) { this.findLocation( true ) };
     } else if ( action == "final" ) {
	 // tear down the plugin
	 vendor.finalize( );
     } else if ( action == "jump" ) { 
	 this.findLocation( );
     } else if ( action == "fix" ) { 
	 this.setFixView( );
     } else if ( action == "zoom" ) { 
	 this.setZoomView( );
     } else {
	 this.setExtentView( );
     }

     handleSpecialMapViews(this);

}

/**
 * update the listing map by extent.
 * this is likely only used internally and looks up everything in the form internally.
 * we could also generalize by passing a map object.  we will now generalize 
 * and pass a map object to the vendor.
 * @param silent if true, during initialization
 * @return true if transformed
 */
function ListingMapRegistrySetExtentView( silent ) {
    var rcode = false;
    var north = this.form.mapExtentNorth.value;
    var south = this.form.mapExtentSouth.value;
    var east = this.form.mapExtentEast.value;
    var west = this.form.mapExtentWest.value;

    if ( north != "" && south != "" && east != "" && west != "" ) {
	north = north - 0;
	south = south - 0;
	east = east - 0;
	west = west - 0;
	var lat = this.form.mapCenterLatitude.value;
	var lon = this.form.mapCenterLongitude.value;
	lat = lat == "" ? ( ( ( north ) + ( south ) ) / 2 ) : lat - 0;
	lon = lon == "" ? ( ( ( east ) + ( west ) ) / 2 ) : lon - 0; 

	var bounds = new Map( "bounds", 
			      new GeoRectangle( north, west, south, east ), 
			      new GeoPoint( lon, lat ) );

	rcode = this.getVendor( ).setExtentView( bounds, silent );
    }

    return rcode;
}

/**
 * update the listing gmap, by zoom.
 * this is likely only used internally and looks up everything in the form internally.
 * we could also generalize by passing a map object.  we will now generalize 
 * and pass a map object to the vendor.
 * @param silent if true
 * @return true if transformed
 */
function ListingMapRegistrySetZoomView( silent ) {
    var rcode = false;
    
    var zoom = this.form.zoomLevel.value;
    var lat = this.form.mapCenterLatitude.value;
    var lon = this.form.mapCenterLongitude.value;

    if ( zoom != "" && lat != "" && lon != "" ) {
	zoom = zoom - 0;
	lat = lat - 0;
	lon = lon - 0;
	var bounds = new Map( "bounds", null, new GeoPoint( lon, lat ), null, zoom );
	rcode = this.getVendor( ).setZoomView( bounds, silent );
    }
    
    return rcode;
}

/**
 * update the listing map by location fixes.
 * this method builds a bounds map by looking internally at the location fixes.
 * the resultant map should present all fixes.
 * @param silent if true, during initialization
 * @return true if transformed
 */
function ListingMapRegistrySetFixView( silent ) {
    var rcode = false;
    var border_default = 5;

    var region = getLocationFixRegion( );
    if ( region != null ) {
	region.border( border_default );
	var bounds = new Map( "bounds", 
			      region, 
			      region.center( ) );

	rcode = this.getVendor( ).setExtentView( bounds, silent );
    }

    return rcode;
}

/**
 * update the listing map, by location find.
 * this is likely only used internally and looks up everything in the form internally.
 * we could also generalize by passing a fix object.  we will now generalize 
 * and pass a locationfix object to define a location.
 * @param locationfix object
 * @param silent if true
 * @return true if found
 */
function ListingMapRegistryFindLocation( silent ) {
    var rcode = false;
    var city = this.form.city1.value;
    var state = this.form.state1.value;
    var zip = this.form.zip1.value;

    zip = zip == "Zip" || zip == "" ? null : zip;
    city = city == "City" || city == "" ? null : city;
    state = state == "" ? null : state;

    var location = new LocationFix( );
    location.city = city;
    location.state = state;
    location.zip = zip;

    if ( location.zip ) {
	location.scheme = LOCATION_FIX_SCHEME.ZIP;
    } else if ( location.city && location.state ) {
	location.scheme = LOCATION_FIX_SCHEME.CITY;
    } else if ( location.state ) {
	location.scheme = LOCATION_FIX_SCHEME.STATE;
    }

    rcode = this.getVendor( ).find( location, silent );

    return rcode;
}

/**
 * Method to add a StateChangeListener, interested in Geocode events.
 * @param StateChangeListener
 */
function ListingMapRegistryAddGeocodeListener( listen ) {
    this.geocodeListeners[this.geocodeListeners.length] = listen;
}

/**
 * MapEventGenerator class.  
 * This small class is designed to correct the lack of mousedown events on the gmap ( and others? ).
 * bubbling of these events is unfortunately disabled.  I will mask the map at opportune times,
 * to generate my own events.  Currently we do this only to provide "draw-the-box".  
 * But, gmap should really not consume events.
 * This class listens to position change events to know when to show/hide the mask.
 * @param parent, ListingMapRegistry object
 */
function MapEventGenerator( parent ) {
     this.map = parent;
     this.positionChange = MapEventGeneratorMask;
 }
 
function MapEventGeneratorMask( positionevent ) {
     var mapAction = this.map.form.centerAction.value;
     if ( mapAction == "extent" ) {
	 // to be totally transparent/encapsulated, this mapMask layer could 
         // be dynamically created  by this class
       var mapMask = document.getElementById( this.map.mask );
       if ( this.map.getVendor( ).isMapPositionOverControl( positionevent ) )  {
          mapMask.style.display = 'none';
       } else {
          mapMask.style.display = 'block';
       }
     }
}

/**
 * Abstract base class for a ListingMapVendor
 * @extends StateChangeSupport
 */
function ListingMapVendor( ) {
    /** common attributes */
    this.node = null;
    this.form = null;

    /** common methods */
    this.getMap = ListingMapVendorGetMap;
}
ListingMapVendor.prototype = new StateChangeSupport( );

/**
 * 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 ListingMapVendorGetMap( 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( );
    return map;
}

/**
 * A listing map icon container
 * The underlying map vendors will need to interpret 
 * the generic icon information
 * Constructor is polymorphic.  
 * @param ListingMapIcon to clone or icon src url, String
 * @param icon size, GeoPoint, x = width, y = height
 * @param icon anchor, GeoPoint, x/y positive right/down respectively
 * @param shadow src url, String
 * @param shadow size, GeoPoint, x = width, y = height
 */
function ListingMapIcon( iSrc, iSize, iAnchor,
			 sSrc, sSize, sAnchor ) {
    if ( iSrc.iSrc ) {
	var icon = iSrc;
    	/** the icon */
	this.iSrc = icon.iSrc;
	this.iSize = new GeoPoint( icon.iSize.x, icon.iSize.y );
	this.iAnchor = new GeoPoint( icon.iAnchor.x, icon.iAnchor.y );

	/** the shadow */
	this.sSrc = icon.sSrc;
	this.sSize = new GeoPoint( icon.sSize.x, icon.sSize.y );
    } else {
	/** the icon */
	this.iSrc = iSrc;
	this.iSize = iSize;
	this.iAnchor = iAnchor;

	/** the shadow */
	this.sSrc = sSrc;
	this.sSize = sSize;
    }

    this.toString = ListingMapIconToString;
}

// to string method for ListingMapIcon
// @return string representation
function ListingMapIconToString( ) {
   var toString = "";
   toString = toString + "iSrc: " + this.iSrc;
   toString = toString + "\niSize: " + this.iSize;
   toString = toString + "\niAnchor: " + this.iAnchor;
   toString = toString + "\nsSrc: " + this.sSrc;
   toString = toString + "\nsSize: " + this.sSize;
   toString = toString + "\nsAnchor: " + this.sAnchor;
   return toString;
}

/**

/** convenience methods for the ui.  to not hold onto object reference */

/**
 * function to initialize the listing map receiver
 * @param form, to pull search params, required
 * @param node, the node representing this map
 * @param mask, the node representing the map mask
 */
function initializeListingMapRegistry( form, node, mask ) {
    LISTING_MAP_REGISTRY = new ListingMapRegistry( form, node, mask );
}

/**
 * method for map vendor registration
 * @param id for this vendor
 * @param ListingMapVendor object
 */
function registerListingMapVendor( id, vendor ) {
    LISTING_MAP_REGISTRY.registerVendor( id, vendor );
}

/**
 * function to add a state change listener to the listing map receiver
 * @param a StateChangeListener
 * @param if true, set as a geocode listener
 */
function addListingMapRegistryStateChangeListener( listen, geocode ) {
    if ( geocode ) {
	LISTING_MAP_REGISTRY.addGeocodeListener( listen );
    } else {
	LISTING_MAP_REGISTRY.addListener( listen );
    }
}

/**
 * function to remove a state change listener from the listing map receiver
 * @param a StateChangeListener
 */
function removeListingMapRegistryStateChangeListener( listen ) {
    LISTING_MAP_REGISTRY.removeListener( listen );
}

/**
 * set the map type
 * @param optional type, if sent, the form field will also be set,
 *  otherwise the value is pulled from the form field
 */
function setMapType( type ) {
    LISTING_MAP_REGISTRY.setMapType( type );
}

/**
 * set the map product
 * @param optional type, if sent, the form field will also be set,
 * otherwie the value is pulled from the form field
 */
function setMapProduct( product ) {
    LISTING_MAP_REGISTRY.setMapProduct( product );
}

/**
  * Workhorse method for adding callouts to the map.
  * This method looks to the stored LocationFix collection.
  * This method will first clear previously set callouts.
  */
function setMapCallouts( ) {
    LISTING_MAP_REGISTRY.setMapCallouts( retrieveLocationFix( ) );
}

/**
 * function to update the listing map.
 * this method holds the map view business logic.
 * @param action being taken
 */
function updateListingMapRegistry( action ) {
    LISTING_MAP_REGISTRY.updateMap( action );
}

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

// Convenience method to fire PositionChangeEvents for the map
// Requires the PositionChangeSupport class
// @param event that triggered the position change
function firePositionChangeEventMap( e ) {
    firePositionChangeEvent( e, LISTING_MAP_REGISTRY.origin, true );
}

/**
 * Simple method to log useful stats to the debug window
 * @note calls logMessage
 */
function logMapStats( ) {
    var msg =  
	" zoom: " + LISTING_MAP_REGISTRY.getVendor( ).getZoom( ) +
	" extent: " + LISTING_MAP_REGISTRY.getVendor( ).getExtent( );
    logMessage( msg );
}

/**
 * Handle special map displays.
 * <p>
 * NOTES: The neccessity for this function is due to some geo searches generating
 *        map views that need adjusting. For example, without this function when
 *        you do a Spokane search the boundary lines extend beyond the map's frame,
 *        this function will do a one level zoom out in that case.
 *        There could be more elegant solutions for handling special map view cases,
 *        feel free to improve. Bob Carpenter 04Nov2010.
 *<p>
 * @param obj accessor for the properties of this object
 */
function handleSpecialMapViews(obj) {
    
    var href = window.location.href;

    try {
	// get href params
	var cit = "";
	var location = "";
	var posCit = href.indexOf('&cit=');
	var posSt = href.indexOf('&st=');
	var posLocation = href.indexOf('&location=');
	var posSearch = href.indexOf('&searchType=');

	if(posCit >= 0 && posSt >= 0) {
	   cit = href.slice(posCit +5, posSt);
	}

	if(posLocation >= 0 && posSearch >= 0) {
	   location = href.slice(posLocation +10, posSearch);
	   location = unescape(location.replace(/\+/g, " ")); //decode the url
	}

	// handle special cases
	if(cit == "Spokane") {
	   // zoom out one level
	   obj.form.zoomLevel.value = 11;
	   obj.setZoomView( );	
	}
	else if(location == "Laurelhurst, Seattle, WA") {
	   obj.form.zoomLevel.value = 14;
	   obj.setZoomView( );	
	}
    }
    catch(ex) {
        console.log("handleSpecialMapViews - ERROR: " +ex);
    }
}

