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 = 17;
var MIN_MAP_ZOOM_LEVEL = 4;

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() {
    // adjust the search results UI to fit allocated space in browser
    adjustSearchResultStyles();
    window.onresize = adjustSearchResultStyles;
    // load the map
    map = new GMap2(document.getElementById("vetHospitalMap"));
    //map.setMapType(new GMapType(G_NORMAL_MAP));
    //map.enableContinuousZoom();
    //map.addControl(new GLargeMapControl(), new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(10, 10)));
    mapControl = new PetsBestMapControl();
    map.addControl(mapControl);
    //writeToDebug(mapControl);
    mapControl.makeVisible();
    // center the map on Boise to start
    map.setCenter(new GLatLng(40, -98), DEFAULT_MAP_ZOOM_LEVEL);
    map.enableScrollWheelZoom();
    mapControl.setSliderPosition(DEFAULT_MAP_ZOOM_LEVEL);
    // map move (e.g. dragged by user) handler
    //   GEvent.addListener(map, "move", function() { 
    //      //alert(selectedMarkerID);
    //      //alert('selectedMapID.isNull = ' + (selectedMarkerID != null));
    //      if (selectedMarkerID != null) {
    //         /* if the last click on the map was on a marker, move the Hospital Detail pane
    //            to the marker's new location */
    //         PinClick(selectedMarkerID, false);   
    //         }
    //      else {                  
    //         // hide the detail pane
    //         moveElement('vetHospitalDetail', 10, 10);
    //         }
    //      });
    // map click handler
    GEvent.addListener(map, "click", function (marker, point) {
        if (marker == null) {
            // reset the selectedMarkerID value
            selectedMarkerID = null;
            //alert('map clicked');
        }
        else {
            selectedMarkerID = marker.PBMarkerIndex;
        }
    });

}

function searchByAddress() {
    try {
        clearMap();     // Clear the map pins, etc.
        $("#searchResults").html('');       // Clear the Vet Results list (Vet Name, address, etc.)
        /* attempt to Geocode the supplied address and send the search results to another method
        that will query the Vet Locator data based on the success of the Geocode operation */
        var geocoder = new GClientGeocoder();
        geocoder.getLocations(document.getElementById('uiTxtAddress').value, me.centerMapThenSearch);
    }
    catch (ex) {
        document.getElementById('searchResults').innerText = (show_props(ex, 'ex'));
    }
}

/* Web Service call that queries for Vet Hospitals located within a specific Latitude / 
Longitude quadrant */
function centerMapThenSearch(returnVal) {
    //writeToDebug(returnVal.Placemark.getItem(0));
    if ((returnVal.Placemark != null) && (returnVal.Placemark[0] != null)) {
        // our search returned exactly one Placemark so center the map on its Lat/Lon
        var searchMatch = returnVal.Placemark[0];
        placeLatLon = searchMatch.Point.coordinates;
        // set the new map zoom level based on the accuracy of the address search
        var newZoomLevel = DEFAULT_MAP_ZOOM_LEVEL;
        var addressAccuracy = searchMatch.AddressDetails.Accuracy;
        if (addressAccuracy >= 5) {
            newZoomLevel = 13;
        }
        else if (addressAccuracy >= 3) {
            newZoomLevel = 12;
        }
        else {
            youAreHereMarker = null;
        }
        // set the new map center
        newCenter = new GLatLng(placeLatLon[1], placeLatLon[0]);
        map.setCenter(newCenter, newZoomLevel);
        map.savePosition();
        // update the map control slider's position
        mapControl.setSliderPosition(newZoomLevel);
        // create/update the "You are Here" marker.
        youAreHereMarker = new GMarker(newCenter, customIconYouAreHere());
        // execute the search
        searchWithinCurrentMap();
    }
    else {
        // search yielded no results, so clear out the map and search results panels
        clearMap();
        populateSearchResults();
    }

}

//   function searchWithinCurrentMap() {
//       /* 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 {
//       VetLocator.VetHospitalsService.GetVetHospitalsByCoords(ne.y, sw.x, sw.y, ne.x, onGetVetHospitalsComplete_GMAPI);
//       //      }
//       //   catch (ex) {
//       //      document.getElementById('debug').innerText = show_props(ex, "ex");
//       //      }
//       //window.status = 'ne:' + ne.y + ',' + ne.x + '; sw:' + sw.y + ',' + sw.x + ';';
//   }

function searchWithinCurrentMap() {
    /* 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();
    //VetLocator.VetHospitalsService.GetVetHospitalsByCoords(ne.y, sw.x, sw.y, ne.x, onGetVetHospitalsComplete_GMAPI);

    var webMethod = 'VetHospitalsService.asmx/GetVetHospitalsByCoords';
    var parameters = "{'nwLat':'" + ne.y + "', 'nwLon':'" + sw.x + "', 'seLat':'" + sw.y + "', 'seLon':'" + ne.x + "'}"
    $.ajax({
        type: "POST",
        url: webMethod,
        data: parameters,
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        //jsonp: "$callback",
        success: onGetVetHospitalsComplete_GMAPI,
        error: mapSearchErrorHandler
    });
}

function mapSearchErrorHandler(data) {
    alert('We apologize, but an error occurred.  Please reload the page and try again.');
}
/* Web Service Callback (used for all Vet Locator Web Service calls).
This "delegate" method takes the list of Vet Hospitals returned by
the web service and applies them as a datasource for both map pushpins
and the templated search results list. */
function onGetVetHospitalsComplete_GMAPI(vetHospitals) {
    //   if (vetHospitals.length == 0) {
    //      alert('Your search returned 0 results.\r\nPlease refine your search and try again.');
    //      }
    createPushpins(vetHospitals.d);
    populateSearchResults(vetHospitals.d);
}

// Populates a templated list of search results using the provided array of VetHospital objects.
function populateSearchResults(vetHospitals) {
    /* add a custom "navigateURL" property to each vetHospital object for 
    databinding purposes only */
    if (vetHospitals != null) {
        for (var i = 0; i < vetHospitals.length; i++) {
            vetHospitals[i].navigateURL = "javascript:PinClick(" + i + ", true);";
            vetHospitals[i].PBMarkerIndex = "searchResult" + i.toString();
            // set search result item CSS class
            if (vetHospitals[i].IsProvider) {
                vetHospitals[i].ResultItemCssClass = "OfficialCon";
            }
            else {
                vetHospitals[i].ResultItemCssClass = "searchResultsItemDefault";
            }
        }
        // bind the data
        //writeToDebug($('searchResults').control);
    }

    // old templating code ...
    // $('searchResults').control.set_data(vetHospitals);
    // new templating code ...
    $("#searchResult").tmpl(vetHospitals).appendTo("#searchResults");
}

// Creates pushpins for the Virtual Earth map based on an array of VetHospital objects.
function createPushpins(vetHospitals) {
    // 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 < vetHospitals.length; i++) {
        vet = vetHospitals[i];
        var point = new GLatLng(vet.Latitude, vet.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.         
        */
        map.addOverlay(createMarker(point, vet, i));
    }
    return;
}

function clearMap() {
    // clear out any map markers
    markers = null;
    markers = new Array();
    map.clearOverlays();
    /* clear out any existing data in the Vet Detail panel by setting the current 
    Vet Hospital ID to 0 */
    loadVetHospitalDetail(0);
}

function createMarker(point, vet, markerIndex) {
    var icon = customIconNotProvider();
    if (vet.IsProvider) {
        icon = customIconProvider();
    }
    var marker = new GMarker(point, icon);
    marker.PBVId = vet.ID;
    marker.PBMarkerIndex = markerIndex;
    marker.IsProvider = vet.IsProvider;
    GEvent.addListener(marker, "click", function () { PinClick(markerIndex, true) });
    markers[markerIndex] = marker;
    return marker;
}

/* 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 = previousSelectedItem.className.replace(" searchResultsSelectedItem", "");
        }
    }
    selectedItem = getSelectedResultItemDiv(markerIndex);
    if (selectedItem != null) {
        selectedItem.className = 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('searchResults');
    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++) {
        var 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];
        var topPosition = zoomMarker.offsetTop;

//        // Detect Chrome browser for displaying the top position            
//        if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
//            topPosition = "91px";
//        }

        slider.style.top = topPosition;
    }
}

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(ex) {
    var debugDiv = $("#debug");
    if (debugDiv != null) {
        debugDiv.html(ex);
    }
}

