// Map Utilities for interacting with Flash, caching information, and positioning and displaying
// DIV information.

// a point object. Ideally we'd want x,y to be ints but that can come later.
function Point(x,y) {
    this.x = x;
    this.y = y;
}

Point.prototype.toString = function () {
    return "(" + this.x + "," + this.y + ")";
}

//tells us the absolute position of the left,top point of object obj
//objects that don't exist are at position (0,0) for now, but we may instead
//choose to have them offscreen entirely
Point.prototype.getAbsolutePosition = function(obj) {
    var X, Y;
    var cur;
    if (!obj) {
        return new Point(0, 0);
    } 
    else 
    {
        X=0;
        Y=0;
        var o = obj;
        if (o.offsetParent) //then this object has a container that affects its position
        {
            while (o.offsetParent)
            {
                X += o.offsetLeft;
                Y += o.offsetTop;
                o = o.offsetParent; //traverse the hierarchy upward
            }
        }
        else if (o.x || o.y) //no container but an absolute position
        {
            if (o.x) X += o.x;
            if (o.y) Y += o.y;
        }

        return new Point(X,Y);        
    }
}  
//tells us the absolute position of the left,top point of object obj
//objects that don't exist are at position (0,0) for now, but we may instead
//choose to have them offscreen entirely
Point.prototype.addAbsolutePosition = function(obj) {
    var X, Y;
    var cur;
    if (!obj) {
        return this;
    } 
    else 
    {
        X=parseInt("0" + this.x);
        Y=parseInt("0" + this.y);
        var o = obj;
        if (o.offsetParent) //then this object has a container that affects its position
        {
            while (o.offsetParent)
            {
                X += parseInt(o.offsetLeft);
                Y += parseInt(o.offsetTop);
                o = o.offsetParent; //traverse the hierarchy upward
            }
        }
        else if (o.x || o.y) //no container but an absolute position
        {
            if (o.x) X += parseInt(o.x);
            if (o.y) Y += parseInt(o.y);
        }

        this.x = X;
        this.y = Y;
        
        return this;        
    }
}



/*
    Callback function from Flash. When the user clicks on a point, flash will call this function
    passing in the x and y coordinates (in pixels) of the clicked point and the corresponding listing
    ID value. 
    
    Assumptions: 
        * There is an HTML object with ID = "flashContainer" which holds the flash movie. The object
          Should be exactly the same size as the movie for best results, as the determination of 
          position happens relative to the flashContainer and not the flash movie itself.
        * There is an HTML object with ID = "listingContainer" which holds the "bubble" to be 
          positioned over the map.
          
    UNKNOWN: 
        We want to be able to have the height of the listingContainer be determined by its contents,
        and yet still be absolutely positioned by its bottom left point. CSS may require an absolute
        height to perform this feat. TBD.
    
    NOTE: there must also be a function for Javascript to call flash and indicate that a particular 
    listing should be highlighted. This function is currently only a stub.
*/
function showLocation(event){
  var obj = document.getElementById("flashContainer");
  var p = new Point(Math.round(event.pixelX),Math.round(event.pixelY));
//alert("orig point: "+p);
  var q = new Point(0,0);
  var r = new Point(0,0);
  q.addAbsolutePosition(obj);
  p.x = parseInt(q.x) + parseInt(p.x) + 8; //+8 is specific to this choice of icons and this version of the map
  p.y = parseInt(q.y) + parseInt(p.y) + 8 //see above;

  var d = document.getElementById("listingContainer");
  if (d) {
    //assumes that the listings array has already been created at the global level

//alert("flash: " + q);
//alert("final point: " + p);
    anchorPoint.x = p.x;
    anchorPoint.y = p.y;
//alert("anchor: " + anchorPoint);
    listings.requestAdd(event.data.index);
  }
}

/*
    This small class describes a map marker, which is little more than a point with an associated index.
    Future expansions may include title, description, or icon type.
*/
function MapMarker(lat, lon, index, listingType, title) {
    this.lat = lat;
    this.lon = lon;
    this.index = index;
    this.listingType = listingType;
    this.title = title;
    this.cluster = false;
    this.submarkers = new Array();
}

/*
    the toString function provides convienient debugging using the alert() function, which calls
    toString() by default.
*/
MapMarker.prototype.toString = function() {
/*
    if (this.featured) {
        return "f:(" + this.lat + "," + this.lon + ")";
    } else {
        return "r:(" + this.lat + "," + this.lon + ")";
    }
*/
    return this.index;
}
/*
    the toLongString function provides more complete information for debugging.
*/
MapMarker.prototype.toLongString = function() {
    return "(lat,lon) = (" + this.lat + "," + this.lon + "), index= " + this.index;
}

/*
    This helper function can set any corner of the specied object to the specified coordinates.
    BEWARE: all coordinate values are relative to the corresponding corner of the page, so 
    setting top=0,left=100 puts the top left corner on the top border 100px to the left of the top 
    left corner of the page, and top=0,right=100 puts the top right corner of the object on the 
    top border 100px to the right of the top right corner of the page! If you're looking to position
    a corner other than the top left with respect to the top left of the page you either must
    know the height and width of the object whose corner you're setting (height for setting a bottom
    corner, width for setting a right corner) OR you have to get clever...
*/
function setPosition(obj, p, corner)
{
    // corner should contain one of top|bottom and one of left|right in some order.
    // case and punctuation don't matter.
    if (!obj || !p) return;

    var top = false;
    var left = false;
    var tExp = /top/i;
    var lExp = /left/i;
    var bExp = /bottom/i;
    var rExp = /right/i;
    
    top = (corner.search(tExp) > -1) || !(corner.search(bExp) > -1);
    left = (corner.search(lExp) > -1) || !(corner.search(rExp) > -1);
    
    if (top) {
        obj.style.top = p.y + "px";
    } else {
        obj.style.bottom = p.y + "px";
    }            
    if (left) {
        obj.style.left = p.x + "px";
    } else {
        obj.style.right = p.x + "px";
    }
    return;
}



/*
    Use this generic function to set the display of any HTML element with an ID to "none". 
    
    Assumptions: 
        * assumes browser supports getElementById() (some older browsers like NN4 don't)
    
*/
function hideObject(objid, clear) {
    var o = document.getElementById(objid);
    highlightListing(-1, false);
    if (o) {
        var m = document.getElementById(currentBubble);
        if (m) {
            m.style.display = "";
            if (m.className.lastIndexOf("Hi") > 0 && m.className.lastIndexOf("Hi") == m.className.length - 2) {
                m.className = m.className.substring(0, m.className.length - 2);
            }
        }
        o.style.display = "none";
        if (clear) {
            currentBubble = "-1"; //need to make this more generic
        }
        return true;
    }
    return false;
}


/*
    This special case of hideObject can be used as a callback function for Flash to hide the 
    "bubble", which is assumed to always have id "listingContainer".
*/
function hideListingContainer() {
    highlightListing(-1, false);
    return hideObject("listingContainer");
    /* we still need this function to unhighlight the highlighted listing on the page, but we don't know
       which one that is unless we can pass an event object here? */
}

/*
    Defintes the Listing class. Note that different verticals will use different properties of this
    class, but all should be able to use the same basic class. Subclassing is possible, but more
    likely not worth the effort at this time.
    
*/

    // Listing constructor
//    function Listing (id, lat, lon, beds, baths, priceMin, priceMax, available, lastUpdate, imgSrc,
//            address, city, state, zip, description, source, type, showcase, builder, url, company)
//    {
//    this.id = id;
//    this.lat = lat;
//    this.lon = lon;
//    this.beds = beds;
//    this.baths = baths;
//    this.priceMin = priceMin;
//    this.priceMax = priceMax;
//    this.available = available;
//    this.lastUpdate = lastUpdate;
//    this.imgSrc = imgSrc;
//    this.address = address, 
//    this.city = city;
//    this.state = state;
//    this.zip = zip;
//    this.description = description;
//    this.source = source;
//    this.type = type;
//    this.showcase = showcase;
//    this.builder = builder;
//    this.url = url;
//    this.company = company;
//    }    

function Listing (id, lat, lon, imgSrc, address, city, state, zip, description, source, type, showcase, url, morePhotosHref, morePhotosImg, headline, ListedBy, ListingDetailURL)
    {
    this.id = id;
    this.lat = lat;
    this.lon = lon;
    this.imgSrc = imgSrc;
    this.address = address, 
    this.city = city;
    this.state = state;
    this.zip = zip;
    this.description = description;
    this.source = source;
    this.type = type;
    this.showcase = showcase;
    this.url = url;
    this.morePhotosHref = morePhotosHref;
    this.morePhotosImg = morePhotosImg;
    this.headline = headline;
    this.ListedBy = ListedBy;
    this.ListingDetailURL = ListingDetailURL;
    }    

    /*
        Javascript uses toString() when converting an object to a string such as when passed to
        the Alert() function. Best to explicitly call it. The user should prefer toString() to 
        the id property in the event that some future revision changes the unique identifier.
        toString() is expected to be unique, although this is no requirement of JavaScript.
    */
    Listing.prototype.toString = function () {
        return this.id;
    }

    /*
        Utility function for seeing the entire contents of a listing object.
    */
//    Listing.prototype.toLongString = function() {
//        var result = "id: " + this.id + "\n";
//        result += "beds: " + this.beds + "\n";
//        result += "lat: " + this.lat + "\n";
//        result += "lon: " + this.lon + "\n";
//        result += "baths: " + this.baths + "\n";
//        result += "priceMin: " + this.priceMin + "\n";
//        result += "priceMax: " + this.priceMax + "\n";
//        result += "available: " + this.available + "\n";
//        result += "lastUpdate: " + this.lastUpdate + "\n";
//        result += "imgSrc: " + this.imgSrc + "\n";
//        result += "address: " + this.address + "\n";
//        result += "city: " + this.city + "\n";
//        result += "state: " + this.state + "\n";
//        result += "zip: " + this.zip + "\n";
//        result += "description: " + this.description + "\n";
//        result += "source: " + this.source + "\n";
//        result += "type: " + this.type + "\n";
//        result += "showcase: " + this.showcase + "\n";
//        result += "builder: " + this.builder + "\n";
//        result += "url: " + this.url + "\n";
//        result += "company: " + this.company;
//        return result;
//    }

Listing.prototype.toLongString = function() {
        var result = "id: " + this.id + "\n";
        result += "lat: " + this.lat + "\n";
        result += "lon: " + this.lon + "\n";
        result += "beds: " + this.beds + "\n";
        result += "baths: " + this.baths + "\n";
        result += "priceMin: " + this.priceMin + "\n";
        result += "priceMax: " + this.priceMax + "\n";
        result += "available: " + this.available + "\n";
        result += "lastUpdate: " + this.lastUpdate + "\n";
        result += "imgSrc: " + this.imgSrc + "\n";
        result += "address: " + this.address + "\n";
        result += "city: " + this.city + "\n";
        result += "state: " + this.state + "\n";
        result += "zip: " + this.zip + "\n";
        result += "description: " + this.description + "\n";
        result += "source: " + this.source + "\n";
        result += "type: " + this.type + "\n";
        result += "showcase: " + this.showcase + "\n";
//        result += "builder: " + this.builder + "\n";
        result += "url: " + this.url + "\n";
//				result += "morePhotosHref: " + this.morePhotosHref + "\n";
//				result += "morePhotosImg: " + this.morePhotosImg + "\n";
//				result += "virtualTourHref: " + this.virtualTourHref + "\n";
//				result += "virtualTourImg: " + this.virtualTourImg + "\n";
//				result += "virtualTourOnClick: " + this.virtualTourOnClick + "\n";
//				result += "sqft: " + this.sqft + "\n";
//				result += "headline: " + this.headline + "\n";
//				result += "ListedBy: " + this.ListedBy + "\n";
//				result += "OfficePhoto: " + this.OfficePhoto + "\n";
//				result += "OfficeUrl: " + this.OfficeUrl + "\n";
//				result += "ListingDetailURL: " + this.ListingDetailURL + "\n";
//				result += "openHouseHref: " + this.openHouseHref + "\n";
//				result += "openHouseImg: " + this.openHouseImg + "\n";
//				result += "openHouseOnClick: " + this.openHouseOnClick + "\n";
        return result;
    }



    /*
        These is the key global Arrays of listigns and map points. Rather than subclass the native 
        javascript Array
        object, we define additional properties and behavior specifically for this Instance of Array.
        As long as only these special methods are used to interact with the array, the entries will be
        sorted by listingID, facilitating lookup.
    */
    var listings = new Array(); 
    var mapMarkers = new Array();
    var anchorPoint = new Point(0,0);   


    /*
        the mapMarkers Array can compute it's own centroid and extent (min and max points), automatically
        excluding points like (0,0) or (-1,-1). Future revisions might also remove from the markers Array
        those points more than <spread> standard deviations from the mean in either X or Y directions.
        Partial code has been implemented along these lines to begin to measure any performance impact.
    */
    mapMarkers.extent = function(spread) {
        if (!spread)  spread = 0.0;
        else spread = parseFloat(spread);
        
        var sumx = 0.0;
        var sumy = 0.0;
        var sumxx = 0.0;
        var sumyy = 0.0;
        var tot = 0;
        var minp = new Point(90, 180);
        var maxp = new Point(-90, -180);
        
        for (var i=0;i<this.length;i++) {
            if (Math.abs(this[i].lat) > 5 && Math.abs(this[i].lon) > 5) {
                sumx = parseFloat(sumx) + parseFloat(this[i].lat);
                sumy = parseFloat(sumy) + parseFloat(this[i].lon);
                sumxx = parseFloat(sumxx) + parseFloat(this[i].lat) * parseFloat(this[i].lat);
                sumyy = parseFloat(sumyy) + parseFloat(this[i].lon) * parseFloat(this[i].lon);
                minp.x = (parseFloat(minp.x) > parseFloat(this[i].lat)) ? this[i].lat : minp.x;
                minp.y = (parseFloat(minp.y) > parseFloat(this[i].lon)) ? this[i].lon : minp.y;
                maxp.x = (parseFloat(maxp.x) < parseFloat(this[i].lat)) ? this[i].lat : maxp.x;
                maxp.y = (parseFloat(maxp.y) < parseFloat(this[i].lon)) ? this[i].lon : maxp.y;
                tot += 1;
            } 
            
        }
        var p
        if (tot > 0) {
            p = new Point(sumx / tot, sumy / tot);
        } else {
            p = new Point(0,0);
        }
        var e = Array(3);
        e[0] = p;
        e[1] = minp;
        e[2] = maxp;
        return e;
    }


    /*
        Use this function to determine at what position within the listings array a new Listing 
        ought to be placed. Function returns -1 if the object is null or has no toString method.
    */
    listings.posInArray = function (obj) {
        if (!obj || !obj.toString) return -1; 
        
        var min=0;
        var max=this.length-1;
        
        if (max < 0) return 0; //if there are no entries, then this would be the first.
        
        var test = Math.floor((min + max) / 2);
        
        while (max - min > 1) 
        {
            //loop guaranteed to end because each iteration moves one endpoint closer to their
            //average (lowering their difference) or returns outright.
            if (obj.toString() == this[test].toString()) {
                return test;
            }
            else if (obj.toString() > this[test].toString()) {
                min = test;
            } else if (obj.toString() < this[test].toString()) {
                max = test;
            }
            test = Math.floor((min + max) / 2);
        }
        
        if (obj.toString() > this[max].toString()) return max+1;
        else if (obj.toString() > this[min].toString()) return max;
        else return min;
    }
    
    /*
        Use this function to determine if a specific listing object is presently in the listings
        array. Note that it depends on use of posInArray, and is subject to the same constraints.
        (A listing is in the array if the element at the position where it would be added has the same
        listing ID)
    */
    listings.isInArray = function(obj) {
        if (!obj) return false;
        
        var pos = this.posInArray(obj);
        if (this.length <= pos) return false;
        else return (obj.toString() == this[pos].toString());

    }
    
    /*
        Use this function to determine where a listingID would be placed in the listings array.
        Unlike posInArray, this function does not require an instantiated Listing object, only 
        the listingID itself, so in real life it's more useful.
    */
    listings.keypos = function (i) {
        var istr = i + ""; //force string
        var min=0;
        var max=this.length-1;
        
        if (max < 0) return 0; // if there are no entries, the new one would be the first.
        
        var test = Math.floor((min + max) / 2);
        while (max - min > 1) 
        {
            //loop guaranteed to end because each iteration moves one endpoint closer to their
            //average (lowering their difference) or returns outright.
            if (istr == this[test].toString()) {
                return test;
            }
            else if (istr > this[test].toString()) {
                min = test;
            } else if (istr < this[test].toString()) {
                max = test;
            }
            test = Math.floor((min + max) / 2);
        }
        
        if (max == min) return max;
        else if (istr > this[max].toString()) return max+1;
        else if (istr > this[min].toString()) return max;
        else return min;
    }
    
    
    /*
        This is the listingID variant of isInArray, and determines whether the specified listing ID
        is presently in the array without requiring an entire Listing object. Note that it depends
        on keypos() -- a listingID is in the array if the Listing at its proper position of insertion
        has a matching listingID.
        
        This function does NOT return a boolean true/false, but rather a -1 if the key is not in 
        the array otherwise the actual index at which the element occurs.
    */
    listings.hasKey = function(i) {
        var pos = this.keypos(i);
        if (this.length <= pos) return -1;
        else if (this[pos].toString() == i) return pos;
        else return -1;
    }
    
    /* TODO: refactor common listings and mapMarkers functions to some "static" array helper class and 
        call those functions passing in the array in question as an additional argument */
    
    /*
        Use this function to determine at what position within the mapMarkers array a new Listing 
        ought to be placed. Function returns -1 if the object is null or has no toString method.
    */
    mapMarkers.posInArray = function (obj) {
        if (!obj || !obj.toString) return -1; 
        
        var min=0;
        var max=this.length-1;
        
        if (max < 0) return 0; //if there are no entries, then this would be the first.
        
        var test = Math.floor((min + max) / 2);
        
        while (max - min > 1) 
        {
            //loop guaranteed to end because each iteration moves one endpoint closer to their
            //average (lowering their difference) or returns outright.
            if (obj.toString() == this[test].toString()) {
                return test;
            }
            else if (obj.toString() > this[test].toString()) {
                min = test;
            } else if (obj.toString() < this[test].toString()) {
                max = test;
            }
            test = Math.floor((min + max) / 2);
        }
        
        if (obj.toString() > this[max].toString()) return max+1;
        else if (obj.toString() > this[min].toString()) return max;
        else return min;
    }
    /*
        Use this function to determine if a specific mapMarker object is presently in the mapMarkers
        array. Note that it depends on use of posInArray, and is subject to the same constraints.
        (A mapMarker is in the array if the element at the position where it would be added has the same
        listing ID)
    */
    mapMarkers.isInArray = function(obj) {
        if (!obj) return false;
        
        var pos = this.posInArray(obj);
        if (this.length <= pos) return false;
        else return (obj.toString() == this[pos].toString());

    }
    
    /*
        Use this function to determine where a mapMarker would be placed in the mapMarkers array.
        Unlike posInArray, this function does not require an instantiated mapMarker object, only 
        the listingID itself, so in real life it's more useful.
        
        Since map markers don't use listingID as their toString() we have to be less generic, alas.
    */
    mapMarkers.keypos = function (i) {
        var istr = i + ""; //force string
        var min=0;
        var max=this.length-1;
        
        if (max < 0) return 0; // if there are no entries, the new one would be the first.
        
        var test = Math.floor((min + max) / 2);
        while (max - min > 1) 
        {
            //loop guaranteed to end because each iteration moves one endpoint closer to their
            //average (lowering their difference) or returns outright.
            if (istr == this[test].index) {
                return test;
            }
            else if (istr > this[test].index) {
                min = test;
            } else if (istr < this[test].index) {
                max = test;
            }
            test = Math.floor((min + max) / 2);
        }
        
        if (max == min) return max;
        else if (istr > this[max].index) return max+1;
        else if (istr > this[min].index) return max;
        else return min;
    }
    
    
    /*
        This is the listingID variant of isInArray, and determines whether the specified listing ID
        is presently in the array without requiring an entire mapMarker object. Note that it depends
        on keypos() -- a mapMarker is in the array if the mapMarker at its proper position of insertion
        has a matching listingID.
        
        This function does NOT return a boolean true/false, but rather a -1 if the key is not in 
        the array otherwise the actual index at which the element occurs.
        
    */
    mapMarkers.hasKey = function(i) {
        var pos = this.keypos(i);
        if (this.length <= pos) return -1;
        else if (this[pos].index == i) return pos;
        else return -1;
    }
    
    
    /*
        This function allows one to add a new Listing Object to the listings array. (actually,
        you can add any object, but we strongly recommend only adding Listing objects.) The object
        to be added is the first parameter and the second parameter is a boolean that controls whether
        duplicates will be permitted. (In real life all current scenarios should forbid duplicates.)
        Setting the boolean to TRUE requires that each element be unique, and will remove an element 
        with matching listingID prior to adding the requested element. (By removing the old one 
        rather than not adding the new one, we allow for an update mechanism.)
        
        The add function returns false if nothing was added, otherwise true.
    */
    listings.add = function (obj, unique) {
        if (!obj) return false;
        var pos = this.posInArray(obj);
        var del = 0;
        if (unique && this.isInArray(obj)) {
            del = 1;
        }
        this.splice(pos, del, obj);
        return true;
    }
    
    /*
        This function allows one to add a new MapMarker Object to the mapMarkers array. (actually,
        you can add any object, but we strongly recommend only adding MapMarker objects.) The object
        to be added is the first parameter and the second parameter is a boolean that controls whether
        duplicates will be permitted. (In real life all current scenarios should forbid duplicates.)
        Setting the boolean to TRUE requires that each element be unique, and will remove an element 
        with matching listingID prior to adding the requested element. (By removing the old one 
        rather than not adding the new one, we allow for an update mechanism.)
        
        The add function returns false if nothing was added, otherwise true.
    */
    mapMarkers.add = function (obj, unique) {
        if (!obj) return false;
        var pos = this.posInArray(obj);
        var del = 0;
        if (unique && this.isInArray(obj)) {
            del = 1;
        }
        this.splice(pos, del, obj);
        return true;
    }
    
    /*
        Use this function to request the addition AND DISPLAY of a listing given its listing ID.
        If the listing is already present, it will be displayed. If not, an asynchronous call will
        be made to retrieve the information, a new listing will be added to the listings array, and
        then the new listing will be displayed.
        
        NOTE -- the URL from which to retrieve the listing data must be on the same server as 
        either this javascript file or the page which loads it (not sure which.) Accordingly,
        individuals may need to change this value.
    */
    listings.requestAdd = function(listingID) {
        if (this.hasKey(listingID) >= 0) {
            this.display(listingID);
        } 
        else {
            this.httpRequest = getHttpObject();
            if (!listings.httpRequest) return false;
            this.httpRequest.onreadystatechange = listings.completeAdd;
            //the following line may be specific to a single vertical's implementation
            this.httpRequest.open('GET', constructListingUrl(listingID), true);
            this.httpRequest.send(null);
        }
        return true;
    }
    
    
    /*
        This is the callback function for an asynchronous call to retrieve a listing.
        After the listing data returns, it's parsed and turned into a Listing object and added
        to the listings array, and then promptly displayed.
    */
    listings.completeAdd = function() {
        if (!listings.httpRequest) {
            return;
        }
        if (listings.httpRequest.readyState == 4) {
            if (listings.httpRequest.status == 200) {
                var result = listings.httpRequest.responseText;
                var L = Listing.prototype.createListing(result);
                listings.add(L,true);
                listings.display(L.id);
            }
        }
    }
    

    // the rest of the functions in this file are not really specific to mapping. Some future
    // refactoring should move them into a generic utility file.

    /*
        This general helper fuction gets a browser-independent object for making http or AJAX requests
    */    
    function getHttpObject() {
        if (window.XMLHttpRequest) 
        { 
          return new XMLHttpRequest();
        } 
        else if (window.ActiveXObject) 
        {
            return new ActiveXObject("Microsoft.XMLHTTP");
        } 
        else return null;
    }
    
    /*
        this general function provides a browser-indenpendent way to replace the content of 
        an html tag with some other piece of html. 
    */
    function replaceContent(container, content) {
        if (document.getElementById && document.createRange && container) {        
            var r = document.createRange();
            r.setStartBefore(container);
            var h = r.createContextualFragment(content);
            while (container.hasChildNodes()) {
                container.removeChild(container.lastChild);
            }
            container.appendChild(h);
        }
        else if (document.all && container) {
            container.innerHTML = content;
        }
    }
    
    /*
        Use this function to format a number as a dollar value, adding commas and the $ sign.
        Note that the signature for the function allows for formatting currencies in other locales,
        but only us-en is implemented. Also note that fractional dollar amounts (e.g. $0.02) are 
        rounded in the current implementation to the nearest dollar.
    */
    function formatCurrency(amount, locale) {
        var iamt = "" + parseInt(amount);
        var result = "";
        var l = iamt.length
        for (i = 0; i<l; i++) {
            if ((i>0) && ((i%3)==0)) {
                result = "," + result;
            }
            result = iamt.charAt(l-i-1) + result;
        }
        result = "$" + result;
        return result;
   }
  
  /*
    Use this function to force a map point's bubble to pop up along with necessary pan/zoom.
    Future expansion will replace "index" with a parameter to name the map point.
    
    this version is now for use with the VE implementation, which breaks flash. for flash, alter the boolean
    isFlash appropriately
  */
  		function popBubble(bubbleIdentifier)
		{
		    var isFlash = false;
		    if (isFlash) {
                var theMap = window.document['map'];
                theMap.clickImgMarker("index", bubbleIdentifier);
            } else {
                var o = document.getElementById(bubbleIdentifier);
                if (o) {
                    var p = new Point(0,0);
                    openBubble(o, p);
                }
            }
		}// function not in use

  		function popBubbleAndReposition(bubbleIdentifier)
		{
		    var isFlash = false;
		    if (isFlash) {
                var theMap = window.document['map'];
                theMap.clickImgMarker("index", bubbleIdentifier);
            } else {
                var o = document.getElementById(bubbleIdentifier);
                if (o) {
                    var p = new Point(0,0);
                    reposition = true;
                    openBubble(o, p);
                }
            }
		}// function not in use
/*
    Yes, cookies aren't really part of maps, but we only need them on the map page.
*/
function setCookie(name, value, expires, path, domain, secure) {
  var curCookie = name + "=" + escape(value) +
      ((expires) ? "; expires=" + expires.toGMTString() : "") +
      ((path) ? "; path=" + path : "") +
      ((domain) ? "; domain=" + domain : "") +
      ((secure) ? "; secure" : "");
  document.cookie = curCookie;
}

function getCookie(name) {
  var dc = document.cookie;
  var prefix = name + "=";
  var begin = dc.indexOf("; " + prefix);
  if (begin == -1) {
    begin = dc.indexOf(prefix);
    if (begin != 0) return null;
  } else
    begin += 2;
  var end = document.cookie.indexOf(";", begin);
  if (end == -1)
    end = dc.length;
  return unescape(dc.substring(begin + prefix.length, end));
}

function deleteCookie(name, path, domain) {
  if (getCookie(name)) {
    document.cookie = name + "=" +
    ((path) ? "; path=" + path : "") +
    ((domain) ? "; domain=" + domain : "") +
    "; expires=Thu, 01-Jan-70 00:00:01 GMT";
  }
}



/*
functions from VE mapping implementation. All *can* be market-independent but may not presently be.

*/

function showBubble (e) {
    //alert ("show");
    if (!e) {
        var e = window.event;
    }
    var targ;
    if (e.target) targ = e.target;
    else if (e.srcElement) targ = e.srcElement;
    if (targ.nodeType == 3) targ = targ.parentNode; // for Safari
    reposition = true;
    openBubble(targ);
}

/*
    different from showBubble in that it takes argument of the bubble's object instead of an event
*/
function openBubble(targ) {
    //alert ("open");
    if (!targ) return;
    var p = new Point(0,0);

    var divOnScreen = true;
    
   var q = new Point(0,0);
    q.addAbsolutePosition(targ);
    p.x = parseInt(q.x);// + parseInt(p.x);
    p.y = parseInt(q.y) + targ.offsetHeight;// + parseInt(p.y);
    //alert("orig point: "+p);
    
    /*
        this code will calculate the deltas for positioning the map so that the bubble is within 
        its borders. TODO: replace flashContainer with a more generic name.
    */
    var f = document.getElementById("flashContainer");
    if (f) {
        //alert("Got Container");
        var mapPt = new Point(0,0);
        mapPt.addAbsolutePosition(f);
        if (mapPt.x > p.x || mapPt.y > p.y || (mapPt.x + f.offsetWidth) < p.x || (mapPt.y + f.offsetHeight) < p.y) { //last condition is so that pan/zoom doesn't then reposition
            divOnScreen = false;
        }
    }
    //alert(divOnScreen);
    var d = document.getElementById("listingContainer");
    if (d && (divOnScreen || reposition)) {
        //alert("Got Listing");
        var c = document.getElementById(currentBubble);
        if (c) {
        //alert("Got Bubble");
            c.style.display = "";
        }
        //alert(targ.id);
        currentBubble = targ.id; 
        //assumes that the listings array has already been created at the global level
        //alert(targ.id);
        
        /*
        commented this line below on 03/23/2006 so that the mapmarker 
        and the innerHtml numbers do not hide when bubbles are displayed...
        */
        /*
        targ.style.display = "none";
        */
        
    //alert("flash: " + q);
    //alert("final point: " + p);
        anchorPoint.x = p.x;
        anchorPoint.y = p.y;
    //alert("anchor: " + anchorPoint);
        listings.requestAdd(targ.id);
    }
}

function repositionMapToListing(listingid) {
    //we need the location and size of the flash container,and the location and size of the
    //listing container. Calculate the needed delta to keep the bubble in the map space and 
    //call the map function to reposition.
    var c = document.getElementById("listingContainer"); //the bubble
    var f = document.getElementById("flashContainer"); //the map
    var index = mapMarkers.hasKey(listingid);
    var p = new Point(0,0);
    p.addAbsolutePosition(c);
    var q = new Point(0,0);
    q.addAbsolutePosition(f);
    reposition = false;
    if (index >= 0) {
        var delX = 0;
        var delY = 0;
        if (map.width +  q.x < p.x + c.offsetWidth) {
            delX = c.offsetWidth + p.x - map.width - q.x + 10;
        }  else if (p.x < q.x) { // then bubble is positioned to the left of the map
            delX = (p.x - q.x - 10);
        }
        if (p.y < q.y) { // then bubble is positioned above map top
            delY = (p.y - q.y - 10);
        } else if (map.height +  q.y < p.y + c.offsetHeight) {
            delY = c.offsetHeight + p.y - map.height - q.y + 10;
        }
        if (delX != 0 || delY != 0) {
            map.PanMap(delX, delY);
            if (currentBubble != "-1") {
                hideListingContainer(); //hide/show will reposition the bubble.
                var b = document.getElementById(currentBubble);
                openBubble(b);
            }
        }
    }
}

function highlightIcon(e) {
    if (!e) {
        var e = window.event;
    }
    var targ;
    if (e.target) targ = e.target;
    else if (e.srcElement) targ = e.srcElement;
    if (targ.nodeType == 3) targ = targ.parentNode; // for Safari
    
    targ.className = targ.className + "Hi";

    return false;
}

function unhighlightIcon(e) {
    if (!e) {
        var e = window.event;
    }
    var targ;
    if (e.target) targ = e.target;
    else if (e.srcElement) targ = e.srcElement;
    if (targ.nodeType == 3) targ = targ.parentNode; // for Safari
    
    if (targ.className.lastIndexOf("Hi") > 0 && targ.className.lastIndexOf("Hi") == targ.className.length - 2) {
        targ.className = targ.className.substring(0, targ.className.length - 2);
    }

    return false;
}

function centerMapOnCurrentBubble() {
    if (currentBubble != "-1" && currentBubble != "") {
        var index = mapMarkers.hasKey(currentBubble);
        if (index >= 0) {
            map.SetCenter(mapMarkers[index].lat, mapMarkers[index].lon);
        }
    }
}

function setZoomSlider() {
    var bar = document.getElementById("mapControlZoomBar");
    var obj = document.getElementById("mapControlZoomSlider");
    if (obj) {
        var topPosition = Math.round((bar.offsetHeight - obj.offsetHeight) * (map.zoomLevel - VE_MapSearchControl.minZoom) / (VE_MapSearchControl.maxZoom - VE_MapSearchControl.minZoom)); 
        obj.style.top = topPosition + "px";
    }
}

function setZoomFromClick(e) {
    if (!e) var e = window.event;
    e.cancelBubble = true;
    if (e.stopPropagation) e.stopPropagation();
    
    var aId;
    if (e.target) aId = e.target;
    else if (e.srcElement) aId = e.srcElement;
    if (aId.nodeType == 3) // safari bug?
        aId = aId.parentNode;

    var obj = document.getElementById("mapControlZoomBar");
    if (obj != aId) {
        return true;
    }

    var p = getPosition(e);

    return setZoomFromPosition(p.y)
}
    
    
    function getPosition(e)
{
	var posx = 0;
	var posy = 0;
	if (!e) var e = window.event;
	if (e.pageX || e.pageY)
	{
		posx = e.pageX;
		posy = e.pageY;
	}
	else if (e.clientX || e.clientY)
	{
	//alert("client: " + e.clientX + "," + e.clientY + " document: " + document.body.scrollLeft + "," + document.body.scrollTop);
		posx = e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft);
		posy = e.clientY + (document.documentElement.scrollTop || document.body.scrollTop);
	}
	return new Point(posx, posy);
}

    function endswith(container, snippet) {
        return ((container.lastIndexOf(snippet) + snippet.length) == container.length);
    }
