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',
	  associations: [],
	  fields: ['name', 'nwregion', 'distance', 'outdoors', 'wine_country', 'urban', 'small_town', 'mountain', 'coast'],
	  name: 'name',
	  locationData: 'location.lat/lng',
	  iconBase: '/img/maps/pins/',
	  infoWindow: {
		    markup: '<h3 class="find-a-getaway">{name}</h3>' +
				    '<div><em>{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', 'multiple_cuisines'],
	  fields: ['business_listing.name', 'business_listing.neighborhood$', 'multiple_cuisines.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>{multiple_cuisines[].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', 'multiple_cuisines'],
	  fields: ['business_listing.name', 'business_listing.neighborhood$', 'multiple_cuisines.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>{multiple_cuisines[].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', 'billiards', 'brewpub', 'dancing', 'distillery', 'dives', 'happy_hour',
			       'karaoke', 'late_night', 'live_entertainment', 'outdoor_patio',
			       'recommended_beer_selection', 'recommended_menu', 'romantic', 'specialty_cocktails', 'sports_bar', 'wine_bar'],
		
	  name: 'business_listing.name',
	  locationData: 'business_listing.address.lat/lng',
	  iconBase: '/img/maps/pins/',
	  infoWindow: {
		    markup: '<h3 class="find-a-bar">{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$',
			       'womens_apparel', 'mens_apparel', 'kids_clothes_toys', 'shoes', 'bath_beauty', 'gifts_jewelry',
			       'home_garden', 'gourmet_specialty_foods', 'antiques', 'books_paper_goods', 'pet_accessories'],
	  name: 'business_listing.name',
	  locationData: 'business_listing.address.lat/lng',
	  iconBase: '/img/maps/pins/',
	  infoWindow: {
		    markup: '<h3 class="find-a-shop">{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 = 45.515300, startLon = -122.679480, startZoom = 13, // default lat/lon and zoom (Portland City Hall)
			      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) {
                    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) {
                    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, fields,
			      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> <span class="' + loc.editors_pick +'"></span></li>'
				        );
			      });
		    };
		    
		    // add icon for editors pick selections
		    /*
var editorsIcon = function () {
		    	if (locations.fields == 'editors_pick') {
		    	
		    	}
		    };
*/
		    
		    // 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;
						            });

                        // food carts are a special case with an optional lat/lon field unrelated to the business listing
                        if (mapType.model == "Food_Cart" && (loc.latitude != 0 && loc.longitude != 0)) {
							              loc.lat = loc.latitude;
							              loc.lon = loc.longitude;
                        } else 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[for='+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
				        terms.update( '<strong>Results for:</strong> ' + (termString.join(' and ') || '<em>all ' + mapType.model.replace(/_/g, ' ') + 's</em>') );

				        // clear map and location list
				        locationList.clear(true);
				        map.clear();
				        
				        var limitedParams = searchParams + '&limit=' + max_results + '&offset=' + offset;
                console.log(limitedParams);
                
				        // 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 () {
						            console.log('Ajax.Request: search failed');
                        alert('The server generated an error during your search. Please try again.');
						            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;
            }
        }
    }
};
