if (typeof window["console"] == 'undefined') {
    var console = {}; console.log = function() {};
}

// definitions of map types
var MAP_TRAIL = {
	  model: 'Trail',
	  associations: ['region', 'activity'],
	  fields: ['name', 'region.id', 'activity.id', 'distance_filter', 'rating'],
	  name: 'name',
	  locationData: 'latitude/longitude',
	  iconBase: '/img/maps/pins/',
	  infoWindow: {
		    markup: '<h3 class="find-a-restaurant">{name}</h3>' +
				    '<dl>' +
					      '<dt>Activity:</dt><dd>{activity.name}</dd>' +
					      '<dt>Distance:</dt><dd>{distance}</dd>' +
					      '<dt>Rating:</dt><dd>{rating}</dd>' +
				    '</dl>' +
            '<a class="more" href="#{id}">Details</a>',
		    offsets: { x: -47, y: 10 }
	  },
	  overlay: VEMapStyle.Road,
	  setFormElements: {}
},

MAP_GETAWAY = {
	  model: 'Getaway',
	  fields: ['name', 'nwregion', 'distance', 'outdoors', 'wine_country', 'urban', 'small_town', 'mountain', 'coast', 'ski_snowboard'],
	  associations: [],
	  name: 'name',
	  locationData: 'location.lat/lng',
	  iconBase: '/img/maps/pins/',
	  infoWindow: {
		    markup: '<h3 class="find-a-getaway">{name}</h3>' +
				    '<div><em rel="esubhead">{esubhead}</em></div>' +
            '<a class="more" href="#{id}">Details</a>',
		    offsets: { x: -47, y: 10 }
	  },
	  overlay: VEMapStyle.Road,
	  setFormElements: {
		    nwregion: function (setting) {
			      var region = $('region'),
				    controls = $('controls');

			      // determine which classname to set on fieldset, which controls display of "miles from portland"
			      if (setting == 'true' || setting == "*" || setting == "none") {
				        if (region.hasClassName('outside')) {
					          region.removeClassName('outside');
					          region.addClassName('inside');
				        }
				        controls['nwregion'][0].checked = true;
			      } else {
				        if (region.hasClassName('inside')) {
					          if (controls['distance'].value) {
						            controls['distance'].value = '';
					          }
					          region.removeClassName('inside');
					          region.addClassName('outside');
				        }
				        controls['nwregion'][1].checked = true;
			      }
		    }
	  }
},

MAP_RESTAURANT = {
	  model: 'Restaurant',
	  associations: ['business_listing', 'neighborhood_quadrant', 'neighborhood', 'cuisine'],
	  fields: ['business_listing.name', 'business_listing.neighborhood$', 'cuisine.id',
			       'editors_pick', 'breakfast', 'lunch', 'dinner', 'brunch', 'parking',
			       'reservations_suggested', 'kid_friendly', 'outdoor_dining', 'carryout',
			       'late_night_dining'],
	  name: 'business_listing.name',
	  locationData: 'business_listing.address.lat/lng',
	  iconBase: '/img/maps/pins/',
	  infoWindow: {
		    markup: '<h3 class="find-a-restaurant">{business_listing.name}</h3>' +
				    '<dl>' +
                '<dt>Cuisine:</dt><dd>{cuisine.name}</dd>' +
                '<dt>Hours:</dt><dd>{business_listing.hours}</dd>' +
				    '</dl>' +
            '<a class="more" href="#{id}">Details</a>',
		    offsets: { x: -47, y: 10 }
	  },
	  overlay: VEMapStyle.Road,
	  setFormElements: {}
},

MAP_FOOD_CART = {
	  model: 'Food_Cart',
	  associations: ['business_listing', 'neighborhood_quadrant', 'neighborhood', 'cuisine'],
	  fields: ['business_listing.name', 'business_listing.neighborhood$', 'cuisine.id',
			       'editors_pick', 'breakfast', 'lunch', 'dinner', 'late_night', 
			       'open_weekends', 'seating_available', 'rain_protection', 'atm_nearby', 'credit_cards_accepted', 'mobile_carts'],
	  name: 'business_listing.name',
	  locationData: 'business_listing.address.lat/lng',
	  iconBase: '/img/maps/pins/',
	  infoWindow: {
		    markup: '<h3 class="find-a-food-cart">{business_listing.name}</h3>' +
				    '<dl>' +
                '<dt>Cuisine:</dt><dd>{cuisine.name}</dd>' +
                '<dt>Hours:</dt><dd>{business_listing.hours}</dd>' +
				    '<dl>' +
            '<a class="more" href="#{id}">Details</a>',
		    offsets: { x: -47, y: 10 }
	  },
    overlay: VEMapStyle.Road,
	  setFormElements: {}
},

MAP_BAR = {
	  model: 'Bar',
	  associations: ['business_listing', 'neighborhood_quadrant', 'neighborhood'],
	  fields: ['business_listing.name', 'business_listing.neighborhood$',
			       'editors_pick', 'singles_scene', 'recommended_menu', 'sports_bar', 'karaoke', 'romantic', 'live_entertainment',
			       'scenic_view', 'outdoor_patio', 'recommended_beer_selection', 'late_night',
			       'private_parties', 'pub', 'wine_bar', 'dancing', 'dives', 'billiards', 'brewery',
			       'gay_bar', 'happy_hour', 'hotel_bar'],
	  name: 'business_listing.name',
	  locationData: 'business_listing.address.lat/lng',
	  iconBase: '/img/maps/pins/',
	  infoWindow: {
		    markup: '<h3>{business_listing.name}</h3>' +
				    '<dl>' +
					      '<dt>Type:</dt><dd>{display_type}</dd>' +
					      '<dt>Hours:</dt><dd>{business_listing.hours}</dd>' +
				    '</dl>' +
            '<a class="more" href="#{id}">Details</a>',
		    offsets: { x: -47, y: 10 }
	  },
	  overlay: VEMapStyle.Road,
	  setFormElements: {}
},

MAP_SHOP = {
	  model: 'Shop',
	  associations: ['business_listing', 'neighborhood_quadrant', 'neighborhood'],
	  fields: ['business_listing.name', 'business_listing.neighborhood$',
			       'gifts_accessories', 'jewelry', 'antiques', 'books_paper_goods', 'home_garden', 'kids_clothes_toys', 'mens_apparel',
			       'pet_accessories', 'shoes', 'womens_apparel', 'bath_beauty', 'gourmet_specialty_foods', 'outdoor_sporting_goods', 'spas'],
	  name: 'business_listing.name',
	  locationData: 'business_listing.address.lat/lng',
	  iconBase: '/img/maps/pins/',
	  infoWindow: {
		    markup: '<h3>{business_listing.name}</h3>' +
				    '<dl>' +
					      '<dt>Type:</dt><dd>{display_type}</dd>' +
					      '<dt>Hours:</dt><dd>{business_listing.hours}</dd>' +
				    '</dl>' +
            '<a class="more" href="#{id}">Details</a>',
		    offsets: { x: -47, y: 10 }
	  },
	  overlay: VEMapStyle.Road,
	  setFormElements: {}
};

// wires up search controls, results list, "more info" windows and the map for a particular map type
    var mapSearch = function (mapType) {

	          // contains all GMap api calls; controls functionality for markers and associated info bubbles
	      var map = function () {
		        var vemap,
			          info = mapType.infoWindow, // shortcut to info window definition
                startLat = 47.62005, startLon = -122.34787, startZoom = 12,
                markers = [],  // list of currently placed markers
			          currentMarker, // currently active marker
			          icon,          // base custom icon object definition
                max_z_index = 1001;  // baseline zIndex for bringing currently selected icon to foreground

		        // shorthand for lat/longs in GLatLng format
		        var point = function (lat, lon) {
			          return new VELatLong(lat, lon);
		        };

		        // finds and creates a view that will show all markers in the marker list
		        var recenter = function () {
                var points = [];

                for (var i = 0, len = markers.length; i < len; ++i)
                    points.push(markers[i].GetPoints()[0]);
                
                vemap.SetMapView(points);
		        };

            function showInfoBox(marker) {
                function details(e) { 
				            e.stop();
				            addressing.setURL({ info: e.target.hash.slice(1) });
                    vemap.HideInfoBox(marker);
			          }

                function hide(e) {
                    e.stop();
                    vemap.HideInfoBox(marker);
                }

                    window.setTimeout(function () {
                        vemap.ShowInfoBox(marker);
                        
                        var more = $$('.info-window .more')[0],
                            close = $$('.info-window .close')[0];
                        
            		        more.observe('click', details);
            		        close.observe('click', hide);
                    }, 10);
            }

		        // on marker click, pan to marker and toggle its info window on and off, as needed;
		        // also highlight cooresponding listing in location list
		        var activateMarker = function (e) {
			          vemap.goToMarker(this.index);
			          locationList.goToLocation(this.index);
		        };

		        // interface to map object
		        return {
			          init: function () {
                    var el = document.getElementById('map'),
                        hover_timer = null,
                        hover_timer_id = null;
                    
					          vemap = new VEMap(el.id);
                    el.style.width = '100%';
                    el.style.height = '100%';
                    
                    vemap.SetCredentials('AlIOjdwGSw_dirX8W6RSRjs7iAudcFlzIw0DgaIHpPkSgk7uYR_qqRj9ZqDPGsFe');
                    vemap.LoadMap(point(startLat, startLon), startZoom, VEMapStyle.Road);
                    vemap.ClearInfoBoxStyles();

                    function markerOverHandler(e) {
                        console.log('in: ' + e.elementID);
                        
                        if (hover_timer_id == e.elementID) return true;
                        else if (hover_timer) window.clearTimeout(hover_timer);
                        hover_timer = hover_timer_id = null;

                        var marker = vemap.GetShapeByID(e.elementID),
                            handled = false;

                        if (marker && marker instanceof VEShape) {
                            hover_timer_id = e.elementID;
                            hover_timer = window.setTimeout(function () {
                                hover_timer = hover_timer_id = null;

                                if (currentMarker) vemap.HideInfoBox(currentMarker);
                                currentMarker = marker;
                                
                                currentMarker.SetZIndex(max_z_index++);
                                locationList.goToLocation(currentMarker.index);
                                showInfoBox(currentMarker);
                            }, 400);

                            handled = true;
                        }

                        return handled;
                        }
                    vemap.AttachEvent('onmouseover', markerOverHandler);

                    function markerOutHandler(e) {
                        console.log('out');

                        var handled = false;

                        if (hover_timer) {
                            window.clearTimeout(hover_timer);
                            hover_timer = hover_timer_id = null;
                            handled = true;
                        }

                        return handled;
                    }
                    vemap.AttachEvent('onmouseout', markerOutHandler);

                    function markerClickHandler(e) {
                        if (hover_timer) window.clearTimeout(hover_timer);
                        hover_timer = hover_timer_id = null;

                        var marker = vemap.GetShapeByID(e.elementID),
                            handled = false;

                        if (marker && marker instanceof VEShape) {
                            if (currentMarker) vemap.HideInfoBox(currentMarker);
                            currentMarker = marker;

                            currentMarker.SetZIndex(max_z_index++);
                            locationList.goToLocation(currentMarker.index);
                            showInfoBox(currentMarker);

                            handled = true;
                        }

                        return handled;
                    }
                    vemap.AttachEvent('onclick', markerClickHandler);

                    // preload infobox graphics
                    (new Image()).src = "/img/maps/bg-infobox.png";
                }, 

			          // remove all markers from the map, and clear the marker list
			          clear: function () {
				            vemap.Clear();
				            markers = [];
				            currentMarker = null;
			          },

			          // create a marker list, generate info windows for each marker, and set a new zoom/center to show
			          // all markers in the list on the map
			          update: function (locs, offset) {
                    var iconType;

				            // generate the marker array from locations, using supplied map and info types
				            (locs.length > 25 || offset > 0) ? iconType = 'generic' : iconType = 'num';
				            locs.each( function (loc, index) {
                        var marker, icon, content;
                        
                        icon = new VECustomIconSpecification();
                        icon.Image = mapType.iconBase + ((iconType == 'num') ? (index < 9 ? '0' + (index + 1) : index + 1) : iconType) + '.png',
                        
                        marker = new VEShape(
                                VEShapeType.Pushpin,
                            point(parseFloat(loc.lat), parseFloat(loc.lon))
                        );
                        marker.SetCustomIcon(icon);

                        content = '<div class="info-window"><div class="info-top"></div><div class="info-content">' + injectData(info.markup, loc) + '</div><div class="close">Close</div><div class="info-bottom"></div><div class="info-beak"></div></div>';
                        marker.SetDescription(content);
					              
					              vemap.AddShape(marker);

					              // this value is used to allow markers to reference themselves in the markers array
					              marker.index = index;
					              markers.push(marker);
				            });

				            // pan and zoom to a viewport that will show all markers
				            recenter();
			          },

			          // pan to a marker in the marker list
			          goToMarker: function (index) {
                    var marker_loc, map_center;
                    
                    function show(e) {
                        showInfoBox(currentMarker);
                        vemap.DetachEvent('onchangeview', show);
                        vemap.DetachEvent('onendpan', show);
                    }
                    
                    if (currentMarker) vemap.HideInfoBox(currentMarker);
				            currentMarker = markers[index];

                    // we need to test if we are actually going to pan at all, because infoboxes are tricky beasts, depending
                    marker_loc = currentMarker.GetPoints()[0];
                        map_center = vemap.GetCenter();
                    
                    // compare to only six digits
                    if (marker_loc.Latitude.toFixed(6) == map_center.Latitude.toFixed(6) && marker_loc.Longitude.toFixed(6) == map_center.Longitude.toFixed(6)) {
                        showInfoBox(currentMarker);
                    } else {
                        vemap.AttachEvent('onchangeview', show);
                        vemap.AttachEvent('onendpan', show);
                            vemap.SetCenter(marker_loc);
                    }
                    
                    currentMarker.SetZIndex(max_z_index++);
			          },

			          reset: function () {
				            vemap.Clear();
				            vemap.SetZoomLevel(startZoom);
				            vemap.SetCenter(point(startLat, startLon));
			          },

			          unload: function () {}
		        };
	      }(); // end map

	      // manages results from the search object, and calls markers in the map object on list item click
	      var locationList = function () {
		        var terms, results, list, fail, instructions,
			      currentLocation,
			      locations = [];

		        // build a results list from the JSON object passed in by search
		        var buildResults = function (offset) {
			          list.update();
			          list.show();

			          locations.each( function (loc, index) {
				            list.insert(
					              '<li id="' + loc.id + '"><a href="#' + (index) + '"><span>' + (index + offset + 1) + '</span>' + loc.name + '</a></li>'
				            );
			          });
		        };
		        
		        // toggle on state for this result list item
		        var activateLocation = function (e) {
			          var index;
			          
			          if (e.target.tagName.toLowerCase() == 'a') {
				            e.stop();
				            index = parseInt(e.target.hash.slice(1), 10);
				            locationList.goToLocation(index);
				            map.goToMarker(index);
			          }
		        };

		        // figure out how much vertical room is left for the results list after term bucket is populated
		        var remainingHeight = function () {
			          var parent = $('map-results');
			          return (parseInt(parent.getStyle('height')) - 
					              (terms.getStyle('display') == 'block' ? terms.offsetHeight + parseInt(terms.getStyle('margin-top')) + parseInt(terms.getStyle('margin-bottom')) : 0))
				            + 'px';
		        };

		            return {
			              init: function () {
				                terms = $('terms');
				                instructions = $('instructions');
				                list = new Element('ol');
				                fail = new Element('p', { id: 'fail' }).insert('Sorry, but there were no results that matched your search criteria.');
				                
				                results = $('results');
				                results.insert({top: fail});
				                results.insert({top: list});
				                list.observe('click', activateLocation);

				                terms.hide();
				                fail.hide();
				                list.hide();

				                results.select('a')[0].observe('click', function (e) {
					                  e.stop();
					                  $('sidebar').className = "search";
				                });

				                results.setStyle({ height: remainingHeight() });
			              },

  		              // Build pagination links for resultsets greater than the max size.
  		              buildPagination: function(mapType, searchParams, maxResults, offset) {
                        searchParams += '&result=count';
		                    
  		                  // submit the search; pass results to the location list on success
				                new Ajax.Request('/api/' + mapType.model + '/search.json?' + searchParams, {
					                  method: 'get',
					                  onSuccess: function (data) {
						                    var totalResults = parseInt(data.responseText.evalJSON().count),
                                className;

						                    if (totalResults > maxResults || offset > 0) {
						                        $('terms').insert({bottom : '&nbsp;(' + totalResults + ')'});
						                        
						                        var pageCount = (totalResults / maxResults).ceil();

				                            // Construct the pagination links.
						                        var paginationLinks = "<span class='left'>Pages:&nbsp;&nbsp;</span>";

						                        for (var i = 0; i < pageCount; ++i) {
                                        ((offset / maxResults).ceil() == i) ? className = 'active' : className = 'inactive';
                                        paginationLinks += '<a href="#" title="'+ i +'" class="'+ className +'">' + (i+1) + '</a>';
                                    }
                                    
						                        $('pagination').update(paginationLinks);
						                        
						                            $$('#pagination a').each(function(el) {
						                                el.observe('click', switchPages);
						                            });

						                    }
						                    
					                  },
					                  onFailure: function () {
					                      alert('/api/' + mapType.model + '/search.json?' + searchParams);
						                    alert('Ajax.Request: search failed');
					                  }
				                });
  		                  
  		                  function switchPages(e) {
                            e.preventDefault();
                            var toPage = e.target.readAttribute('title');

                            // Set all links back to red.
                            $$('#pagination a').each(function(el) {
  			                        el.setStyle({color: '#c11530'});
  			                    });

                            search.submit(SWFAddress.getValue().match(/search:([\w\s\=\*\.\$,&-']+)\//)[1], toPage * maxResults);
                        }
  		                  
  		              },
                    
			              // remove all overlays and clear out the location list
			              clear: function (showLoader) {
				                if (showLoader) {
					                  $('map-results').addClassName('loading');
				                }

				                terms.hide();
				                fail.hide();

				                list.update();
				                currentLocation = null;
				                locations = [];

				                // get the more info window out of the way
				                if (moreInfo.isOpen()) {
					                  addressing.setURL({info: null});
				                }
			              }, 

			              // update the results list with the JSON data returned from the server
			              update: function (data, offset) {	
				                var locationData, name, latlon, ref;
				                
				                if (data) {
					                  locationData = mapType.locationData.split('.');
					                  latlon = locationData.last().split('/');
					                  locationData = locationData.slice(0, -1);

					                  name = mapType.name.split('.');

					                  data.each( function (loc) {
						                    // normalize the location of lat/lon
						                    ref = loc;
						                    locationData.each( function (assoc) {
							                      ref = ref ? ref[assoc] : null;
						                    });

						                    // safeguard against lat/lng associations that didn't have values set; they will be weeded out in the loop below
						                    if (ref) {
							                      loc.lat = ref[latlon[0]];
							                      loc.lon = ref[latlon[1]];
						                    }

						                    // normalize location name
						                    if (!loc.name) {
							                      ref = loc;
							                      name.each( function (assoc) {
								                        ref = ref ? ref[assoc] : null;
							                      });
							                      if (ref) {
								                        loc.name = ref;
							                      }
						                    }
					                  });
					                  
					                  // weed out all invalid data
					                  locations = data.select( function(loc) {
						                    return (loc.lat != undefined) && (loc.lon != undefined) && (loc.name != undefined);
					                  });
				                }

				                // clear spinner and set height of scrollable result container
				                terms.show();
				                $('map-results').removeClassName('loading');
				                results.setStyle({ height: remainingHeight() });

				                // if there are any locations, build result list and tell map to draw new markers
				                if (locations.length) {
					                      // populate locations across map and result list
					                  buildResults(offset);
					                  map.update(locations, offset);

					                  // attach a class name to fix potential horizontal scrolling issues
					                  // because IE6/7 are being stupid about overflow:auto and horizontal scrollbars
					                  if (Prototype.Browser.IE && results.getHeight() < (list.getHeight() + instructions.getHeight())) {
						                    list.addClassName('resize');
					                  }
				                } else {
					                  // no results were returned
					                  list.hide();
					                  fail.show();
				                }

				                results.show();
			              },

			              // toggle a result list item on or off, and scroll it into view
			              goToLocation: function (index) {
				                var loc = locations[index],
					              li = $('' + loc.id);    // grab li container based on id saved in location
				                
				                // close "more info" window, if it's open
				                if (moreInfo.isOpen()) {
					                  addressing.setURL({info: null});
				                }

				                // scroll this list element into view...
				                // check if it's below current window...
				                if (results.scrollTop + results.clientHeight < li.offsetTop + li.getHeight()) {
					                  results.scrollTop = (li.offsetTop + li.getHeight()) - results.clientHeight;

					                  // or check if it's above
 				                } else if (li.offsetTop < results.scrollTop) {
					                  results.scrollTop = li.offsetTop;
				                }

				                // toggle the on state
 				                if (currentLocation && currentLocation.id != loc.id) {
					                  $('' + currentLocation.id).removeClassName('on');
					                  if (loc == currentLocation) {
						                    currentLocation = undefined;
						                    return;
					                  }
				                }
				                li.addClassName('on');
				                currentLocation = loc;
			              },

			              unload: function () {
				                $('map-results').update();
				                results = null;
			              }
		            };
	      }(); // end locationList



	      // handles the search functionality and interfaces with form controls
	      var search = function () {
		        var controls, terms, toggle;

		        // serialize form values
		        var buildSearchURL = function () {
			          var searchString = mapType.fields.slice(0).map( function (field) {

				            var tagName = controls[field].tagName ? controls[field].tagName.toLowerCase() : controls[field][0].tagName.toLowerCase(),
					          type = controls[field].type ? controls[field].type : controls[field][0].type;
				            
				            switch (tagName) {
				                  case 'input':
					              switch (type) {
					                case 'text':
						                if (controls[field].value) {
							                  return field + '=' + controls[field].value;
						                }
						                break;
					                case 'checkbox':
						                // is this the only control by that name?
						                    if (!controls[field].length) {
							                      if (controls[field].checked) {
								                        return field + "=" + controls[field].value;
							                      }

						                        // if field name is not unique, iterate over the node list
						                    } else {
							                      var list = [];
							                      for (var i = 0, len = controls[field].length; i < len; i++) {
								                        if (controls[field][i].checked) {
									                          list.push(controls[field][i].value);
								                        }
							                      }
							                      if (list) return field + "=" + list.join(",");
						                    }
						                break;
					                case 'radio':
						                // iterate over the node list
						                for (var i = 0, len = controls[field].length; i < len; i++) {
							                  if (controls[field][i].checked) {
								                    return field + "=" + controls[field][i].value;
							                  }
						                }
						                break;
					              default:
						                return null;
					              }
					              break;
				              case 'select':
					              if (controls[field].value && controls[field].value != '') {
						                return field + '=' + controls[field].selectedIndex;
					              }
					              break;
				            default:
					              return null;
				            }
			          }).without(null).join('&');

			          if (!searchString.length) {
				            searchString = '*';
			          }
			          
			          addressing.setURL({search: searchString, info: null});
		        };

		        return {
			          init: function () {
				            toggle = $('sidebar');
				            controls = $('controls');
				            terms = $('terms');

				            // handlers
				            controls.observe('submit', function(e) {
					              e.stop();
					              buildSearchURL();
					              addressing.setURL({info: null});
				            });
				            toggle.select('ul')[0].observe('click', function (e) {
					              e.stop();
					              toggle.className = e.target.hash.slice(1);
				            });

				            // turn on the form
				            $('submit').disabled = false;
			          },

			          // submit search to server and pass results to location list
			          submit: function (string, offset) {
				            var searchParams = '', // parameter string sent to server
					          searchList = [],   // input values collected from search form
					          termString = [],   // search terms that are shown to user
					          multiSelect = [],  // used to join checkbox option sets with different names
					          multiGroups = {},   // used to concat multi-select checkbox options with the same name
					          associations = mapType.associations, // array of associations to include in search
					          special_offers = false,
					          max_results = 100;

				            // grab values and gather search terms from all search fields
				            searchList = mapType.fields.inject([], function (array, field) {
					              var name = field,
						            tagName = controls[field].tagName ? controls[field].tagName.toLowerCase() : controls[field][0].tagName.toLowerCase(),
						            value = controls[field].value,
						            type = controls[field].type ? controls[field].type : controls[field][0].type,
						            selectedIndex = controls[field].selectedIndex;
					              
					              switch (tagName) {
					                case 'input':
						                switch (type) {
						                  case 'text':
							                  if (value) {
								                    // check for a rel attribute on the field; this indicates that the db query needs to be
								                    // specially formatted based on the value of the rel attribute
								                    if (controls[field].getAttribute('rel')) {
									                      value = controls[field].getAttribute('rel').replace(/\$/g, value);
									                      array.push( value );
									                      // use the label for this control as the search term, if it's not an overlabel
									                      var label = controls.select('label[for='+controls[field].id+']')[0];
									                      if (label.className != 'overlabel-apply') {
										                        termString.push( '<em>"' + controls[field].value + ' ' + label.firstChild.nodeValue + '"</em>' );
									                      } else {
										                        termString.push( '<em>"' + controls[field].value + '"</em>');
									                      }
								                    } else {
									                      array.push( escape(name) + ':' + escape(value) );
									                      termString.push( '<em>"' + value + '"</em>');
								                    }
							                  }
							                  break;
						                  case 'checkbox':
							                  if (!controls[field].length) {
								                    if (controls[field].checked) {
									                      if (controls[field].id == 'special_offers') {
										                        special_offers = true;
									                      } else {
										                        multiSelect.push( escape(name) + '!' + controls[field].value );
									                      }
									                      // use the label for this control as the search term
									                      termString.push( '<em>"' + controls.select('label.'+controls[field].id+'')[0].firstChild.nodeValue + '"</em>' );
								                    }
							                  } else {
								                    var el;
								                    for (var i = 0, len = controls[field].length; i < len; i++) {
									                      el = controls[field][i];
									                      if (el.checked) {
										                        if (el.id == 'special_offers') {
											                          special_offers = true;
										                        } else {
											                          if (!multiGroups[field]) multiGroups[field] = [];
											                          multiGroups[field].push( escape(name) + '!' + controls[field][i].value );
										                        }
										                        // use the label for this control as the search term
										                        termString.push( '<em>"' + controls.select('label[for='+el.id+']')[0].firstChild.nodeValue + '"</em>');
									                      }
								                    }
							                  }
							                  break;
						                  case 'radio':
							                  // iterate over the node list
							                  var el;
							                  for (var i = 0, len = controls[field].length; i < len; i++) {
								                    el = controls[field][i];
								                    if (el.checked) {
									                      array.push( escape(name) + '!' + el.value );
									                      // use the label for this control as the search term
									                      termString.push( '<em>"' + controls.select('label[for='+el.id+']')[0].firstChild.nodeValue + '"</em>');
								                    }
							                  }
							                  break;
						                    }
						                break;
					                case 'select':
						                if (value && value != '') {
							                  // check for a dollar sign in the field name; this indicates that the data can be pulled
							                  // from multiple locations; we need to grab and concatenate the full name/association 
							                  // from the selected value's title field
							                  if (field.indexOf('$') != -1) {
								                    name = field.replace('\$', controls[field][selectedIndex].getAttribute('rel'));
							                  }
							                  array.push( escape(name) + '!' + escape(value) );
							                  termString.push( '<em>"' + controls[field][selectedIndex].text + '"</em>');
						                }
						                break;
					              }
					              
					              return array;
				            });

				            // if there are any single-select checkbox options, join those up based on map type
				            if (multiSelect.length) {
					              if (mapType.model == 'Restaurant') {
						                searchList.push( '(' + multiSelect.join('@') + ')' );
					              } else {
						                searchList.push( '(' + multiSelect.join('|') + ')' );
					              }
				            }
				            
				            // if there are any single-select checkbox options, join those up based on map type
				            if (multiSelect.length) {
					              if (mapType.model == 'Food_Cart') {
						                searchList.push( '(' + multiSelect.join('@') + ')' );
					              } else {
						                searchList.push( '(' + multiSelect.join('|') + ')' );
					              }
				            }

				            // join any multi-select option groups
				            multiGroups = $H(multiGroups);
				            multiGroups.each( function (pair) {
					              searchList.push( 
						                '(' + pair[1].join('|') + ')'
					              );
				            });

				            if (special_offers) {
					              searchList.push( 'special_offers!1' );
				            }

				            // create query
				            if (searchList.length) {
				                searchParams = 'logic=(' + searchList.join('@') + ')';
				            } else {
				                searchParams = '';
				            }
				            
				            if (associations.length) {
				                if (searchParams != '') { searchParams += '&'; }
					              searchParams += 'include=' + associations.join(',');
				            }
				            
				            //searchParams += '&order=asc&order_by=id';

				            // display the search terms
				            if (mapType.model == "Food_Cart") {
				        	      terms.update( '<strong>Results for:</strong> ' + (termString.join(' and ') || '<em>all ' + 'Food Cart' + 's</em>') );
				            }
				            else {
				        	      terms.update( '<strong>Results for:</strong> ' + (termString.join(' and ') || '<em>all ' + mapType.model + 's</em>') );
				            }

				            // clear map and location list
				            locationList.clear(true);
				            map.clear();
				            
				            limitedParams = searchParams + '&limit=' + max_results + '&offset=' + offset;

				            // submit the search; pass results to the location list on success
				            new Ajax.Request('/api/' + mapType.model + '/search.json?' + limitedParams, {
					              method: 'get',
					              onSuccess: function (data) {
						                var resultList = data.responseText.evalJSON();
						                locationList.update(resultList, offset);

                            // Build pagination links if this is the first view.
						                if (resultList.size() == max_results || offset > 0) {
						                        locationList.buildPagination(mapType, searchParams, max_results, offset);
						                } else {
						                    $('pagination').update('');
						                }
						                
					              },
					              onFailure: function () {
						                alert('Ajax.Request: search failed');
						                locationList.update();
					              }
				            });
							      
			          },

			          checkFormState: function (params) {
				            var changed = false,
					          states = {};

				            // see if we're doing an all-inclusive search (or no search)
				            if (params == '*' || params == 'none') {

					              mapType.fields.each( function(field) {

						                if (typeof mapType.setFormElements[field] === "function") {
							                  mapType.setFormElements[field]('*');

						                } else {
							                  var tagName = controls[field].tagName ? controls[field].tagName.toLowerCase() : controls[field][0].tagName.toLowerCase(),
								                type = controls[field].type ? controls[field].type : controls[field][0].type;

							                  switch (tagName) {
							                    case 'input':
								                    switch (type) {
								                      case 'text':
									                      if (controls[field].value !== '' ) {
										                        controls[field].value = '';
										                        changed = true;
									                      }
									                      break;
								                      case 'checkbox':
									                      if (!controls[field].length) {
										                        if (controls[field].checked) {
											                          controls[field].checked = false;
											                          changed = true;
										                        }

									                      } else {
										                        for (var i = 0, len = controls[field].length; i < len; i++) {
											                          if (controls[field][i].checked) {
												                            controls[field][i].checked = false;
												                            changed = true;
											                          }
										                        }
									                      }
									                      break;
								                      case 'radio':
									                      if (controls[field][0].checked != true) {
										                        controls[field][0].checked = true;
										                        changed = true;
									                      }
									                      break;
								                    }
								                    break;
							                    case 'select':
								                    if (controls[field].selectedIndex) {
									                      controls[field].selectedIndex = 0;
									                      changed = true;
								                    }
								                    break;
							                  }
						                }
					              });

				            } else {

					              // break up each param into a key/value pair of field states
					              params.split('&').each( function (param) {
						                var pair = param.split('=');
						                states[unescape(pair[0])] = unescape(pair[1]);
					              });

					              // loop over each form field and look for matching field state names
					              mapType.fields.each( function(field) {

						                if (typeof mapType.setFormElements[field] === "function") {
							                  mapType.setFormElements[field](states[field]);

						                } else {
							                  var tagName = controls[field].tagName ? controls[field].tagName.toLowerCase() : controls[field][0].tagName.toLowerCase(),
								                type = controls[field].type ? controls[field].type : controls[field][0].type;

							                  // check to see if fields that should be set are
							                  if (states[field] !== undefined) {

								                    switch (tagName) {
								                      case 'input':
									                      switch (type) {
									                        case 'text':
										                        if (controls[field].value !== states[field]) {
											                          controls[field].value = states[field];
											                          changed = true;
										                        }
										                        break;
									                        case 'checkbox':
										                        if (!controls[field].length) {
											                          if (!controls[field].checked) {
												                            controls[field].checked = true;
												                            changed = true;
											                          }
										                        } else {
											                          var list = states[field].split(','),
												                        el;
											                          // loop through each element, and try to find it in the array
											                          for (var i = 0, len = controls[field].length; i < len; i++) {
												                            el = controls[field][i];
												                            list.each( function (value) {
													                              if (el.value == value && !el.checked) {
														                                el.checked = true;
														                                changed = true;
													                              }
												                            });
											                          }
										                        }
										                        break;
									                        case 'radio':
										                        var el;
										                        for (var i = 0, len = controls[field].length; i < len; i++) {
											                          el = controls[field][i];
											                          if (el.value == states[field] && !el.checked) {
												                            el.checked = true;
												                            changed = true;
											                              }
										                        }
										                        break;
									                      }
									                      break;
								                      case 'select':
									                          if (controls[field].selectedIndex != parseInt(states[field])) {
										                            controls[field].selectedIndex = states[field];
										                            changed = true;
									                          }
									                      break;
								                    }

							                      // otherwise, check to see if fields that shouldn't be set aren't
							                  } else {

								                    switch (tagName) {
								                      case 'input':
									                      switch (type) {
									                        case 'text':
										                        if (controls[field].value !== states[field] ) {
											                          controls[field].value = '';
											                          changed = true;
										                        }
										                        break;
									                        case 'checkbox':
										                        if (!controls[field].length) {
											                          if (controls[field].checked) {
												                            controls[field].checked = false;
												                            changed = true;
											                          }
										                        } else {
											                          var list = states[field].split(','),
												                        el;
											                          // if element is checked but is not in the array, uncheck it
											                          for (var i = 0, len = controls[field].length; i < len; i++) {
												                            el = controls[field][i];
												                            if (list.indexOf(el.value) == -1 && el.checked) {
													                              el.checked = false;
													                              changed = true;
												                            }
											                          }
										                        }

										                        if (controls[field].checked) {
											                          controls[field].checked = false;
											                          changed = true;
										                        }
										                        break;
									                        case 'radio':
										                        if (!controls[field][0].checked) {
											                          controls[field][0].checked = true;
											                          changed = true;
										                        }
										                        break;
									                      }
									                      break;
								                          case 'select':
									                      if (controls[field].selectedIndex) {
										                        controls[field].selectedIndex = 0;
										                        changed = true;
									                      }
									                      break;
								                    }

							                  }
						                }
					              });
				            }

				            resetOverLabels(controls);
				            if (params != 'none') {
					              toggle.className = 'results';
				            }
			          },

			          clear: function () {
				            controls.select('*[name]').each( function(field) {
					              var tagname = field.tagName.toLowerCase(),
						            type = field.getAttribute('type');

					              if (tagname == 'select') {
						                field.selectedIndex = 0;
					              } else if (tagname == 'input' && (type == 'radio' || type == 'checkbox')) {
						                field.checked = false;
					              } else if (tagname == 'input' && type == 'text') {
						                field.value = '';
					              }
				            });

				            resetOverLabels(controls);
				            terms.update();
			          },
			          
			          unload: function () {
				            controls = terms = null;
			          }
		        };
	      }(); // end search



	      // controls the "more info" window display for the current location, grabbing info and comments from server as needed
	      var moreInfo = function () {
		        var overlay, wrapper, 
			      content, options, print, ad, actions,
			      error,
			      open = false;

		        // email the current info window url to a friend
		        var shareThis = function (e) {
			          var error = false,
				        fields = $A(e.target.getElementsByClassName('req'));

			          e.stop();

			          // check for required fields (not currently checking validity of fields)
			          if (fields.length) {
				            fields.each( function (field) {
					              if (field.value === '') {
						                $('email-response').hide();
						                field.addClassName('error');
						                error = true;
					              } else {
						                field.removeClassName('error');
					              }
				            });
			          }

			          if (error) {
				            e.target.addClassName('error');
			          } else {
				            // send email request
				            new Ajax.Request('/api/pokoencephalon', {
					              method: 'post',
					              parameters: Form.serialize(e.target) + '&email[url]=' + escape('<' + window.location.toString() + '>') + '&' + tok.k() + '=' + tok.v(),
					              onLoading: function () { $('share-spinner').setStyle({visibility: 'visible'}); },
					              onSuccess: function () {
						                // clear the form
						                e.target.reset();
						                $('share-spinner').setStyle({visibility: 'hidden'});
						                e.target.removeClassName('error');
						                resetOverLabels(e.target);
						                $('email-response').show();
						                $('email-response').update('Your message has been sent. Thank you.');
						                tok.r();
					              },
					              onFailure: function () {
						                $('share-spinner').setStyle({visibility: 'hidden'});
						                $('email-response').addClassName('error').insert('An error occurred while attempting to send your message');
					              }
				            });
			          }
		        };

		        // post a new comment back to the server, which will reply with JSON data containing the new comment
		        var postDiscussComment = function (e) {
			          var params = Form.serialize(e.target) + '&' + tok.k() + '=' + tok.v();

			          e.stop();

			          // post the comment
			          new Ajax.Request('/api/comment.json', {
				            method: 'post',
				            parameters: params,
				            onSuccess: function (data) {
					              e.target.reset();
					              resetOverLabels(e.target);
					              tok.r(); 
					              updateDiscussComments(data.responseText.evalJSON());
				            },
				            onFailure: function () {
					              alert('Ajax.Request: comment post failed');
				            }
			          });
		        };

		        // update the currently displayed comment list with a JSON comment object called from postDiscussComment function
		        var updateDiscussComments = function (comment) {
			          var comments = $('comments'),
				        list = $('comment-list');

			          if (!list) {
				            comments.select('p')[0].remove();
				            list = new Element('dl', { id: 'comment-list' });
				            $('comments').insert({ bottom: list });
			          }

			          list.insert({ top: '<dt><em>' + comment.name + '</em> wrote:</dt>' + '<dd>' + comment.text + '</dd>' });
		        };

		        return {
			          init: function () {
				            var close = new Element('a', { className: 'close', href: '#info' }).insert('Close');

				            // create the more info window skeleton
				            overlay = new Element('div', { id: 'more-info-overlay', style: 'display:none;' });
				                wrapper = new Element('div', { id: 'more-info-wrapper', className: 'description' });
				            options = new Element('ul', { id: 'more-info-options' }).insert(
					              new Element('li', { className: 'description' }).insert(
						                new Element('a', { href: '#description' }).insert('Description')))
				                .insert(
					                  new Element('li', { className: 'discuss' }).insert(
						                    new Element('a', { href: '#discuss' }).insert('Discuss')))
				                .insert(
					                  new Element('li', { className: 'email' }).insert(
						                    new Element('a', { href: '#email' }).insert('Email To A Friend')))
    		                .insert(
    			                  new Element('li', { className: 'sharethis' }).insert(
    				                    new Element('a', { href: '#sharethis' }).insert('Share This')));

				            
				            // they'd rather the user print the pdf on the trails map
				            if (mapType.model != "Trail") {
					              print = new Element('a', { href: '#', id: 'more-info-print' }).insert('Print');
				            }

				            wrapper.insert(options);
				            wrapper.insert(close);
				            wrapper.insert(print);
				            overlay.insert(wrapper);
				            $('map-wrapper').insert({ top: overlay });

				            // close button
				            close.observe('click', function (e) {
					              addressing.setURL({info: null});
					              e.stop();
				            });

				            // option links
				            options.observe('click', function (e) {
					              e.stop();
					              if (e.target.tagName.toLowerCase() == 'a') {
						                wrapper.className = e.target.hash.slice('1');
					              }
				            });

				            // print link
				            if (print) {
					              print.observe('click', function (e) {
						                e.stop();
						                moreInfo.print();
					              });
				            }

				            // preload the background png
				            (new Image()).src = '/img/more-info/bg-wrapper.png';
			          },

			          isOpen: function () {
				            return open;
			          },

			          show: function (id) {
				            if (!open) {
					              new Ajax.Request('/snippets/' + mapType.model.toLowerCase().replace(/_/, '-') + '/more-info/' + id + '/', {
						                method: 'get',
						                onSuccess: function (data) {
                                var meta;
                                
							                  wrapper.insert(data.responseText);
							                  initOverLabels();
							                  external_links();

							                  content = $('more-info-content');
							                  actions = $('event-actions');
							                  ad = $('more-info-ad');

							                  // capture form submits
							                  var discuss = wrapper.select('#more-info-discuss form');
							                  if (discuss && discuss.length) discuss[0].observe('submit', postDiscussComment);
							                  wrapper.select('#more-info-email form')[0].observe('submit', shareThis);

                                // build title for meta-description tag on redirect for share popovers, then construct the share links.
                                meta = ($$("#title h3")[0].innerHTML + " - " + $$('#descontent p')[0].innerHTML).unescapeHTML().replace(/&/g, 'and').replace(/ /g, '_').truncate(150, '...');
						                    build_share_links("http://"+window.location.hostname+"/extensions/popover/?type="+$("popover-type").innerHTML+"&id=" + $("popover-id").innerHTML, ($$("#title h3")[0].innerHTML.replace(/&amp;/g, 'and')), meta);
						                },
						                onFailure: function () {
							                  error = new Element('p', { className: 'error' }).insert(
								                    'There was a problem getting your request from the server.'
							                  );
							                  wrapper.insert(error);
						                }
					              });

					              overlay.show();
					              open = true;
				            }
			          },
			          
			          hide: function () {
				            if (moreInfo.isOpen()) {
					              overlay.hide();

					              // reset pre-display defaults
					              if (content) content.remove();
					              if (actions) actions.remove();
					              if (ad) ad.remove();
					              if (error) error.remove();
					              content = ad = actions = error = undefined;
					              
					              wrapper.className = 'description';
					              open = false;
				            }
			          },

			          print: function () {
				            $(document.body).addClassName('print-info');
				            window.print();
			          },
                
			          unload: function () {}
		        };
	      }();

	      // swfaddress utilities / event dispatching
	      var addressing = function () {
		        var currentSearch, currentInfo;

		        return {
			          init: function () {
				            currentSearch = 'none',
				            SWFAddress.addEventListener(SWFAddressEvent.CHANGE, addressing.dispatch);
			          },

			          setURL: function (params) {
				            var state = unescape(SWFAddress.getValue()),
					              changed = false;

				            params = $H(params);
				            params.each( function (pair) {
					              var value, test,
						            search = new RegExp(pair.key + ':([^/]+)/');

					              value = pair.value ? pair.key + ':' + pair.value + '/' : '';
					              test = state.match(search);

					              if ( test && test[0] ) {
						                if (test[0] != value) {
							                  state = state.replace(test[0], value);
							                  changed = true;
						                }
					              } else {
						                state += value;
						                changed = true;
					              }
				            });

				            if (changed) {
					              SWFAddress.setValue(state);
				            }
			          },

			          dispatch: function () {
				            var states = unescape(SWFAddress.getValue()),
					          searchString, info;

				            // check for search and filter settings
				            searchString = states.match(/search:([\w\s\=\*\.\$,&-']+)\//);
				            if (searchString && searchString[1]) {
					              searchString = searchString[1];
					              search.checkFormState(searchString);
				            } else {
					              searchString = 'none';
				            }

				            if (currentSearch != searchString) {
					              currentSearch = searchString;

					              if (searchString == 'none') {
						                locationList.clear();
						                search.clear();
						                moreInfo.hide();
						                map.reset();
					              } else {
						                search.submit(searchString, 0);
					              }
				            }

				            // check for info box
				            info = states.match(/info:([\d]+)\//);
				            if (info && info[1]) {
					              // make sure there's something else set in the URL first
					              if (!addressing.checkURL('search')) {
						                addressing.setURL({search: search});
					              } else {
						                info = parseInt(info[1]);
						                if (currentInfo != info) {
							                  currentInfo = info;
							                  moreInfo.show(info);
						                }
					              }
				            } else {
					              if (currentInfo != undefined) {
						                currentInfo = undefined;
						                moreInfo.hide();
					              }
				            }
			          },

			          checkURL: function (param) {
				            return unescape(SWFAddress.getValue()).match(param);
			          }
		        };
	      }();

	      // finally, load the object interfaces on domready
	      document.observe('dom:loaded', function () {
		        addressing.init();
		        moreInfo.init();
		        search.init();
		        locationList.init();
		        map.init();
	      });

	      Element.observe(window, 'unload', function () {
		        locationList.unload();
		        search.unload();
		        moreInfo.unload();
		        map.unload();
	      });

	      // utility functions
  
	  // injects JSON 'data' into HTML elements within 'node' which are tagged with rel attributes that matches
	  // a key in 'data'
	  var injectData = function (markup, data) {
        var m,
            placeholderMatch = /\{([^}]+)\}/,
            source = new String(markup);

        // this has to be done recursively to accomodate arrays in the notation path
        function descend(data, notation) {
            if (notation[0]) {
                if (/\[\]$/.test(notation[0])) {
                    var list = data[notation.shift().match(/^(.*)\[\]$/)[1]],
                        results = [];
                    
                    for (var i = 0; i < list.length; i++) {
                        results.push(descend(list[i], notation.slice(0)));
                    }
                    
                    return results.join(', ');
                    
                } else {
                    return descend(data[notation.shift()], notation);
                }
            }

            // notation was empty, so we're at the end
            return data;
        }

        while ((m = placeholderMatch.exec(source)) != null) {
            var res = null,
                notation = m[1].split('.');
            
            if (notation[0]) {
                if (/\[\]$/.test(notation[0])) {
                    var list = data[notation.shift().match(/^(.*)\[\]$/)[1]],
                        results = [];
                    
                    for (var i = 0; i < list.length; i++) {
                        results.push(descend(list[i], notation.slice(0)));
                    }
                    
                    res = results.join(', ');
                    
                } else {
                    res = descend(data[notation.shift()], notation);
                }
            }

/*          for (var j = 0; j < notation.length; j++) {
                if (res != null) {
                    res = res[notation[j]];
                } else {
                    res = data[notation[j]];
                }
            }*/
                
            if (res == null) { res = ''; }
            source = source.replace(m[0],res);
        }

        return source;
    };

	      // Douglas Crawford's function to fix some memory leak issues in pre-patched IE6
	      function purge(d) {
		        var a = d.attributes, i, l, n;
		        if (a) {
			          l = a.length;
			          for (i = 0; i < l; i += 1) {
				            n = a[i].name;
				            if (typeof d[n] === 'function') {
					              d[n] = null;
				            }
			          }
		        }
		        a = d.childNodes;
		        if (a) {
			          l = a.length;
			          for (i = 0; i < l; i += 1) {
				            purge(d.childNodes[i]);
			          }
		        }
	      }

    }; // end mapSearch

/*
   There's something wrong with the positioning code for infoboxes in the Bing API; I had to
   step on this class to force the infobox to appear in the correct position when the browser
   window is smaller than the map, and has been scrolled down. Sorry.

   - Mark Mahoney, 8/20/10
*/
var ERO = {
    Classes: {
        ContainerNoBeak: "ero ero-noBeak",
        ContainerRightBeak: "ero ero-rightBeak",
        ContainerLeftBeak: "ero ero-leftBeak",
        Beak: "ero-beak",
        Shadow: "ero-shadow",
        Body: "ero-body",
        Actions: "ero-actions",
        ActionsBackground: "ero-actionsBackground",
        PreviewArea: "ero-previewArea",
        PaddingHack: "ero-paddingHack",
        ProgressAnimation: "ero-progressAnimation"
    },
    DefaultClasses: null,
    BeakDirection: {
        Right: 0,
        Left: 1
    },
    DockPosition: {
        Top: 0,
        Center: 1
    },
    m_theEro: null,
    BeakHeight: 34,
    Glitz: function(d, e, b, c) {
        var a = this;
        this.useBeak = d;
        this.useFade = e;
        this.useProgressTimer = b;
        this.isTemporary = c;
        this.copy = function() {
            return new ERO.Glitz(a.useBeak, a.useFade, a.useProgressTimer, a.isTemporary);
        };
    },
    EROEventArgs: function(c, a, b) {
        this.superclass = Msn.VE.OO.Eventable.EventArgs;
        this.superclass(c, a);
        this.Entity = b;
    },
    getInstance: function() {
        var a = Msn.VE.Geometry;
        if (!ERO.m_theEro) {
            ERO.m_theEro = new b;
            ERO.m_theEro.setBoundingArea(null);
        }
        ERO.m_theEro.addToPage();
        return ERO.m_theEro;
        function b() {
            this.superclass = Msn.VE.OO.Eventable.EventableObject;
            this.superclass();
            var c = this,
            r = null,
            f = null,
            k = null,
            h = false,
            o = 500,
            n = 0,
            B = true,
            i = new ERO.Glitz(true, true, true, false),
            C = i.copy(),
            w = 0,
            z = false,
            b = document.createElement("div");
            b.className = ERO.Classes.ContainerLeftBeak;
            if (typeof b.addEventListener != "undefined") {
                b.addEventListener("mouseover", x, false);
                b.addEventListener("mouseout", y, false);
            } else {
                b.attachEvent("onmouseover", x);
                b.attachEvent("onmouseout", y);
            }
            var s = document.createElement("div");
            s.className = ERO.Classes.Shadow;
            var j = document.createElement("div");
            j.className = ERO.Classes.Body;
            var q = document.createElement("div");
            q.className = ERO.Classes.Actions;
            var p = document.createElement("ul"),
            m = document.createElement("div");
            m.className = ERO.Classes.ActionsBackground;
            var l = document.createElement("div");
            l.className = ERO.Classes.PreviewArea;
            var t = document.createElement("div");
            t.className = ERO.Classes.Beak;
            var v = document.createElement("div");
            v.className = ERO.Classes.PaddingHack;
            b.appendChild(s);
            b.appendChild(t);
            s.appendChild(j);
            j.appendChild(m);
            m.appendChild(l);
            m.appendChild(q);
            q.appendChild(p);
            m.appendChild(v);
            var d = document.createElement("div");
            d.className = ERO.Classes.ProgressAnimation;
            var e = new Msn.VE.Animation.Movie(d, 75);
            e.addFrame('<div class = "frame1"></div>');
            e.addFrame('<div class = "frame2"></div>');
            e.addFrame('<div class = "frame3"></div>');
            e.addFrame("");
            e.addFrame("");
            e.addFrame('<div class = "frame2"></div><div class = "frame3"></div>', false);
            e.addFrame('<div class = "frame3"></div>', false);
            e.Repeat = false;
            this.destroy = function() {
                if (b) {
                    if (typeof b.removeEventListener != "undefined") {
                        b.removeEventListener("mouseover", x, false);
                        b.removeEventListener("mouseout", y, false);
                    } else {
                        b.detachEvent("onmouseover", x);
                        b.detachEvent("onmouseout", y);
                    }
                    if (j.shimElement) {
                        j.shimElement.removeNode(true);
                        j.shimElement = null;
                    }
                    b.parentNode.removeChild(b);
                    d.parentNode.removeChild(d);
                    b = null;
                    s = null;
                    j = null;
                    q = null;
                    p = null;
                    m = null;
                    l = null;
                    t = null;
                    v = null;
                }
                ERO.m_theEro = null;
                k = null;
            };
            this.getElement = function() {
                return b;
            };
            this.getBody = function() {
                return j;
            };
            this.getAnimation = function() {
                return e;
            };
            this.getDelay = function() {
                return o + n;
            };
            this.setDelay = function(a) {
                o = a || o;
            };
            this.getDelayDelta = function() {
                return n;
            };
            this.setDelayDelta = function(a, b) {
                B = b == false ? false: true;
                if (typeof a == "number") {
                    n = a;
                    if (!h && r != -1) c.hide();
                }
            };
            this.setClasses = function(b, d) {
                var a;
                if (ERO.DefaultClasses === null) {
                    ERO.DefaultClasses = {};
                    for (a in ERO.Classes) ERO.DefaultClasses[a] = ERO.Classes[a];
                }
                if (d !== false) c.setClasses(ERO.DefaultClasses, false);
                for (a in b) if (typeof ERO.Classes[a] != "undefined") ERO.Classes[a] = b[a];
                D();
            };
            this.setBeak = function(a) {
                if (a == ERO.BeakDirection.Left) g(b).removeClass(ERO.Classes.ContainerRightBeak).addClass(ERO.Classes.ContainerLeftBeak);
                else g(b).removeClass(ERO.Classes.ContainerLeftBeak).addClass(ERO.Classes.ContainerRightBeak);
            };
            this.setContent = function(c) {
                var a = document.createElement("div");
                a.className = "firstChild";
                a.innerHTML = c;
                var b = l.firstChild;
                if (b) l.replaceChild(a, b);
                else l.appendChild(a);
                a = null;
                b = null;
            };
            this.addAction = function(b) {
                var a = document.createElement("li");
                if (!b) return;
                a.innerHTML = b;
                p.appendChild(a);
                a = null;
            };
            this.clearActions = function() {
                var a = p.getElementsByTagName("li"),
                c = a.length;
                for (var b = 0; b < c; b++) p.removeChild(a[0]);
            };
            this.dockToText = function(e, b, i) {
                b = typeof b != "undefined" ? b: typeof window.event != "undefined" ? window.event: null;
                var k = g(e).getPagePosition(),
                h = Gimme.Screen.getMousePosition(b).x,
                j = new a.Point(h, k.y),
                f = new a.Point(0, parseInt(d.offsetHeight / 2, 10));
                c.dockToPoint(j, f, e, i);
            };
            this.dockToElement = function(b, e) {
                var d = g(b).getPagePosition(),
                f = new a.Rectangle(d, new a.Point(d.x + b.offsetWidth, d.y + b.offsetHeight));
                c.dockToRect(f, null, b, e);
            };
            this.dockToPoint = function(b, d, f, e) {
                c.dockToRect(new a.Rectangle(b, b), d, f, e);
            };
            this.dockToRect = function(m, q, I, C) {
                if (k === I) {
                    clearTimeout(r);
                    if (h) return;
                } else if (k !== null) {
                    clearTimeout(r);
                    A();
                }
                var v = "px";
                h = true;
                k = I;
                b.style.visibility = "hidden";
                c.setBeak(ERO.BeakDirection.Left);
                if (typeof q == "undefined" || q == null) q = {
                    x: 0,
                    y: 0
                };
                C = C || "";
                j.style.width = C;
                var g = l.offsetHeight - ERO.BeakHeight;
                d.style.top = m.getP1().y - d.offsetHeight + q.y + v;
                d.style.left = m.getP2().x - d.offsetWidth + q.x + v;
                var s = m.getP2().x,
                E = m.getP2().y - g - ERO.BeakHeight / 2 - m.getHeight() / 2,
                y = c.getSize(),
                G = y.getP2().y - y.getP1().y,
                B = y.getP2().x - y.getP1().x,
                J = new a.Rectangle(new a.Point(s, E), new a.Point(s + B, E + G)),
                D = f.getOverlap(J),
                p = D.getRange(),
                x,
                w;
                if (p & a.Overlap.Range.InXRange) w = s;
/*                if (p & a.Overlap.Range.InYRange)*/ x = E;
                if (p & a.Overlap.Range.GreaterThanX) {
                    c.setBeak(ERO.BeakDirection.Right);
                    w = s > f.getP2().x ? f.getP1().x + f.getWidth() - B: s - B - m.getWidth();
                }
                if (p & a.Overlap.Range.LessThanX) {
                    c.setBeak(ERO.BeakDirection.Left);
                    w = f.getP1().x;
                }
/*                if (p & a.Overlap.Range.GreaterThanY) {
                    x = f.getP1().y + f.getHeight() - G;
                    var H = D.getBottomYBleed();
                    g += H;
                    if (g > b.offsetHeight - ERO.BeakHeight) g = b.offsetHeight - ERO.BeakHeight - 4
                }*/
                if (p & a.Overlap.Range.LessThanY) {
                    x = f.getP1().y;
                    var H = D.getTopYBleed();
                    g -= H;
                    if (g < 0) g = 0;
                }
                b.style.top = x + v;
                b.style.left = w + v;
                t.style.top = g + "px";
                c.executeEvent("beforeshow", c, new ERO.EROEventArgs("beforeshow", b, k));
                if (!i.useBeak) b.className = ERO.Classes.ContainerNoBeak;
                z = false;
                if (i.useProgressTimer) {
                    e.start();
                    if (!i.useFade) {
                        setTimeout(u, o + n);
                        return;
                    }
                }
                if (i.useFade) setTimeout(F, o + n);
                else u();
            };
            this.showImmediate = function() {
                z = h = true;
                e.end();
                u();
            };
            this.hide = function(a) {
                h = false;
                if (a === true) A();
                else {
                    clearTimeout(r);
                    r = setTimeout(A, o + n);
                }
            };
            this.setGlitz = function(c, d, a, b) {
                if (c != null) i.useBeak = c;
                if (d != null) i.useFade = d;
                if (a != null) i.useProgressTimer = a;
                if (b === true) i.isTemporary = b;
                else C = i.copy();
            };
            this.setBoundingArea = function(e, g) {
                if (e === null) {
                    var b = Gimme.Screen.getScrollPosition(),
                    c = Gimme.Screen.getViewportSize(),
                    d = new a.Rectangle(new a.Point(0, 0), new a.Point(c.width, c.height));
                    d.move(new a.Point(b.x, b.y));
                    f = d;
                } else f = new a.Rectangle(e, g);
            };
            this.getBoundingArea = function() {
                return f;
            };
            this.isInUse = function() {
                return h;
            };
            this.isVisible = function() {
                return b.style.visibility == "visible";
            };
            this.addToPage = function() {
                b.style.visibility = "hidden";
                d.style.visibility = "hidden";
                document.body.appendChild(b);
                document.body.appendChild(d);
            };
            this.getSize = function() {
                var c = b.offsetLeft,
                d = b.offsetTop,
                f = c + b.offsetWidth,
                g = d + b.offsetHeight,
                e = new a.Rectangle(new a.Point(c, d), new a.Point(f, g));
                return e;
            };
            function E(b, a) {
                if (b == a) return false;
                while (a && a != b) a = a.parentNode;
                return a == b;
            }
            function x() {
                h = true;
            }
            function y(a) {
                var d = a.relatedTarget || a.toElement || a.srcElement;
                if (!E(b, d)) c.hide();
            }
            function u() {
                if (b && h) {
                    if (b.style.visibility != "visible") b.style.visibility = "visible";
                    if (typeof b.style.opacity != "undefined") b.style.opacity = 1;
                    c.executeEvent("aftershow", c, new ERO.EROEventArgs("aftershow", b, k));
                    i = C.copy();
                }
            }
            function A() {
                if (!h && b) {
                    c.executeEvent("beforehide", c, new ERO.EROEventArgs("beforehide", b, k));
                    b.style.visibility = "hidden";
                    e.hide();
                    if (!Msn.VE.API) {
                        d.style.left = b.style.left = "0";
                        d.style.top = b.style.top = "0";
                    }
                    k = null;
                    c.executeEvent("afterhide", c, new ERO.EROEventArgs("afterhide", b, k));
                }
                if (B) n = 0;
            }
            function F() {
                if (z || !h || !b) return;
                if (b.style && typeof b.style.filter != "undefined") {
                    b.style.filter = "progid:DXImageTransform.Microsoft.Fade(duration=.5)";
                    b.filters[0].Apply();
                    b.style.visibility = "visible";
                    b.style.display = "block";
                    b.filters[0].Play();
                    var c = setInterval(function() {
                        if (b.filters[0].status == 0) {
                            clearInterval(c);
                            u();
                        }
                    },
                    10);
                } else {
                    b.style.visibility = "visible";
                    if (w === 0) a();
                }
                function a() {
                    if (h && ++w <= 10) {
                        var c = w * .09999999;
                        b.style.opacity = c;
                        setTimeout(a, 50);
                    } else {
                        u();
                        w = 0;
                    }
                }
            }
            function D() {
                b.className = ERO.Classes.Container;
                s.className = ERO.Classes.Shadow;
                j.className = ERO.Classes.Body;
                t.className = ERO.Classes.Beak;
                q.className = ERO.Classes.Actions;
                m.className = ERO.Classes.ActionsBackground;
                l.className = ERO.Classes.PreviewArea;
                v.className = ERO.Classes.PaddingHack;
                d.className = ERO.Classes.ProgressAnimation;
            }
        }
    }
};
