﻿var me = this;
var map = null;
var mapControl = null;
var mapTypeControl = null;
var youAreHereMarker = null;
var markers = new Array();
var selectedMarkerID = null;
var selectedMarker = null;
var selectedMarkerOriginal = null;
var DEFAULT_MAP_ZOOM_LEVEL = 4;
var MAX_MAP_ZOOM_LEVEL = 13;
var MIN_MAP_ZOOM_LEVEL = 4;
//var mapMoveEventHandler = null;

// Create a base icon for all of our markers that specifies the
// shadow, icon dimensions, etc.
var baseIcon = new GIcon(G_DEFAULT_ICON);
baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png";
baseIcon.iconSize = new GSize(12, 20);
baseIcon.shadowSize = new GSize(0, 0);
baseIcon.iconAnchor = new GPoint(9, 34);
baseIcon.infoWindowAnchor = new GPoint(9, 2);

function Set_Cookie(name, value, expires, path, domain, secure) {
    // set time, it's in milliseconds
    var today = new Date();
    today.setTime(today.getTime());

    /*
    if the expires variable is set, make the correct
    expires time, the current script below will set
    it for x number of days, to make it for hours,
    delete * 24, for minutes, delete * 60 * 24
    */
    if (expires) {
        expires = expires * 1000 * 60 * 60 * 24;
    }
    var expires_date = new Date(today.getTime() + (expires));

    document.cookie = name + "=" + escape(value) +
((expires) ? ";expires=" + expires_date.toGMTString() : "") +
((path) ? ";path=" + path : "") +
((domain) ? ";domain=" + domain : "") +
((secure) ? ";secure" : "");
}

function Get_Cookie(check_name) {
    // first we'll split this cookie up into name/value pairs
    // note: document.cookie only returns name=value, not the other components
    var a_all_cookies = document.cookie.split(';');
    var a_temp_cookie = '';
    var cookie_name = '';
    var cookie_value = '';
    var b_cookie_found = false; // set boolean t/f default f

    for (i = 0; i < a_all_cookies.length; i++) {
        // now we'll split apart each name=value pair
        a_temp_cookie = a_all_cookies[i].split('=');

        // and trim left/right whitespace while we're at it
        cookie_name = a_temp_cookie[0].replace(/^\s+|\s+$/g, '');

        // if the extracted name matches passed check_name
        if (cookie_name == check_name) {
            b_cookie_found = true;
            // we need to handle case where cookie has no value but exists (no = sign, that is):
            if (a_temp_cookie.length > 1) {
                cookie_value = unescape(a_temp_cookie[1].replace(/^\s+|\s+$/g, ''));
            }
            // note that in cases where cookie is initialized but no value, null is returned
            return cookie_value;
            break;
        }
        a_temp_cookie = null;
        cookie_name = '';
    }
    if (!b_cookie_found) {
        return null;
    }
}

function adjustSearchResultStyles() {
    // set the appropriate height for the search results panel      
    var resultsHeader = document.getElementById('VetResultsHeaderRow');
    var resultsHeaderTop = resultsHeader.offsetTop;
    var resultsContainerCell = document.getElementById('VetResultsContainerCell');
    var resultsContainer = document.getElementById('ResultsContainer');
    var outerTable = document.getElementById('OuterTable');
    /* temporarily set the size of the search results container to 0 to
    prevent it from interfering with the outerTable resize operation */
    resultsContainer.style.height = 1;
    outerTable.style.height = document.body.clientHeight - 1;
    resultsContainer.style.height = resultsContainerCell.clientHeight;
}

function GetMap(buyerUID) {
    // load the map
    map = new GMap2(document.getElementById("leadsMap"));
    //map.addControl(new GSmallMapControl());
    map.addControl(new GSmallZoomControl3D());
    // START COUNTY OVERLAY CONFIG //
    /*
    var CustomGetTileUrl = function(point, zoom) {
    var url = "datatiles/" + zoom + "/x" + point.x + "_y" + point.y + ".png";
    return url;
    }

    tilelayers = new GTileLayer(new GCopyrightCollection("USNaviguide LLC"), 5, 9);
    tilelayers.getCopyright = function(a, b) {
    return { prefix: "USNaviguide LLC", copyrightTexts: ["Apache 2.0"] };
    }
    tilelayers.getOpacity = function() {
    return .95;
    }
    tilelayers.getTileUrl = CustomGetTileUrl;
    var hybridLayer = [tilelayers, G_HYBRID_MAP.getTileLayers()[1]];
    hybrid = new GMapType(hybridLayer, G_SATELLITE_MAP.getProjection(), "Counties", { maxResolution: 9, minResolution: 5, errorMessage: "No Data Available" });

    map.addMapType(hybrid);
    */
    // END COUNTY OVERLAY CONFIG //

    // center the map on Boise to start if a "last viewed lat/lon" value is not detected ...
    var lastPosDetected = false;
    var lastLat = Get_Cookie('LastLat');
    var lastLon = Get_Cookie('LastLon');
    var lastZoom = Get_Cookie('LastZoom');
    if (((lastLat != undefined) && (!isNaN(lastLat))) && ((lastLon != undefined) && (!isNaN(lastLon))) && ((lastZoom != undefined) && (!isNaN(lastZoom)))) {
        lastPosDetected = true;
    }

    if (lastPosDetected) {
        map.setCenter(new GLatLng(lastLat, lastLon), parseInt(lastZoom));
        //map.setZoom(lastZoom);
    }
    else {
        map.setCenter(new GLatLng(40, -98), DEFAULT_MAP_ZOOM_LEVEL);
    }
    // configure the event handler that will fire at the end of a map move (e.g. dragged by user)
    GEvent.addListener(map, "dragend", function() {
        GetLeads();
        SaveCurrentMapLocation();
    });
    GEvent.addListener(map, "zoomend", function() {
        GetLeads();
        SaveCurrentMapLocation();
    });
    // (re)display leads already selected by the current Buyer (if any)
    if (buyerUID != null) {
        AdminSite.Services.Leads.GetSelectedLeadsByBuyer(buyerUID, onGetBuyerSelectedLeadsSuccess, onGetBuyerSelectedLeadsFailure);
    } 
    // initialize the map
    GetLeads();
}

function onGetBuyerSelectedLeadsSuccess(buyerSelectedLeads) {
    for (var i = 0; i < buyerSelectedLeads.length; i++) {
        appendLeadInfoToSidebar(buyerSelectedLeads[i]);
    }    
}

function onGetBuyerSelectedLeadsFailure() {
    // no-op
}

function RemoveMapMoveEndEventHandler() {
    if (mapMoveEventHandler != null) {
        GEvent.removeListener(mapMoveEventHandler);
    }
}

function SaveCurrentMapLocation() {
    // save new lat/lon position to a cookie
    var mapCenterLatLon = map.getCenter();
    Set_Cookie('LastLat', mapCenterLatLon.lat(), '', '/', '', '');
    Set_Cookie('LastLon', mapCenterLatLon.lng(), '', '/', '', '');
    Set_Cookie('LastZoom', map.getZoom(), '', '/', '', '');
}

function GetLeads() {
    mapZoomLevel = map.getZoom();
    if (mapZoomLevel <= DEFAULT_MAP_ZOOM_LEVEL) {
        AdminSite.Services.Leads.GetAllStateInfoWithLeadCount(onGetLeadsSuccess, onGetLeadsFailure);
        // hide the search options form and return the map to the default starting position
        $('#searchForm').slideUp(500);
    }
    else {
        // show the search options form
        $('#searchForm').slideDown(500);
        // do not attempt the query unless the user has selected at least one mineral right type
        if (($get('uiCbMrtOilAndGas').checked == false) && ($get('uiCbMrtCoal').checked == false) && ($get('uiCbMrtOtherHardMinerals').checked == false)) {
            alert('Please select at least one type of Mineral Right to continue.');
        }
        else {
            // create a single pushpin per Zip Code in the viewable area ...
            var mapBounds = map.getBounds();
            var ne = mapBounds.getNorthEast();
            var sw = mapBounds.getSouthWest();
            // show loading panel animation
            showLoadingPanel();
            // get Zip codes that contain 1 or more leads that match the selection criteria
            AdminSite.Services.Leads.GetLeadCountForAllZipCodesInMapView(ne.y, ne.x, sw.y, sw.x, ($get('uiCbRtSale').checked) ? "true" : "", ($get('uiCbRtLease').checked) ? "true" : "", ($get('uiCbRtLeasesRoyalties').checked) ? "true" : "", ($get('uiCbMrtOilAndGas').checked) ? "true" : "", ($get('uiCbMrtCoal').checked) ? "true" : "", ($get('uiCbMrtOtherHardMinerals').checked) ? "true" : "", onGetLeadsSuccess, onGetLeadsFailure);
            //AdminSite.Services.Leads.GetLeadsInMapView(ne.y, ne.x, sw.y, sw.x, ($get('uiCbRtSale').checked) ? "true" : "", ($get('uiCbRtLease').checked) ? "true" : "", ($get('uiCbRtLeasesRoyalties').checked) ? "true" : "", ($get('uiCbMrtOilAndGas').checked) ? "true" : "", ($get('uiCbMrtCoal').checked) ? "true" : "", ($get('uiCbMrtOtherHardMinerals').checked) ? "true" : "", onGetLeadsSuccess, onGetLeadsFailure);
        }
    }
}

function showLoadingPanel() {
    //most effect types need no options passed by default
    var options = {};
    //run the effect
    $("#searchingLeadsLoadingPanel").show('clip', options, 500, showLoadingPanelCallback);
}

function hideLoadingPanel() {
    hideLoadingPanelCustomTimeLength(1000);
}

function hideLoadingPanelCustomTimeLength(milsecs) {
    $("#searchingLeadsLoadingPanel:visible").hide().fadeOut(milsecs);
}

//callback function to bring a hidden box back
function showLoadingPanelCallback() {
    setTimeout(function() {
        $("#searchingLeadsLoadingPanel:visible").hide().fadeOut();
    }, 1100);
};

/* Web Service Call Success Handler
This "delegate" method takes the list of leads returned by the web service and applies them 
as a datasource for both map pushpins and the templated search results list. */
function onGetLeadsSuccess(leads) {
    createPushpins(leads);
    // hide loading panel animation
    hideLoadingPanel();
}

/* Web Service Call Failure Handler
This "delegate" method takes the exception returned by the web service and displays its details */
function onGetLeadsFailure(ex) {
    alert(show_props(ex, "ex"));
}

// Creates pushpins for the map based on an array of Leads objects...
function createPushpins(leads) {
    // remove any existing items from the global pins collection and map before generating new ones
    clearMap();
    // if it exists, add the "You are Here" marker back to the page...
    if (youAreHereMarker != null) map.addOverlay(youAreHereMarker);
    // create the new markers and add them to the map
    for (var i = 0; i < leads.length; i++) {
        lead = leads[i];
        var point = new GLatLng(lead.Latitude, lead.Longitude);
        /* it is CRITICAL that you understand WHY this next line is constructed the
        way it is.  If we instantiate the "marker" variable in THIS "createPushpins" 
        method, it will behave as a GLOBAL variable of sorts (Strange, I know, but
        that is how JS works) and any time a marker "click" event is captured it will
        always reference the same marker variable, which will of course still be set
        to the last marker you added to the map.
        Having a separate method instantiate a "marker" variable and return a marker
        object resolves this issue as it obviously creates a new variable with each
        call.         
        */
        if (lead.ZipCode == undefined) {
            // this is a stateInfo object
            map.addOverlay(createStateMarker(point, lead, i));
        }
        else {
            // this is a ZipCode object
            map.addOverlay(createZipCodeMarker(point, lead, i));
        }
    }
    return;
}

function createStateMarker(point, lead) {
    var customIcon = new GIcon(baseIcon);
    // select custom icon based on Lead count
    customIcon.Label = lead.StateName;
    if (lead.LeadCount < 5) {
        customIcon.image = "/Images/GoogleMapIcons/Icon1.png";
        customIcon.iconSize = new GSize(25, 36);
    }
    else if (lead.LeadCount >= 5 && lead.LeadCount < 25) {
        customIcon.image = "/Images/GoogleMapIcons/BigIcon2.png";
        customIcon.iconSize = new GSize(42, 53);
    }
    else if (lead.LeadCount >= 25) {
        customIcon.image = "/Images/GoogleMapIcons/BigIcon3.png";
        customIcon.iconSize = new GSize(42, 53);
    }
    // Set up our GMarkerOptions object
    markerOptions = {
        icon: customIcon,
        title: lead.StateName + " has " + lead.LeadCount + " total leads.",
        noCloseOnClick: true
    };
    var marker = new GMarker(point, markerOptions);

    GEvent.addListener(marker, "click", function() {
        map.setCenter(marker.getLatLng());
        map.zoomIn();
    });

    return marker;
}

function createZipCodeMarker(point, lead) {
    var customIcon = new GIcon(baseIcon);
    // select custom icon based on Lead count
    //customIcon.Label = "Oil and Gas";
    if (lead.LeadCount < 3) {
        customIcon.image = "/Images/GoogleMapIcons/Icon1.png";
        customIcon.iconSize = new GSize(25, 36);
    }
    else if (lead.LeadCount >= 3 && lead.LeadCount < 6) {
        customIcon.image = "/Images/GoogleMapIcons/Icon2.png";
        customIcon.iconSize = new GSize(25, 36);
    }
    else if (lead.LeadCount >= 6 && lead.LeadCount < 10) {
        customIcon.image = "/Images/GoogleMapIcons/Icon3.png";
        customIcon.iconSize = new GSize(25, 36);
    }
    else if (lead.LeadCount >= 10) {
        customIcon.image = "/Images/GoogleMapIcons/Icon4.png";
        customIcon.iconSize = new GSize(25, 36);
    }
    // Set up our GMarkerOptions object
    markerOptions = {
        icon: customIcon,
        title: "Zip Code: " + lead.ZipCode + " - " + lead.LeadCount + " total leads."
    };
    var marker = new GMarker(point, markerOptions);

    GEvent.addListener(marker, "click", function() {
        DisplayLeadsForZipCode(lead.ZipCode, marker);
    });
    return marker;
}

function DisplayLeadsForZipCode(zipCode, marker) {
    selectedMarker = marker;
    // get list of all leads in the current Zip Code
    AdminSite.Services.Leads.GetLeadsByZip(zipCode, onGetLeadsByZipCodeSuccess, onGetLeadsByZipCodeFailure);
}

function onGetLeadsByZipCodeSuccess(zipCodeLeads) {
    /* merge the leads returned for the selected Zip Code with the markup template and display the result
    in the marker's InfoWindow */
    //the template
    var templ = new Template($get('balloonItemTemplate').innerHTML);

    // let's format each object
    var itemContent = '';
    var newItemContent = '';
    if (zipCodeLeads != null) {
        zipCodeLeads.each(function(zipCodeLead) {
            newItemContent = (templ.evaluate(zipCodeLead));
            itemContent = itemContent + newItemContent.replace(/#LeadUID#/g, zipCodeLead.UID);
        });
    }

    // inject the itemContent into the scrollable DIV container
    var mapBalloon = $get('MapBalloon');
    mapBalloon.innerHTML = itemContent;
    // generate the title bar content
    var balloonTitleBarTemplate = new Template($get('balloonTitleBarTemplate').innerHTML);
    var titleBarContent = balloonTitleBarTemplate.evaluate(zipCodeLeads[0]);
    // combine all content
    var balloonContent = titleBarContent + $get('MapBalloonContainer').innerHTML;
    // configure the infoWindow options
    infoWindowOptions = {
        noCloseOnClick: true
    };
    // send the constructed balloon content to the balloon
    selectedMarker.openInfoWindowHtml(balloonContent, infoWindowOptions);
}

function onGetLeadsByZipCodeFailure(result) {
    selectedMarker.openInfoWindowHtml("An error occurred while attempting to retrieve available properties for this Zip Code.");
}

function centerMapOnState() {
    var x = new GClientGeocoder();
    //alert($get('uiDdlState')[$get('uiDdlState').selectedIndex].value);
    var stateName = $get('uiDdlState')[$get('uiDdlState').selectedIndex].text;
    //    var address = '';
    //    if (stateName.toUpperCase() == 'LOUISIANA')
    //    {
    //        address = 'Alexandria, ' + stateName;
    //    }
    x.getLatLng(stateName, centerMapOnStateSuccess)
}

function zoomToLocation(latitude, longitude) {
    map.setCenter(new GLatLng(latitude, longitude), 12);
}

function viewEntireUS() {
    map.setCenter(new GLatLng(40, -98), DEFAULT_MAP_ZOOM_LEVEL);
}

function searchWithinCurrentMap(buyerUID) {
    var selectedStateOption = $get('uiDdlState')[$get('uiDdlState').selectedIndex];
    if (selectedStateOption.text == "") {
        alert("Please select a state");
        return;
    }
    //redirectIfBuyerUIDIsInvalid(buyerUID);
    /* get the LatLon values of the corners of the newly centered map and use those values as
    query filter criteria */
    var mapBounds = map.getBounds();
    var ne = mapBounds.getNorthEast();
    var sw = mapBounds.getSouthWest();
    //   try {
    AdminSite.Services.Leads.GetLeadsInMapView(buyerUID, selectedStateOption.value, ne.y, ne.x, sw.y, sw.x, ($get('uiCbRtSale').checked) ? "true" : "", ($get('uiCbRtLease').checked) ? "true" : "", ($get('uiCbRtLeasesRoyalties').checked) ? "true" : "", ($get('uiCbMrtOilAndGas').checked) ? "true" : "", ($get('uiCbMrtCoal').checked) ? "true" : "", ($get('uiCbMrtOtherHardMinerals').checked) ? "true" : "", onGetLeadsSuccess, onGetLeadsFailure);
    //      }
    //   catch (ex) {
    //      document.getElementById('debug').innerText = show_props(ex, "ex");
    //      }
    //window.status = 'ne:' + ne.y + ',' + ne.x + '; sw:' + sw.y + ',' + sw.x + ';';
}

function addLead(buyerUID, leadUID) {
    redirectIfBuyerUIDIsInvalid(buyerUID, leadUID);
    if ((buyerUID != null) && (buyerUID.length > 0)) {
        AdminSite.Services.Leads.AddInfoRequest(buyerUID, leadUID, onAddLeadSuccess, onAddLeadFailure);        
    }
}

function redirectIfBuyerUIDIsInvalid(buyerUID, leadUID) {
    if ((buyerUID == null) || (buyerUID == "")) {
       location.href = '/buyerinfo.aspx?returnUrl=' + document.URL + '&l=' + leadUID;
    }
    return;
}

function onAddLeadSuccess(result) {
    if (!result.BuyerNotSpecified) {
        if (result.AddSuccess) {
            // InfoRequest added successfully
            //alert('InfoRequest added successfully');
            configureInfoRequestUI(result.LeadUID, true);

            // add this lead to the SelectedLeads list in the sidebar
            appendLeadInfoToSidebar(result.Lead);
        }
        else {
            // an error occurred while trying to add the InfoRequest.
            if (result.InfoRequestAlreadyExists) {
                alert('You have already requested information about this property.');
            }
            else {
                alert(show_props(result));
                alert('An error occurred while trying to add the InfoRequest. Please contact us via email or telephone for more information.');
            }
        }
    }
    else {
        // no buyer information in Session, redirect the user to the BuyerInfo page
        location.href = '/buyerinfo.aspx?returnUrl=' + document.URL;
    }
}

function appendLeadInfoToSidebar(lead) {
    var selectedLeadTemplate = new Template($get('SelectedLeadTemplate').innerHTML);
    var selectedLeadContent = selectedLeadTemplate.evaluate(lead);

    var div = jQuery(selectedLeadContent);
    div.appendTo('div#SelectedLeadContainer');
}

function onAddLeadFailure(result) {
    alert(show_props(result));
}

function removeLead(buyerUID, leadUID) {
    if ((buyerUID == null) || (buyerUID == "") || (leadUID == null) || (leadUID == "")) {
        location.href = '/buyerinfo.aspx?returnUrl=' + document.URL;
    }
    AdminSite.Services.Leads.RemoveInfoRequest(buyerUID, leadUID, onRemoveLeadSuccess, onRemoveLeadFailure);
}

function onRemoveLeadSuccess(result) {
    if (!result.BuyerNotSpecified) {
        if (result.RemoveSuccess) {
            // InfoRequest removed successfully
            configureInfoRequestUI(result.LeadUID, false);
            // remove the lead from the sidebar
            $('div#sbItem' + result.LeadUID).remove();
            // re-display the "Request Info button"
            configureInfoRequestUI(result.LeadUID, false);
        }
        else {
            // an error occurred while trying to remove the InfoRequest.
            alert(show_props(result));
            alert('An error occurred while attempting to remove this InfoRequest');
        }
    }
    else {
        // no buyer information in Session, redirect the user to the BuyerInfo page
        location.href = '/buyerinfo.aspx?returnUrl=' + document.URL;
    }
}

function onRemoveLeadFailure(result) {
    alert(show_props(result));
}

function configureInfoRequestUI(leadUID, previouslyRequested) {
    if (document.getElementById(leadUID + '_LeadAlreadyAdded') != null) {
        document.getElementById(leadUID).style.display = (previouslyRequested) ? 'none' : '';
        document.getElementById(leadUID + '_LeadAlreadyAdded').style.display = (previouslyRequested) ? '' : 'none';
    }
}

// Populates a templated list of search results using the provided array of VetHospital objects.
function populateSearchResults(leads) {
    /* add a custom "navigateURL" property to each vetHospital object for 
    databinding purposes only */
    ////   if (leads != null) {
    ////      for (var i = 0; i < vetHospitals.length; i++) {
    ////         leads[i].navigateURL = "javascript:PinClick(" + i + ", true);";
    ////         leads[i].PBMarkerIndex = "searchResult" + i.toString();
    ////      }
    ////   }

    //the template
    var templ = new Template($get('leadLayoutTemplate').innerHTML);

    // let's format each object
    // [conversion1, conversion2, conversion3].each( function(conv){
    var resultsList = $get("Results");
    var content = '';
    var newContent = '';
    if (resultsList != null) {
        leads.each(function(lead) {
            newContent = (templ.evaluate(lead));
            content = content + newContent.replace(/#LeadUID#/g, lead.UID);
        });
    }
    // alert(content);
    resultsList.innerHTML = content;
    /* now that all the leads to be displayed have been added to the DOM, configure the 
    InfoRequested UI for each */
    if (resultsList != null) {
        leads.each(function(lead) {
            configureInfoRequestUI(lead.UID, lead.InfoRequested);
        });
    }
}

function clearMap() {
    // clear out any map markers
    markers = null;
    markers = new Array();
    map.clearOverlays();
}

/* The code that runs when a pushpin is clicked.  The primary purpose is to move/position
the VetHospitalDetail panel right next to the pushpin that was just clicked and also
populate that panel (div) with detailed information about the selected Vet Hospital
(Hours of Operation, Services Provided, image(s) of the facility, etc.) */
function PinClick(markerIndex, queryDB) {
    try {
        highlightSelectedSearchResult(markerIndex);

        // reset the previous selected marker
        if (selectedMarker != null) {
            map.removeOverlay(selectedMarker);
            map.addOverlay(selectedMarkerOriginal);
        }
        // replace the marker that was clicked with a "fancy" selected icon   
        var marker = markers[markerIndex];
        selectedMarkerOriginal = marker;
        map.removeOverlay(marker);
        selectedMarker = new GMarker(marker.getPoint(), customIconCurrentlySelected(marker.IsProvider));
        //writeToDebug(marker.getPoint(), "s");
        map.addOverlay(selectedMarker);

        if (queryDB != false) {
            loadVetHospitalDetail(marker.PBVId);
        }
        //var point = ; //marker.MapXY;
        var mapSize = map.getSize();
        var centerXY = map.fromLatLngToDivPixel(map.getCenter());
        var markerXY = map.fromLatLngToDivPixel(marker.getPoint());
        // center the clicked marker
        map.panTo(marker.getPoint());
        // move the Hospital Detail pane next to the marker that was clicked...
        //var hospitalDetailPane = document.getElementById('vetHospitalDetail');
        /* map and the div that contains the map have different XYs (map starts out the same as the
        container but as you drag it around you will see that the center of the map is actually
        relative to the point on the map that corresponded with 0,0 of the container, in this 
        case a DIV) */
        //alert(show_props(centerXY, 'thePoint'));
        /////hospitalDetailPane.style.left = (mapSize.width/2 + (markerXY.x - centerXY.x)) + 30;
        /////hospitalDetailPane.style.top = (mapSize.height/2 + (markerXY.y - centerXY.y)) - 15;
        /* unless queryDB == false (would occur when detail has already been displayed and
        the user has simply dragged the map to a new location, for example), call the 
        function that updates the Hospital Detail pane with the data for the selected 
        Vet Hospital */
    }
    catch (ex) {
        writeToDebug(show_props(ex, "theException"));
    }
}

function moveElement(id, left, top) {
    element = document.getElementById(id);
    element.style.left = left;
    element.style.top = top;
}

// custom icons
function customIconProvider() {
    var icon = new GIcon();
    icon.image = "App_Themes/Main/Images/PushPin_Provider.png";
    icon.shadow = "App_Themes/Main/Images/Shadow.png";
    icon.iconSize = new GSize(35, 45);
    icon.shadowSize = new GSize(35, 45);
    icon.iconAnchor = new GPoint(11, 39);
    icon.infoWindowAnchor = new GPoint(22, 5);
    icon.infoShadowAnchor = new GPoint(0, 0);
    return icon;
}

function customIconNotProvider() {
    var icon = new GIcon();
    icon.image = "App_Themes/Main/Images/PushPin.png";
    icon.shadow = "App_Themes/Main/Images/Shadow.png";
    icon.iconSize = new GSize(35, 45);
    icon.shadowSize = new GSize(35, 45);
    icon.iconAnchor = new GPoint(11, 39);
    icon.infoWindowAnchor = new GPoint(22, 5);
    icon.infoShadowAnchor = new GPoint(0, 0);
    return icon;
}

function customIconCurrentlySelected(isProvider) {
    var icon = new GIcon();
    var imageUrl = (isProvider ? "App_Themes/Main/Images/OfficialActive.gif" : "App_Themes/Main/Images/Active.gif");
    icon.image = imageUrl;
    icon.shadow = "App_Themes/Main/Images/Shadow.png";
    icon.iconSize = new GSize(35, 45);
    icon.shadowSize = new GSize(35, 45);
    icon.iconAnchor = new GPoint(11, 39);
    icon.infoWindowAnchor = new GPoint(22, 5);
    icon.infoShadowAnchor = new GPoint(0, 0);
    return icon;
}

function customIconYouAreHere() {
    var icon = new GIcon();
    icon.image = "App_Themes/Main/Images/YouHerePin.png";
    icon.iconSize = new GSize(60, 60);
    icon.shadowSize = new GSize(60, 60);
    icon.iconAnchor = new GPoint(11, 39);
    icon.infoWindowAnchor = new GPoint(22, 5);
    icon.infoShadowAnchor = new GPoint(0, 0);
    return icon;
}

function defaultGoogleIcon() {
    return G_DEFAULT_ICON;
}

// search results panel interaction
function highlightSelectedSearchResult(markerIndex) {
    /* if another search result was previously selected, remove the "selected" 
    styling from that result item. */
    if (selectedMarkerOriginal != null) {
        previousSelectedItem = getSelectedResultItemDiv(selectedMarkerOriginal.PBMarkerIndex);
        if (previousSelectedItem != null) {
            previousSelectedItem.className = "";
        }
    }
    selectedItem = getSelectedResultItemDiv(markerIndex);
    if (selectedItem != null) {
        selectedItem.className = "searchResultsSelectedItem";
        var container = document.getElementById('ResultsContainer');
        /* scroll the search results panel such that the selected result appears 
        at the top of the pane. */
        if (selectedItem.offsetTop < container.scrollTop) selectedItem.scrollIntoView(true);
        if (selectedItem.offsetTop > (container.clientHeight + container.scrollTop)) selectedItem.scrollIntoView(false);
    }
}

/* returns the Nth item (div) from the search results list.  This method 
should work fine in any browser that supports the DOM. */
function getSelectedResultItemDiv(markerIndex) {
    var resultContainer = document.getElementById('vhTemplate');
    var returnObj = null;
    /* we want to return the "markerIndex"th DIV that represents a search 
    result item and ignore everything else. */
    var x = 0;
    for (var i = 0; i < resultContainer.childNodes.length; i++) {
        searchResult = resultContainer.childNodes[i];
        /* IF this is a div with an id of vhItemTemplate (i.e. not a #Text 
        DOM element, etc.), it is one of the nodes we're interested in. */
        if ((searchResult.nodeType == 1) && (searchResult.id == 'vhItemTemplate')) {
            if (x == markerIndex) {
                returnObj = searchResult;
                break;
            }
            else {
                x++;
            }
        }
    }
    return returnObj;
}

////// Custom map control (zoom / positioning)
////function PetsBestMapControl() {
////}
////PetsBestMapControl.prototype = new GControl();
////PetsBestMapControl.prototype.initialize = function(map) {

////   //var sliderBox = document.getElementById('MapZoomArea');
////   var controlContainerDiv = document.getElementById('PetsBestMapControl');

////   // apply map type change events
////   this.applyMapTypeChangeEvent('RoadMapButton', G_NORMAL_MAP);
////   this.applyMapTypeChangeEvent('SatelliteMapButton', G_HYBRID_MAP);
////   // apply map pan events
////   this.applyPanEvent('MapButtonNorth', 0, 1);
////   this.applyPanEvent('MapButtonEast', -1, 0);
////   this.applyPanEvent('MapButtonSouth', 0, -1);
////   this.applyPanEvent('MapButtonWest', 1, 0);
////   this.applyMapCenterEvent('MapButtonCenter');
////   // apply map zoom events
////   this.applyZoomEvent('MapZoomButtonPlus', 'in');
////   this.applyZoomEvent('MapZoomButtonMinus', 'out');
////   // add the map slider
////   var zoomScaleContainer = document.getElementById('MapZoomArea');
////   var sliderImg = document.createElement("img");
////   sliderImg.id = "MapZoomSlider";
////   sliderImg.src = "App_Themes/Main/Images/Slider.png";
////   zoomScaleContainer.appendChild(sliderImg);
////   // apply map zoom scale events
////   for (var i = MAX_MAP_ZOOM_LEVEL; i >= MIN_MAP_ZOOM_LEVEL; i--) {
////      zoomScaleContainer.appendChild(this.createZoomMarker(i));
////   }
////   // add the div that contains the control background image to the map surface
////   var bgDiv = document.getElementById('PetsBestMapControlBG');
////   map.getContainer().appendChild(bgDiv);
////   // add the custom control to the map surface
////   map.getContainer().appendChild(controlContainerDiv);
////   return controlContainerDiv;
////}

////PetsBestMapControl.prototype.makeVisible = function() {
////   document.getElementById('PetsBestMapControl').style.visibility = "visible";
////   document.getElementById('PetsBestMapControlBG').style.visibility = "visible";
////}

////PetsBestMapControl.prototype.createZoomMarker = function(newZoomLevel) {
////   var zoomMarker = document.createElement("div");
////   zoomMarker.id = "HashMark";
////   zoomMarker.className = "HashMark";
////   var hashMarkImg = document.createElement("img");
////   hashMarkImg.src = "App_Themes/Main/Images/Hashmark.png";
////   zoomMarker.appendChild(hashMarkImg);
////   // add the click event for this zoom level
////   GEvent.addDomListener(zoomMarker, "click", function() {
////      mapControl.setSliderPosition(newZoomLevel);
////   });
////   return zoomMarker;
////}

////PetsBestMapControl.prototype.setSliderPosition = function(newZoomLevel) {
////   if ((newZoomLevel <= MAX_MAP_ZOOM_LEVEL) && (newZoomLevel >= MIN_MAP_ZOOM_LEVEL)) {
////      map.setZoom(newZoomLevel);
////      // get a reference to the slider div
////      var slider = document.getElementById('MapZoomSlider');
////      // locate the div element that corresponds with the new zoom level
////      var zoomLevelContainer = document.getElementById('MapZoomArea');
////      /* set the zoom slider's position = that of the "newZoomLevel"th zoomLevel
////      (we obviously want to "skip over" text nodes and any other types of child
////      elements that do not represent "Hash Marks" */
////      var targetZoomPosIndex = MAX_MAP_ZOOM_LEVEL - newZoomLevel;
////      var hashMarkItemIndex = 0;
////      var targetChildNodeIndex = 0;
////      var nodes = zoomLevelContainer.childNodes;
////      for (var i = 0; i < nodes.length; i++) {
////         var nodeToCheck = nodes[i];
////         if ((nodeToCheck.nodeType == 1) && (nodeToCheck.id == 'HashMark')) {
////            if (hashMarkItemIndex == targetZoomPosIndex) {
////               targetChildNodeIndex = i;
////               break;
////            }
////            else {
////               hashMarkItemIndex++;
////            }
////         }
////      }
////      var zoomMarker = zoomLevelContainer.childNodes[targetChildNodeIndex];
////      slider.style.top = zoomMarker.offsetTop;
////   }
////}

////PetsBestMapControl.prototype.applyMapTypeChangeEvent = function(controlID, mapType) {
////   var btn = document.getElementById(controlID);
////   GEvent.addDomListener(btn, "click", function() {
////      map.setCenter();
////      map.setMapType(mapType);
////   });
////}

////PetsBestMapControl.prototype.applyMapCenterEvent = function(controlID) {
////   var btn = document.getElementById(controlID);
////   GEvent.addDomListener(btn, "click", function() {
////      map.returnToSavedPosition();
////   });
////}

////PetsBestMapControl.prototype.applyPanEvent = function(controlID, xDir, yDir) {
////   var btn = document.getElementById(controlID);
////   GEvent.addDomListener(btn, "click", function() {
////      map.panDirection(xDir, yDir);
////   });
////   //writeToDebug(btn);
////}

////PetsBestMapControl.prototype.applyZoomEvent = function(controlID, zoomDir) {
////   var btn = document.getElementById(controlID);
////   switch (zoomDir) {
////      case "in":
////         GEvent.addDomListener(btn, "click", function() { mapControl.setSliderPosition(map.getZoom() + 1); });
////         break;
////      case "out":
////         GEvent.addDomListener(btn, "click", function() { mapControl.setSliderPosition(map.getZoom() - 1); });
////         break;
////   }
////}

////// By default, the control will appear in the top left corner of the
////// map with 7 pixels of padding.
////PetsBestMapControl.prototype.getDefaultPosition = function() {
////   return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(7, 7));
////}

// Utility methods

/* Generic JavaScript utility function that iterates over all of the properties of the 
object passed in and concatenates a name/value pair for each property into a single string */
function show_props(obj, objName) {
    var result = "";
    for (var i in obj) {
        result += objName + "." + i + " = " + obj[i] + "\n";
    }
    return result;
}

function showDetailItem(id) {
    var element = document.getElementById(id)
    if (element != null) {
        element.style.display = "block";
    }
}

function hideDetailItems(ids) {
    if (ids.length > 0) {
        items = ids.split(",");
        for (var i = 0; i < items.length; i++) {
            var element = document.getElementById(items[i])
            if (element != null) {
                element.style.display = "none";
            }
        }
    }
}

function writeToDebug(obj, objName) {
    var debugDiv = document.getElementById('debug');
    if (debugDiv != null) {
        debugDiv.innerText = show_props(obj, objName);
    }
}   
