// vc_id = "$Id: listingMapXHR.js 12623 2009-09-21 21:46:29Z robert $"
// This library represents specific mapping functions for the
// xhr listings map.  This library builds upon the listing map ( listingMap.js )
// @author Robert D. Rice

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

var LISTING_MAP_XHR = new XHR( "map" );
var LISTING_MAP_URL = null;
var LISTING_MAP_FORM = null;

var LISTING_COLLECTION_XHR = new XHR( "search" );
var LISTING_COLLECTION_URL = null;
var LISTING_COLLECTION_FORM = null;
var LISTING_COLLECTION_FIX_DELTA = null;
var LISTING_COLLECTION_COUNT = 0;
var LISTING_COLLECTION_REGION_RESOLVER = null;

var LISTING_DETAIL_XHR = new XHR( "property" );

var LISTING_RMIMS_IMAGE_DEFAULT = basepath + "map/esri_west_627_500.jpg ";
var LISTING_RMIMS_PROJECTION_LATITUDE_RADIUS =  69.17058637201978;
var LISTING_RMIMS_PROJECTION_LONGITUDE_RADIUS = 54.87671574814528;
var LISTING_RMIMS_PROJECTION_LONGITUDE_CENTRAL_MERIDIAN = -96.0;
var LISTING_RMIMS_HORIZONTAL_CONTROL = new GeoRectangle( 26, 6, 40, 60);
var LISTING_RMIMS_VERTICAL_CONTROL = new GeoRectangle( 7, 23, 230, 43 );


/**
 * ListingRMIMS.  
 * This is an implementation of ListingMapIF in terms of RMIMS.
 * The ListingMapIF extends StateChangeSupport.
 * This is a prototype for all ListingMapImpl.
 * @constructor
 * @param base url of the xhr request
 * @return new ListingRMIMS instance.
 */
function ListingRMIMS( url )  {
   // attributes
   // StateChangeListener objects.
   this.listeners = new Array( );
   this.name = "map";
   this.state = STATE_CHANGE_STATE_UNINITIALIZED;
   this.status = STATE_CHANGE_STATUS_OK;
   this.stateChange = ListingRMIMSStateChange;
   this.addListener( this );

   // StateChangeListener methods
   this.fireStart = ListingRMIMSFireStart;
   this.fireStop = ListingRMIMSFireStop;

   // RMIMS attributes
   this.form = null;
   this.node = null;
   this.icons = null;
   this.url = url;
   this.args = null;

   // markers
   this.markers = document.createElement( 'div' );
   this.controls = document.createElement( 'div' );
   this.getMarker = ListingRMIMSGetMarker;
   this.createMarker = ListingRMIMSCreateMarker;
   this.clearMarkers = ListingRMIMSClearMarkers;
   this.setMarkerImage = ListingRMIMSSetMarkerImage;
   
   // ListingMapIF methods
   this.initialize = ListingRMIMSInitialize;
   this.finalize = ListingRMIMSFinalize;
   this.setMapType = ListingRMIMSSetMapType;
   this.getMapType = ListingRMIMSGetMapType;
   this.setMapCallouts = ListingRMIMSSetMapCallouts;
   this.setMapAction = ListingRMIMSSetMapAction;
   this.isMapPositionOverControl = ListingRMIMSIsMapPositionOverControl;
   this.setExtentView = ListingRMIMSSetExtentView;
   this.setZoomView = ListingRMIMSSetZoomView;
   this.find = ListingRMIMSFind;
   this.getZoom = ListingRMIMSGetZoom;
   this.getZoomMin = ListingRMIMSGetZoomMin;
   this.getZoomMax = ListingRMIMSGetZoomMax;
   this.getExtent = ListingRMIMSGetExtent;
   this.getProjectedExtent = ListingRMIMSGetProjectedExtent;
   this.addGeocodeListener = ListingRMIMSAddGeocodeListener;
   this.projectMap = ListingRMIMSProjectMap;
   this.projectLatitude = ListingRMIMSProjectLatitude;
   this.projectLongitude = ListingRMIMSProjectLongitude;
   this.getMap = ListingRMIMSGetMap;
}
ListingRMIMS.prototype = new XHR( );

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

/**
 * internal state change method, to update the map image
 * @param xhr
 */
function ListingRMIMSStateChange( xhr ) {
    if ( xhr.success( ) ) {
	var xml = xhr.responseXML( );
	var imageURL = XHRFirstElementsValueByName( xml, "mapURL" );
	document.getElementById( this.node ).style.backgroundImage = "url('" + imageURL + "')";
    }
}

/**
 * Fire the start of a move to the listeners
 */
function ListingRMIMSFireStart(  ) {
    this.setState( STATE_CHANGE_STATE_LOADING );
}

/**
 * Fire the stop of a move to the listeners
 * @param optional switch to unlock before operation
 */
function ListingRMIMSFireStop(  ) {
    this.setState( STATE_CHANGE_STATE_COMPLETE );
}

/** start of ListingMapIF methods */

/**
 * initialize the ListingMapIF
 * @note called when plugin is activated, or
 *   page is loaded or resized
 */
function ListingRMIMSInitialize( ) {
    document.getElementById( this.node ).appendChild( this.markers );
    document.getElementById( this.node ).appendChild( this.controls );
    this.controls.id = "RMIMSZoomCompassControl";

    // add controls
    var overlay = new OVCompass( 'RMIMSZoomCompassControl' );
    overlay.setPosition(5,5);
    overlay.setMinZoom( this.getZoomMin( ) );
    overlay.setMaxZoom( this.getZoomMax( ) );
    overlay.setCurrentZoom( Math.round( ( this.getZoomMin( ) + this.getZoomMax( ) ) / 2 ) );
    overlay.printOverlay( );
    overlay.setMap( this );
    
    this.args = "?mapExtentBorder=0&width=" +  this.form.mapWidth.value + 
	"&height=" + this.form.mapHeight.value + 
	"&visibleLayers=" + this.form.mapVisibleLayers.value;
}

/**
 * finalize the ListingMapIF
 * @note called when plugin is deactivated, or
 *  page is unloaded
 */
function ListingRMIMSFinalize( ) {
    document.getElementById( this.node ).removeChild( this.markers );
    document.getElementById( this.node ).removeChild( this.controls );
    document.getElementById( this.node ).innerHTML = '';
    document.getElementById( this.node ).style.backgroundImage = '';
}

/**
 * set the map type
 * @param map type
 */
function ListingRMIMSSetMapType( type ) {
}

/**
 * get the map type
 * @return map type
 */
function ListingRMIMSGetMapType(  ) {
    return "map";
}

/**
 * set the map action
 * looks for a center action of pan or extent
 * @param pan or extent
 */
function ListingRMIMSSetMapAction( 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 ListingRMIMSSetMapCallouts( fixes ) {
    var index = 0;
    for ( key in fixes ) {
	var fix = fixes[key];
        var el = this.getMarker( index );
        el.style.top = ( fix.screen.y - this.icons[1].std_drk.iAnchor.y ) + 'px';
        el.style.left = ( fix.screen.x - this.icons[1].std_drk.iAnchor.x ) + 'px';
	el.style.display = 'block';
	index++;
    }

    // clear the existing markers
    this.clearMarkers( index );
}

/**
 * Get a marker by index
 * @param index of marker, zeroth based
 * @return div node
 */
function ListingRMIMSGetMarker( index ) {
    if ( index >=  this.markers.childNodes.length ) {
	this.markers.appendChild( this.createMarker( ) );
   }
    return  this.markers.childNodes[index];
}

/**
 * Constructs a node suitable as a marker 
 * @return div node
 */
function ListingRMIMSCreateMarker( ) {
    var el = document.createElement( 'div' );
    el.style.position = 'absolute';
    el.style.display = 'none';

    var icon = document.createElement( 'div' );
    icon.style.position = 'absolute';
    icon.style.display = 'block';
    this.setMarkerImage( icon, 
			 this.icons[1].std_drk.iSrc, 
			 this.icons[1].std_drk.iSize.x, 
			 this.icons[1].std_drk.iSize.y );
    
    var shadow = document.createElement( 'div' );
    shadow.style.position = 'absolute';
    shadow.style.display = 'block';
    this.setMarkerImage( shadow, 
			 this.icons[1].std_drk.sSrc, 
			 this.icons[1].std_drk.sSize.x, 
			 this.icons[1].std_drk.sSize.y );
 
    el.appendChild( shadow );
    el.appendChild( icon );

    return el;
}

/**
 * Set the marker image, handles png icons
 * @param div object
 * @param src url
 * @param width of image
 * @param height of image
 */
function ListingRMIMSSetMarkerImage( el, src, width, height ) {
    if ( document.body.filters && document.body.filters.item && 
	 ( src.toLowerCase( ).indexOf( '.png' ) == ( src.length - 4 ) ) ) {
	el.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "', sizingMethod='scale')";
    } else {
	el.style.backgroundImage = "url('" + src + "')";
    }
    el.style.width = width + 'px';
    el.style.height = height + 'px';
}

/**
 * Clear the markers
 * @param start index for clearing, optional, defaults to 0 to clear all
 */
function ListingRMIMSClearMarkers( index ) {
    var flags = this.markers.childNodes;
    var length = flags.length;
    var i = index ? index : 0;
    for ( i; i < length; i++ ) {
	flags[i].style.display = 'none';
    }
}

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

	if ( LISTING_RMIMS_HORIZONTAL_CONTROL.isInside( positionevent.screen, true ) || 
	     LISTING_RMIMS_VERTICAL_CONTROL.isInside( positionevent.screen, true ) ) {
	    over = true;
	}
	
	return over;
}

/**
 * update the listing map, by extent.
 * 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 ListingRMIMSSetExtentView( bounds, silent ) {
    var rcode = true;
   
    this.setAction( this.url + this.args );

    var project = this.projectMap( bounds );
    if ( project.extent ) {
	this.setAction( this.getAction( ) +
			"&mapExtentTop=" + project.extent.top +
			"&mapExtentLeft=" + project.extent.left +
			"&mapExtentBottom=" + project.extent.bottom +
			"&mapExtentRight=" + project.extent.right );
    }
    if ( project.center ) {
	this.setAction( this.getAction( ) +
			"&mapCenterX=" + project.center.x +
			"&mapCenterY=" + project.center.y  );
    }

    // logMessage( this.action );
    this.run( null );

    return rcode;
}



/**
 * update the listing map, 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 ListingRMIMSSetZoomView( bounds, silent ) {
    var rcode = true;
   
    this.setAction( this.url + this.args );

    var project = this.projectMap( bounds );
    if ( project.zoomLevel ) {
	this.setAction( this.getAction( ) +
			"&zoomLevel=" + project.zoomLevel  );
    }
    if ( project.center ) {
	this.setAction( this.getAction( ) +
			"&mapCenterX=" + project.center.x +
			"&mapCenterY=" + project.center.y  );
    }

    // logMessage( this.action );
    this.run( null );

    return rcode;
}

/**
 * update the listing map, 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 silent if true
 * @return true if found
 */
function ListingRMIMSFind( location, silent ) {
    var rcode = true;
   
    this.setAction( this.url + this.args );
    var city = location.city ? location.city : "";
    var state = location.state ? location.state : "";
    var zip = location.zip ? location.zip : "";
   
    this.setAction( this.getAction( ) +
		    "&city=" + city +
		    "&state=" + state +
		    "&zip=" + zip +
		    "&showLocation=false"  );

    // logMessage( this.action );
    this.run( null );

    return rcode;
}

/**
 * method to project a map from earth coords
 * these projection methods are only required til we refactor the esri impl to accept lat/lon
 * @param map in lat/long
 * @return map in map coords
 */
function ListingRMIMSProjectMap( bounds ) {
    var project = new Map( );
    project.id = bounds.id;
    project.zoomLevel = bounds.zoomLevel;
    if ( bounds.extent ) {
	project.extent = new GeoRectangle( this.projectLatitude( bounds.extent.top ), this.projectLongitude( bounds.extent.left ),
					 this.projectLatitude( bounds.extent.bottom ), this.projectLongitude( bounds.extent.right )  );
    }
    if ( bounds.center ) {
	project.center = new GeoPoint( this.projectLongitude( bounds.center.x ), this.projectLatitude( bounds.center.y ) );
    }
    return project;
}

/**
 * project the latitude, assumes Equidistant Cylindrical
 * these projection methods are only required til we refactor the esri impl to accept lat/lon
 * @param latitude in decimal degrees
 * @return double y, projected
 */
function ListingRMIMSProjectLatitude( lat ) {
    return LISTING_RMIMS_PROJECTION_LATITUDE_RADIUS * lat;
}

/**
 * project the longitude, assumes Equidistant Cylindrical
 * these projection methods are only required til we refactor the esri impl to accept lat/lon
 * @param longitude in decimal degrees
 * @return double x, projected
 */
function ListingRMIMSProjectLongitude( lon ) {
    return LISTING_RMIMS_PROJECTION_LONGITUDE_RADIUS * ( lon - LISTING_RMIMS_PROJECTION_LONGITUDE_CENTRAL_MERIDIAN );
}

/**
 * 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 ListingRMIMSGetMap( 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;
}

/**
 * function to get the resolved zoom
 * @return zoom level, number
 */
function ListingRMIMSGetZoom( ) {
    var xml = this.responseXML( );
    return XHRFirstElementsValueByName( xml, "zoomLevel", null, true );
}

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

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

/**
 * function to get the resolved extent
 * in earth coords
 * @return GeoRectangle
 */
function ListingRMIMSGetExtent( ) {
    var xml = this.responseXML( );
    return new GeoRectangle( 
	    XHRFirstElementsValueByName( xml, "mapExtentNorth", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentWest", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentSouth", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentEast", null, true ) );
}

/**
 * function to get the resolved extent
 * in map coords
 * @return GeoRectangle
 */
function ListingRMIMSGetProjectedExtent( ) {
    var xml = this.responseXML( );
    return new GeoRectangle( 
            XHRFirstElementsValueByName( xml, "mapExtentTop", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentLeft", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentBottom", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentRight", null, true ) );
}

/** end of ListingMapIF methods */

// function to initialize the listing map xhr
// @param base url, this will be updated, required
// @param form, to pull search params, required
function initializeListingMapXHR( url, form ) {
    LISTING_MAP_URL = url + "?"; // we may need to dynamically choose delim
    LISTING_MAP_FORM = form;
    addListingMapXHRStateChangeListener( new XHRStateChangeListener( listingMapXHRStateChange ) );
}

// function to update the listing map xhr
// @action being taken
function updateListingMapXHR( action ) {
    // this needs refactor.  display specific pieces are currently in here.  plus
    // other non-display specific pieces are currently in submitMapForm function of
    // dspInteractiveMap
    LISTING_MAP_XHR.setAction( LISTING_MAP_URL + queryStringFromForm( LISTING_MAP_FORM ) + 
			       "&mapExtentBorder=0&width=" + ( LISTING_MAP_FORM.mapWidth.value - 43 ) + 
			       "&height=" + LISTING_MAP_FORM.mapHeight.value + 
                               "&visibleLayers=" + LISTING_MAP_FORM.mapVisibleLayers.value );
    if ( action == "jump" || action == "init" ) {	  
	LISTING_MAP_XHR.setAction( LISTING_MAP_XHR.getAction( ) +
				   "&city=" + LISTING_MAP_FORM.city1.value +
				   "&state=" + LISTING_MAP_FORM.state1.value +
				   "&zip=" + LISTING_MAP_FORM.zip1.value +
				   "&showLocation=false"  );
    }
    LISTING_MAP_XHR.run( null );
}

// function to add a state change listener to the listing map xhr
// @param XHRStateChangeListener
function addListingMapXHRStateChangeListener( listener ) {
    LISTING_MAP_XHR.addListener( listener );
}

// listing map callback function
// @param xhr object
function listingMapXHRStateChange( xhr ) {
    if ( xhr.success( ) ) {
	var xml = xhr.responseXML( );
	hideBounds( document.getElementById( 'box' ) ); 
	var imageURL = XHRFirstElementsValueByName( xml, "mapURL" );
	document.getElementById( 'mapImage' ).src = imageURL;
  
	initializeListingMap(
           new GeoRectangle( 
            XHRFirstElementsValueByName( xml, "mapExtentTop", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentLeft", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentBottom", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentRight", null, true ) ),
           new GeoRectangle( 
            XHRFirstElementsValueByName( xml, "mapExtentNorth", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentWest", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentSouth", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentEast", null, true ) ),
           new GeoRectangle( 0, 0,
            XHRFirstElementsValueByName( xml, "height", null, true ),
            XHRFirstElementsValueByName( xml, "width", null, true ) ),
            XHRFirstElementsValueByName( xml, "zoomLevel", null, true ) );
        initializePositionChangeSupport(
           new GeoRectangle( 
            XHRFirstElementsValueByName( xml, "mapExtentTop", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentLeft", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentBottom", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentRight", null, true ) ),
           new GeoRectangle( 
            XHRFirstElementsValueByName( xml, "mapExtentNorth", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentWest", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentSouth", null, true ),
            XHRFirstElementsValueByName( xml, "mapExtentEast", null, true ) ),
           new GeoRectangle( 0, 0,
            XHRFirstElementsValueByName( xml, "height", null, true ),
            XHRFirstElementsValueByName( xml, "width", null, true ) ) );
        registerListingMap( );

        LISTING_MAP_FORM.mapExtentLeft.value = retrieveMap( LISTING_MAP ).extent.left;
        LISTING_MAP_FORM.mapExtentRight.value = retrieveMap( LISTING_MAP ).extent.right;
        LISTING_MAP_FORM.mapExtentTop.value = retrieveMap( LISTING_MAP ).extent.top;
        LISTING_MAP_FORM.mapExtentBottom.value = retrieveMap( LISTING_MAP ).extent.bottom;
        LISTING_MAP_FORM.mapExtentNorth.value = retrieveMap( LISTING_EARTH ).extent.top;
        LISTING_MAP_FORM.mapExtentSouth.value = retrieveMap( LISTING_EARTH ).extent.bottom;
        LISTING_MAP_FORM.mapExtentWest.value = retrieveMap( LISTING_EARTH ).extent.left;
        LISTING_MAP_FORM.mapExtentEast.value = retrieveMap( LISTING_EARTH ).extent.right;
        LISTING_MAP_FORM.extentNorth.value = retrieveMap( LISTING_EARTH ).extent.top;
        LISTING_MAP_FORM.extentSouth.value = retrieveMap( LISTING_EARTH ).extent.bottom;
        LISTING_MAP_FORM.extentWest.value = retrieveMap( LISTING_EARTH ).extent.left;
        LISTING_MAP_FORM.extentEast.value = retrieveMap( LISTING_EARTH ).extent.right;

	if ( XHRFirstElementsValueByName( xml, "fixCount", null, true ) > 0 ||
	     LISTING_MAP_FORM.mapExtentTopDefault.value == "" ||
             LISTING_MAP_FORM.mapExtentBottomDefault.value == "" ||
             LISTING_MAP_FORM.mapExtentRightDefault.value == "" ||
             LISTING_MAP_FORM.mapExtentLeftDefault.value == "" ) {       
          LISTING_MAP_FORM.mapExtentTopDefault.value = LISTING_MAP_FORM.mapExtentTop.value;
          LISTING_MAP_FORM.mapExtentBottomDefault.value = LISTING_MAP_FORM.mapExtentBottom.value;
          LISTING_MAP_FORM.mapExtentRightDefault.value = LISTING_MAP_FORM.mapExtentRight.value;
          LISTING_MAP_FORM.mapExtentLeftDefault.value = LISTING_MAP_FORM.mapExtentLeft.value;
        }
    }
}

// function to initialize the listing collection xhr
// @param base url, this will be updated, required
// @param form, to pull search params, required
function initializeListingCollectionXHR( url, form ) {
    LISTING_COLLECTION_URL = url + "&"; // we may need to dynamically choose delim
    LISTING_COLLECTION_FORM = form;
    addListingCollectionXHRStateChangeListener( new XHRStateChangeListener( listingCollectionXHRStateChange ) );
    addListingMapXHRStateChangeListener( new XHRStateChangeListener( fireListingCollectionXHRUpdate ) );
}

// function to update the listing collection xhr
function updateListingCollectionXHR(  ) {
    LISTING_COLLECTION_XHR.setAction( LISTING_COLLECTION_URL + queryStringFromForm( LISTING_COLLECTION_FORM, AMPERSAND_DELIM, true ) );
    if ( LISTING_COLLECTION_FORM.searchConstraintType && 
        LISTING_COLLECTION_FORM.searchConstraintType.value == "mls" ) {
	if ( LISTING_COLLECTION_XHR.getAction( ).indexOf( "&ln=" ) == -1 ) {
	    // our data is dirty and has listings with empty strings for ln.  they have all been pruned
	    // create a "null" to impossibly limit the query.  we could also not run the query, 
	    // but downstream subscribers currently expect the result set
	    LISTING_COLLECTION_XHR.setAction( LISTING_COLLECTION_XHR.getAction( ) + "&ln=bs23nob127082" );
	}
    }
    // logMessage( LISTING_COLLECTION_XHR.getAction( ) );
    LISTING_COLLECTION_XHR.run( null );
}

// function to add a state change listener to the listing collection xhr
// @param XHRStateChangeListener
function addListingCollectionXHRStateChangeListener( listener ) {
    LISTING_COLLECTION_XHR.addListener( listener );
}

// function to remove a state change listener to the listing collection xhr
// @param XHRStateChangeListener
function removeListingCollectionXHRStateChangeListener( listener ) {
    LISTING_COLLECTION_XHR.removeListener( listener );
}

// listing collection callback function
// @param xhr object
function listingCollectionXHRStateChange( xhr ) {
    if ( xhr.success( ) ) {
	var xml = xhr.responseXML( );
	LISTING_COLLECTION_COUNT = XHRFirstElementsValueByName( xml, "listingCount", null, true );

	// process the listings
	var listings = xml.getElementsByTagName( "listing" );
        clearLocationFix( );
	if ( listings.length > 0 ) {
	    var id = 0;
	    var fixFound = true;
	    var fixScheme = "Latitude/Longitude";
	    var fixProvider = "mls";
	    var fixPositionX = 0;
	    var fixPositionY = 0;
	    var fixLatitude = 0;
	    var fixLongitude = 0;
	    var locPid = null;
	    var locAddress = null;
	    var locBedBath = null;
	    var locPrice = null;
	    var locThumb = null;
	    var locFull = null;
	    var locPtyp = null;
	    var locAttributes = 0;
	    var locMls = null;
	    var locCompany = null;
	    for ( var i = 0; i < listings.length; i++ ) {
		locPid = XHRFirstElementsValueByName( listings[i], "guid", "" );
		id = ( i + 1 );
		locBedBath = XHRFirstElementsValueByName( listings[i], "bedrooms", "" ) + "/" +
		    XHRFirstElementsValueByName( listings[i], "bathrooms", "" );
		locPrice = XHRFirstElementsValueByName( listings[i], "listPrice", "$N/A" );
		var locations = listings[i].getElementsByTagName( "location" );
		if ( locations.length > 0 ) {
		    // process the first location only
		    fixLatitude = XHRFirstElementsValueByName( locations[0], "latitude", null, true );
		    fixLongitude = XHRFirstElementsValueByName( locations[0], "longitude", null, true );
		    locAddress = XHRFirstElementsValueByName( locations[0], "address", "Address not disclosed" );
		} else {
		    fixLatitude = 0;
		    fixLongitude = 0;
		    locAddress = "";
		}
		locPtyp = XHRFirstElementsValueByName( listings[i], "propertyType", "" );
		locUrl = XHRFirstElementsValueByName( listings[i], "url", "" );
		var images = listings[i].getElementsByTagName( "image" );
		if ( images.length > 0 ) {
		    // process the first image only
		    locThumb = XHRFirstElementsValueByName( images[0], "thumbUrl", "" );
		    locFull = XHRFirstElementsValueByName( images[0], "fullUrl", "" );
		} else {
		    locThumb = "";
		    locFull = "";
		}
		locAttributes = XHRFirstElementsValueByName( listings[i], "attributes", locAttributes ) - 0;
		locMls = XHRFirstElementsValueByName( listings[i], "mls", null, true );
		locCompany = XHRFirstElementsValueByName( listings[i], "company", null, false, true ); 

		addLocationFix( id,
				fixFound, fixScheme, fixProvider,
				fixPositionX, fixPositionY, 
				fixLatitude, fixLongitude,
				locPid, locAddress, locBedBath, 
				locPrice, locPtyp, locUrl, locFull, locThumb, new ListingAttribute( locAttributes ), 
                                retrieveMLS( locMls ), locCompany );
	    }
	    initializeLocationFixBounds( retrieveMap( POSITION_EARTH ), LISTING_COLLECTION_FIX_DELTA );
	}
    }
}

// set the fix delta
// @param fix delta, GeoRectangle
function setListingCollectionXHRFixDelta( delta ) {
    LISTING_COLLECTION_FIX_DELTA = delta;
}

// get the current listing count
// @return integer representing the count
function getListingCollectionXHRCount( ) {
    return LISTING_COLLECTION_COUNT;
}

// callback from the map xhr, to fire the listing collection update
// @param xhr object
function fireListingCollectionXHRUpdate( xhr ) {
    if ( xhr.success( ) ) {
	updateListingCollectionXHR( );
    }
}

// function to initialize the listing detail xhr
function initializeListingDetailXHR(  ) {
    addListingDetailXHRStateChangeListener( new XHRStateChangeListener( listingDetailXHRStateChange ) );
}

// function to update the listing detail xhr
// @param url of the detail
function updateListingDetailXHR( url ) {
    LISTING_DETAIL_XHR.setAction( url );
    LISTING_DETAIL_XHR.run( null );
}

// function to add a state change listener to the listing detail xhr
// @param XHRStateChangeListener
function addListingDetailXHRStateChangeListener( listener ) {
    LISTING_DETAIL_XHR.addListener( listener );
}

// listing detail callback function
// @param xhr object
function listingDetailXHRStateChange( xhr ) {
    if ( xhr.success( ) ) {
	;
    }
}

// object designed to update the listing collection
// and conditionally reset the view based upon 
// the return of the collection.  the update method
// triggers all.  
function ListingCollectionRegionResolver( form ) {
    this.form = form;
    this.resolve = 0;
    this.stateChange = ListingCollectionRegionResolverStateChange;
    this.update = ListingCollectionRegionResolverUpdate;
    addListingCollectionXHRStateChangeListener( this );
}

// update method
// @param resolve the view, if false only update the collection  
function ListingCollectionRegionResolverUpdate( resolve ) {
    if ( resolve ) {
	this.resolve = 1;

	/** cache then flush the form of location information */
	var settings = extractFormSettings( this.form );
	for ( prop in settings ) {
	    if ( prop.indexOf( "extent" ) == 0 ||
		 prop.indexOf( "mapExtent" ) == 0 ) {
		updateFormElement( this.form[ prop ], "" );
	    }
	}

	/** update the collection  */
	updateListingCollectionXHR( );

	/** reset the form with location information in case of 0 listings */
	for ( prop in settings ) {
	    if ( prop.indexOf( "extent" ) == 0 ||
		 prop.indexOf( "mapExtent" ) == 0 ) {
		updateFormElement( this.form[ prop ], settings[prop][0] );
	    }
	}
    } else {
	this.resolve = 0;
	updateListingCollectionXHR( );
    }
}

// state change callback method, this is self registered
function ListingCollectionRegionResolverStateChange( scs ) {
    if ( scs.complete( ) && this.resolve ) {
	this.resolve = 0;
	if ( scs.success( ) ) { updateListingMapRegistry( "fix" ); }
    }
}

// function to initialize the listing collection region resolver
// @param form, to "zero" region in resolve mode, required
function initializeListingCollectionRegionResolver( form ) {
    LISTING_COLLECTION_REGION_RESOLVER = new ListingCollectionRegionResolver( form );
}

// function to update the listing collection xhr and conditionally
// resolve the region.
// one can always call this method
// instead of the original updateListingCollectionXHR.
// it should be backwards compatible, and safer as it
// will reset the resolve flag properly
// @param resolve the view, if false only update the collection  
function updateListingCollectionRegionResolver( resolve ) {
    LISTING_COLLECTION_REGION_RESOLVER.update( resolve );
}

/**
 * Listing Pagination with state change support
 * @param name of this pagination
 * @param function to call when there is a listing change.
 *  sends the new LocationFix object.  
 *  This function could go away in the future, in favor
 *  of notification of the listing change through scs and
 *  a fireChange event.
 * @param searchOnly
 */
function ListingPaginationSCS( name, listingChange, searchOnly ) {
    /** scs attributes */
    this.listeners = new Array( );
    this.name = name ? name : null;
    this.state = STATE_CHANGE_STATE_UNINITIALIZED;
    this.status = STATE_CHANGE_STATUS_OK;
    
    /** listing attributes */
    this.listing = null;
    this.listingChange = listingChange;
    this.stateChange = ListingPaginationSCSStateChange;
    this.setup = ListingPaginationSCSSetup;
    this.teardown = ListingPaginationSCSTeardown;
    this.addListener( this );
    addListingCollectionXHRStateChangeListener( this ); 
    if ( !searchOnly ) {
      addListingDetailFrameLoaderStateChangeListener( this );
    }
}
ListingPaginationSCS.prototype = new PaginationSCS( );

/**
 * Listing Pagination State Change
 */
function ListingPaginationSCSStateChange( scs ) {
    if ( scs.name == this.name ) {
	if ( scs.success( ) ) {
	    var fix = retrieveLocationFix( scs.getPage( ) );
	    if ( fix && fix.pid && fix != this.listing ) {
		this.listing = fix;
		this.listingChange( this.listing );
	    }
	}
    } else if ( scs.name == "search" ) {
	if ( scs.success( ) ) {
	    var fix = this.listing ? retrieveLocationFixByPID( this.listing.pid ) : null;
	    if ( fix ) {
		/** set the listing reference and the book size and index */
		this.listing = fix;
		this.setSize( countLocationFix( ), false, true );
		this.setPage( this.listing.id );
	    } else {
		/** set the book size and index */
		this.setSize( countLocationFix( ), false, true );
		this.setPage( 0, true );
	    }
	}
    } else if ( scs.name == "property" ) {
	if ( scs.success( ) ) {
	    var url = document.getElementById( scs.id ).src;
	    var pattern = /listingid=(\d+)/i;
	    var match = pattern.exec( url );
	    if ( match ) {
		var listingid = match[1];
		var fix = retrieveLocationFixByPID( listingid );
		if ( fix ) {
		    /** set the listing reference and the book index */
		    this.listing = fix;
		    this.setPage( this.listing.id );
		} else {
		    this.setPage( 0, true );
		}
	    }
	}
    }
}

/**
 * Listing Pagination Setup
 * @param listingid
 */
function ListingPaginationSCSSetup( listingid  ) {
    this.setSize( countLocationFix( ), false, true );
    if ( listingid ) {
	var fix = retrieveLocationFixByPID( listingid );
	if ( fix ) {
	    /** set the listing reference and the book index */
	    this.listing = fix;
	    this.setPage( this.listing.id, false, true );
	}
    }
    this.setState( STATE_CHANGE_STATE_COMPLETE );
}

/**
 * Listing Pagination Teardown
 * @param searchOnly
 */
function ListingPaginationSCSTeardown( searchOnly ) {
    removeListingCollectionXHRStateChangeListener( this );
    if ( !searchOnly ) {
      removeListingDetailFrameLoaderStateChangeListener( this );
    }
}

/**
 * Listing Pagination Constructor convenience function
 * This method is utilized to build a new ListingPaginationSCS
 * from an alternate frame.
 * This function is totally in support of ie, where I can't
 * seem to construct objects across windows/frames.
 */
function constructListingPaginationSCS( name, listingChange, searchOnly ) {
    return new ListingPaginationSCS( name, listingChange, searchOnly );
}
